diff --git a/.github/workflows/addon-checker.yml b/.github/workflows/addon-checker.yml
new file mode 100644
index 0000000000..91630d38fc
--- /dev/null
+++ b/.github/workflows/addon-checker.yml
@@ -0,0 +1,34 @@
+name: Kodi Addon-Checker
+
+on: [pull_request]
+
+jobs:
+ kodi-addon-checker:
+ runs-on: ubuntu-latest
+ name: Kodi Addon-Checker
+ steps:
+
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: '3.12'
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip3 install --user kodi-addon-checker
+
+ - name: Extract job variables
+ shell: bash
+
+ run: |
+ echo "addon=$(git diff --diff-filter=d --name-only HEAD~ | grep / | cut -d / -f1 | sort | uniq)" >> $GITHUB_OUTPUT
+ id: extract_vars
+
+ - name: Addon-Check
+ run: $HOME/.local/bin/kodi-addon-checker --branch=${{ github.event.pull_request.base.ref }} --PR ${{ steps.extract_vars.outputs.addon }}
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000000..7ddf15f462
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,21 @@
+dist: bionic
+language: python
+python: 3.8
+
+install:
+ - pip install kodi-addon-checker
+
+before_script:
+- git config core.quotepath false
+
+# command to run our tests
+script:
+ - kodi-addon-checker --branch=matrix $([ "$TRAVIS_PULL_REQUEST" != "false" ] && echo --PR $(git diff --diff-filter=d --name-only HEAD~ | grep / | cut -d / -f1 | sort | uniq))
+
+notifications:
+ webhooks: https://www.travisbuddy.com/
+ email:
+ on_failure: change # default: always
+
+travisBuddy:
+ successBuildLog: "true"
diff --git a/README.md b/README.md
index c33146f25d..bb049eb9ba 100644
--- a/README.md
+++ b/README.md
@@ -8,10 +8,11 @@ This branch is not used for public. Please use one of the other branches availab
## Quick Kodi development links
-* [Add-on rules](https://github.com/xbmc/xbmc/blob/master/CONTRIBUTING.md)
+* [Add-on rules](https://github.com/xbmc/repo-plugins/blob/master/CONTRIBUTING.md)
* [Submitting an add-on details](http://kodi.wiki/view/Submitting_Add-ons)
* [Code guidelines](http://kodi.wiki/view/Official:Code_guidelines_and_formatting_conventions)
* [Kodi development](http://kodi.wiki/view/Development)
+* [Kodi Addon checker](https://pypi.org/project/kodi-addon-checker/)
## Other useful links
diff --git a/plugin.audio.ampache/LICENSE b/plugin.audio.ampache/LICENSE
new file mode 100644
index 0000000000..c0a3c35c63
--- /dev/null
+++ b/plugin.audio.ampache/LICENSE
@@ -0,0 +1,17 @@
+XBMC Ampache Plugin
+ Copyright (C) 2011-2017 Michael Better, Carlo Sardi, Jeffmeister, abeiro, Chris Slamar, Reno Reckling
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
diff --git a/plugin.audio.ampache/README b/plugin.audio.ampache/README
new file mode 100644
index 0000000000..7acf5c1d7f
--- /dev/null
+++ b/plugin.audio.ampache/README
@@ -0,0 +1,33 @@
+ampache-xbmc-plugin
+
+This is a plugin for XBMC/KODI providing basic connectivity to the Streaming Software Ampache.
+The plugin is included in Kodi Official Repository
+
+If you want to use the cutting edge version, download the zip file and add it to your XBMC/KODI via System->AddOns->Install from ZIP.
+In the new version of KODI you have to do AddOns->Addon Icon ( top/left in Kodi default Skin )->Install from ZIP.
+
+
+This plugin supports the ampache API from 350001 to the last one.
+
+Tested with kodi 19, 20
+
+Tested with web controls and kore app ( kore app offers only a partial plugin support )
+
+It is tested with the latest stable ampache server and nextcloud music.
+
+
+Troubleshooting:
+
+Nextcloud music doesn't use api-key, but username/password, if you have problems to connect unckeck api_key box
+
+Web controls work better with the old search interface, it is possibile to enable it in the settings
+
+The crashes in kodi are due to bugs in kodi from 18.5 to 20 ( double busy dialog bug ) and in kore app.
+To avoid random crashes in kodi don't do any operation in the last five seconds of a song,
+due to a kodi bug, playing next song generates a busy dialog ( impossible to avoid )
+and operating on kodi generates another busy dialog.
+The combination of two busy dialogs working crashes kodi (https://github.com/xbmc/xbmc/issues/16756)
+
+When you update the 2.0 version from an old version, expecially on raspberry pi, the plugin could not work.
+This behaviour is due to the kodi addon cache. To correct this one time problem, it is necessary to reboot the
+mediacenter or, if the problem continues, uninstall and reinstall the plugin.
diff --git a/plugin.audio.ampache/addon.xml b/plugin.audio.ampache/addon.xml
new file mode 100644
index 0000000000..37613242a8
--- /dev/null
+++ b/plugin.audio.ampache/addon.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+ audio
+
+
+
+
+ Stream music from Ampache XML-API
+ Streame Musik von der Ampache XML-API
+ Streaming Musicale per l'XML-API di Ampache
+ Retransmitir música a través de la XML-API de Ampache
+ A web based audio/video streaming application and file manager allowing you to access your music and videos from anywhere, using almost any internet enabled device.
+ all
+ https://forum.kodi.tv/showthread.php?tid=230736
+ https://ampache.org/
+ GPL-2.0-or-later
+
+ resources/icon.png
+ resources/fanart.jpg
+
+
+
diff --git a/plugin.audio.ampache/default.py b/plugin.audio.ampache/default.py
new file mode 100644
index 0000000000..2610938cbf
--- /dev/null
+++ b/plugin.audio.ampache/default.py
@@ -0,0 +1,4 @@
+
+from resources.lib.ampache_plugin import Main
+
+Main()
diff --git a/plugin.audio.ampache/resources/__init__.py b/plugin.audio.ampache/resources/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugin.audio.ampache/resources/fanart.jpg b/plugin.audio.ampache/resources/fanart.jpg
new file mode 100644
index 0000000000..765771c5ba
Binary files /dev/null and b/plugin.audio.ampache/resources/fanart.jpg differ
diff --git a/plugin.audio.ampache/resources/icon.png b/plugin.audio.ampache/resources/icon.png
new file mode 100644
index 0000000000..3872e7410b
Binary files /dev/null and b/plugin.audio.ampache/resources/icon.png differ
diff --git a/plugin.audio.ampache/resources/language/resource.language.de_de/strings.po b/plugin.audio.ampache/resources/language/resource.language.de_de/strings.po
new file mode 100644
index 0000000000..6a4f9fd623
--- /dev/null
+++ b/plugin.audio.ampache/resources/language/resource.language.de_de/strings.po
@@ -0,0 +1,409 @@
+# Kodi Media Center language file
+# Addon Name: Ampache plugin
+# Addon id: plugin.audio.ampache
+# Addon Provider: lusum
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2020-02-06 22:42+0100\n"
+"Last-Translator: \n"
+"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/"
+"language/en/)\n"
+"Language: de_DE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+# settings
+msgctxt "#30001"
+msgid "General"
+msgstr ""
+
+msgctxt "#30002"
+msgid "Servers"
+msgstr ""
+
+msgctxt "#30010"
+msgid "Random Items"
+msgstr ""
+
+msgctxt "#30011"
+msgid "Disable Ssl certs"
+msgstr ""
+
+# empty string with id 32003
+msgctxt "#30012"
+msgid "Api Version"
+msgstr ""
+
+msgctxt "#30013"
+msgid "Old search gui (web controller friendly)"
+msgstr ""
+
+msgctxt "#30020"
+msgid "Add Server"
+msgstr ""
+
+msgctxt "#30021"
+msgid "Remove Server"
+msgstr ""
+
+msgctxt "#30022"
+msgid "Modify Server"
+msgstr ""
+
+msgctxt "#30023"
+msgid "Switch Server"
+msgstr ""
+
+# code
+msgctxt "#30101"
+msgid "Search..."
+msgstr ""
+
+msgctxt "#30102"
+msgid "Quick access..."
+msgstr ""
+
+msgctxt "#30103"
+msgid "Explore..."
+msgstr ""
+
+msgctxt "#30104"
+msgid "Library..."
+msgstr ""
+
+msgctxt "#30105"
+msgid "Settings"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Artist"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Album"
+msgstr ""
+
+msgctxt "#30108"
+msgid "Song"
+msgstr ""
+
+msgctxt "#30109"
+msgid "Playlist"
+msgstr ""
+
+msgctxt "#30110"
+msgid "All"
+msgstr ""
+
+msgctxt "#30111"
+msgid "Tag"
+msgstr ""
+
+msgctxt "#30112"
+msgid "Artist tag"
+msgstr ""
+
+msgctxt "#30113"
+msgid "Album tag"
+msgstr ""
+
+msgctxt "#30114"
+msgid "Song tag"
+msgstr ""
+
+msgctxt "#30115"
+msgid "Artists"
+msgstr ""
+
+msgctxt "#30116"
+msgid "Albums"
+msgstr ""
+
+msgctxt "#30117"
+msgid "Songs"
+msgstr ""
+
+msgctxt "#30118"
+msgid "Playlists"
+msgstr ""
+
+msgctxt "#30119"
+msgid "Tags"
+msgstr ""
+
+msgctxt "#30120"
+msgid "Search Artists..."
+msgstr ""
+
+msgctxt "#30121"
+msgid "Search Albums..."
+msgstr ""
+
+msgctxt "#30122"
+msgid "Search Songs..."
+msgstr ""
+
+msgctxt "#30123"
+msgid "Search Playlists..."
+msgstr ""
+
+msgctxt "#30124"
+msgid "Search All..."
+msgstr ""
+
+msgctxt "#30125"
+msgid "Search Tags..."
+msgstr ""
+
+msgctxt "#30126"
+msgid "Recent Artists..."
+msgstr ""
+
+msgctxt "#30127"
+msgid "Recent Albums..."
+msgstr ""
+
+msgctxt "#30128"
+msgid "Recent Songs..."
+msgstr ""
+
+msgctxt "#30129"
+msgid "Recent Playlists..."
+msgstr ""
+
+msgctxt "#30130"
+msgid "Last Update"
+msgstr ""
+
+msgctxt "#30131"
+msgid "1 Week"
+msgstr ""
+
+msgctxt "#30132"
+msgid "1 Month"
+msgstr ""
+
+msgctxt "#30133"
+msgid "3 Months"
+msgstr ""
+
+msgctxt "#30134"
+msgid "Random Artists..."
+msgstr ""
+
+msgctxt "#30135"
+msgid "Random Albums..."
+msgstr ""
+
+msgctxt "#30136"
+msgid "Random Songs..."
+msgstr ""
+
+msgctxt "#30137"
+msgid "Random Playlists..."
+msgstr ""
+
+msgctxt "#30138"
+msgid "Show artist from this song"
+msgstr ""
+
+msgctxt "#30139"
+msgid "Show album from this song"
+msgstr ""
+
+msgctxt "#30140"
+msgid "Search all songs with this title"
+msgstr ""
+
+msgctxt "#30141"
+msgid "Show all albums from artist"
+msgstr ""
+
+msgctxt "#30142"
+msgid "Artist tags..."
+msgstr ""
+
+msgctxt "#30143"
+msgid "Album tags..."
+msgstr ""
+
+msgctxt "#30144"
+msgid "Song tags..."
+msgstr ""
+
+msgctxt "#30145"
+msgid "Recent..."
+msgstr ""
+
+msgctxt "#30146"
+msgid "Random..."
+msgstr ""
+
+msgctxt "#30147"
+msgid "Server playlist..."
+msgstr ""
+
+msgctxt "#30148"
+msgid "Highest Rated..."
+msgstr ""
+
+msgctxt "#30149"
+msgid "Highest Rated Artists..."
+msgstr ""
+
+msgctxt "#30150"
+msgid "Highest Rated Albums..."
+msgstr ""
+
+msgctxt "#30151"
+msgid "Highest Rated Songs..."
+msgstr ""
+
+msgctxt "#30152"
+msgid "Frequent Artists..."
+msgstr ""
+
+msgctxt "#30153"
+msgid "Frequent Albums..."
+msgstr ""
+
+msgctxt "#30154"
+msgid "Frequent Songs..."
+msgstr ""
+
+msgctxt "#30155"
+msgid "Flagged Artists..."
+msgstr ""
+
+msgctxt "#30156"
+msgid "Flagged Albums..."
+msgstr ""
+
+msgctxt "#30157"
+msgid "Flagged Songs..."
+msgstr ""
+
+msgctxt "#30158"
+msgid "Forgotten Artists..."
+msgstr ""
+
+msgctxt "#30159"
+msgid "Forgotten Albums..."
+msgstr ""
+
+msgctxt "#30160"
+msgid "Forgotten Songs..."
+msgstr ""
+
+msgctxt "#30161"
+msgid "Newest Artists..."
+msgstr ""
+
+msgctxt "#30162"
+msgid "Newest Albums..."
+msgstr ""
+
+msgctxt "#30163"
+msgid "Newest Songs..."
+msgstr ""
+
+msgctxt "#30164"
+msgid "Frequent..."
+msgstr ""
+
+msgctxt "#30165"
+msgid "Flagged..."
+msgstr ""
+
+msgctxt "#30166"
+msgid "Forgotten..."
+msgstr ""
+
+msgctxt "#30167"
+msgid "Newest..."
+msgstr ""
+
+msgctxt "#30168"
+msgid "Modify the data, cancel to exit"
+msgstr ""
+
+msgctxt "#30169"
+msgid "Choose a default server"
+msgstr ""
+
+msgctxt "#30170"
+msgid "Enter the Server name"
+msgstr ""
+
+msgctxt "#30171"
+msgid "Enter the url of the server"
+msgstr ""
+
+msgctxt "#30173"
+msgid "Do you want to use an api-key?"
+msgstr ""
+
+msgctxt "#30174"
+msgid "Enter the Api key"
+msgstr ""
+
+msgctxt "#30175"
+msgid "Enter the username"
+msgstr ""
+
+msgctxt "#30177"
+msgid "The server needs a password?"
+msgstr ""
+
+msgctxt "#30178"
+msgid "Enter the password"
+msgstr ""
+
+msgctxt "#30179"
+msgid "Choose a server to remove"
+msgstr ""
+
+msgctxt "#30180"
+msgid "Modify a server"
+msgstr ""
+
+msgctxt "#30181"
+msgid "Server name"
+msgstr ""
+
+msgctxt "#30182"
+msgid "Server url"
+msgstr ""
+
+msgctxt "#30183"
+msgid "Username"
+msgstr ""
+
+msgctxt "#30184"
+msgid "Enable password"
+msgstr ""
+
+msgctxt "#30185"
+msgid "Password"
+msgstr ""
+
+msgctxt "#30186"
+msgid "Use api key"
+msgstr ""
+
+msgctxt "#30187"
+msgid "Api key"
+msgstr ""
+
+msgctxt "#30188"
+msgid "Are you sure?"
+msgstr ""
+
+msgctxt "#30189"
+msgid "Ampache plugin"
+msgstr ""
diff --git a/plugin.audio.ampache/resources/language/resource.language.en_gb/strings.po b/plugin.audio.ampache/resources/language/resource.language.en_gb/strings.po
new file mode 100644
index 0000000000..33fbcbb877
--- /dev/null
+++ b/plugin.audio.ampache/resources/language/resource.language.en_gb/strings.po
@@ -0,0 +1,495 @@
+# Kodi Media Center language file
+# Addon Name: Ampache plugin
+# Addon id: plugin.audio.ampache
+# Addon Provider: lusum
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2020-02-06 18:18+0100\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+# settings
+msgctxt "#30001"
+msgid "General"
+msgstr ""
+
+msgctxt "#30002"
+msgid "Servers"
+msgstr ""
+
+msgctxt "#30010"
+msgid "Random Items"
+msgstr ""
+
+msgctxt "#30011"
+msgid "Disable Ssl certs"
+msgstr ""
+
+msgctxt "#30012"
+msgid "Api Version"
+msgstr ""
+
+msgctxt "#30013"
+msgid "Old search gui (web controller friendly)"
+msgstr ""
+
+msgctxt "#30014"
+msgid "Enable images on long lists"
+msgstr ""
+
+msgctxt "#30015"
+msgid "Auto fullscreen"
+msgstr ""
+
+msgctxt "#30020"
+msgid "Add Server"
+msgstr ""
+
+msgctxt "#30021"
+msgid "Remove Server"
+msgstr ""
+
+msgctxt "#30022"
+msgid "Modify Server"
+msgstr ""
+
+msgctxt "#30023"
+msgid "Switch Server"
+msgstr ""
+
+# code
+msgctxt "#30101"
+msgid "Search..."
+msgstr ""
+
+msgctxt "#30102"
+msgid "Quick access..."
+msgstr ""
+
+msgctxt "#30103"
+msgid "Explore..."
+msgstr ""
+
+msgctxt "#30104"
+msgid "Library..."
+msgstr ""
+
+msgctxt "#30105"
+msgid "Settings"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Artist"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Album"
+msgstr ""
+
+msgctxt "#30108"
+msgid "Song"
+msgstr ""
+
+msgctxt "#30109"
+msgid "Playlist"
+msgstr ""
+
+msgctxt "#30110"
+msgid "All"
+msgstr ""
+
+msgctxt "#30111"
+msgid "Tag"
+msgstr ""
+
+msgctxt "#30112"
+msgid "Artist tag"
+msgstr ""
+
+msgctxt "#30113"
+msgid "Album tag"
+msgstr ""
+
+msgctxt "#30114"
+msgid "Song tag"
+msgstr ""
+
+msgctxt "#30115"
+msgid "Artists"
+msgstr ""
+
+msgctxt "#30116"
+msgid "Albums"
+msgstr ""
+
+msgctxt "#30117"
+msgid "Songs"
+msgstr ""
+
+msgctxt "#30118"
+msgid "Playlists"
+msgstr ""
+
+msgctxt "#30119"
+msgid "Tags"
+msgstr ""
+
+msgctxt "#30120"
+msgid "Search Artists..."
+msgstr ""
+
+msgctxt "#30121"
+msgid "Search Albums..."
+msgstr ""
+
+msgctxt "#30122"
+msgid "Search Songs..."
+msgstr ""
+
+msgctxt "#30123"
+msgid "Search Playlists..."
+msgstr ""
+
+msgctxt "#30124"
+msgid "Search All..."
+msgstr ""
+
+msgctxt "#30125"
+msgid "Search Tags..."
+msgstr ""
+
+msgctxt "#30126"
+msgid "Recently Added Artists..."
+msgstr ""
+
+msgctxt "#30127"
+msgid "Recently Added Albums..."
+msgstr ""
+
+msgctxt "#30128"
+msgid "Recently Added Songs..."
+msgstr ""
+
+msgctxt "#30129"
+msgid "Recently Added Playlists..."
+msgstr ""
+
+msgctxt "#30130"
+msgid "Last Update"
+msgstr ""
+
+msgctxt "#30131"
+msgid "1 Week"
+msgstr ""
+
+msgctxt "#30132"
+msgid "1 Month"
+msgstr ""
+
+msgctxt "#30133"
+msgid "3 Months"
+msgstr ""
+
+msgctxt "#30134"
+msgid "Random Artists..."
+msgstr ""
+
+msgctxt "#30135"
+msgid "Random Albums..."
+msgstr ""
+
+msgctxt "#30136"
+msgid "Random Songs..."
+msgstr ""
+
+msgctxt "#30137"
+msgid "Random Playlists..."
+msgstr ""
+
+msgctxt "#30138"
+msgid "Show artist from this song"
+msgstr ""
+
+msgctxt "#30139"
+msgid "Show album from this song"
+msgstr ""
+
+msgctxt "#30140"
+msgid "Search all songs with this title"
+msgstr ""
+
+msgctxt "#30141"
+msgid "Show all albums from artist"
+msgstr ""
+
+msgctxt "#30142"
+msgid "Artist tags..."
+msgstr ""
+
+msgctxt "#30143"
+msgid "Album tags..."
+msgstr ""
+
+msgctxt "#30144"
+msgid "Song tags..."
+msgstr ""
+
+msgctxt "#30145"
+msgid "Recently Added..."
+msgstr ""
+
+msgctxt "#30146"
+msgid "Random..."
+msgstr ""
+
+msgctxt "#30147"
+msgid "Server playlist..."
+msgstr ""
+
+msgctxt "#30148"
+msgid "Highest Rated..."
+msgstr ""
+
+msgctxt "#30149"
+msgid "Highest Rated Artists..."
+msgstr ""
+
+msgctxt "#30150"
+msgid "Highest Rated Albums..."
+msgstr ""
+
+msgctxt "#30151"
+msgid "Highest Rated Songs..."
+msgstr ""
+
+msgctxt "#30152"
+msgid "Frequent Artists..."
+msgstr ""
+
+msgctxt "#30153"
+msgid "Frequent Albums..."
+msgstr ""
+
+msgctxt "#30154"
+msgid "Frequent Songs..."
+msgstr ""
+
+msgctxt "#30155"
+msgid "Flagged Artists..."
+msgstr ""
+
+msgctxt "#30156"
+msgid "Flagged Albums..."
+msgstr ""
+
+msgctxt "#30157"
+msgid "Flagged Songs..."
+msgstr ""
+
+msgctxt "#30158"
+msgid "Forgotten Artists..."
+msgstr ""
+
+msgctxt "#30159"
+msgid "Forgotten Albums..."
+msgstr ""
+
+msgctxt "#30160"
+msgid "Forgotten Songs..."
+msgstr ""
+
+msgctxt "#30161"
+msgid "Newest Artists..."
+msgstr ""
+
+msgctxt "#30162"
+msgid "Newest Albums..."
+msgstr ""
+
+msgctxt "#30163"
+msgid "Newest Songs..."
+msgstr ""
+
+msgctxt "#30164"
+msgid "Frequent..."
+msgstr ""
+
+msgctxt "#30165"
+msgid "Flagged..."
+msgstr ""
+
+msgctxt "#30166"
+msgid "Forgotten..."
+msgstr ""
+
+msgctxt "#30167"
+msgid "Newest..."
+msgstr ""
+
+msgctxt "#30168"
+msgid "Modify the data, cancel to exit"
+msgstr ""
+
+msgctxt "#30169"
+msgid "Choose a default server"
+msgstr ""
+
+msgctxt "#30170"
+msgid "Enter the Server name"
+msgstr ""
+
+msgctxt "#30171"
+msgid "Enter the url of the server"
+msgstr ""
+
+msgctxt "#30173"
+msgid "Do you want to use an api-key?"
+msgstr ""
+
+msgctxt "#30174"
+msgid "Enter the Api key"
+msgstr ""
+
+msgctxt "#30175"
+msgid "Enter the username"
+msgstr ""
+
+msgctxt "#30177"
+msgid "The server needs a password?"
+msgstr ""
+
+msgctxt "#30178"
+msgid "Enter the password"
+msgstr ""
+
+msgctxt "#30179"
+msgid "Choose a server to remove"
+msgstr ""
+
+msgctxt "#30180"
+msgid "Modify a server"
+msgstr ""
+
+msgctxt "#30181"
+msgid "Server name"
+msgstr ""
+
+msgctxt "#30182"
+msgid "Server url"
+msgstr ""
+
+msgctxt "#30183"
+msgid "Username"
+msgstr ""
+
+msgctxt "#30184"
+msgid "Enable password"
+msgstr ""
+
+msgctxt "#30185"
+msgid "Password"
+msgstr ""
+
+msgctxt "#30186"
+msgid "Use api key"
+msgstr ""
+
+msgctxt "#30187"
+msgid "Api key"
+msgstr ""
+
+msgctxt "#30188"
+msgid "Are you sure?"
+msgstr ""
+
+msgctxt "#30189"
+msgid "Ampache plugin"
+msgstr ""
+
+msgctxt "#30190"
+msgid "Recently Played Artists..."
+msgstr ""
+
+msgctxt "#30191"
+msgid "Recently Played Albums..."
+msgstr ""
+
+msgctxt "#30192"
+msgid "Recently Played Songs..."
+msgstr ""
+
+msgctxt "#30193"
+msgid "Recently Played..."
+msgstr ""
+
+msgctxt "#30194"
+msgid "Next items..."
+msgstr ""
+
+msgctxt "#30195"
+msgid "Disk"
+msgstr ""
+
+msgctxt "#30197"
+msgid "Information"
+msgstr ""
+
+msgctxt "#30198"
+msgid "Error"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Connection Error"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Connection OK"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Permission error. If you are using Nextcloud don't check api_key box"
+msgstr ""
+
+msgctxt "#30220"
+msgid "Video"
+msgstr ""
+
+msgctxt "#30221"
+msgid "Videos"
+msgstr ""
+
+msgctxt "#30222"
+msgid "Search Videos..."
+msgstr ""
+
+msgctxt "#30225"
+msgid "Podcast"
+msgstr ""
+
+msgctxt "#30226"
+msgid "Podcasts"
+msgstr ""
+
+msgctxt "#30227"
+msgid "Search Podcasts..."
+msgstr ""
+
+msgctxt "#30228"
+msgid "Live stream"
+msgstr ""
+
+msgctxt "#30229"
+msgid "Live streams"
+msgstr ""
+
+msgctxt "#30230"
+msgid "Search Live Streams..."
+msgstr ""
diff --git a/plugin.audio.ampache/resources/language/resource.language.es_es/strings.po b/plugin.audio.ampache/resources/language/resource.language.es_es/strings.po
new file mode 100644
index 0000000000..2c8969a436
--- /dev/null
+++ b/plugin.audio.ampache/resources/language/resource.language.es_es/strings.po
@@ -0,0 +1,493 @@
+# Kodi Media Center language file
+# Addon Name: Ampache plugin
+# Addon id: plugin.audio.ampache
+# Addon Provider: lusum
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-03-18 00:01+0100\n"
+"Last-Translator: \n"
+"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/"
+"language/en/)\n"
+"Language: es_ES\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 2.4.1\n"
+
+msgctxt "#30001"
+msgid "General"
+msgstr "General"
+
+msgctxt "#30002"
+msgid "Servers"
+msgstr "Servidores"
+
+msgctxt "#30010"
+msgid "Random Items"
+msgstr "Núumero de elementos aleatorios"
+
+msgctxt "#30011"
+msgid "Disable Ssl certs"
+msgstr "Deshabilitar certificados Ssl"
+
+# empty string with id 32003
+msgctxt "#30012"
+msgid "Api Version"
+msgstr "Versión de la Api"
+
+msgctxt "#30013"
+msgid "Old search gui (web controller friendly)"
+msgstr "Interfaz de búsqueda para el controlador web"
+
+msgctxt "#30014"
+msgid "Enable images on long lists"
+msgstr "Habilita imágenes en listas largas"
+
+msgctxt "#30015"
+msgid "Auto fullscreen"
+msgstr "Pantalla completa automática"
+
+msgctxt "#30020"
+msgid "Add Server"
+msgstr "Añadir servidor"
+
+msgctxt "#30021"
+msgid "Remove Server"
+msgstr "Eliminar servidor"
+
+msgctxt "#30022"
+msgid "Modify Server"
+msgstr "Modificar servidor"
+
+msgctxt "#30023"
+msgid "Switch Server"
+msgstr "Cambiar servidor"
+
+# code
+msgctxt "#30101"
+msgid "Search..."
+msgstr "Buscar..."
+
+msgctxt "#30102"
+msgid "Quick access..."
+msgstr "Acceso rápido..."
+
+msgctxt "#30103"
+msgid "Explore..."
+msgstr "Explorar..."
+
+msgctxt "#30104"
+msgid "Library..."
+msgstr "Librería..."
+
+msgctxt "#30105"
+msgid "Settings"
+msgstr "Configuración"
+
+msgctxt "#30106"
+msgid "Artist"
+msgstr "Artista"
+
+msgctxt "#30107"
+msgid "Album"
+msgstr "Álbum"
+
+msgctxt "#30108"
+msgid "Song"
+msgstr "Canción"
+
+msgctxt "#30109"
+msgid "Playlist"
+msgstr "Lista de reproducción"
+
+msgctxt "#30110"
+msgid "All"
+msgstr "Todo"
+
+msgctxt "#30111"
+msgid "Tag"
+msgstr "Género"
+
+msgctxt "#30112"
+msgid "Artist tag"
+msgstr "Género del artista"
+
+msgctxt "#30113"
+msgid "Album tag"
+msgstr "Género del álbum"
+
+msgctxt "#30114"
+msgid "Song tag"
+msgstr "Género de la canción"
+
+msgctxt "#30115"
+msgid "Artists"
+msgstr "Artistas"
+
+msgctxt "#30116"
+msgid "Albums"
+msgstr "Álbumes"
+
+msgctxt "#30117"
+msgid "Songs"
+msgstr "Canciones"
+
+msgctxt "#30118"
+msgid "Playlists"
+msgstr "Listas de reproducción"
+
+msgctxt "#30119"
+msgid "Tags"
+msgstr "Géneros"
+
+# code
+msgctxt "#30120"
+msgid "Search Artists..."
+msgstr "Buscar de artistas..."
+
+# code
+msgctxt "#30121"
+msgid "Search Albums..."
+msgstr "Buscar de álbumes..."
+
+# code
+msgctxt "#30122"
+msgid "Search Songs..."
+msgstr "Buscar de canciones..."
+
+# code
+msgctxt "#30123"
+msgid "Search Playlists..."
+msgstr "Buscar de listas de reproducción..."
+
+# code
+msgctxt "#30124"
+msgid "Search All..."
+msgstr "Buscar todo..."
+
+# code
+msgctxt "#30125"
+msgid "Search Tags..."
+msgstr "Buscar géneros..."
+
+msgctxt "#30126"
+msgid "Recently Added Artists..."
+msgstr "Artistas añadidos recientemente..."
+
+msgctxt "#30127"
+msgid "Recently Added Albums..."
+msgstr "Álbumes añadidos recientemente..."
+
+msgctxt "#30128"
+msgid "Recently Added Songs..."
+msgstr "Canciones añadidas recientemente..."
+
+msgctxt "#30129"
+msgid "Recently Added Playlists..."
+msgstr "Listas de reproducción añadidas recientemente..."
+
+msgctxt "#30130"
+msgid "Last Update"
+msgstr "Últimas actualizaciones"
+
+msgctxt "#30131"
+msgid "1 Week"
+msgstr "1 semana"
+
+msgctxt "#30132"
+msgid "1 Month"
+msgstr "1 mes"
+
+msgctxt "#30133"
+msgid "3 Months"
+msgstr "3 meses"
+
+msgctxt "#30134"
+msgid "Random Artists..."
+msgstr "Artistas aleatorios..."
+
+msgctxt "#30135"
+msgid "Random Albums..."
+msgstr "Álbumes aleatorios..."
+
+msgctxt "#30136"
+msgid "Random Songs..."
+msgstr "Canciones aleatorios..."
+
+msgctxt "#30137"
+msgid "Random Playlists..."
+msgstr "Listas de reproducción aleatorias..."
+
+msgctxt "#30138"
+msgid "Show artist from this song"
+msgstr "Mostrar artistas de esta canción"
+
+msgctxt "#30139"
+msgid "Show album from this song"
+msgstr "Mostrar álbum de esta canción"
+
+msgctxt "#30140"
+msgid "Search all songs with this title"
+msgstr "Buscar todas las canciones con este título"
+
+msgctxt "#30141"
+msgid "Show all albums from artist"
+msgstr "Mostrar todos los álbumes del artista"
+
+msgctxt "#30142"
+msgid "Artist tags..."
+msgstr "Géneros de artistas..."
+
+msgctxt "#30143"
+msgid "Album tags..."
+msgstr "Géneros de álbumes..."
+
+msgctxt "#30144"
+msgid "Song tags..."
+msgstr "Géneros de canciones..."
+
+msgctxt "#30145"
+msgid "Recently Added..."
+msgstr "Añadido recientemente..."
+
+msgctxt "#30146"
+msgid "Random..."
+msgstr "Aleatorio..."
+
+msgctxt "#30147"
+msgid "Server playlist..."
+msgstr "Lista de reproducción del servidor..."
+
+msgctxt "#30148"
+msgid "Highest Rated..."
+msgstr "Puntuaciones mas altas..."
+
+msgctxt "#30149"
+msgid "Highest Rated Artists..."
+msgstr "Artistas con mejor puntuación..."
+
+msgctxt "#30150"
+msgid "Highest Rated Albums..."
+msgstr "Álbumes con mejor puntuación..."
+
+msgctxt "#30151"
+msgid "Highest Rated Songs..."
+msgstr "Canciones con mejor puntuación..."
+
+msgctxt "#30152"
+msgid "Frequent Artists..."
+msgstr "Artitas frequentes..."
+
+msgctxt "#30153"
+msgid "Frequent Albums..."
+msgstr "Álbumes frequentes..."
+
+msgctxt "#30154"
+msgid "Frequent Songs..."
+msgstr "Canciones frequentes..."
+
+msgctxt "#30155"
+msgid "Flagged Artists..."
+msgstr "Artistas favoritos..."
+
+msgctxt "#30156"
+msgid "Flagged Albums..."
+msgstr "Álbumes favoritos..."
+
+msgctxt "#30157"
+msgid "Flagged Songs..."
+msgstr "Canciones favoritas..."
+
+msgctxt "#30158"
+msgid "Forgotten Artists..."
+msgstr "Artistas olvidados..."
+
+msgctxt "#30159"
+msgid "Forgotten Albums..."
+msgstr "Álbumes olvidados..."
+
+msgctxt "#30160"
+msgid "Forgotten Songs..."
+msgstr "Canciones olvidadas..."
+
+msgctxt "#30161"
+msgid "Newest Artists..."
+msgstr "Artistas añadidos recientemente..."
+
+msgctxt "#30162"
+msgid "Newest Albums..."
+msgstr "Álbumes añadidos recientemente"
+
+msgctxt "#30163"
+msgid "Newest Songs..."
+msgstr "Canciones añadidas recientemente..."
+
+msgctxt "#30164"
+msgid "Frequent..."
+msgstr "Frequentes..."
+
+msgctxt "#30165"
+msgid "Flagged..."
+msgstr "Favoritos..."
+
+msgctxt "#30166"
+msgid "Forgotten..."
+msgstr "Olvidados..."
+
+msgctxt "#30167"
+msgid "Newest..."
+msgstr "Añadido recientemente..."
+
+msgctxt "#30168"
+msgid "Modify the data, cancel to exit"
+msgstr "Modifique los datos, para salir pulse cancelar"
+
+msgctxt "#30169"
+msgid "Choose a default server"
+msgstr "Elija el servidor predeterminador"
+
+msgctxt "#30170"
+msgid "Enter the Server name"
+msgstr "Inserte el nombre del servidor"
+
+msgctxt "#30171"
+msgid "Enter the url of the server"
+msgstr "Inserte la url del servidor"
+
+msgctxt "#30173"
+msgid "Do you want to use an api-key?"
+msgstr "¿Quiere usar una clave api?"
+
+msgctxt "#30174"
+msgid "Enter the Api key"
+msgstr "Inserte la clave Api"
+
+msgctxt "#30175"
+msgid "Enter the username"
+msgstr "Inserte el nombre de usuario"
+
+msgctxt "#30177"
+msgid "The server needs a password?"
+msgstr "¿Necesita contraseña el servidor?"
+
+msgctxt "#30178"
+msgid "Enter the password"
+msgstr "Inserte la contraseña"
+
+msgctxt "#30179"
+msgid "Choose a server to remove"
+msgstr "Elija un servidor a eliminar"
+
+msgctxt "#30180"
+msgid "Modify a server"
+msgstr "Modificar un servidor"
+
+msgctxt "#30181"
+msgid "Server name"
+msgstr "Nombre del servidor"
+
+msgctxt "#30182"
+msgid "Server url"
+msgstr "Url del servidor"
+
+msgctxt "#30183"
+msgid "Username"
+msgstr "Nombre de usuario"
+
+msgctxt "#30184"
+msgid "Enable password"
+msgstr "Habilitar contraseña"
+
+msgctxt "#30185"
+msgid "Password"
+msgstr "Contraseña"
+
+msgctxt "#30186"
+msgid "Use api key"
+msgstr "Usar clave api"
+
+msgctxt "#30187"
+msgid "Api key"
+msgstr "Clave api"
+
+msgctxt "#30188"
+msgid "Are you sure?"
+msgstr "Está seguro?"
+
+msgctxt "#30189"
+msgid "Ampache plugin"
+msgstr "Plugin Ampache"
+
+msgctxt "#30190"
+msgid "Recently Played Artists..."
+msgstr "Artistas reproducidos recientemente..."
+
+msgctxt "#30191"
+msgid "Recently Played Albums..."
+msgstr "Álbumes reproducidos recientemente..."
+
+msgctxt "#30192"
+msgid "Recently Played Songs..."
+msgstr "Canciones reproducidas recientemente..."
+
+msgctxt "#30193"
+msgid "Recently Played..."
+msgstr "Reproducido recientemente..."
+
+msgctxt "#30194"
+msgid "Next items..."
+msgstr "Siguientes elementos..."
+
+msgctxt "#30195"
+msgid "Disk"
+msgstr "Disco"
+
+msgctxt "#30197"
+msgid "Information"
+msgstr "Información"
+
+msgctxt "#30198"
+msgid "Error"
+msgstr "Error"
+
+msgctxt "#30202"
+msgid "Connection Error"
+msgstr "Error de conexión"
+
+msgctxt "#30203"
+msgid "Connection OK"
+msgstr "Conexión correcta"
+
+msgctxt "#30204"
+msgid "Permission error. If you are using Nextcloud don't check api_key box"
+msgstr "Error de permiso. Si se está usando Nextcloud, no utilizar clave api"
+
+msgctxt "#30220"
+msgid "Video"
+msgstr "Vídeo"
+
+msgctxt "#30221"
+msgid "Videos"
+msgstr "Videos"
+
+# code
+msgctxt "#30222"
+msgid "Search Videos..."
+msgstr "Buscar vídeos..."
+
+msgctxt "#30225"
+msgid "Podcast"
+msgstr "Podcast"
+
+msgctxt "#30226"
+msgid "Podcasts"
+msgstr "Podcasts"
+
+# code
+msgctxt "#30227"
+msgid "Search Podcasts..."
+msgstr "Busca podcasts..."
diff --git a/plugin.audio.ampache/resources/language/resource.language.it_it/strings.po b/plugin.audio.ampache/resources/language/resource.language.it_it/strings.po
new file mode 100644
index 0000000000..ada0013128
--- /dev/null
+++ b/plugin.audio.ampache/resources/language/resource.language.it_it/strings.po
@@ -0,0 +1,506 @@
+# Kodi Media Center language file
+# Addon Name: Ampache plugin
+# Addon id: plugin.audio.ampache
+# Addon Provider: lusum
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2022-02-07 19:47+0100\n"
+"Last-Translator: \n"
+"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/"
+"language/en/)\n"
+"Language: it_IT\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 3.0\n"
+
+msgctxt "#30001"
+msgid "General"
+msgstr "Generale"
+
+msgctxt "#30002"
+msgid "Servers"
+msgstr "Server"
+
+msgctxt "#30010"
+msgid "Random Items"
+msgstr "Numero di elementi casuali"
+
+msgctxt "#30011"
+msgid "Disable Ssl certs"
+msgstr "Disabilita i certificati Ssl"
+
+# empty string with id 32003
+msgctxt "#30012"
+msgid "Api Version"
+msgstr "Versione dell'Api"
+
+msgctxt "#30013"
+msgid "Old search gui (web controller friendly)"
+msgstr "Interfaccia di ricerca per il controller web"
+
+msgctxt "#30014"
+msgid "Enable images on long lists"
+msgstr "Abilita le immagini sulle liste lunghe"
+
+msgctxt "#30015"
+msgid "Auto fullscreen"
+msgstr "Fullscreen auto"
+
+msgctxt "#30020"
+msgid "Add Server"
+msgstr "Aggiungi Server"
+
+msgctxt "#30021"
+msgid "Remove Server"
+msgstr "Rimuovi Server"
+
+msgctxt "#30022"
+msgid "Modify Server"
+msgstr "Modifica Server"
+
+msgctxt "#30023"
+msgid "Switch Server"
+msgstr "Cambia Server"
+
+# code
+msgctxt "#30101"
+msgid "Search..."
+msgstr "Ricerca..."
+
+msgctxt "#30102"
+msgid "Quick access..."
+msgstr "Accesso Veloce..."
+
+msgctxt "#30103"
+msgid "Explore..."
+msgstr "Esplora..."
+
+msgctxt "#30104"
+msgid "Library..."
+msgstr "Libreria..."
+
+msgctxt "#30105"
+msgid "Settings"
+msgstr "Impostazioni"
+
+msgctxt "#30106"
+msgid "Artist"
+msgstr "Artista"
+
+msgctxt "#30107"
+msgid "Album"
+msgstr ""
+
+msgctxt "#30108"
+msgid "Song"
+msgstr "Canzone"
+
+msgctxt "#30109"
+msgid "Playlist"
+msgstr ""
+
+msgctxt "#30110"
+msgid "All"
+msgstr "Tutto"
+
+msgctxt "#30111"
+msgid "Tag"
+msgstr "Genere"
+
+msgctxt "#30112"
+msgid "Artist tag"
+msgstr "Genere artista"
+
+msgctxt "#30113"
+msgid "Album tag"
+msgstr "Genere album"
+
+msgctxt "#30114"
+msgid "Song tag"
+msgstr "Genere canzone"
+
+msgctxt "#30115"
+msgid "Artists"
+msgstr "Artisti"
+
+msgctxt "#30116"
+msgid "Albums"
+msgstr "Album"
+
+msgctxt "#30117"
+msgid "Songs"
+msgstr "Canzoni"
+
+msgctxt "#30118"
+msgid "Playlists"
+msgstr ""
+
+msgctxt "#30119"
+msgid "Tags"
+msgstr "Generi"
+
+# code
+msgctxt "#30120"
+msgid "Search Artists..."
+msgstr "Ricerca artisti..."
+
+# code
+msgctxt "#30121"
+msgid "Search Albums..."
+msgstr "Ricerca album..."
+
+# code
+msgctxt "#30122"
+msgid "Search Songs..."
+msgstr "Ricerca canzoni..."
+
+# code
+msgctxt "#30123"
+msgid "Search Playlists..."
+msgstr "Ricerca playlist..."
+
+# code
+msgctxt "#30124"
+msgid "Search All..."
+msgstr "Ricerca tutto..."
+
+# code
+msgctxt "#30125"
+msgid "Search Tags..."
+msgstr "Ricerca tag..."
+
+msgctxt "#30126"
+msgid "Recently Added Artists..."
+msgstr "Artisti aggiunti recentemente..."
+
+msgctxt "#30127"
+msgid "Recently Added Albums..."
+msgstr "Album aggiunti recentemente..."
+
+msgctxt "#30128"
+msgid "Recently Added Songs..."
+msgstr "Canzoni aggiunte recentemente..."
+
+msgctxt "#30129"
+msgid "Recently Added Playlists..."
+msgstr "Playlist aggiunte recentemente..."
+
+msgctxt "#30130"
+msgid "Last Update"
+msgstr "Ultimo aggiornamento"
+
+msgctxt "#30131"
+msgid "1 Week"
+msgstr "1settimana"
+
+msgctxt "#30132"
+msgid "1 Month"
+msgstr "1 mese"
+
+msgctxt "#30133"
+msgid "3 Months"
+msgstr "3 mesi"
+
+msgctxt "#30134"
+msgid "Random Artists..."
+msgstr "Artisti casuali..."
+
+msgctxt "#30135"
+msgid "Random Albums..."
+msgstr "Album casuali..."
+
+msgctxt "#30136"
+msgid "Random Songs..."
+msgstr "Canzoni casuali..."
+
+msgctxt "#30137"
+msgid "Random Playlists..."
+msgstr "Playlist casuali..."
+
+msgctxt "#30138"
+msgid "Show artist from this song"
+msgstr "Mostra l'artista della canzone"
+
+msgctxt "#30139"
+msgid "Show album from this song"
+msgstr "Mostra l'album della canzone"
+
+msgctxt "#30140"
+msgid "Search all songs with this title"
+msgstr "Cerca tutte le canzoni con questo titolo"
+
+msgctxt "#30141"
+msgid "Show all albums from artist"
+msgstr "Mostra tutti gli album di questo artista"
+
+msgctxt "#30142"
+msgid "Artist tags..."
+msgstr "Generi Artisti..."
+
+msgctxt "#30143"
+msgid "Album tags..."
+msgstr "Generi Album..."
+
+msgctxt "#30144"
+msgid "Song tags..."
+msgstr "Generi Canzoni..."
+
+msgctxt "#30145"
+msgid "Recently Added..."
+msgstr "Aggiunti recentemente..."
+
+msgctxt "#30146"
+msgid "Random..."
+msgstr "Casuali..."
+
+msgctxt "#30147"
+msgid "Server playlist..."
+msgstr "Playlist casuale del server..."
+
+msgctxt "#30148"
+msgid "Highest Rated..."
+msgstr "Punteggio più alto..."
+
+msgctxt "#30149"
+msgid "Highest Rated Artists..."
+msgstr "Artisti col punteggio più alto..."
+
+msgctxt "#30150"
+msgid "Highest Rated Albums..."
+msgstr "Album col punteggio più alto..."
+
+msgctxt "#30151"
+msgid "Highest Rated Songs..."
+msgstr "Canzoni col punteggio più alto..."
+
+msgctxt "#30152"
+msgid "Frequent Artists..."
+msgstr "Artisti frequenti..."
+
+msgctxt "#30153"
+msgid "Frequent Albums..."
+msgstr "Album frequenti..."
+
+msgctxt "#30154"
+msgid "Frequent Songs..."
+msgstr "Canzoni frequenti..."
+
+msgctxt "#30155"
+msgid "Flagged Artists..."
+msgstr "Artisti contrassegnati..."
+
+msgctxt "#30156"
+msgid "Flagged Albums..."
+msgstr "Album contrassegnati..."
+
+msgctxt "#30157"
+msgid "Flagged Songs..."
+msgstr "Brani contrassegnati..."
+
+msgctxt "#30158"
+msgid "Forgotten Artists..."
+msgstr "Artisti Dimenticati..."
+
+msgctxt "#30159"
+msgid "Forgotten Albums..."
+msgstr "Album Dimenticati..."
+
+msgctxt "#30160"
+msgid "Forgotten Songs..."
+msgstr "Canzoni Dimenticate..."
+
+msgctxt "#30161"
+msgid "Newest Artists..."
+msgstr "Nuovi artisti..."
+
+msgctxt "#30162"
+msgid "Newest Albums..."
+msgstr "Nuovi album..."
+
+msgctxt "#30163"
+msgid "Newest Songs..."
+msgstr "Nuove canzoni..."
+
+msgctxt "#30164"
+msgid "Frequent..."
+msgstr "Frequenti..."
+
+msgctxt "#30165"
+msgid "Flagged..."
+msgstr "Contrassegnati..."
+
+msgctxt "#30166"
+msgid "Forgotten..."
+msgstr "Dimenticati..."
+
+msgctxt "#30167"
+msgid "Newest..."
+msgstr "Nuovi.."
+
+msgctxt "#30168"
+msgid "Modify the data, cancel to exit"
+msgstr "Modifica i dati, premi il tasto annulla per uscire"
+
+msgctxt "#30169"
+msgid "Choose a default server"
+msgstr "Scegli il server attivo"
+
+msgctxt "#30170"
+msgid "Enter the Server name"
+msgstr "Inserisci il nome del server"
+
+msgctxt "#30171"
+msgid "Enter the url of the server"
+msgstr "Inserisci l'url del server"
+
+msgctxt "#30173"
+msgid "Do you want to use an api-key?"
+msgstr "Vuoi utilizzare una chiave api?"
+
+msgctxt "#30174"
+msgid "Enter the Api key"
+msgstr "Inserisci la chiave api"
+
+msgctxt "#30175"
+msgid "Enter the username"
+msgstr "Inserisci il nome utente"
+
+msgctxt "#30177"
+msgid "The server needs a password?"
+msgstr "Il server necessita di una password?"
+
+msgctxt "#30178"
+msgid "Enter the password"
+msgstr "Inserisci la password"
+
+msgctxt "#30179"
+msgid "Choose a server to remove"
+msgstr "Scegli un server da rimuovere"
+
+msgctxt "#30180"
+msgid "Modify a server"
+msgstr "Modifica un server"
+
+msgctxt "#30181"
+msgid "Server name"
+msgstr "Nome server"
+
+msgctxt "#30182"
+msgid "Server url"
+msgstr "Url del server"
+
+msgctxt "#30183"
+msgid "Username"
+msgstr "Nome utente"
+
+msgctxt "#30184"
+msgid "Enable password"
+msgstr "Abilita la password"
+
+msgctxt "#30185"
+msgid "Password"
+msgstr ""
+
+msgctxt "#30186"
+msgid "Use api key"
+msgstr "Usa la chiave api"
+
+msgctxt "#30187"
+msgid "Api key"
+msgstr "Chiave api"
+
+msgctxt "#30188"
+msgid "Are you sure?"
+msgstr "Sei sicuro?"
+
+msgctxt "#30189"
+msgid "Ampache plugin"
+msgstr "Plugin Ampache"
+
+msgctxt "#30190"
+msgid "Recently Played Artists..."
+msgstr "Artisti ascoltati recentemente..."
+
+msgctxt "#30191"
+msgid "Recently Played Albums..."
+msgstr "Album ascoltati recentemente..."
+
+msgctxt "#30192"
+msgid "Recently Played Songs..."
+msgstr "Canzoni ascoltate recentemente..."
+
+msgctxt "#30193"
+msgid "Recently Played..."
+msgstr "Ascoltati recentemente..."
+
+msgctxt "#30194"
+msgid "Next items..."
+msgstr "Prossimi elementi..."
+
+msgctxt "#30195"
+msgid "Disk"
+msgstr "Disco"
+
+msgctxt "#30197"
+msgid "Information"
+msgstr "Informazione"
+
+msgctxt "#30198"
+msgid "Error"
+msgstr "Errore"
+
+msgctxt "#30202"
+msgid "Connection Error"
+msgstr "Errore di connessione"
+
+msgctxt "#30203"
+msgid "Connection OK"
+msgstr "Connessione stabilita"
+
+msgctxt "#30204"
+msgid "Permission error. If you are using Nextcloud don't check api_key box"
+msgstr "Permessi errati. Se state usando Nextcloud non utilizzare l'api_key"
+
+msgctxt "#30220"
+msgid "Video"
+msgstr ""
+
+msgctxt "#30221"
+msgid "Videos"
+msgstr "Video"
+
+# code
+msgctxt "#30222"
+msgid "Search Videos..."
+msgstr "Ricerca video..."
+
+msgctxt "#30225"
+msgid "Podcast"
+msgstr ""
+
+msgctxt "#30226"
+msgid "Podcasts"
+msgstr ""
+
+# code
+msgctxt "#30227"
+msgid "Search Podcasts..."
+msgstr "Ricerca podcast..."
+
+msgctxt "#30228"
+msgid "Live stream"
+msgstr "Stazione Radio"
+
+msgctxt "#30229"
+msgid "Live streams"
+msgstr "Stazioni Radio"
+
+# code
+msgctxt "#30230"
+msgid "Search Live Streams..."
+msgstr "Ricerca stazioni radio..."
diff --git a/plugin.audio.ampache/resources/lib/__init__.py b/plugin.audio.ampache/resources/lib/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugin.audio.ampache/resources/lib/ampache_connect.py b/plugin.audio.ampache/resources/lib/ampache_connect.py
new file mode 100644
index 0000000000..493847bfc2
--- /dev/null
+++ b/plugin.audio.ampache/resources/lib/ampache_connect.py
@@ -0,0 +1,259 @@
+from future import standard_library
+from future.utils import PY2
+standard_library.install_aliases()
+from builtins import str
+from builtins import object
+import hashlib
+import ssl
+import socket
+import time
+import urllib.request, urllib.parse, urllib.error
+import xbmc, xbmcaddon, xbmcgui
+import sys
+import xml.etree.ElementTree as ET
+
+#main plugin library
+from resources.lib import json_storage
+from resources.lib import utils as ut
+from resources.lib.art_clean import clean_settings
+
+class AmpacheConnect(object):
+
+ class ConnectionError(Exception):
+ pass
+
+ def __init__(self):
+ self._ampache = xbmcaddon.Addon("plugin.audio.ampache")
+ jsStorServer = json_storage.JsonStorage("servers.json")
+ serverStorage = jsStorServer.getData()
+ self._connectionData = serverStorage["servers"][serverStorage["current_server"]]
+ #self._connectionData = None
+ self.filter=None
+ self.add=None
+ self.limit=None
+ self.offset=None
+ self.type=None
+ self.exact=None
+ self.mode=None
+ self.id=None
+ self.rating=None
+ #force the latest version on the server
+ self.version="600001"
+
+ def getBaseUrl(self):
+ return '/server/xml.server.php'
+
+ def fillConnectionSettings(self,tree,nTime):
+ clean_settings()
+ token = tree.findtext('auth')
+ version = tree.findtext('api')
+ if not version:
+ #old api
+ version = tree.findtext('version')
+ #setSettings only string or unicode
+ self._ampache.setSetting("api-version",version)
+ self._ampache.setSetting("artists", tree.findtext("artists"))
+ self._ampache.setSetting("albums", tree.findtext("albums"))
+ self._ampache.setSetting("songs", tree.findtext("songs"))
+ apiVersion = int(version)
+ if apiVersion < 500001:
+ self._ampache.setSetting("playlists", tree.findtext("playlists"))
+ else:
+ self._ampache.setSetting("playlists", tree.findtext("playlists_searches"))
+ self._ampache.setSetting("videos", tree.findtext("videos") )
+ self._ampache.setSetting("podcasts", tree.findtext("podcasts") )
+ self._ampache.setSetting("live_streams", tree.findtext("live_streams") )
+ self._ampache.setSetting("session_expire", tree.findtext("session_expire"))
+ self._ampache.setSetting("add", tree.findtext("add"))
+ self._ampache.setSetting("token", token)
+ #not 24000 seconds ( 6 hours ) , but 2400 ( 40 minutes ) expiration time
+ self._ampache.setSetting("token-exp", str(nTime+2400))
+
+ def getCodeMessError(self,tree):
+ errormess = None
+ errornode = tree.find("error")
+ if errornode is not None:
+ #ampache api 4 and below
+ try:
+ errormess = tree.findtext('error')
+ return errormess
+ except:
+ #do nothing
+ pass
+ #ampache api 5 and above
+ try:
+ errormess = errornode.findtext("errorMessage")
+ return errormess
+ except:
+ #do nothing
+ pass
+
+ return errormess
+
+ def getHashedPassword(self,timeStamp):
+ enablePass = self._connectionData["enable_password"]
+ if enablePass:
+ sdf = self._connectionData["password"]
+ else:
+ sdf = ""
+ hasher = hashlib.new('sha256')
+ sdf = sdf.encode()
+ hasher.update(sdf)
+ myKey = hasher.hexdigest()
+ hasher = hashlib.new('sha256')
+ timeK = timeStamp + myKey
+ timeK = timeK.encode()
+ hasher.update(timeK)
+ passwordHash = hasher.hexdigest()
+ return passwordHash
+
+ def get_user_pwd_login_url(self,nTime):
+ myTimeStamp = str(nTime)
+ myPassphrase = self.getHashedPassword(myTimeStamp)
+ myURL = self._connectionData["url"] + self.getBaseUrl() + '?action=handshake&auth='
+ myURL += myPassphrase + "×tamp=" + myTimeStamp
+ myURL += '&version=' + self.version + '&user=' + self._connectionData["username"]
+ return myURL
+
+ def get_auth_key_login_url(self):
+ myURL = self._connectionData["url"] + self.getBaseUrl() + '?action=handshake&auth='
+ myURL += self._connectionData["api_key"]
+ myURL += '&version=' + self.version
+ return myURL
+
+ def handle_request(self,url):
+ xbmc.log("AmpachePlugin::handle_request: url " + url, xbmc.LOGDEBUG)
+ ssl_certs_str = self._ampache.getSetting("disable_ssl_certs")
+ try:
+ req = urllib.request.Request(url)
+ if ut.strBool_to_bool(ssl_certs_str):
+ if PY2:
+ response = urllib.request.urlopen(req, timeout=400)
+ else:
+ gcontext = ssl.create_default_context()
+ gcontext.check_hostname = False
+ gcontext.verify_mode = ssl.CERT_NONE
+ response = urllib.request.urlopen(req, context=gcontext, timeout=400)
+ xbmc.log("AmpachePlugin::handle_request: disable ssl certificates",xbmc.LOGDEBUG)
+ else:
+ if PY2:
+ gcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ response = urllib.request.urlopen(req, context=gcontext, timeout=400)
+ else:
+ response = urllib.request.urlopen(req, timeout=400)
+ xbmc.log("AmpachePlugin::handle_request: ssl certificates",xbmc.LOGDEBUG)
+ except urllib.error.HTTPError as e:
+ xbmc.log("AmpachePlugin::handle_request: HTTPError " +\
+ repr(e),xbmc.LOGDEBUG)
+ raise self.ConnectionError
+ except urllib.error.URLError as e:
+ xbmc.log("AmpachePlugin::handle_request: URLError " +\
+ repr(e),xbmc.LOGDEBUG)
+ raise self.ConnectionError
+ except Exception as e:
+ xbmc.log("AmpachePlugin::handle_request: Generic Error " +\
+ repr(e),xbmc.LOGDEBUG)
+ raise self.ConnectionError
+ headers = response.headers
+ contents = response.read()
+ response.close()
+ return headers,contents
+
+ def AMPACHECONNECT(self,showok=False):
+ socket.setdefaulttimeout(3600)
+ nTime = int(time.time())
+ use_api_key = self._connectionData["use_api_key"]
+ if ut.strBool_to_bool(use_api_key):
+ xbmc.log("AmpachePlugin::AMPACHECONNECT api_key",xbmc.LOGDEBUG)
+ myURL = self.get_auth_key_login_url()
+ else:
+ xbmc.log("AmpachePlugin::AMPACHECONNECT login password",xbmc.LOGDEBUG)
+ myURL = self.get_user_pwd_login_url(nTime)
+ try:
+ headers,contents = self.handle_request(myURL)
+ except self.ConnectionError:
+ xbmc.log("AmpachePlugin::AMPACHECONNECT ConnectionError",xbmc.LOGDEBUG)
+ #connection error
+ xbmcgui.Dialog().notification(ut.tString(30198),ut.tString(30202))
+ raise self.ConnectionError
+ except Exception as e:
+ xbmc.log("AmpachePlugin::AMPACHECONNECT: Generic Error " +\
+ repr(e),xbmc.LOGDEBUG)
+ try:
+ xbmc.log("AmpachePlugin::AMPACHECONNECT: contents " +\
+ contents.decode(),xbmc.LOGDEBUG)
+ except Exception as e:
+ xbmc.log("AmpachePlugin::AMPACHECONNECT: unable to print contents " + \
+ repr(e) , xbmc.LOGDEBUG)
+ tree=ET.XML(contents)
+ errormess = self.getCodeMessError(tree)
+ if errormess:
+ #connection error
+ xbmcgui.Dialog().notification(ut.tString(30198),ut.tString(30202))
+ raise self.ConnectionError
+ xbmc.log("AmpachePlugin::AMPACHECONNECT ConnectionOk",xbmc.LOGDEBUG)
+ if showok:
+ #use it only if notification of connection is necessary, like
+ #switch server, display connection ok and the name of the
+ #current server
+ amp_notif = ut.tString(30203) + "\n" + ut.tString(30181) +\
+ " : " + self._connectionData["name"]
+ #connection ok
+ xbmcgui.Dialog().notification(ut.tString(30197),amp_notif)
+ self.fillConnectionSettings(tree,nTime)
+ return
+
+ #handle request to the xml api that return binary files
+ def ampache_binary_request(self,action):
+ thisURL = self.build_ampache_url(action)
+ try:
+ headers,contents = self.handle_request(thisURL)
+ except self.ConnectionError:
+ raise self.ConnectionError
+ return headers,contents
+
+ #handle request to the xml api that return xml content
+ def ampache_http_request(self,action):
+ thisURL = self.build_ampache_url(action)
+ try:
+ headers,contents = self.handle_request(thisURL)
+ except self.ConnectionError:
+ raise self.ConnectionError
+ if PY2:
+ contents = contents.replace("\0", "")
+ try:
+ xbmc.log("AmpachePlugin::ampache_http_request: contents " + \
+ contents.decode(),xbmc.LOGDEBUG)
+ except Exception as e:
+ xbmc.log("AmpachePlugin::ampache_http_request: unable print contents " + \
+ repr(e) , xbmc.LOGDEBUG)
+ tree=ET.XML(contents)
+ errormess = self.getCodeMessError(tree)
+ if errormess:
+ raise self.ConnectionError
+ return tree
+
+ def build_ampache_url(self,action):
+ token = self._ampache.getSetting("token")
+ thisURL = self._connectionData["url"] + self.getBaseUrl() + '?action=' + action
+ thisURL += '&auth=' + token
+ if self.limit:
+ thisURL += '&limit=' +str(self.limit)
+ if self.offset:
+ thisURL += '&offset=' +str(self.offset)
+ if self.filter:
+ thisURL += '&filter=' +urllib.parse.quote_plus(str(self.filter))
+ if self.add:
+ thisURL += '&add=' + self.add
+ if self.type:
+ thisURL += '&type=' + self.type
+ if self.mode:
+ thisURL += '&mode=' + self.mode
+ if self.exact:
+ thisURL += '&exact=' + self.exact
+ if self.id:
+ thisURL += '&id=' + self.id
+ if self.rating:
+ thisURL += '&rating=' + self.rating
+ return thisURL
+
diff --git a/plugin.audio.ampache/resources/lib/ampache_monitor.py b/plugin.audio.ampache/resources/lib/ampache_monitor.py
new file mode 100644
index 0000000000..20cf71e787
--- /dev/null
+++ b/plugin.audio.ampache/resources/lib/ampache_monitor.py
@@ -0,0 +1,50 @@
+import xbmc
+import xbmcaddon
+
+#service class
+ampache = xbmcaddon.Addon("plugin.audio.ampache")
+
+from resources.lib.utils import get_objectId_from_fileURL
+
+class AmpacheMonitor( xbmc.Monitor ):
+
+ onPlay = False
+
+ def __init__(self):
+ xbmc.log( 'AmpacheMonitor::ServiceMonitor called', xbmc.LOGDEBUG)
+
+ # start mainloop
+ def run(self):
+ while not self.abortRequested():
+ if self.waitForAbort(1):
+ # Abort was requested while waiting. We should exit
+ break
+
+ def close(self):
+ pass
+
+ def onNotification(self, sender, method, data):
+ #i don't know why i have called monitor.onNotification, but now it
+ #seems useless
+ #xbmc.Monitor.onNotification(self, sender, method, data)
+ xbmc.log('AmpacheMonitor:Notification %s from %s, params: %s' % (method, sender, str(data)))
+
+ #a little hack to avoid calling rate every time a song start
+ if method == 'Player.OnStop':
+ self.onPlay = False
+ if method == 'Player.OnPlay':
+ self.onPlay = True
+ #called on infoChanged ( rating )
+ if method == 'Info.OnChanged' and self.onPlay:
+ #call setRating
+ if xbmc.Player().isPlaying():
+ try:
+ file_url = xbmc.Player().getPlayingFile()
+ #it is not our file
+ if not (get_objectId_from_fileURL( file_url )):
+ return
+ except:
+ xbmc.log("AmpacheMonitor::no playing file " , xbmc.LOGDEBUG)
+ return
+ xbmc.executebuiltin('RunPlugin(plugin://plugin.audio.ampache/?mode=205)')
+
diff --git a/plugin.audio.ampache/resources/lib/ampache_plugin.py b/plugin.audio.ampache/resources/lib/ampache_plugin.py
new file mode 100644
index 0000000000..5f71d53609
--- /dev/null
+++ b/plugin.audio.ampache/resources/lib/ampache_plugin.py
@@ -0,0 +1,1125 @@
+from future import standard_library
+from future.utils import PY2
+standard_library.install_aliases()
+from builtins import str
+from builtins import range
+import xbmc,xbmcaddon,xbmcplugin,xbmcgui
+import urllib.request,urllib.parse,urllib.error
+import sys, os
+import math,random
+import xml.etree.ElementTree as ET
+import threading
+
+#main plugin
+from resources.lib import ampache_connect
+from resources.lib import servers_manager
+from resources.lib import gui
+from resources.lib import utils as ut
+from resources.lib import art
+
+# Shared resources
+
+#addon name : plugin.audio.ampache
+#do not use xbmcaddon.Addon() to avoid crashes when kore app is used ( it is
+#possible to start a song without initialising the plugin
+ampache = xbmcaddon.Addon("plugin.audio.ampache")
+
+def searchGui():
+ dialog = xbmcgui.Dialog()
+ ret = dialog.contextmenu([ut.tString(30106),ut.tString(30107),ut.tString(30108),\
+ ut.tString(30109),ut.tString(30110),ut.tString(30220),ut.tString(30225),ut.tString(30228),ut.tString(30111)])
+ endDir = False
+ if ret == 0:
+ endDir = do_search("artists")
+ elif ret == 1:
+ endDir = do_search("albums")
+ elif ret == 2:
+ endDir = do_search("songs")
+ elif ret == 3:
+ endDir = do_search("playlists")
+ elif ret == 4:
+ endDir = do_search("songs","search_songs")
+ elif ret == 5:
+ endDir = do_search("videos")
+ elif ret == 6:
+ endDir = do_search("podcasts")
+ elif ret == 7:
+ endDir = do_search("songs","live_streams")
+ elif ret == 8:
+ ret2 = dialog.contextmenu([ut.tString(30112),ut.tString(30113),ut.tString(30114)])
+ if(int(ampache.getSetting("api-version"))) < 500000:
+ if ret2 == 0:
+ endDir = do_search("tags","tag_artists")
+ elif ret2 == 1:
+ endDir = do_search("tags","tag_albums")
+ elif ret2 == 2:
+ endDir = do_search("tags","tag_songs")
+ else:
+ if ret2 == 0:
+ endDir = do_search("genres","genre_artists")
+ elif ret2 == 1:
+ endDir = do_search("genres","genre_albums")
+ elif ret2 == 2:
+ endDir = do_search("genres","genre_songs")
+ return endDir
+
+#necessary due the api changes in 6.0
+def get_name(node,amType):
+ if(int(ampache.getSetting("api-version"))) < 600000:
+ artist_name = str(node.findtext(amType))
+ else:
+ artist_name = str(getNestedTypeText(node, "name" ,amType))
+ return artist_name
+
+#return album and artist name, only album could be confusing
+def get_album_artist_name(node):
+
+ disknumber = str(node.findtext("disk"))
+ album_name = str(node.findtext("name"))
+ artist_name = get_name(node,"artist")
+ fullname = album_name
+
+ if PY2:
+ fullname += u" - "
+ else:
+ #no encode utf-8 in python3, not necessary
+ fullname += " - "
+ fullname += artist_name
+ #disknumber = "None" when disk number is not sent
+ if disknumber!="None" and disknumber != "1" and disknumber !="0":
+ if PY2:
+ fullname = fullname + u" - [ " + ut.tString(30195) + u" " +\
+ disknumber + u" ]"
+ else:
+ fullname = fullname + " - [ " + ut.tString(30195) + " " + disknumber + " ]"
+ return fullname
+
+def get_infolabels(elem_type , node):
+ infoLabels = None
+ rating = ut.getRating(node.findtext("rating"))
+ if elem_type == 'album':
+ infoLabels = {
+ #'Title' : str(node.findtext("name")) ,
+ 'Album' : str(node.findtext("name")) ,
+ 'Artist' : get_name(node,"artist"),
+ 'DiscNumber' : str(node.findtext("disk")),
+ 'Year' : node.findtext("year") ,
+ 'UserRating' : rating,
+ 'Mediatype' : 'album'
+ }
+
+ elif elem_type == 'artist':
+ infoLabels = {
+ #'Title' : str(node.findtext("name")) ,
+ 'Artist' : str(node.findtext("name")),
+ 'Mediatype' : 'artist'
+ }
+
+ elif elem_type == 'song':
+ infoLabels = {
+ 'Title' : str(node.findtext("title")) ,
+ 'Artist' : get_name(node,"artist"),
+ 'Album' : get_name(node,"album"),
+ 'Size' : node.findtext("size") ,
+ 'Duration' : node.findtext("time"),
+ 'Year' : node.findtext("year") ,
+ 'TrackNumber' : node.findtext("track"),
+ 'UserRating' : rating,
+ 'Mediatype' : 'song'
+ }
+
+ elif elem_type == 'podcast_episode':
+ infoLabels = {
+ 'Title' : str(node.findtext("title")) ,
+ 'UserRating' : rating,
+ 'Mediatype' : 'song'
+ }
+
+ elif elem_type == 'video':
+ infoLabels = {
+ 'Title' : str(node.findtext("name")) ,
+ 'Size' : node.findtext("size") ,
+ 'Mediatype' : 'video'
+ }
+
+ return infoLabels
+
+def getNestedTypeText(node, elem_tag ,elem_type):
+ try:
+ obj_elem = node.find(elem_type)
+ if obj_elem is not None or obj_elem != '':
+ obj_tag = obj_elem.findtext(elem_tag)
+ return obj_tag
+ except:
+ return None
+ return None
+
+def getNestedTypeId(node,elem_type):
+ try:
+ obj_elem = node.find(elem_type)
+ if obj_elem is not None or obj_elem != '':
+ obj_id = obj_elem.attrib["id"]
+ return obj_id
+ except:
+ return None
+ return None
+
+#this function is used to speed up the loading of the images using differents
+#theads, one for request
+def precacheArt(elem,elem_type):
+
+ allid=set()
+ if elem_type != "album" and elem_type != "song" and\
+ elem_type != "artist" and elem_type != "podcast" and elem_type!= "playlist":
+ return
+
+ threadList = []
+ for node in elem.iter(elem_type):
+ if elem_type == "song":
+ art_type = "album"
+ object_id = getNestedTypeId(node, "album")
+ else:
+ art_type = elem_type
+ object_id = node.attrib["id"]
+ #avoid to have duplicate threads with the same object_id
+ if object_id not in allid:
+ allid.add(object_id)
+ else:
+ continue
+ image_url = node.findtext("art")
+ if not object_id or not image_url:
+ continue
+ x = threading.Thread(target=art.get_art,args=(object_id,art_type,image_url,))
+ threadList.append(x)
+ #start threads
+ for x in threadList:
+ x.start()
+ #join threads
+ for x in threadList:
+ x.join()
+
+def addLinks(elem,elem_type,useCacheArt,mode):
+
+ image = "DefaultFolder.png"
+ it=[]
+ allid = set()
+
+ for node in elem.iter(elem_type):
+ cm = []
+ object_id = node.attrib["id"]
+ if not object_id:
+ continue
+
+ name = str(node.findtext("name"))
+
+ if elem_type == "album":
+ #remove duplicates in album names ( workaround for a problem in server comunication )
+ if object_id not in allid:
+ allid.add(object_id)
+ else:
+ continue
+ artist_id = getNestedTypeId(node, "artist")
+ if artist_id:
+ cm.append( ( ut.tString(30141),"Container.Update(%s?object_id=%s&mode=1&submode=6)" %
+ ( sys.argv[0],artist_id ) ) )
+
+ name = get_album_artist_name(node)
+ if useCacheArt:
+ image_url = node.findtext("art")
+ image = art.get_art(object_id,elem_type,image_url)
+ elif elem_type == "artist":
+ if useCacheArt:
+ image_url = node.findtext("art")
+ image = art.get_art(object_id,elem_type,image_url)
+ elif elem_type == "podcast":
+ if useCacheArt:
+ image = art.get_art(object_id,"podcast")
+ elif elem_type == "playlist":
+ if useCacheArt:
+ image = art.get_art(object_id,"playlist")
+ try:
+ numItems = str(node.findtext("items"))
+ name = name + " (" + numItems + ")"
+ except:
+ pass
+ else:
+ useCacheArt = False
+
+ infoLabels=get_infolabels(elem_type,node)
+
+ if infoLabels == None:
+ infoLabels={ "Title": name }
+
+ liz=xbmcgui.ListItem(name)
+ liz.setInfo( type="Music", infoLabels=infoLabels )
+
+ if useCacheArt:
+ #faster loading for libraries
+ liz.setArt( art.get_artLabels(image) )
+ liz.setProperty('IsPlayable', 'false')
+
+ if cm:
+ liz.addContextMenuItems(cm)
+
+ u=sys.argv[0]+"?object_id="+object_id+"&mode="+str(mode)+"&submode=71"
+ #xbmc.log("AmpachePlugin::addLinks: u - " + u, xbmc.LOGDEBUG )
+ isFolder=True
+ tu= (u,liz,isFolder)
+ it.append(tu)
+
+ xbmcplugin.addDirectoryItems(handle=int(sys.argv[1]),items=it,totalItems=len(elem))
+
+# Used to populate items for songs on XBMC. Calls plugin script with mode ==
+# 45 and play_url == (ampache item url)
+def addPlayLinks(elem, elem_type):
+
+ it=[]
+
+ #we don't use sort method for track cause songs are already sorted
+ #by the server and it make a mess in random playlists
+ if elem_type == "video":
+ xbmcplugin.addSortMethod(int(sys.argv[1]),xbmcplugin.SORT_METHOD_TITLE)
+ elif elem_type == "podcast_episode":
+ xbmcplugin.addSortMethod(int(sys.argv[1]),xbmcplugin.SORT_METHOD_DATE)
+
+ allid=set()
+ albumTrack={}
+
+ for node in elem.iter(elem_type):
+ object_id = node.attrib["id"]
+ if not object_id:
+ continue
+
+ play_url = str(node.findtext("url"))
+ object_title = str(node.findtext("title"))
+ if elem_type == "live_stream":
+ object_title = str(node.findtext("name"))
+
+ liz=xbmcgui.ListItem(object_title)
+ liz.setProperty("IsPlayable", "true")
+ liz.setPath(play_url)
+
+ if elem_type == "song":
+ image_url = node.findtext("art")
+ #speed up art management for album songs, avoid duplicate
+ #calls
+ album_id = getNestedTypeId(node,"album")
+ if album_id:
+ if album_id not in allid:
+ allid.add(album_id)
+ albumArt = art.get_art(album_id,"album",image_url)
+ albumTrack[album_id]=albumArt
+ else:
+ albumArt=albumTrack[album_id]
+ else:
+ albumArt = art.get_art(None,"album",image_url)
+
+ liz.setArt( art.get_artLabels(albumArt) )
+ liz.setInfo( type="music", infoLabels=get_infolabels("song", node) )
+ liz.setMimeType(node.findtext("mime"))
+
+ cm = []
+
+ artist_id = getNestedTypeId(node, "artist")
+ if artist_id:
+ cm.append( ( ut.tString(30138),
+ "Container.Update(%s?object_id=%s&mode=1&submode=6)" % (
+ sys.argv[0],artist_id ) ) )
+
+ if album_id:
+ cm.append( ( ut.tString(30139),
+ "Container.Update(%s?object_id=%s&mode=2&submode=6)" % (
+ sys.argv[0],album_id ) ) )
+
+ cm.append( ( ut.tString(30140),
+ "Container.Update(%s?title=%s&mode=3&submode=12)" % (
+ sys.argv[0],urllib.parse.quote_plus(object_title) ) ) )
+
+ if cm != []:
+ liz.addContextMenuItems(cm)
+ elif elem_type == "podcast_episode":
+ liz.setInfo( type="music", infoLabels=get_infolabels(elem_type, node) )
+ elif elem_type == "video":
+ liz.setInfo( type="video", infoLabels=get_infolabels("video", node) )
+ liz.setMimeType(node.findtext("mime"))
+
+ track_parameters = { "mode": 200, "play_url" : play_url}
+ url = sys.argv[0] + '?' + urllib.parse.urlencode(track_parameters)
+ tu= (url,liz)
+ it.append(tu)
+
+ xbmcplugin.addDirectoryItems(handle=int(sys.argv[1]),items=it,totalItems=len(elem))
+
+#The function that actually plays an Ampache URL by using setResolvedUrl
+def play_track(url):
+ if url == None:
+ xbmc.log("AmpachePlugin::play_track url null", xbmc.LOGINFO )
+ return
+
+ #read here the setting, cause delay problems
+ autofull = ut.strBool_to_bool(ampache.getSetting("auto-fullscreen"))
+
+ liz = xbmcgui.ListItem()
+ liz.setPath(url)
+
+ xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=True,listitem=liz)
+
+ #enable auto fullscreen playing the track ( closes #17 )
+ if autofull is True:
+ xbmc.executebuiltin("ActivateWindow(visualisation)")
+
+#Main function to add xbmc plugin elements
+def addDir(name,mode,submode,offset=None,object_id=None):
+ infoLabels={ "Title": name }
+
+ liz=xbmcgui.ListItem(name)
+ liz.setInfo( type="Music", infoLabels=infoLabels )
+ liz.setProperty('IsPlayable', 'false')
+
+ handle=int(sys.argv[1])
+
+ u=sys.argv[0]+"?mode="+str(mode)+"&submode="+str(submode)
+ #offset, in case of very long lists
+ if offset:
+ u = u + "&offset="+str(offset)
+ if object_id:
+ u = u + "&object_id="+object_id
+ xbmc.log("AmpachePlugin::addDir url " + u, xbmc.LOGDEBUG)
+ xbmcplugin.addDirectoryItem(handle=handle,url=u,listitem=liz,isFolder=True)
+
+#this function add items to the directory using the low level addLinks of ddSongLinks functions
+def addItems( object_type, elem, object_subtype=None,precache=True):
+
+ ut.setContent(int(sys.argv[1]), object_type)
+
+ xbmc.log("AmpachePlugin::addItems: object_type - " + str(object_type) , xbmc.LOGDEBUG )
+ if object_subtype:
+ xbmc.log("AmpachePlugin::addItems: object_subtype - " + str(object_subtype) , xbmc.LOGDEBUG )
+
+ elem_type = ut.otype_to_type(object_type,object_subtype)
+ xbmc.log("AmpachePlugin::addItems: elem_type - " + str(elem_type) , xbmc.LOGDEBUG )
+
+ useCacheArt = True
+
+ if elem_type != "song":
+ limit = len(elem.findall(elem_type))
+ if limit > 100:
+ #to not overload servers
+ if (not ut.strBool_to_bool(ampache.getSetting("images-long-list"))):
+ useCacheArt = False
+
+ if useCacheArt and precache:
+ precacheArt(elem,elem_type)
+
+ if object_type == 'songs' or object_type == 'videos':
+ addPlayLinks(elem,elem_type)
+ else:
+ #set the mode
+ mode = ut.otype_to_mode(object_type, object_subtype)
+ addLinks(elem,elem_type,useCacheArt,mode)
+ return
+
+def get_all(object_type, mode ,offset=None):
+ if offset == None:
+ offset=0
+ try:
+ limit = int(ampache.getSetting(object_type))
+ if limit == 0:
+ return
+ except:
+ return
+
+ step = 500
+ newLimit = offset+step
+ get_items(object_type, limit=step, offset=offset)
+ if newLimit < limit:
+ pass
+ else:
+ newLimit = None
+
+ if newLimit:
+ addDir(ut.tString(30194),mode,5,offset=newLimit)
+
+#this functions handles the majority of the requests to the server
+#so, we have a lot of optional params
+def get_items(object_type, object_id=None, add=None,\
+ thisFilter=None,limit=5000, object_subtype=None,\
+ exact=None, offset=None ):
+
+ if object_type:
+ xbmc.log("AmpachePlugin::get_items: object_type " + object_type, xbmc.LOGDEBUG)
+ else:
+ #it should be not possible
+ xbmc.log("AmpachePlugin::get_items: object_type set to None" , xbmc.LOGDEBUG)
+ return
+
+ if object_subtype:
+ xbmc.log("AmpachePlugin::get_items: object_subtype " + object_subtype, xbmc.LOGDEBUG)
+
+ #object_id could be None in some requests, like recently added and get_all
+ #items
+ if object_id:
+ xbmc.log("AmpachePlugin::get_items: object_id " + object_id, xbmc.LOGDEBUG)
+
+ if limit == None:
+ limit = int(ampache.getSetting(object_type))
+
+ #default: object_type is the action,otherwise see the if list below
+ action = object_type
+
+ artist_action_subtypes = [
+ 'artist_albums','tag_albums','genre_albums','album']
+
+ album_action_subtypes = [ 'tag_artists','genre_artists','artist']
+
+ song_action_subtypes = [ 'tag_songs','genre_songs', 'playlist_songs',
+ 'album_songs', 'artist_songs','search_songs',
+ 'podcast_episodes','live_streams']
+
+ #do not use action = object_subtype cause in tags it is used only to
+ #discriminate between subtypes
+ if object_type == 'albums':
+ if object_subtype == 'artist_albums':
+ addDir("All Songs",1,72, object_id=object_id)
+ #do not use elif, artist_albums is checked two times
+ if object_subtype in artist_action_subtypes:
+ action = object_subtype
+ elif object_type == 'artists':
+ if object_subtype in album_action_subtypes:
+ action = object_subtype
+ elif object_type == 'songs':
+ if object_subtype in song_action_subtypes:
+ action = object_subtype
+
+ if object_id:
+ thisFilter = object_id
+
+ #here the documentation for an ampache connection
+ #first create the connection object
+ #second choose the api function to call in action variable
+ #third add params using public AmpacheConnect attributes
+ #( i know, it is ugly, but python doesnt' support structs, so..., if
+ #someone has a better idea, i'm open to change )
+ #if the params are not set, they simply are not added to the url
+ #forth call ampache_http_request if the server return an xml file
+ #or ampache_binary_request if the server return a binary file (eg. an
+ #image )
+ #it could be very simply to add json api, but we have to rewrite all
+ #function that rely on xml input, like additems
+
+ try:
+ ampConn = ampache_connect.AmpacheConnect()
+ ampConn.add = add
+ ampConn.filter = thisFilter
+ ampConn.limit = limit
+ ampConn.exact = exact
+ ampConn.offset = offset
+
+ elem = ampConn.ampache_http_request(action)
+ addItems( object_type, elem, object_subtype)
+ except:
+ return
+
+
+def setRating():
+ try:
+ file_url = xbmc.Player().getPlayingFile()
+ xbmc.log("AmpachePlugin::setRating url " + file_url , xbmc.LOGDEBUG)
+ except:
+ xbmc.log("AmpachePlugin::no playing file " , xbmc.LOGDEBUG)
+ return
+
+ object_id = ut.get_objectId_from_fileURL( file_url )
+ if not object_id:
+ return
+ rating = xbmc.getInfoLabel('MusicPlayer.UserRating')
+ if rating == "":
+ rating = "0"
+
+ xbmc.log("AmpachePlugin::setRating, user Rating " + rating , xbmc.LOGDEBUG)
+ #converts from five stats ampache rating to ten stars kodi rating
+ amp_rating = math.ceil(int(rating)/2.0)
+
+ try:
+ ampConn = ampache_connect.AmpacheConnect()
+
+ action = "rate"
+ ampConn.id = object_id
+ ampConn.type = "song"
+ ampConn.rating = str(amp_rating)
+
+ ampConn.ampache_http_request(action)
+ except:
+ #do nothing
+ return
+
+def do_search(object_type,object_subtype=None,thisFilter=None):
+ """
+ do_search(object_type,object_subtype=None,thisFilter=None) -> boolean
+ requires:
+ object_type : ( albums, songs... )
+ object_subtype : ( search song, tag artists )
+ filter : the test to search
+ return true or false, used to check if call endDirectoryItem or not
+ """
+ if not thisFilter:
+ thisFilter = gui.getFilterFromUser()
+ if thisFilter:
+ get_items(object_type=object_type,thisFilter=thisFilter,object_subtype=object_subtype)
+ return True
+ return False
+
+def get_stats(object_type, object_subtype=None, limit=5000 ):
+
+ xbmc.log("AmpachePlugin::get_stats ", xbmc.LOGDEBUG)
+
+ action = 'stats'
+ if(int(ampache.getSetting("api-version"))) < 400001:
+ amtype = object_subtype
+ thisFilter = None
+ else:
+ amtype = ut.otype_to_type(object_type)
+ thisFilter = object_subtype
+
+ try:
+ ampConn = ampache_connect.AmpacheConnect()
+
+ ampConn.filter = thisFilter
+ ampConn.limit = limit
+ ampConn.type = amtype
+
+ elem = ampConn.ampache_http_request(action)
+ addItems( object_type, elem)
+ except:
+ return
+
+def get_recent(object_type,submode,object_subtype=None):
+
+ if submode == 31:
+ update = ampache.getSetting("add")
+ xbmc.log(update[:10],xbmc.LOGINFO)
+ get_items(object_type=object_type,add=update[:10],object_subtype=object_subtype)
+ elif submode == 32:
+ get_items(object_type=object_type,add=ut.get_time(-7),object_subtype=object_subtype)
+ elif submode == 33:
+ get_items(object_type=object_type,add=ut.get_time(-30),object_subtype=object_subtype)
+ elif submode == 34:
+ get_items(object_type=object_type,add=ut.get_time(-90),object_subtype=object_subtype)
+
+def get_random(object_type, num_items):
+ #object type can be : albums, artists, songs, playlists
+
+ tot_items = int(ampache.getSetting(object_type))
+
+ xbmc.log("AmpachePlugin::get_random: object_type " + object_type + " num_items " + str(num_items) + " tot_items " +\
+ str(tot_items), xbmc.LOGDEBUG)
+
+ if num_items > tot_items:
+ #if tot_items are less than num_itmes, return all items
+ get_items(object_type, limit=tot_items)
+ return
+
+ seq = random.sample(list(range(tot_items)),num_items)
+ action = object_type
+ xbmc.log("AmpachePlugin::get_random: seq " + str(seq), xbmc.LOGDEBUG )
+ ampConn = ampache_connect.AmpacheConnect()
+ for item_id in seq:
+ try:
+ ampConn.offset = item_id
+ ampConn.limit = 1
+ elem = ampConn.ampache_http_request(action)
+ addItems( object_type, elem,precache=False)
+ except:
+ pass
+
+def switchFromMusicPlaylist(addon_url, mode, submode, object_id=None, title=None):
+ """
+ this function checks if musicplaylist window is active and switchs to the music window
+ necessary when we have to call a function like "get album from this
+ artist"
+ """
+ if xbmc.getCondVisibility("Window.IsActive(musicplaylist)"):
+ #close busydialog to activate music window
+ #remove the line below once the busydialog bug is correct
+ xbmc.executebuiltin('Dialog.Close(busydialog)')
+ xbmc.executebuiltin("ActivateWindow(music)")
+ if object_id:
+ xbmc.executebuiltin("Container.Update(%s?object_id=%s&mode=%s&submode=%s)" %\
+ ( addon_url,object_id, mode, submode ) )
+ elif title:
+ xbmc.executebuiltin("Container.Update(%s?title=%s&mode=%s&submode=%s)" %\
+ ( addon_url,title, mode, submode ) )
+
+
+def main_params(plugin_url):
+ """
+ main_params(plugin_url) -> associative array
+ this function extracts the params from plugin url
+ and put the in an associative array
+ not all params are present in url so we need to handle it with exceptions
+ """
+ m_params={}
+ m_params['mode'] = None
+ m_params['submode'] = None
+ m_params['object_id'] = None
+ m_params['title'] = None
+ #used only in play tracks
+ m_params['play_url'] = None
+ #used to managed very long lists
+ m_params['offset'] = None
+
+ params=ut.get_params(plugin_url)
+
+ try:
+ m_params['mode']=int(params["mode"])
+ xbmc.log("AmpachePlugin::mode " + str(m_params['mode']), xbmc.LOGDEBUG)
+ except:
+ pass
+ try:
+ m_params['submode']=int(params["submode"])
+ xbmc.log("AmpachePlugin::submode " + str(m_params['submode']), xbmc.LOGDEBUG)
+ except:
+ pass
+ try:
+ m_params['object_id']=params["object_id"]
+ xbmc.log("AmpachePlugin::object_id " + m_params['object_id'], xbmc.LOGDEBUG)
+ except:
+ pass
+ try:
+ m_params['title']=urllib.parse.unquote_plus(params["title"])
+ xbmc.log("AmpachePlugin::title " + m_params['title'], xbmc.LOGDEBUG)
+ except:
+ pass
+ try:
+ m_params['play_url']=urllib.parse.unquote_plus(params["play_url"])
+ xbmc.log("AmpachePlugin::play_url " + m_params['play_url'], xbmc.LOGDEBUG)
+ except:
+ pass
+ try:
+ m_params['offset']=int(params["offset"])
+ xbmc.log("AmpachePlugin::offset " + str(m_params['offset']), xbmc.LOGDEBUG)
+ except:
+ pass
+
+ return m_params
+
+#add new line in case of new stat function implemented, checking the version
+#in menus
+def manage_stats_menu(object_type,submode):
+
+ num_items = (int(ampache.getSetting("random_items"))*3)+3
+ apiVersion = int(ampache.getSetting("api-version"))
+
+ if submode == 40:
+ #playlists are not in the new stats api, so, use the old mode
+ if(apiVersion < 400001 or (object_type == 'playlists' and apiVersion < 510000 )):
+ get_random(object_type, num_items)
+ else:
+ get_stats(object_type=object_type,object_subtype="random",limit=num_items)
+ elif submode == 41:
+ get_stats(object_type=object_type,object_subtype="highest",limit=num_items)
+ elif submode == 42:
+ get_stats(object_type=object_type,object_subtype="frequent",limit=num_items)
+ elif submode == 43:
+ get_stats(object_type=object_type,object_subtype="flagged",limit=num_items)
+ elif submode == 44:
+ get_stats(object_type=object_type,object_subtype="forgotten",limit=num_items)
+ elif submode == 45:
+ get_stats(object_type=object_type,object_subtype="newest",limit=num_items)
+ elif submode == 46:
+ get_stats(object_type=object_type,object_subtype="recent",limit=num_items)
+
+def Main():
+
+ mode=None
+ object_id=None
+ #sometimes we need to not endDirectory, but
+ #we need to check if the connection is alive
+ #until endDirectoryMode -> endDirectoy and checkConnection
+ #from endDirectoryMode to endCheckConnection -> no endDirectory but checkConnection
+ #else no end and no check
+ endDirectoryMode = 200
+ endCheckConnection = 300
+ modeMax = 1000
+ endDir = True
+
+ addon_url = sys.argv[0]
+ handle = int(sys.argv[1])
+ plugin_url=sys.argv[2]
+
+ xbmc.log("AmpachePlugin::init handle: " + str(handle) + " url: " + plugin_url, xbmc.LOGDEBUG)
+
+ m_params=main_params(plugin_url)
+ #faster to change variable
+ mode = m_params['mode']
+ submode = m_params['submode']
+ object_id = m_params['object_id']
+
+ #check if the connection is expired
+ #connect to the server
+ #do not connect on main screen and when we operate setting;
+ #do not block the main screen in case the connection to a server it is not available and we kwow it
+ if mode!=None and mode < endCheckConnection:
+ if ut.check_tokenexp():
+ try:
+ #check server file only when necessary
+ servers_manager.initializeServer()
+ ampacheConnect = ampache_connect.AmpacheConnect()
+ ampacheConnect.AMPACHECONNECT()
+ except:
+ pass
+
+ apiVersion = int(ampache.getSetting("api-version"))
+
+ #start menu
+ if mode==None:
+ #search
+ addDir(ut.tString(30101),53,None)
+ #quick access
+ addDir(ut.tString(30102),52,None)
+ #explore
+ addDir(ut.tString(30103),50,None)
+ #library
+ addDir(ut.tString(30104),51,None)
+ #switch server
+ addDir(ut.tString(30023),304,None)
+ #settings
+ addDir(ut.tString(30105),300,None)
+
+ #artist mode
+ elif mode==1:
+ #artist, album, songs, playlist follow the same structure
+ #get all artists
+ if submode == 5:
+ get_all("artists", mode ,m_params['offset'])
+ #get the artist from this album's artist_id
+ elif submode == 6:
+ switchFromMusicPlaylist(addon_url, mode, submode, object_id=object_id )
+ get_items(object_type="artists",object_id=object_id,object_subtype="artist")
+ #search function
+ #10-30 search
+ elif submode == 10:
+ endDir = do_search("artists")
+ #recent function
+ #30-40 recent
+ elif submode > 30 and submode < 35:
+ get_recent( "artists", submode )
+ #submode between 40-46( random.. recent )
+ #40-70 stats
+ elif submode >= 40 and submode <= 46:
+ manage_stats_menu("artists",submode)
+ #get all albums from an artist_id
+ elif submode == 71:
+ get_items(object_type="albums",object_id=object_id,object_subtype="artist_albums")
+ #get all songs from an artist_id
+ elif submode == 72:
+ get_items(object_type="songs",object_id=object_id,object_subtype="artist_songs" )
+
+
+ #albums mode
+ elif mode==2:
+ #get all albums
+ if submode == 5:
+ get_all("albums", mode ,m_params['offset'])
+ #get the album from the song's album_id
+ elif submode == 6:
+ switchFromMusicPlaylist(addon_url, mode, submode, object_id=object_id )
+ get_items(object_type="albums",object_id=object_id,object_subtype="album")
+ elif submode == 10:
+ endDir = do_search("albums")
+ elif submode > 30 and submode < 35:
+ get_recent( "albums", submode )
+ elif submode >= 40 and submode <= 46:
+ manage_stats_menu("albums",submode)
+ #get all songs from an album_id
+ elif submode == 71:
+ get_items(object_type="songs",object_id=object_id,object_subtype="album_songs")
+
+ #song mode
+ elif mode == 3:
+ #10-30 search
+ if submode == 10:
+ endDir = do_search("songs")
+ # submode 11 : search all
+ elif submode == 11:
+ endDir = do_search("songs","search_songs")
+ #get all song with this title
+ elif submode == 12:
+ switchFromMusicPlaylist(addon_url, mode,submode,title=m_params['title'] )
+ endDir = do_search("songs",thisFilter=m_params['title'])
+ #30-40 recent
+ elif submode > 30 and submode < 35:
+ get_recent( "songs", submode )
+ #40-70 stats
+ elif submode >= 40 and submode <= 46:
+ manage_stats_menu("songs",submode)
+
+ #playlist mode
+ elif mode==4:
+ if submode == 5:
+ get_all("playlists", mode ,m_params['offset'])
+ elif submode == 10:
+ endDir = do_search("playlists")
+ elif submode > 30 and submode < 35:
+ get_recent( "playlists", submode )
+ elif submode == 40:
+ manage_stats_menu("playlists", submode)
+ #get all songs from a playlist_id
+ elif submode == 71:
+ get_items(object_type="songs",object_id=object_id,object_subtype="playlist_songs")
+
+ #podcasts
+ elif mode==5:
+ if submode == 5:
+ get_all("podcasts", mode ,m_params['offset'])
+ elif submode == 10:
+ endDir = do_search("podcasts")
+ #get all episodes
+ elif submode == 71:
+ if apiVersion >= 440000:
+ get_items(object_type="songs",object_id=object_id,object_subtype="podcast_episodes")
+
+ #live_streams
+ elif mode==6:
+ if submode == 10:
+ endDir = do_search("songs","live_streams")
+ #get all streams
+ elif submode == 71:
+ if apiVersion >= 440000:
+ get_items(object_type="songs",object_id=object_id,object_subtype="live_streams")
+
+ #video
+ elif mode==8:
+ if submode == 5:
+ get_all("videos", mode ,m_params['offset'])
+ elif submode == 10:
+ endDir = do_search("videos")
+
+ #19-21 tags/genres mode
+ elif mode>=19 and mode <=21:
+ object_type, object_subtype = ut.mode_to_tags(mode)
+ #get_all tags/genres
+ if submode == 5:
+ get_items(object_type = object_type, object_subtype=object_subtype)
+ #search tag/genre
+ elif submode == 10:
+ endDir = do_search(object_type,object_subtype)
+ #get all songs from a tag_id/genre_id
+ elif submode == 71:
+ if mode == 19:
+ get_items(object_type="artists", object_subtype=object_subtype,object_id=object_id)
+ elif mode == 20:
+ get_items(object_type="albums", object_subtype=object_subtype,object_id=object_id)
+ elif mode == 21:
+ get_items(object_type="songs", object_subtype=object_subtype,object_id=object_id)
+
+ #main menus 50-100
+ #explore
+ elif mode==50:
+ #recently added
+ addDir(ut.tString(30145),107,None)
+ #random
+ addDir(ut.tString(30146),100,None)
+ if apiVersion >= 400001:
+ #highest
+ addDir(ut.tString(30148),101,None)
+ #frequent
+ addDir(ut.tString(30164),102,None)
+ #flagged
+ addDir(ut.tString(30165),103,None)
+ #forgotten
+ addDir(ut.tString(30166),104,None)
+ #newest
+ addDir(ut.tString(30167),105,None)
+ #recent
+ addDir(ut.tString(30193),106,None)
+
+ #Library
+ elif mode==51:
+ addDir(ut.tString(30115) +" (" + ampache.getSetting("artists")+ ")",1,5)
+ addDir(ut.tString(30116) + " (" + ampache.getSetting("albums") + ")",2,5)
+ addDir(ut.tString(30118) + " (" + ampache.getSetting("playlists")+ ")",4,5)
+ if ampache.getSetting("videos"):
+ addDir(ut.tString(30221) + " (" + ampache.getSetting("videos")+ ")",8,5)
+ if ampache.getSetting("podcasts"):
+ addDir(ut.tString(30226) + " (" + ampache.getSetting("podcasts")+ ")",5,5)
+ if ampache.getSetting("live_streams"):
+ addDir(ut.tString(30229) + " (" +
+ ampache.getSetting("live_streams")+ ")",6,71)
+ if apiVersion >= 380001:
+ #get all tags ( submode 5 )
+ addDir(ut.tString(30119),54,5)
+
+ #quick access
+ elif mode==52:
+ #random album
+ addDir(ut.tString(30135),2,40)
+ if apiVersion >= 400001:
+ #newest albums
+ addDir(ut.tString(30162),2,45)
+ #frequent albums
+ addDir(ut.tString(30153),2,42)
+ #recently played albums
+ addDir(ut.tString(30191),2,46)
+ else:
+ #use recently added albums for old api versions
+ addDir(ut.tString(30127),55,32)
+ #server playlist ( AKA random songs )
+ addDir(ut.tString(30147),3,40)
+
+ #search mode
+ elif mode==53:
+ if not (ut.strBool_to_bool(ampache.getSetting("old-search-gui"))):
+ endDir = searchGui()
+ else:
+ #old search gui
+ #search artist
+ addDir(ut.tString(30120),1,10)
+ #search album
+ addDir(ut.tString(30121),2,10)
+ #search song
+ addDir(ut.tString(30122),3,10)
+ #search playlist
+ addDir(ut.tString(30123),4,10)
+ #search all
+ addDir(ut.tString(30124),3,11)
+ #search tag
+ addDir(ut.tString(30125),54,10)
+ #search video
+ addDir(ut.tString(30222),8,10)
+ #search podcast
+ addDir(ut.tString(30227),5,10)
+ #search live_streams
+ addDir(ut.tString(30230),6,10)
+
+ #search tags
+ elif mode==54:
+ #search tag_artist
+ addDir(ut.tString(30142),19,submode)
+ #search tag_album
+ addDir(ut.tString(30143),20,submode)
+ #search tag_song
+ addDir(ut.tString(30144),21,submode)
+
+ #screen with recent time possibilities ( subscreen of recent artists,
+ #recent albums, recent songs )
+ elif mode==55:
+ mode_new = submode - 30
+
+ #last update
+ addDir(ut.tString(30130),mode_new,31)
+ #1 week
+ addDir(ut.tString(30131),mode_new,32)
+ addDir(ut.tString(30132),mode_new,33)
+ addDir(ut.tString(30133),mode_new,34)
+
+
+ #stats 100-150
+ #random
+ elif mode==100:
+ #artists
+ addDir(ut.tString(30134),1,40)
+ #albums
+ addDir(ut.tString(30135),2,40)
+ #songs
+ addDir(ut.tString(30136),3,40)
+ #playlists
+ addDir(ut.tString(30137),4,40)
+
+ #highest
+ elif mode==101:
+ #artists
+ addDir(ut.tString(30149),1,41)
+ #albums
+ addDir(ut.tString(30150),2,41)
+ #songs
+ addDir(ut.tString(30151),3,41)
+
+ #frequent
+ elif mode==102:
+ addDir(ut.tString(30152),1,42)
+ addDir(ut.tString(30153),2,42)
+ addDir(ut.tString(30154),3,42)
+
+ #flagged
+ elif mode==103:
+ addDir(ut.tString(30155),1,43)
+ addDir(ut.tString(30156),2,43)
+ addDir(ut.tString(30157),3,43)
+
+ #forgotten
+ elif mode==104:
+ addDir(ut.tString(30158),1,44)
+ addDir(ut.tString(30159),2,44)
+ addDir(ut.tString(30160),3,44)
+
+ #newest
+ elif mode==105:
+ addDir(ut.tString(30161),1,45)
+ addDir(ut.tString(30162),2,45)
+ addDir(ut.tString(30163),3,45)
+
+ #recently added
+ elif mode==106:
+ addDir(ut.tString(30190),1,46)
+ addDir(ut.tString(30191),2,46)
+ addDir(ut.tString(30192),3,46)
+
+ # recent
+ elif mode==107:
+ #recently added artist
+ addDir(ut.tString(30126),55,31)
+ #recently added album
+ addDir(ut.tString(30127),55,32)
+ #recently added song
+ addDir(ut.tString(30128),55,33)
+ #recently added playlist
+ addDir(ut.tString(30129),55,34)
+
+
+ #others mode 200-250
+ #play track mode ( mode set in add_links function )
+ #mode 200 to avoid endDirectory
+ elif mode==200:
+ #workaround busydialog bug
+ xbmc.executebuiltin('Dialog.Close(busydialog)')
+ play_track(m_params['play_url'])
+
+ #change rating
+ elif mode==205:
+ setRating()
+
+ #settings mode 300-350
+ #settings
+ elif mode==300:
+ ampache.openSettings()
+
+ #the four modes below are used to manage servers
+ elif mode==301:
+ servers_manager.initializeServer()
+ if servers_manager.addServer():
+ servers_manager.switchServer()
+
+ elif mode==302:
+ servers_manager.initializeServer()
+ if servers_manager.deleteServer():
+ servers_manager.switchServer()
+
+ elif mode==303:
+ servers_manager.initializeServer()
+ servers_manager.modifyServer()
+
+ elif mode==304:
+ servers_manager.initializeServer()
+ servers_manager.switchServer()
+
+ #no end directory item ( problem with failed searches )
+ #endDir is the result of the search function
+ if endDir == False:
+ mode = modeMax
+
+ if mode == None or mode < endDirectoryMode:
+ xbmc.log("AmpachePlugin::endOfDirectory " + str(handle), xbmc.LOGDEBUG)
+ xbmcplugin.endOfDirectory(handle)
+
+
diff --git a/plugin.audio.ampache/resources/lib/ampache_service.py b/plugin.audio.ampache/resources/lib/ampache_service.py
new file mode 100644
index 0000000000..b2eb0d751f
--- /dev/null
+++ b/plugin.audio.ampache/resources/lib/ampache_service.py
@@ -0,0 +1,7 @@
+from resources.lib.ampache_monitor import AmpacheMonitor
+from resources.lib.art_clean import clean_cache_art, clean_settings
+
+def Main():
+ clean_settings()
+ clean_cache_art()
+ AmpacheMonitor().run()
diff --git a/plugin.audio.ampache/resources/lib/art.py b/plugin.audio.ampache/resources/lib/art.py
new file mode 100644
index 0000000000..a62b267848
--- /dev/null
+++ b/plugin.audio.ampache/resources/lib/art.py
@@ -0,0 +1,108 @@
+from future.utils import PY2
+import os
+import cgi
+import xbmc,xbmcaddon
+import xbmcvfs
+
+#main plugin library
+
+from resources.lib import ampache_connect
+
+ampache = xbmcaddon.Addon("plugin.audio.ampache")
+
+#different functions in kodi 19 (python3) and kodi 18 (python2)
+if PY2:
+ user_dir = xbmc.translatePath( ampache.getAddonInfo('profile'))
+ user_dir = user_dir.decode('utf-8')
+else:
+ user_dir = xbmcvfs.translatePath( ampache.getAddonInfo('profile'))
+user_mediaDir = os.path.join( user_dir , 'media' )
+cacheDir = os.path.join( user_mediaDir , 'cache' )
+
+def cacheArt(imageID,elem_type,url=None):
+ if not imageID and not url:
+ raise NameError
+
+ cacheDirType = os.path.join( cacheDir , elem_type )
+
+ possible_ext = ["jpg", "png" , "bmp", "gif", "tiff"]
+ for ext in possible_ext:
+ imageName = imageID + "." + ext
+ pathImage = os.path.join( cacheDirType , imageName )
+ if os.path.exists( pathImage ):
+ xbmc.log("AmpachePlugin::CacheArt: cached, id " + imageID + " extension " + ext ,xbmc.LOGDEBUG)
+ return pathImage
+
+ #no return, not found
+ ampacheConnect = ampache_connect.AmpacheConnect()
+ action = 'get_art'
+ ampacheConnect.id = imageID
+ ampacheConnect.type = elem_type
+
+ try:
+ if(int(ampache.getSetting("api-version"))) < 400001:
+ #old api version
+ headers,contents = ampacheConnect.handle_request(url)
+ else:
+ headers,contents = ampacheConnect.ampache_binary_request(action)
+ except AmpacheConnect.ConnectionError:
+ raise NameError
+ #xbmc.log("AmpachePlugin::CacheArt: File needs fetching, id " + imageID,xbmc.LOGDEBUG)
+ extension = headers['content-type']
+ if extension:
+ mimetype, options = cgi.parse_header(extension)
+ #little hack when content-type is not standard
+ if mimetype == "JPG" or mimetype == "jpeg":
+ maintype = "image"
+ subtype = "jpg"
+ else:
+ try:
+ maintype, subtype = mimetype.split("/")
+ except ValueError:
+ xbmc.log("AmpachePlugin::CacheArt: content-type not standard " +\
+ mimetype,xbmc.LOGDEBUG)
+ raise NameError
+ if maintype == 'image':
+ if subtype == "jpeg":
+ fname = imageID + ".jpg"
+ else:
+ fname = imageID + '.' + subtype
+
+ pathImage = os.path.join( cacheDirType , fname )
+ with open( pathImage, 'wb') as f:
+ f.write(contents)
+ f.close()
+ #xbmc.log("AmpachePlugin::CacheArt: Cached " + fname, xbmc.LOGDEBUG )
+ return pathImage
+ else:
+ xbmc.log("AmpachePlugin::CacheArt: It didnt work, id " + imageID , xbmc.LOGDEBUG )
+ raise NameError
+ else:
+ xbmc.log("AmpachePlugin::CacheArt: No file found, id " + imageID , xbmc.LOGDEBUG )
+ raise NameError
+
+def get_artLabels(albumArt):
+ art_labels = {
+ 'banner' : albumArt,
+ 'thumb': albumArt,
+ 'icon': albumArt,
+ 'fanart': albumArt
+ }
+ return art_labels
+
+#get_art, url is used for legacy purposes
+def get_art(object_id,elem_type,url=None):
+
+ albumArt = "DefaultFolder.png"
+ #no url, no art, so no need to activate a connection
+ if not object_id and not url:
+ return albumArt
+ try:
+ albumArt = cacheArt(object_id,elem_type,url)
+ except NameError:
+ albumArt = "DefaultFolder.png"
+
+ #xbmc.log("AmpachePlugin::get_art: id - " + object_id + " - albumArt - " + str(albumArt), xbmc.LOGDEBUG )
+ return albumArt
+
+
diff --git a/plugin.audio.ampache/resources/lib/art_clean.py b/plugin.audio.ampache/resources/lib/art_clean.py
new file mode 100644
index 0000000000..1e94c11bc1
--- /dev/null
+++ b/plugin.audio.ampache/resources/lib/art_clean.py
@@ -0,0 +1,59 @@
+from future.utils import PY2
+import os
+import xbmc,xbmcaddon
+import xbmcvfs
+
+#split the art library to not have import problems, as the function is used by
+#service and the main plugin
+#library used both by service and main plugin, DO NOT INCLUDE OTHER LOCAL
+#LIBRARIES
+
+ampache = xbmcaddon.Addon("plugin.audio.ampache")
+
+#different functions in kodi 19 (python3) and kodi 18 (python2)
+if PY2:
+ user_dir = xbmc.translatePath( ampache.getAddonInfo('profile'))
+ user_dir = user_dir.decode('utf-8')
+else:
+ user_dir = xbmcvfs.translatePath( ampache.getAddonInfo('profile'))
+user_mediaDir = os.path.join( user_dir , 'media' )
+cacheDir = os.path.join( user_mediaDir , 'cache' )
+
+def clean_settings():
+ ampache.setSetting("session_expire", "")
+ ampache.setSetting("add", "")
+ ampache.setSetting("token", "")
+ ampache.setSetting("token-exp", "")
+ ampache.setSetting("artists", "")
+ ampache.setSetting("albums", "")
+ ampache.setSetting("songs", "")
+ ampache.setSetting("playlists", "")
+ ampache.setSetting("videos", "")
+ ampache.setSetting("podcasts", "")
+ ampache.setSetting("live_streams", "")
+
+def clean_cache_art():
+ #hack to force the creation of profile directory if don't exists
+ if not os.path.isdir(user_dir):
+ ampache.setSetting("api-version","350001")
+
+ cacheTypes = ["album", "artist" , "song", "podcast","playlist"]
+ #if cacheDir doesn't exist, create it
+ if not os.path.isdir(user_mediaDir):
+ os.mkdir(user_mediaDir)
+ if not os.path.isdir(cacheDir):
+ os.mkdir(cacheDir)
+ for c_type in cacheTypes:
+ cacheDirType = os.path.join( cacheDir , c_type )
+ if not os.path.isdir(cacheDirType):
+ os.mkdir( cacheDirType )
+
+ #clean cache on start
+ for c_type in cacheTypes:
+ cacheDirType = os.path.join( cacheDir , c_type )
+ for currentFile in os.listdir(cacheDirType):
+ #xbmc.log("Clear Cache Art " + str(currentFile),xbmc.LOGDEBUG)
+ pathDel = os.path.join( cacheDirType, currentFile)
+ os.remove(pathDel)
+
+
diff --git a/plugin.audio.ampache/resources/lib/gui.py b/plugin.audio.ampache/resources/lib/gui.py
new file mode 100644
index 0000000000..0d173730d6
--- /dev/null
+++ b/plugin.audio.ampache/resources/lib/gui.py
@@ -0,0 +1,15 @@
+
+import xbmc
+import xbmcgui
+
+#main plugin library
+
+def getFilterFromUser(title='',thisType=xbmcgui.INPUT_ALPHANUM):
+ kb = xbmcgui.Dialog()
+ result = kb.input(title, type=thisType)
+ if result:
+ thisFilter = result
+ else:
+ return False
+ return(thisFilter)
+
diff --git a/plugin.audio.ampache/resources/lib/json_storage.py b/plugin.audio.ampache/resources/lib/json_storage.py
new file mode 100644
index 0000000000..2e2738525a
--- /dev/null
+++ b/plugin.audio.ampache/resources/lib/json_storage.py
@@ -0,0 +1,37 @@
+from builtins import object
+from future.utils import PY2
+import json
+import os
+import xbmc
+import xbmcaddon
+import xbmcvfs
+from copy import deepcopy
+
+#main plugin library
+
+class JsonStorage(object):
+
+ def __init__(self,filename):
+ ampache = xbmcaddon.Addon("plugin.audio.ampache")
+ if PY2:
+ base_dir = xbmc.translatePath( ampache.getAddonInfo('profile'))
+ base_dir = base_dir.decode('utf-8')
+ else:
+ base_dir = xbmcvfs.translatePath( ampache.getAddonInfo('profile'))
+ self._filename = os.path.join(base_dir, filename)
+ self._data = dict()
+ self.load()
+
+ def load(self):
+ if xbmcvfs.exists(self._filename):
+ with open(self._filename, 'r') as fd:
+ self._data = json.load(fd)
+
+ def save(self,data):
+ if data != self._data:
+ self._data = deepcopy(data)
+ with open(self._filename, 'w') as fd:
+ json.dump(self._data, fd, indent=4, sort_keys=True)
+
+ def getData(self):
+ return deepcopy(self._data)
diff --git a/plugin.audio.ampache/resources/lib/servers_manager.py b/plugin.audio.ampache/resources/lib/servers_manager.py
new file mode 100644
index 0000000000..de6765da35
--- /dev/null
+++ b/plugin.audio.ampache/resources/lib/servers_manager.py
@@ -0,0 +1,208 @@
+from __future__ import print_function
+
+import xbmc,xbmcgui
+
+#main plugin library
+
+from resources.lib import gui
+from resources.lib.art_clean import clean_cache_art
+from resources.lib import utils as ut
+from resources.lib import json_storage
+from resources.lib import ampache_connect
+
+def initializeServer():
+ jsStorServer = json_storage.JsonStorage("servers.json")
+ serverData = jsStorServer.getData()
+ if serverData:
+ pass
+ else:
+ xbmc.log( "AmpachePlugin::initializeServer: no servers file",xbmc.LOGDEBUG)
+ serverData["servers"] = {}
+ tempd = {}
+ tempd["0"] = {}
+ serverData["servers"].update(tempd)
+ serverData["servers"]["0"]["name"] = "Develop Demo"
+ serverData["servers"]["0"]["url"] = "http://develop.ampache.dev/"
+ serverData["servers"]["0"]["use_api_key"] = "false"
+ serverData["servers"]["0"]["enable_password"] = "true"
+ serverData["servers"]["0"]["username"] = "kodi_demo"
+ serverData["servers"]["0"]["password"] = "aNNKvApsECw7Tpc"
+ serverData["servers"]["0"]["api_key"] = ""
+ serverData["current_server"] = "0"
+ jsStorServer.save(serverData)
+
+
+#input: serverData and title
+#output: number of server in data
+def serversDialog(data,title=''):
+ templist = []
+ showlist = []
+ dialog = xbmcgui.Dialog()
+ for i in data["servers"]:
+ item = data["servers"][i]["name"]
+ if i == data["current_server"]:
+ item = item + " *"
+ showlist.append(item)
+ templist.append(data["servers"][i]["name"])
+ ret = dialog.select(title, showlist)
+ i_temp= ""
+ if ret == -1:
+ return False
+ for i in data["servers"]:
+ if(data["servers"][i]["name"]) == templist[ret]:
+ i_temp = i
+ return i_temp
+
+def showServerData(data,title=ut.tString(30168)):
+ padding_size = 20
+ #order of the data
+ ordlist = ["name","url","username","enable_password","password","use_api_key","api_key"]
+ #name to display
+ dispList = [ut.tString(30181),ut.tString(30182),ut.tString(30183),ut.tString(30184),ut.tString(30185),ut.tString(30186),ut.tString(30187)]
+ templist = []
+ showlist = []
+ n = 0
+ dialog = xbmcgui.Dialog()
+ for i in ordlist:
+ templist.append(i)
+ pad_i = dispList[n] + " "*(padding_size - len(i))
+ tempStr = pad_i + data[i]
+ showlist.append(tempStr)
+ n = n + 1
+ ret = dialog.select(title, showlist)
+ i_temp= ""
+ if ret == -1:
+ return False
+ for i in data:
+ if i == templist[ret]:
+ i_temp = i
+ return i_temp
+
+def switchServer():
+ jsStorServer = json_storage.JsonStorage("servers.json")
+ serverData = jsStorServer.getData()
+ i_curr = serversDialog(serverData,ut.tString(30169))
+ if i_curr == False:
+ return
+ xbmc.executebuiltin("PlayerControl(Stop)")
+ serverData["current_server"] = i_curr
+ jsStorServer.save(serverData)
+ #clean cache_art, the server is different, so the cache is invalid
+ clean_cache_art()
+ #if we switch, reconnect
+ try:
+ ampacheConnect = ampache_connect.AmpacheConnect()
+ ampacheConnect.AMPACHECONNECT(showok=True)
+ except:
+ pass
+
+def addServer():
+ xbmc.log("AmpachePlugin::addServer" , xbmc.LOGDEBUG )
+ jsStorServer = json_storage.JsonStorage("servers.json")
+ serverData = jsStorServer.getData()
+ if len(list(serverData["servers"])) > 0:
+ #choose the max number of the server list plus one
+ stnum = str(max([int(i) for i in list(serverData["servers"])])+1)
+ else:
+ #empty list
+ stnum = "0"
+ username = ""
+ password = ""
+ apikey = ""
+ enablepassword = True
+ is_api_key = False
+ tempd = {}
+ tempd[stnum] = {}
+ serverData["servers"].update(tempd)
+ servername = gui.getFilterFromUser(ut.tString(30170))
+ if servername == False:
+ return False
+ url = gui.getFilterFromUser(ut.tString(30171))
+ if url == False:
+ return False
+ dialog = xbmcgui.Dialog()
+ is_api_key = dialog.yesno(ut.tString(30189),ut.tString(30173))
+ if is_api_key == True:
+ apikey = gui.getFilterFromUser(ut.tString(30174))
+ if apikey == False:
+ return False
+ else:
+ username = gui.getFilterFromUser(ut.tString(30175))
+ if username == False:
+ return False
+ enablepassword = dialog.yesno(ut.tString(30189),ut.tString(30177))
+ if enablepassword == True:
+ password = gui.getFilterFromUser(ut.tString(30178))
+ if password == False:
+ return False
+ serverData["servers"][stnum]["name"] = servername
+ serverData["servers"][stnum]["url"] = url
+ serverData["servers"][stnum]["use_api_key"] = ut.int_to_strBool(is_api_key)
+ serverData["servers"][stnum]["username"] = username
+ serverData["servers"][stnum]["enable_password"] = ut.int_to_strBool(enablepassword)
+ serverData["servers"][stnum]["password"] = password
+ serverData["servers"][stnum]["api_key"] = apikey
+ jsStorServer.save(serverData)
+ showServerData(serverData["servers"][stnum])
+ return True
+
+def deleteServer():
+ jsStorServer = json_storage.JsonStorage("servers.json")
+ serverData = jsStorServer.getData()
+ i_rem = serversDialog(serverData,ut.tString(30179))
+ if i_rem == False:
+ return False
+ dialog = xbmcgui.Dialog()
+ confirm = dialog.yesno(ut.tString(30189),ut.tString(30188))
+ if confirm:
+ #replace old server position with the latest server in the list
+ repl_num = str(max([int(i) for i in list(serverData["servers"])]))
+ serverData["servers"][i_rem] = serverData["servers"][repl_num].copy()
+ del serverData["servers"][repl_num]
+ jsStorServer.save(serverData)
+ return True
+ else:
+ return False
+
+def modifyServer():
+ jsStorServer = json_storage.JsonStorage("servers.json")
+ serverData = jsStorServer.getData()
+ i = serversDialog(serverData,ut.tString(30180))
+ if i == False:
+ return
+ while True:
+ if xbmc.Monitor().abortRequested():
+ return
+ key = showServerData(serverData["servers"][i])
+ if key == False:
+ break
+ elif key == "use_api_key":
+ dialog = xbmcgui.Dialog()
+ value_int = dialog.yesno(ut.tString(30189),ut.tString(30173))
+ value = ut.int_to_strBool(value_int)
+ elif key == "enable_password":
+ dialog = xbmcgui.Dialog()
+ value_int = dialog.yesno(ut.tString(30189),ut.tString(30177))
+ value = ut.int_to_strBool(value_int)
+ elif key == "name":
+ value = gui.getFilterFromUser(ut.tString(30181))
+ elif key == "url":
+ value = gui.getFilterFromUser(ut.tString(30182))
+ elif key == "username":
+ value = gui.getFilterFromUser(ut.tString(30183))
+ elif key == "password":
+ value = gui.getFilterFromUser(ut.tString(30185))
+ elif key == "api_key":
+ value = gui.getFilterFromUser(ut.tString(30187))
+ else:
+ pass
+ if value != False:
+ serverData["servers"][i][key] = value
+ xbmc.executebuiltin("PlayerControl(Stop)")
+ jsStorServer.save(serverData)
+ #just to be sure, having potentially changed default server
+ try:
+ ampacheConnect = ampache_connect.AmpacheConnect()
+ ampacheConnect.AMPACHECONNECT()
+ except:
+ pass
diff --git a/plugin.audio.ampache/resources/lib/utils.py b/plugin.audio.ampache/resources/lib/utils.py
new file mode 100644
index 0000000000..4d9a60123e
--- /dev/null
+++ b/plugin.audio.ampache/resources/lib/utils.py
@@ -0,0 +1,173 @@
+import time
+import datetime
+import xbmcaddon,xbmcplugin
+import sys
+
+#main plugin/service library
+ampache = xbmcaddon.Addon("plugin.audio.ampache")
+
+def setContent(handle,object_type):
+ if object_type == 'artists' or object_type == 'albums' or object_type == 'songs' or object_type == 'videos':
+ xbmcplugin.setContent(handle, object_type)
+
+def otype_to_mode(object_type, object_subtype=None):
+ mode = None
+ if object_type == 'artists':
+ mode = 1
+ elif object_type == 'albums':
+ mode = 2
+ elif object_type == 'songs':
+ mode = 3
+ elif object_type == 'playlists':
+ mode = 4
+ elif object_type == 'podcasts':
+ mode = 5
+ elif object_type == 'live_streams':
+ mode = 6
+ elif object_type == 'videos':
+ mode = 8
+ elif object_type == 'tags' or object_type == 'genres':
+ if object_subtype == 'tag_artists' or object_subtype == 'genre_artists':
+ mode = 19
+ elif object_subtype == 'tag_albums' or object_subtype == 'genre_albums':
+ mode = 20
+ elif object_subtype == 'tag_songs' or object_subtype == 'genre_songs':
+ mode = 21
+
+ return mode
+
+def mode_to_tags(mode):
+ if(int(ampache.getSetting("api-version"))) < 500000:
+ if mode == 19:
+ return "tags","tag_artists"
+ if mode == 20:
+ return "tags","tag_albums"
+ if mode == 21:
+ return "tags","tag_songs"
+ else:
+ if mode == 19:
+ return "genres","genre_artists"
+ if mode == 20:
+ return "genres","genre_albums"
+ if mode == 21:
+ return "genres","genre_songs"
+
+def otype_to_type(object_type,object_subtype=None):
+ if object_type == 'albums':
+ return 'album'
+ elif object_type == 'artists':
+ return 'artist'
+ elif object_type == 'playlists':
+ return 'playlist'
+ elif object_type == 'tags':
+ return 'tag'
+ elif object_type == 'genres':
+ return 'genre'
+ elif object_type == 'podcasts':
+ return 'podcast'
+ elif object_type == 'videos':
+ return 'video'
+ elif object_type == 'songs':
+ if object_subtype == 'podcast_episodes':
+ return 'podcast_episode'
+ elif object_subtype == 'live_streams':
+ return 'live_stream'
+ return 'song'
+ return None
+
+def int_to_strBool(s):
+ if s == 1:
+ return 'true'
+ elif s == 0:
+ return 'false'
+ else:
+ raise ValueError
+
+# string to bool function : from string 'true' or 'false' to boolean True or
+# False, raise ValueError
+def strBool_to_bool(s):
+ if s == 'true':
+ return True
+ elif s == 'false':
+ return False
+ else:
+ raise ValueError
+
+def check_tokenexp():
+ session_time = ampache.getSetting("session_expire")
+ if session_time is None or session_time == "":
+ return True
+
+ #from python 3.7 we can easly compare the dates, otherwise we use the old
+ #method
+
+ if sys.version_info >= (3, 7):
+ try:
+ s_time = datetime.datetime.fromisoformat(session_time)
+ if datetime.datetime.now(datetime.timezone.utc) > s_time:
+ return True
+ return False
+ except:
+ return False
+ else:
+ try:
+ tokenexp = int(ampache.getSetting("token-exp"))
+ if int(time.time()) > tokenexp:
+ return True
+ return False
+ except:
+ return True
+
+def get_time(time_offset):
+ d = datetime.date.today()
+ dt = datetime.timedelta(days=time_offset)
+ nd = d + dt
+ return nd.isoformat()
+
+#return the translated String
+def tString(code):
+ return ampache.getLocalizedString(code)
+
+def get_params(plugin_url):
+ param=[]
+ paramstring=plugin_url
+ if len(paramstring)>=2:
+ params=plugin_url
+ cleanedparams=params.replace('?','')
+ if (params[len(params)-1]=='/'):
+ params=params[0:len(params)-2]
+ pairsofparams=cleanedparams.split('&')
+ param={}
+ for i in range(len(pairsofparams)):
+ splitparams={}
+ splitparams=pairsofparams[i].split('=')
+ if (len(splitparams))==2:
+ param[splitparams[0]]=splitparams[1]
+
+ return param
+
+def get_objectId_from_fileURL( file_url ):
+ params = get_params(file_url)
+ object_id = None
+ #i use two kind of object_id, i don't know, but sometime i have different
+ #url, btw, no problem, i handle both and i solve the problem in this way
+ try:
+ object_id=params["object_id"]
+ xbmc.log("AmpachePlugin::object_id " + object_id, xbmc.LOGDEBUG)
+ except:
+ pass
+ try:
+ object_id=params["oid"]
+ xbmc.log("AmpachePlugin::object_id " + object_id, xbmc.LOGDEBUG)
+ except:
+ pass
+ return object_id
+
+def getRating(rating):
+ if rating:
+ #converts from five stats ampache rating to ten stars kodi rating
+ rating = int(float(rating)*2)
+ else:
+ #zero equals no rating
+ rating = 0
+ return rating
diff --git a/plugin.audio.ampache/resources/media/images/refresh_icon.png b/plugin.audio.ampache/resources/media/images/refresh_icon.png
new file mode 100644
index 0000000000..b162e2c476
Binary files /dev/null and b/plugin.audio.ampache/resources/media/images/refresh_icon.png differ
diff --git a/plugin.audio.ampache/resources/settings.xml b/plugin.audio.ampache/resources/settings.xml
new file mode 100644
index 0000000000..eba32f12c2
--- /dev/null
+++ b/plugin.audio.ampache/resources/settings.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugin.audio.ampache/service.py b/plugin.audio.ampache/service.py
new file mode 100644
index 0000000000..d90231cb37
--- /dev/null
+++ b/plugin.audio.ampache/service.py
@@ -0,0 +1,4 @@
+
+from resources.lib.ampache_service import Main
+
+Main()
diff --git a/plugin.audio.arteconcert/LICENSE.txt b/plugin.audio.arteconcert/LICENSE.txt
new file mode 100644
index 0000000000..23cb790338
--- /dev/null
+++ b/plugin.audio.arteconcert/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/plugin.audio.arteconcert/addon.xml b/plugin.audio.arteconcert/addon.xml
new file mode 100644
index 0000000000..6d1aca976a
--- /dev/null
+++ b/plugin.audio.arteconcert/addon.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+ audio
+
+
+ all
+ an de fr
+ GPL-2.0-only
+ https://github.com/sarbes/plugin.audio.arteconcert
+ https://forum.kodi.tv/showthread.php?tid=353899
+ https://www.arte.tv/en/arte-concert/
+ This add-on allows access to the concert section of the French/German broadcaster Arte.
+ This add-on allows access to the concert section of the French/German broadcaster Arte.
+ Dieses Add-on bietet Zugriff auf die Konzerte von Arte.
+ Dieses Add-on bietet Zugriff auf die Konzerte von Arte. Das Angebot reicht von Klassischer Musik bis hin zu Elektro.
+ Dieses Add-on wird von keiner Sendeanstalt unterstützt und ist daher inoffiziell.
+
+ resources/icon.png
+ resources/fanart.png
+
+
+
diff --git a/plugin.audio.arteconcert/default.py b/plugin.audio.arteconcert/default.py
new file mode 100644
index 0000000000..d0bd34ba95
--- /dev/null
+++ b/plugin.audio.arteconcert/default.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+import libarte
+
+
+class arteMusic(libarte.libarte):
+ def __init__(self):
+ libarte.libarte.__init__(self)
+ self.modes['libArteListCategories'] = self.libArteListCategories
+
+ def libArteListMain(self):
+ l = []
+ l.append({'metadata':{'name':self.translation(32032)}, 'params':{'mode':'libArteListData', 'data':'VIDEO_LISTING', 'uriParams':'{"category":"ARS","videoType":"MOST_RECENT"}'}, 'type':'dir'})
+ l.append({'metadata':{'name':self.translation(32031)}, 'params':{'mode':'libArteListData', 'data':'VIDEO_LISTING', 'uriParams':'{"category":"ARS","videoType":"MOST_VIEWED"}'}, 'type':'dir'})
+ l.append({'metadata':{'name':self.translation(32033)}, 'params':{'mode':'libArteListData', 'data':'VIDEO_LISTING', 'uriParams':'{"category":"ARS","videoType":"LAST_CHANCE"}'}, 'type':'dir'})
+ l.append({'metadata':{'name':self.translation(30503)}, 'params':{'mode':'libArteListCategories'}, 'type':'dir'})
+ return {'items':l,'name':'root'}
+
+ def libArteListCategories(self):
+ l = []
+ l.append({'metadata':{'name':self.translation(30510)}, 'params':{'mode':'libArteListData', 'data':'VIDEO_LISTING', 'uriParams':'{"category":"ARS","subcategories":"MUA","videoType":"MOST_RECENT"}'}, 'type':'dir'})
+ l.append({'metadata':{'name':self.translation(30511)}, 'params':{'mode':'libArteListData', 'data':'VIDEO_LISTING', 'uriParams':'{"category":"ARS","subcategories":"MUE","videoType":"MOST_RECENT"}'}, 'type':'dir'})
+ l.append({'metadata':{'name':self.translation(30512)}, 'params':{'mode':'libArteListData', 'data':'VIDEO_LISTING', 'uriParams':'{"category":"ARS","subcategories":"HIP","videoType":"MOST_RECENT"}'}, 'type':'dir'})
+ l.append({'metadata':{'name':self.translation(30513)}, 'params':{'mode':'libArteListData', 'data':'VIDEO_LISTING', 'uriParams':'{"category":"ARS","subcategories":"MET","videoType":"MOST_RECENT"}'}, 'type':'dir'})
+ l.append({'metadata':{'name':self.translation(30514)}, 'params':{'mode':'libArteListData', 'data':'VIDEO_LISTING', 'uriParams':'{"category":"ARS","subcategories":"CLA","videoType":"MOST_RECENT"}'}, 'type':'dir'})
+ l.append({'metadata':{'name':self.translation(30515)}, 'params':{'mode':'libArteListData', 'data':'VIDEO_LISTING', 'uriParams':'{"category":"ARS","subcategories":"OPE","videoType":"MOST_RECENT"}'}, 'type':'dir'})
+ l.append({'metadata':{'name':self.translation(30516)}, 'params':{'mode':'libArteListData', 'data':'VIDEO_LISTING', 'uriParams':'{"category":"ARS","subcategories":"JAZ","videoType":"MOST_RECENT"}'}, 'type':'dir'})
+ l.append({'metadata':{'name':self.translation(30517)}, 'params':{'mode':'libArteListData', 'data':'VIDEO_LISTING', 'uriParams':'{"category":"ARS","subcategories":"MUD","videoType":"MOST_RECENT"}'}, 'type':'dir'})
+ l.append({'metadata':{'name':self.translation(30518)}, 'params':{'mode':'libArteListData', 'data':'VIDEO_LISTING', 'uriParams':'{"category":"ARS","subcategories":"ADS","videoType":"MOST_RECENT"}'}, 'type':'dir'})
+ return {'items':l,'name':self.translation(30503)}
+
+
+o = arteMusic()
+o.action()
\ No newline at end of file
diff --git a/plugin.audio.arteconcert/resources/fanart.png b/plugin.audio.arteconcert/resources/fanart.png
new file mode 100644
index 0000000000..c56fd088c7
Binary files /dev/null and b/plugin.audio.arteconcert/resources/fanart.png differ
diff --git a/plugin.audio.arteconcert/resources/icon.png b/plugin.audio.arteconcert/resources/icon.png
new file mode 100644
index 0000000000..513034421a
Binary files /dev/null and b/plugin.audio.arteconcert/resources/icon.png differ
diff --git a/plugin.audio.arteconcert/resources/language/resource.language.de_de/strings.po b/plugin.audio.arteconcert/resources/language/resource.language.de_de/strings.po
new file mode 100644
index 0000000000..53e26ef422
--- /dev/null
+++ b/plugin.audio.arteconcert/resources/language/resource.language.de_de/strings.po
@@ -0,0 +1,100 @@
+# KODI Media Center language file
+# Addon Name: arteconcert
+# Addon id: plugin.video.arteconcert
+# Addon version: 1.0.0
+# Addon Provider: sarbes
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Main\n"
+
+msgctxt "#30000"
+msgid "General"
+msgstr "Allgmein"
+
+msgctxt "#30010"
+msgid "Language"
+msgstr "Sprache"
+
+msgctxt "#30011"
+msgid "System (if available)"
+msgstr "System (wenn verfügbar)"
+
+msgctxt "#30012"
+msgid "English"
+msgstr "Englisch"
+
+msgctxt "#30013"
+msgid "German"
+msgstr "Deutsch"
+
+msgctxt "#30014"
+msgid "Spanish"
+msgstr "Spanisch"
+
+msgctxt "#30015"
+msgid "French"
+msgstr "Französisch"
+
+msgctxt "#30016"
+msgid "Hungarian"
+msgstr "Ungarisch"
+
+msgctxt "#30017"
+msgid "Italian"
+msgstr "Italienisch"
+
+msgctxt "#30018"
+msgid "Polish"
+msgstr "Polnisch"
+
+msgctxt "#30019"
+msgid "Portuguese"
+msgstr "Portugisisch"
+
+msgctxt "#30020"
+msgid "Romainian"
+msgstr "Romänisch"
+
+msgctxt "#30021"
+msgid "Ukrainian"
+msgstr "Ukrainisch"
+
+msgctxt "#30503"
+msgid "Categories"
+msgstr "Kategorien"
+
+msgctxt "#30510"
+msgid "Pop & Rock"
+msgstr "Pop & Rock"
+
+msgctxt "#30511"
+msgid "Electronic"
+msgstr "Electronic"
+
+msgctxt "#30512"
+msgid "Hip-Hop"
+msgstr "Hip-Hop"
+
+msgctxt "#30513"
+msgid "Metal"
+msgstr "Metal"
+
+msgctxt "#30514"
+msgid "Classical"
+msgstr "Klassik"
+
+msgctxt "#30515"
+msgid "Opera"
+msgstr "Oper"
+
+msgctxt "#30516"
+msgid "Jazz"
+msgstr "Jazz"
+
+msgctxt "#30517"
+msgid "World Music"
+msgstr "Welt Musik"
+
+msgctxt "#30518"
+msgid "Performing Arts"
+msgstr "Bühnenperformance"
\ No newline at end of file
diff --git a/plugin.audio.arteconcert/resources/language/resource.language.en_gb/strings.po b/plugin.audio.arteconcert/resources/language/resource.language.en_gb/strings.po
new file mode 100644
index 0000000000..41f8812f27
--- /dev/null
+++ b/plugin.audio.arteconcert/resources/language/resource.language.en_gb/strings.po
@@ -0,0 +1,100 @@
+# KODI Media Center language file
+# Addon Name: arteconcert
+# Addon id: plugin.video.arteconcert
+# Addon version: 1.0.0
+# Addon Provider: sarbes
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Main\n"
+
+msgctxt "#30000"
+msgid "General"
+msgstr "General"
+
+msgctxt "#30010"
+msgid "Language"
+msgstr "Language"
+
+msgctxt "#30011"
+msgid "System (if available)"
+msgstr "System (if available)"
+
+msgctxt "#30012"
+msgid "English"
+msgstr "English"
+
+msgctxt "#30013"
+msgid "German"
+msgstr "German"
+
+msgctxt "#30014"
+msgid "Spanish"
+msgstr "Spanish"
+
+msgctxt "#30015"
+msgid "French"
+msgstr "French"
+
+msgctxt "#30016"
+msgid "Hungarian"
+msgstr "Hungarian"
+
+msgctxt "#30017"
+msgid "Italian"
+msgstr "Italian"
+
+msgctxt "#30018"
+msgid "Polish"
+msgstr "Polish"
+
+msgctxt "#30019"
+msgid "Portuguese"
+msgstr "Portuguese"
+
+msgctxt "#30020"
+msgid "Romainian"
+msgstr "Romainian"
+
+msgctxt "#30021"
+msgid "Ukrainian"
+msgstr "Ukrainian"
+
+msgctxt "#30503"
+msgid "Categories"
+msgstr "Categories"
+
+msgctxt "#30510"
+msgid "Pop & Rock"
+msgstr "Pop & Rock"
+
+msgctxt "#30511"
+msgid "Electronic"
+msgstr "Electronic"
+
+msgctxt "#30512"
+msgid "Hip-Hop"
+msgstr "Hip-Hop"
+
+msgctxt "#30513"
+msgid "Metal"
+msgstr "Metal"
+
+msgctxt "#30514"
+msgid "Classical"
+msgstr "Classical"
+
+msgctxt "#30515"
+msgid "Opera"
+msgstr "Opera"
+
+msgctxt "#30516"
+msgid "Jazz"
+msgstr "Jazz"
+
+msgctxt "#30517"
+msgid "World Music"
+msgstr "World Music"
+
+msgctxt "#30518"
+msgid "Performing Arts"
+msgstr "Performing Arts"
\ No newline at end of file
diff --git a/plugin.audio.arteconcert/resources/settings.xml b/plugin.audio.arteconcert/resources/settings.xml
new file mode 100644
index 0000000000..d897865b39
--- /dev/null
+++ b/plugin.audio.arteconcert/resources/settings.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
diff --git a/plugin.audio.bbcpodcasts/addon.py b/plugin.audio.bbcpodcasts/addon.py
new file mode 100644
index 0000000000..94d950aa8c
--- /dev/null
+++ b/plugin.audio.bbcpodcasts/addon.py
@@ -0,0 +1,7 @@
+from resources.lib.bbcpodcasts.bbcpodcastsaddon import BbcPodcastsAddon
+import sys
+
+if __name__ == '__main__':
+
+ bbcPodcastsAddon = BbcPodcastsAddon(int(sys.argv[1]))
+ bbcPodcastsAddon.handle(sys.argv)
diff --git a/plugin.audio.bbcpodcasts/addon.xml b/plugin.audio.bbcpodcasts/addon.xml
new file mode 100644
index 0000000000..b517abc90e
--- /dev/null
+++ b/plugin.audio.bbcpodcasts/addon.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+ audio
+
+
+ Podcasts vom BBC in KODI
+ Podcasts by BBC in KODI
+ Kodi Addon für Podcasts der BBC. Alle Inhalte werden gestreamt von https://www.bbc.co.uk/podcasts
+ Podcasts by BBC in KODI. All contents are streamed from https://www.bbc.co.uk/podcasts
+ "Der Author des Addons ist nicht verantwortlich für die Inhalte, die auf Ihr Gerät gestreamt werden. Insbesondere liegt es in der alleinigen Verantwortung und Pflicht des Addon-Nutzers sich davon zu überzeugen, dass die Urheberrechts- und Nutzungsvereinbarungen der zum Streaming aufgerufenden Websites und Inhalte nicht verletzt werden. Alle Inhalte stammen von https://www.bbc.co.uk/podcasts"
+ The author of this addon is not responsible for the contents which are streamed to this device. Especially the user of this addon is in responsibility to convince that copyrights and right of use are not violated. All contents are streamed from https://www.bbc.co.uk/podcasts
+ de_DE
+ en_GB
+ all
+ MIT
+ https://github.com/Heckie75/kodi-addon-bbc-podcasts
+ https://github.com/Heckie75/kodi-addon-bbc-podcasts/tree/main/plugin.audio.bbcpodcasts
+
+v2.0.7 (2024-02-21)
+- Minor bugfix
+
+v2.0.6 (2024-02-10)
+- Changed parser after changes on website, i.e. switched to API
+
+v2.0.5 (2023-11-15)
+- Changed parser after changes on website, i.e. fix paging
+
+v2.0.4 (2023-02-02)
+- Improved thumbnail if item is added to favourites
+
+v2.0.3 (2022-06-02)
+- Changed parser after changes on website, i.e. fix paging of all podcasts
+- Improved performance when loading rss feed with episodes
+
+v2.0.2 (2022-01-31)
+- Changed parser after changes on website
+
+v2.0.1 (2021-09-12)
+- Fixed exception so that 'all podcasts' menu didn't work caused by NoneType
+
+v2.0.0 (2021-08-14)
+- Changes after relaunch of BBC podcasts website
+
+v1.0.1 (2021-08-01)
+- migrated to new settings format
+
+v1.0.0 (2021-05-24)
+- Initial version
+
+
+ resources/assets/icon.png
+ resources/assets/fanart.png
+ resources/assets/screenshot_1.png
+ resources/assets/screenshot_2.png
+ resources/assets/screenshot_3.png
+
+
+
diff --git a/plugin.audio.bbcpodcasts/resources/assets/fanart.png b/plugin.audio.bbcpodcasts/resources/assets/fanart.png
new file mode 100644
index 0000000000..bfb93ea672
Binary files /dev/null and b/plugin.audio.bbcpodcasts/resources/assets/fanart.png differ
diff --git a/plugin.audio.bbcpodcasts/resources/assets/icon.png b/plugin.audio.bbcpodcasts/resources/assets/icon.png
new file mode 100644
index 0000000000..0f13791cdf
Binary files /dev/null and b/plugin.audio.bbcpodcasts/resources/assets/icon.png differ
diff --git a/plugin.audio.bbcpodcasts/resources/assets/icon_arrow_left.png b/plugin.audio.bbcpodcasts/resources/assets/icon_arrow_left.png
new file mode 100644
index 0000000000..600bf69edb
Binary files /dev/null and b/plugin.audio.bbcpodcasts/resources/assets/icon_arrow_left.png differ
diff --git a/plugin.audio.bbcpodcasts/resources/assets/icon_arrow_right.png b/plugin.audio.bbcpodcasts/resources/assets/icon_arrow_right.png
new file mode 100644
index 0000000000..83489092f2
Binary files /dev/null and b/plugin.audio.bbcpodcasts/resources/assets/icon_arrow_right.png differ
diff --git a/plugin.audio.bbcpodcasts/resources/assets/icon_category.png b/plugin.audio.bbcpodcasts/resources/assets/icon_category.png
new file mode 100644
index 0000000000..0b35cb8631
Binary files /dev/null and b/plugin.audio.bbcpodcasts/resources/assets/icon_category.png differ
diff --git a/plugin.audio.bbcpodcasts/resources/assets/icon_search.png b/plugin.audio.bbcpodcasts/resources/assets/icon_search.png
new file mode 100644
index 0000000000..8772563a02
Binary files /dev/null and b/plugin.audio.bbcpodcasts/resources/assets/icon_search.png differ
diff --git a/plugin.audio.bbcpodcasts/resources/assets/icon_selection.png b/plugin.audio.bbcpodcasts/resources/assets/icon_selection.png
new file mode 100644
index 0000000000..5c4e1a2dda
Binary files /dev/null and b/plugin.audio.bbcpodcasts/resources/assets/icon_selection.png differ
diff --git a/plugin.audio.bbcpodcasts/resources/assets/icon_station.png b/plugin.audio.bbcpodcasts/resources/assets/icon_station.png
new file mode 100644
index 0000000000..8eaf015f3d
Binary files /dev/null and b/plugin.audio.bbcpodcasts/resources/assets/icon_station.png differ
diff --git a/plugin.audio.bbcpodcasts/resources/assets/screenshot_1.png b/plugin.audio.bbcpodcasts/resources/assets/screenshot_1.png
new file mode 100644
index 0000000000..9ae9ea3af6
Binary files /dev/null and b/plugin.audio.bbcpodcasts/resources/assets/screenshot_1.png differ
diff --git a/plugin.audio.bbcpodcasts/resources/assets/screenshot_2.png b/plugin.audio.bbcpodcasts/resources/assets/screenshot_2.png
new file mode 100644
index 0000000000..b1bf55fc49
Binary files /dev/null and b/plugin.audio.bbcpodcasts/resources/assets/screenshot_2.png differ
diff --git a/plugin.audio.bbcpodcasts/resources/assets/screenshot_3.png b/plugin.audio.bbcpodcasts/resources/assets/screenshot_3.png
new file mode 100644
index 0000000000..fa51198163
Binary files /dev/null and b/plugin.audio.bbcpodcasts/resources/assets/screenshot_3.png differ
diff --git a/plugin.audio.bbcpodcasts/resources/language/resource.language.de_de/strings.po b/plugin.audio.bbcpodcasts/resources/language/resource.language.de_de/strings.po
new file mode 100644
index 0000000000..f5aee763b6
--- /dev/null
+++ b/plugin.audio.bbcpodcasts/resources/language/resource.language.de_de/strings.po
@@ -0,0 +1,74 @@
+# Kodi Media Center language file
+msgid ""
+msgstr ""
+"Project-Id-Version: Kodi Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@kodi.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: de\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#32001"
+msgid "Editor's picks"
+msgstr "Editor's picks"
+
+msgctxt "#32002"
+msgid ""
+msgstr ""
+
+msgctxt "#32003"
+msgid ""
+msgstr ""
+
+msgctxt "#32004"
+msgid "Search podcast ..."
+msgstr "Suche Podcasts ..."
+
+msgctxt "#32005"
+msgid "Disclaimer"
+msgstr "Haftungsauschluss"
+
+msgctxt "#32006"
+msgid "I haven't agreed yet"
+msgstr "Ich habe noch nicht zugestimmt"
+
+msgctxt "#32007"
+msgid "I have agreed"
+msgstr "Ich habe zugestimmt"
+
+msgctxt "#32008"
+msgid "Agreement"
+msgstr "Zustimmung"
+
+msgctxt "#32010"
+msgid "The author of this addon is not responsible for the contents which are streamed to this device. Especially the user of this addon is in responsibility to convince that copyrights and right of use are not violated. All contents are taken and streamed from https://www.bbc.co.uk/podcasts. Do you agree?"
+msgstr "Der Author des Addons ist nicht verantwortlich für die Inhalte, die auf Ihr Gerät gestreamt werden. Insbesondere liegt es in der alleinigen Verantwortung und Pflicht des Addon-Nutzers sich davon zu überzeugen, dass die Urheberrechts- und Nutzungsvereinbarungen der zum Streaming aufgerufenden Websites und Inhalte nicht verletzt werden. Alle Inhalte stammen von https://www.bbc.co.uk/podcasts. Bist Du damit einverstanden?"
+
+msgctxt "#32101"
+msgid "Most recent episode"
+msgstr "aktuelle Sendung"
+
+msgctxt "#32151"
+msgid "Connection Error"
+msgstr "Verbindungsfehler"
+
+msgctxt "#32152"
+msgid "HTTP Method %s not supported"
+msgstr "HTTP Methode %s wird nicht unterstützt"
+
+msgctxt "#32153"
+msgid "Request Exception: Pls. check URL and port. See logs for further details."
+msgstr "Request Fehler: Prüfe URL und Port. Für weitere Details prüfe die Log-Datei."
+
+msgctxt "#32154"
+msgid "Unexpected HTTP Status %i for %s"
+msgstr "Unerwarteter HTTP Status %i für %s"
+
+msgctxt "#32155"
+msgid "Unexpected content for podcast"
+msgstr "Unerwarteter Inhalt für Podcast"
diff --git a/plugin.audio.bbcpodcasts/resources/language/resource.language.en_gb/strings.po b/plugin.audio.bbcpodcasts/resources/language/resource.language.en_gb/strings.po
new file mode 100644
index 0000000000..663633377a
--- /dev/null
+++ b/plugin.audio.bbcpodcasts/resources/language/resource.language.en_gb/strings.po
@@ -0,0 +1,74 @@
+# Kodi Media Center language file
+msgid ""
+msgstr ""
+"Project-Id-Version: Kodi Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@kodi.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: de\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#32001"
+msgid "Editor's picks"
+msgstr ""
+
+msgctxt "#32002"
+msgid ""
+msgstr ""
+
+msgctxt "#32003"
+msgid ""
+msgstr ""
+
+msgctxt "#32004"
+msgid "Search podcast ..."
+msgstr ""
+
+msgctxt "#32005"
+msgid "Disclaimer"
+msgstr ""
+
+msgctxt "#32006"
+msgid "I haven't agreed yet"
+msgstr ""
+
+msgctxt "#32007"
+msgid "I have agreed"
+msgstr ""
+
+msgctxt "#32008"
+msgid "Agreement"
+msgstr ""
+
+msgctxt "#32010"
+msgid "The author of this addon is not responsible for the contents which are streamed to this device. Especially the user of this addon is in responsibility to convince that copyrights and right of use are not violated. All contents are taken and streamed from https://www.bbc.co.uk/podcasts. Do you agree?"
+msgstr ""
+
+msgctxt "#32101"
+msgid "Most recent episode"
+msgstr ""
+
+msgctxt "#32151"
+msgid "Connection Error"
+msgstr ""
+
+msgctxt "#32152"
+msgid "HTTP Method %s not supported"
+msgstr ""
+
+msgctxt "#32153"
+msgid "Request Exception: Pls. check URL and port. See logs for further details."
+msgstr ""
+
+msgctxt "#32154"
+msgid "Unexpected HTTP Status %i for %s"
+msgstr ""
+
+msgctxt "#32155"
+msgid "Unexpected content for podcast"
+msgstr ""
diff --git a/plugin.audio.bbcpodcasts/resources/lib/bbcpodcasts/bbcpodcastsaddon.py b/plugin.audio.bbcpodcasts/resources/lib/bbcpodcasts/bbcpodcastsaddon.py
new file mode 100644
index 0000000000..2c09d9a756
--- /dev/null
+++ b/plugin.audio.bbcpodcasts/resources/lib/bbcpodcasts/bbcpodcastsaddon.py
@@ -0,0 +1,137 @@
+import json
+import os
+
+import xbmcgui
+import xbmcplugin
+from resources.lib.rssaddon.abstract_rss_addon import AbstractRssAddon
+from resources.lib.rssaddon.http_client import http_request
+
+
+class BbcPodcastsAddon(AbstractRssAddon):
+
+ API_BASE = "https://rms.api.bbc.co.uk"
+ API_SPEECH = "/v2/experience/inline/speech"
+ RSS_URL_PATTERN = "https://podcasts.files.bbci.co.uk/%s.rss"
+
+ def __init__(self, addon_handle) -> None:
+
+ super().__init__(addon_handle)
+
+ def _make_root_menu(self) -> None:
+
+ entries = list()
+ entries += self._get_entries_for_categories()
+
+ for entry in entries:
+ self.add_list_item(entry, "")
+
+ xbmcplugin.addSortMethod(
+ self.addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
+ xbmcplugin.addSortMethod(
+ self.addon_handle, xbmcplugin.SORT_METHOD_LABEL)
+
+ xbmcplugin.endOfDirectory(self.addon_handle, updateListing=True)
+
+ def _make_menu(self, path: str, params: 'dict[str]') -> None:
+
+ if path.endswith("/"):
+ path = path[:-1]
+
+ entries = self._get_podcasts(path, params)
+ for entry in entries:
+ self.add_list_item(entry, path)
+
+ xbmcplugin.addSortMethod(
+ self.addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
+ xbmcplugin.addSortMethod(
+ self.addon_handle, xbmcplugin.SORT_METHOD_LABEL)
+
+ xbmcplugin.endOfDirectory(self.addon_handle, updateListing=False)
+
+ def _get_podcasts(self, url: str, params: 'dict[str]') -> 'list[dict]':
+
+ url_param = "?%s" % "&".join(
+ ["%s=%s" % (k, params[k][0]) for k in params]) if len(params) > 0 else ""
+ url = self.API_BASE + "/" + "/".join(url.split("/")[2:])
+ _data, _cookies = http_request(self.addon, url + url_param)
+ _json = json.loads(_data)
+
+ entries = list()
+
+ for _d in _json["data"]:
+ if "uris" not in _d or "download" not in _d or not _d["download"] or "quality_variants" not in _d["download"] or not _d["container"]:
+ continue
+
+ has_media = [True for _quality in _d["download"]["quality_variants"]
+ if _d["download"]["quality_variants"][_quality]["file_url"]]
+
+ entry = {
+ "path": "",
+ "name": _d["titles"]["primary"] + ("" if has_media else " ˟"),
+ "icon": _d["image_url"].replace("{recipe}", "896x896"),
+ "type": "music",
+ "params": [
+ {
+ "rss": self.RSS_URL_PATTERN % _d["container"]["id"]
+ }
+ ],
+ "node": []
+ }
+
+ entries.append(entry)
+
+ return entries
+
+ def _get_entries_for_categories(self) -> 'list[dict]':
+
+ _data, _cookies = http_request(
+ self.addon, "%s%s" % (self.API_BASE, self.API_SPEECH))
+ _json = json.loads(_data)
+
+ result = list()
+
+ for _d in _json["data"]:
+
+ if _d["type"] != "inline_display_module" or not _d["uris"]:
+ continue
+
+ _name = _d["title"].strip()
+ _path: str = _d["uris"]["pagination"]["uri"]
+ _path = _path.replace("{offset}", str(
+ _d["uris"]["pagination"]["offset"]))
+ _path = _path.replace("{limit}", str(
+ _d["uris"]["pagination"]["total"]))
+ _data = "data" in _d and type(_d["data"]) == list
+
+ if _path and _name and _data:
+ result.append({
+ "path": "/__CATEGORIES__%s" % _path,
+ "name": _name,
+ "icon": os.path.join(self.addon_dir, "resources", "assets", "icon_category.png"),
+ "node": []
+ })
+
+ return result
+
+ def check_disclaimer(self) -> bool:
+
+ if self.addon.getSetting("agreement") != "1":
+ answer = xbmcgui.Dialog().yesno(self.addon.getLocalizedString(32005),
+ self.addon.getLocalizedString(32010))
+
+ if answer:
+ self.addon.setSetting("agreement", "1")
+ return True
+ else:
+ return False
+
+ else:
+ return True
+
+ def route(self, path: str, url_params: 'dict[str]') -> None:
+
+ if path in ["/"]:
+ self._make_root_menu()
+
+ elif "__CATEGORIES__" in path:
+ self._make_menu(path, url_params)
diff --git a/plugin.audio.bbcpodcasts/resources/lib/rssaddon/abstract_rss_addon.py b/plugin.audio.bbcpodcasts/resources/lib/rssaddon/abstract_rss_addon.py
new file mode 100644
index 0000000000..3525df1daa
--- /dev/null
+++ b/plugin.audio.bbcpodcasts/resources/lib/rssaddon/abstract_rss_addon.py
@@ -0,0 +1,316 @@
+import base64
+import os
+import re
+import urllib.parse
+from datetime import datetime
+from io import StringIO
+from xml.etree.ElementTree import iterparse
+
+import xbmc
+import xbmcaddon
+import xbmcgui
+import xbmcplugin
+import xbmcvfs
+from resources.lib.rssaddon.http_client import http_request
+from resources.lib.rssaddon.http_status_error import HttpStatusError
+
+# see https://forum.kodi.tv/showthread.php?tid=112916
+_MONTHS = ["Jan", "Feb", "Mar", "Apr", "May",
+ "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+
+
+class AbstractRssAddon:
+
+ addon = None
+ addon_handle = None
+ addon_dir = None
+ anchor_for_latest = True
+
+ def __init__(self, addon_handle):
+
+ self.addon = xbmcaddon.Addon()
+ self.addon_handle = addon_handle
+ self.addon_dir = xbmcvfs.translatePath(self.addon.getAddonInfo('path'))
+
+ def handle(self, argv: 'list[str]') -> None:
+
+ path = urllib.parse.urlparse(argv[0]).path.replace("//", "/")
+ url_params = urllib.parse.parse_qs(argv[2][1:])
+
+ if not self.check_disclaimer():
+ path = "/"
+ url_params = list()
+
+ if "rss" in url_params:
+ url = self.decode_param(url_params["rss"][0])
+ limit = int(self.decode_param(
+ url_params["limit"][0])) if "limit" in url_params else 0
+ offset = int(self.decode_param(
+ url_params["offset"][0])) if "offset" in url_params else 0
+ self.render_rss(path, url, limit=limit, offset=offset)
+
+ elif "play_latest" in url_params:
+ url = self.decode_param(url_params["play_latest"][0])
+ self.play_latest(url)
+ else:
+ self.route(path, url_params)
+
+ def decode_param(self, encoded_param: str) -> str:
+
+ return base64.urlsafe_b64decode(encoded_param).decode("utf-8")
+
+ def check_disclaimer(self) -> bool:
+
+ return True
+
+ def route(self, path: str, url_params):
+
+ pass
+
+ def is_force_http(self) -> bool:
+
+ return False
+
+ def _load_rss(self, url: str) -> 'tuple[str,str,str,list[dict]]':
+
+ def parse_rss_feed(xml: str) -> 'tuple[str,str,str,list[dict]]':
+
+ path = list()
+
+ title = None
+ description = ""
+ image = None
+ items = list()
+
+ for event, elem in iterparse(StringIO(xml), ("start", "end")):
+
+ if event == "start":
+ path.append(elem.tag)
+
+ if path == ["rss", "channel", "item"]:
+ item = dict()
+
+ elif event == "end":
+
+ if path == ["rss", "channel"]:
+ pass
+
+ elif path == ["rss", "channel", "title"] and elem.text:
+ title = elem.text.strip()
+
+ elif path == ["rss", "channel", "description"] and elem.text:
+ description = elem.text.strip()
+
+ elif path == ["rss", "channel", "image", "url"] and elem.text:
+ image = elem.text.strip()
+
+ elif (path == ["rss", "channel", "{http://www.itunes.com/dtds/podcast-1.0.dtd}image"]
+ and "href" in elem.attrib and not image):
+ image = elem.attrib["href"]
+
+ elif path == ["rss", "channel", "item", "title"] and elem.text:
+ item["name"] = elem.text.strip()
+
+ elif path == ["rss", "channel", "item", "description"] and elem.text:
+ item["description"] = elem.text.strip()
+
+ elif path == ["rss", "channel", "item", "enclosure"]:
+ item["stream_url"] = elem.attrib["url"] if not self.is_force_http(
+ ) else elem.attrib["url"].replace("https://", "http://")
+ item["type"] = "video" if elem.attrib["type"].split(
+ "/")[0] == "video" else "music"
+
+ elif (path == ["rss", "channel", "item", "{http://www.itunes.com/dtds/podcast-1.0.dtd}image"]
+ and elem.attrib["href"]):
+ item["icon"] = elem.attrib["href"]
+
+ elif path == ["rss", "channel", "item", "pubDate"] and elem.text:
+ _f = re.findall(
+ "(\d{1,2}) (\w{3}) (\d{4}) (\d{2}):(\d{2}):(\d{2})", elem.text)
+
+ if _f:
+ _m = _MONTHS.index(_f[0][1]) + 1
+ item["date"] = datetime(year=int(_f[0][2]), month=_m, day=int(_f[0][0]), hour=int(
+ _f[0][3]), minute=int(_f[0][4]), second=int(_f[0][5]))
+
+ elif path == ["rss", "channel", "item", "{http://www.itunes.com/dtds/podcast-1.0.dtd}duration"] and elem.text:
+ try:
+ duration = 0
+ for i, s in enumerate(reversed(elem.text.split(":"))):
+ duration += 60**i * int(s)
+
+ item["duration"] = duration
+
+ except:
+ pass
+
+ elif path == ["rss", "channel", "item"]:
+
+ if "description" not in item:
+ item["description"] = ""
+
+ if "icon" not in item:
+ item["icon"] = image
+
+ if "stream_url" in item and item["stream_url"]:
+ items.append(item)
+
+ elem.clear()
+ path.pop()
+
+ return title, description, image, items
+
+ xml, cookies = http_request(self.addon, url)
+
+ if not xml.startswith(" None:
+
+ pass
+
+ def _create_list_item(self, item: dict) -> xbmcgui.ListItem:
+
+ li = xbmcgui.ListItem(label=item["name"])
+
+ if "description" in item:
+ li.setProperty("label2", item["description"])
+
+ if "stream_url" in item:
+ li.setPath(item["stream_url"])
+
+ if "type" in item:
+ infos = {
+ "title": item["name"]
+ }
+
+ if item["type"] == "video":
+ infos["plot"] = item["description"] if "description" in item else ""
+
+ if "duration" in item and item["duration"] >= 0:
+ infos["duration"] = item["duration"]
+
+ li.setInfo(item["type"], infos)
+
+ if "icon" in item and item["icon"]:
+ li.setArt({"thumb": item["icon"]})
+ else:
+ addon_dir = xbmcvfs.translatePath(self.addon.getAddonInfo('path'))
+ li.setArt({"icon": os.path.join(
+ addon_dir, "resources", "assets", "icon.png")}
+ )
+
+ if "date" in item and item["date"]:
+ if "setDateTime" in dir(li): # available since Kodi v20
+ li.setDateTime(item["date"].strftime("%Y-%m-%dT%H:%M:%SZ"))
+ else:
+ pass
+
+ if "specialsort" in item:
+ li.setProperty("SpecialSort", item["specialsort"])
+
+ return li
+
+ def add_list_item(self, entry: dict, path: str) -> None:
+
+ def _build_param_string(params: 'list[str]', current="") -> str:
+
+ if params == None:
+ return current
+
+ for obj in params:
+ for name in obj:
+ enc_value = base64.urlsafe_b64encode(
+ obj[name].encode("utf-8"))
+ current += "?" if len(current) == 0 else "&"
+ current += name + "=" + str(enc_value, "utf-8")
+
+ return current
+
+ if path == "/":
+ path = ""
+
+ item_path = path + "/" + entry["path"]
+
+ param_string = ""
+ if "params" in entry:
+ param_string = _build_param_string(entry["params"],
+ current=param_string)
+
+ li = self._create_list_item(entry)
+
+ if "stream_url" in entry:
+ url = entry["stream_url"]
+
+ else:
+ url = "".join(
+ ["plugin://", self.addon.getAddonInfo("id"), item_path, param_string])
+
+ is_folder = "node" in entry
+ li.setProperty("IsPlayable", "false" if is_folder else "true")
+
+ xbmcplugin.addDirectoryItem(handle=self.addon_handle,
+ listitem=li,
+ url=url,
+ isFolder=is_folder)
+
+ def render_rss(self, path: str, url: str, limit=0, offset=0) -> None:
+
+ try:
+ title, description, image, items = self._load_rss(url)
+
+ except HttpStatusError as error:
+ xbmc.log("HTTP Status Error: %s, path=%s" %
+ (error.message, path), xbmc.LOGERROR)
+ xbmcgui.Dialog().notification(self.addon.getLocalizedString(32151), error.message)
+
+ else:
+ if len(items) > 0 and self.anchor_for_latest:
+ entry = {
+ "path": "latest",
+ "name": "%s (%s)" % (title, self.addon.getLocalizedString(32101)),
+ "description": description,
+ "icon": image,
+ "date": datetime.now(),
+ "specialsort": "top",
+ "type": items[0]["type"],
+ "params": [
+ {
+ "play_latest": url
+ }
+ ]
+ }
+ self.add_list_item(entry, path)
+
+ li = None
+ for i, item in enumerate(items):
+ if i >= offset and (not limit or i < offset + limit):
+ li = self._create_list_item(item)
+ xbmcplugin.addDirectoryItem(handle=self.addon_handle,
+ listitem=li,
+ url=item["stream_url"],
+ isFolder=False)
+
+ if li and "setDateTime" in dir(li): # available since Kodi v20
+ xbmcplugin.addSortMethod(
+ self.addon_handle, xbmcplugin.SORT_METHOD_DATE)
+ xbmcplugin.endOfDirectory(self.addon_handle)
+
+ def play_latest(self, url: str) -> None:
+
+ try:
+ title, description, image, items = self._load_rss(url)
+ item = items[0]
+ li = self._create_list_item(item)
+ xbmcplugin.setResolvedUrl(self.addon_handle, True, li)
+
+ except HttpStatusError as error:
+
+ xbmcgui.Dialog().notification(self.addon.getLocalizedString(32151), error.message)
diff --git a/plugin.audio.bbcpodcasts/resources/lib/rssaddon/http_client.py b/plugin.audio.bbcpodcasts/resources/lib/rssaddon/http_client.py
new file mode 100644
index 0000000000..2f34fd4cf1
--- /dev/null
+++ b/plugin.audio.bbcpodcasts/resources/lib/rssaddon/http_client.py
@@ -0,0 +1,36 @@
+from resources.lib.rssaddon.http_status_error import HttpStatusError
+
+import requests
+
+import xbmc
+
+def http_request(addon, url, headers=dict(), method="GET"):
+
+ useragent = f"{addon.getAddonInfo('id')}/{addon.getAddonInfo('version')} (Kodi/{xbmc.getInfoLabel('System.BuildVersionShort')})"
+ headers["User-Agent"] = useragent
+
+ if method == "GET":
+ req = requests.get
+ elif method == "POST":
+ req = requests.post
+ else:
+ raise HttpStatusError(
+ addon.getLocalizedString(32152) % method)
+
+ try:
+ res = req(url, headers=headers)
+ except requests.exceptions.RequestException as error:
+ xbmc.log("Request Exception: %s" % str(error), xbmc.LOGERROR)
+ raise HttpStatusError(addon.getLocalizedString(32153))
+
+ if res.status_code == 200:
+ if res.encoding and res.encoding != "utf-8":
+ rv = res.text.encode(res.encoding).decode("utf-8")
+ else:
+ rv = res.text
+
+ return rv, res.cookies
+
+ else:
+ raise HttpStatusError(addon.getLocalizedString(
+ 32154) % (res.status_code, url))
\ No newline at end of file
diff --git a/plugin.audio.bbcpodcasts/resources/lib/rssaddon/http_status_error.py b/plugin.audio.bbcpodcasts/resources/lib/rssaddon/http_status_error.py
new file mode 100644
index 0000000000..ae3185f615
--- /dev/null
+++ b/plugin.audio.bbcpodcasts/resources/lib/rssaddon/http_status_error.py
@@ -0,0 +1,7 @@
+class HttpStatusError(Exception):
+
+ message = ""
+
+ def __init__(self, msg):
+
+ self.message = msg
\ No newline at end of file
diff --git a/plugin.audio.bbcpodcasts/resources/settings.xml b/plugin.audio.bbcpodcasts/resources/settings.xml
new file mode 100644
index 0000000000..5689ea2d35
--- /dev/null
+++ b/plugin.audio.bbcpodcasts/resources/settings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+ 0
+
+ false
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugin.audio.deutschlandfunk/addon.py b/plugin.audio.deutschlandfunk/addon.py
new file mode 100644
index 0000000000..d8e4da48fa
--- /dev/null
+++ b/plugin.audio.deutschlandfunk/addon.py
@@ -0,0 +1,8 @@
+from resources.lib.deutschlandfunk.deutschlandfunkaddon import DeutschlandfunkAddon
+
+import sys
+
+if __name__ == '__main__':
+
+ deutschlandfunkAddon = DeutschlandfunkAddon(int(sys.argv[1]))
+ deutschlandfunkAddon.handle(sys.argv)
diff --git a/plugin.audio.deutschlandfunk/addon.xml b/plugin.audio.deutschlandfunk/addon.xml
new file mode 100644
index 0000000000..554f97efd8
--- /dev/null
+++ b/plugin.audio.deutschlandfunk/addon.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+ audio
+
+
+ Sender & Podcasts des Deutschlandfunks in KODI
+ Kodi Addon für Deutschlandfunk, Deutschlandfunk Kultur und
+Deutschlandfunk Nova für Livestream und Podcasts
+
+Mit diesem Addon können Radioprogramme des Deutschlandfunks gehört werden.
+Es können sowohl die Live Streams vom Deutschlandfunk,
+Deutschlandfunk Kultur und Deutschlandfunk Nova als auch
+sämtliche Podcasts der Sender gestreamt werden.
+ Der Author des Addons ist nicht verantwortlich für die Inhalte, die auf Ihr Gerät gestreamt werden. Insbesondere liegt es in der alleinigen Verantwortung und Pflicht des Addon-Nutzers sich davon zu überzeugen, dass die Urheberrechtsvereinbarungen der zum Streaming aufgerufenden Websites und Inhalte nicht verletzt werden.
+ de_DE
+ all
+ MIT
+ https://github.com/Heckie75/kodi-addon-deutschlandfunk
+ https://github.com/Heckie75/kodi-addon-deutschlandfunk/tree/master/plugin.audio.deutschlandfunk
+
+v2.0.8 (2023-11-26)
+- Fixed URLs of live streams
+
+v2.0.7 (2023-03-05)
+- Changed URL of Deutschlandfunk Kultur podcasts
+
+v2.0.6 (2023-02-02)
+- Improved icon if item is added to favourites
+
+v2.0.5 (2022-07-04)
+- Changed parser after changes on website
+- Improved performance when loading rss feed with episodes
+
+v2.0.4 (2021-12-12)
+- Changed URL for DRK to new location
+
+v2.0.3 (2021-11-15)
+- Changed scraper for DLF and DRK again since previous version v2.0.2 didn't caught all contents
+
+v2.0.2 (2021-11-14)
+- Changed scraper for DLF and DRK after changes on websites
+
+v2.0.1 (2021-06-27)
+- Fixed incorrect navigation from specific podcast to station's podcasts menu when using Kodi Kore App
+- Refactoring
+
+v2.0.0 (2021-05-13)
+- Migration to Kodi 19 (Matrix)
+- almost total rewrite
+
+
+ resources/assets/icon.png
+ resources/assets/fanart.png
+ resources/assets/screen1.png
+ resources/assets/screen2.png
+ resources/assets/screen3.png
+
+
+
diff --git a/plugin.audio.deutschlandfunk/resources/assets/fanart.png b/plugin.audio.deutschlandfunk/resources/assets/fanart.png
new file mode 100644
index 0000000000..645b33b27a
Binary files /dev/null and b/plugin.audio.deutschlandfunk/resources/assets/fanart.png differ
diff --git a/plugin.audio.deutschlandfunk/resources/assets/icon.png b/plugin.audio.deutschlandfunk/resources/assets/icon.png
new file mode 100644
index 0000000000..26c96fcc2d
Binary files /dev/null and b/plugin.audio.deutschlandfunk/resources/assets/icon.png differ
diff --git a/plugin.audio.deutschlandfunk/resources/assets/icon_dlf.png b/plugin.audio.deutschlandfunk/resources/assets/icon_dlf.png
new file mode 100644
index 0000000000..eef26806f0
Binary files /dev/null and b/plugin.audio.deutschlandfunk/resources/assets/icon_dlf.png differ
diff --git a/plugin.audio.deutschlandfunk/resources/assets/icon_dlf_rss.png b/plugin.audio.deutschlandfunk/resources/assets/icon_dlf_rss.png
new file mode 100644
index 0000000000..3c23b8cdad
Binary files /dev/null and b/plugin.audio.deutschlandfunk/resources/assets/icon_dlf_rss.png differ
diff --git a/plugin.audio.deutschlandfunk/resources/assets/icon_drk.png b/plugin.audio.deutschlandfunk/resources/assets/icon_drk.png
new file mode 100644
index 0000000000..b6721e6bfd
Binary files /dev/null and b/plugin.audio.deutschlandfunk/resources/assets/icon_drk.png differ
diff --git a/plugin.audio.deutschlandfunk/resources/assets/icon_drk_rss.png b/plugin.audio.deutschlandfunk/resources/assets/icon_drk_rss.png
new file mode 100644
index 0000000000..6b3667e337
Binary files /dev/null and b/plugin.audio.deutschlandfunk/resources/assets/icon_drk_rss.png differ
diff --git a/plugin.audio.deutschlandfunk/resources/assets/icon_nova.png b/plugin.audio.deutschlandfunk/resources/assets/icon_nova.png
new file mode 100644
index 0000000000..51a780cb5b
Binary files /dev/null and b/plugin.audio.deutschlandfunk/resources/assets/icon_nova.png differ
diff --git a/plugin.audio.deutschlandfunk/resources/assets/icon_nova_rss.png b/plugin.audio.deutschlandfunk/resources/assets/icon_nova_rss.png
new file mode 100644
index 0000000000..05a16a3108
Binary files /dev/null and b/plugin.audio.deutschlandfunk/resources/assets/icon_nova_rss.png differ
diff --git a/plugin.audio.deutschlandfunk/resources/assets/screen1.png b/plugin.audio.deutschlandfunk/resources/assets/screen1.png
new file mode 100644
index 0000000000..aa8680d938
Binary files /dev/null and b/plugin.audio.deutschlandfunk/resources/assets/screen1.png differ
diff --git a/plugin.audio.deutschlandfunk/resources/assets/screen2.png b/plugin.audio.deutschlandfunk/resources/assets/screen2.png
new file mode 100644
index 0000000000..d9724981b9
Binary files /dev/null and b/plugin.audio.deutschlandfunk/resources/assets/screen2.png differ
diff --git a/plugin.audio.deutschlandfunk/resources/assets/screen3.png b/plugin.audio.deutschlandfunk/resources/assets/screen3.png
new file mode 100644
index 0000000000..237a236da6
Binary files /dev/null and b/plugin.audio.deutschlandfunk/resources/assets/screen3.png differ
diff --git a/plugin.audio.deutschlandfunk/resources/language/resource.language.de_de/strings.po b/plugin.audio.deutschlandfunk/resources/language/resource.language.de_de/strings.po
new file mode 100644
index 0000000000..a199b1c921
--- /dev/null
+++ b/plugin.audio.deutschlandfunk/resources/language/resource.language.de_de/strings.po
@@ -0,0 +1,38 @@
+# Kodi Media Center language file
+msgid ""
+msgstr ""
+"Project-Id-Version: Kodi Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@kodi.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: de\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#32101"
+msgid "Most recent episode"
+msgstr "aktuelle Sendung"
+
+msgctxt "#32151"
+msgid "Connection Error"
+msgstr "Verbindungsfehler"
+
+msgctxt "#32152"
+msgid "HTTP Method %s not supported"
+msgstr "HTTP Methode %s wird nicht unterstützt"
+
+msgctxt "#32153"
+msgid "Request Exception: Pls. check URL and port. See logs for further details."
+msgstr "Request Fehler: Prüfe URL und Port. Für weitere Details prüfe die Log-Datei."
+
+msgctxt "#32154"
+msgid "Unexpected HTTP Status %i for %s"
+msgstr "Unerwarteter HTTP Status %i für %s"
+
+msgctxt "#32155"
+msgid "Unexpected content for podcast"
+msgstr "Unerwarteter Inhalt für Podcast"
diff --git a/plugin.audio.deutschlandfunk/resources/language/resource.language.en_gb/strings.po b/plugin.audio.deutschlandfunk/resources/language/resource.language.en_gb/strings.po
new file mode 100644
index 0000000000..2e7af84773
--- /dev/null
+++ b/plugin.audio.deutschlandfunk/resources/language/resource.language.en_gb/strings.po
@@ -0,0 +1,38 @@
+# Kodi Media Center language file
+msgid ""
+msgstr ""
+"Project-Id-Version: Kodi Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@kodi.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#32101"
+msgid "Most recent episode"
+msgstr ""
+
+msgctxt "#32151"
+msgid "Connection Error"
+msgstr ""
+
+msgctxt "#32152"
+msgid "HTTP Method %s not supported"
+msgstr ""
+
+msgctxt "#32153"
+msgid "Request Exception: Pls. check URL and port. See logs for further details."
+msgstr ""
+
+msgctxt "#32154"
+msgid "Unexpected HTTP Status %i for %s"
+msgstr ""
+
+msgctxt "#32155"
+msgid "Unexpected content for podcast"
+msgstr ""
diff --git a/plugin.audio.deutschlandfunk/resources/lib/deutschlandfunk/deutschlandfunkaddon.py b/plugin.audio.deutschlandfunk/resources/lib/deutschlandfunk/deutschlandfunkaddon.py
new file mode 100644
index 0000000000..7829f01d2a
--- /dev/null
+++ b/plugin.audio.deutschlandfunk/resources/lib/deutschlandfunk/deutschlandfunkaddon.py
@@ -0,0 +1,241 @@
+import json
+import os
+
+import xbmc
+import xbmcaddon
+import xbmcgui
+import xbmcplugin
+from bs4 import BeautifulSoup
+from resources.lib.rssaddon.abstract_rss_addon import AbstractRssAddon
+from resources.lib.rssaddon.http_client import http_request
+from resources.lib.rssaddon.http_status_error import HttpStatusError
+
+
+class DeutschlandfunkAddon(AbstractRssAddon):
+
+ __PLUGIN_ID__ = "plugin.audio.deutschlandfunk"
+
+ URL_STREAM_DLF = "https://st01.sslstream.dlf.de/dlf/01/high/aac/stream.aac?aggregator=web"
+ URL_STREAM_DFK = "https://st02.sslstream.dlf.de/dlf/02/high/aac/stream.aac?aggregator=web"
+ URL_STREAM_NOVA = "https://st03.sslstream.dlf.de/dlf/03/high/aac/stream.aac?aggregator=web"
+
+ URL_PODCASTS_DLF = "https://www.deutschlandfunk.de/podcasts"
+ URL_PODCASTS_DFK = "https://www.deutschlandfunkkultur.de/program-and-podcast"
+ URL_PODCASTS_NOVA = "https://www.deutschlandfunknova.de/podcasts"
+
+ PATH_DLF = "dlf"
+ PATH_DLK = "dkultur"
+ PATH_NOVA = "nova"
+ PATH_PODCASTS = "podcasts"
+
+ addon = xbmcaddon.Addon(id=__PLUGIN_ID__)
+
+ def __init__(self, addon_handle):
+
+ super().__init__(addon_handle)
+
+ def _make_root_menu(self):
+
+ nodes = [
+ {
+ "path": DeutschlandfunkAddon.PATH_DLF,
+ "name": "Deutschlandfunk",
+ "icon": os.path.join(
+ self.addon_dir, "resources", "assets", "icon_dlf.png"),
+ "node": []
+ },
+ {
+ "path": DeutschlandfunkAddon.PATH_DLK,
+ "name": "Deutschlandfunk Kultur",
+ "icon": os.path.join(
+ self.addon_dir, "resources", "assets", "icon_drk.png"),
+ "node": []
+ },
+ {
+ "path": DeutschlandfunkAddon.PATH_NOVA,
+ "name": "Deutschlandfunk Nova",
+ "icon": os.path.join(
+ self.addon_dir, "resources", "assets", "icon_nova.png"),
+ "node": []
+ }
+ ]
+
+ for entry in nodes:
+ self.add_list_item(entry, "/")
+
+ xbmcplugin.endOfDirectory(self.addon_handle, updateListing=False)
+
+ def _make_station_menu(self, station):
+
+ nodes = list()
+ if DeutschlandfunkAddon.PATH_DLF == station:
+ nodes.append({
+ "path": "stream",
+ "name": "Deutschlandfunk",
+ "icon": os.path.join(
+ self.addon_dir, "resources", "assets", "icon_dlf.png"),
+ "stream_url": DeutschlandfunkAddon.URL_STREAM_DLF,
+ "type": "music",
+ "specialsort": "top"
+ })
+ nodes.append({
+ "path": DeutschlandfunkAddon.PATH_PODCASTS,
+ "name": "Podcasts",
+ "icon": os.path.join(
+ self.addon_dir, "resources", "assets", "icon_dlf_rss.png"),
+ "node": []
+ })
+
+ elif DeutschlandfunkAddon.PATH_DLK == station:
+ nodes.append({
+ "path": "stream",
+ "name": "Deutschlandfunk Kultur",
+ "icon": os.path.join(
+ self.addon_dir, "resources", "assets", "icon_drk.png"),
+ "stream_url": DeutschlandfunkAddon.URL_STREAM_DFK,
+ "type": "music",
+ "specialsort": "top"
+ })
+ nodes.append({
+ "path": DeutschlandfunkAddon.PATH_PODCASTS,
+ "name": "Podcasts",
+ "icon": os.path.join(
+ self.addon_dir, "resources", "assets", "icon_drk_rss.png"),
+ "node": []
+ })
+
+ elif DeutschlandfunkAddon.PATH_NOVA == station:
+ nodes.append({
+ "path": "stream",
+ "name": "Deutschlandfunk Nova",
+ "icon": os.path.join(
+ self.addon_dir, "resources", "assets", "icon_nova.png"),
+ "stream_url": DeutschlandfunkAddon.URL_STREAM_NOVA,
+ "type": "music",
+ "specialsort": "top"
+ })
+ nodes.append({
+ "path": DeutschlandfunkAddon.PATH_PODCASTS,
+ "name": "Podcasts",
+ "icon": os.path.join(
+ self.addon_dir, "resources", "assets", "icon_nova_rss.png"),
+ "node": []
+ })
+
+ for entry in nodes:
+ self.add_list_item(entry, "/%s" % station)
+
+ xbmcplugin.endOfDirectory(self.addon_handle, updateListing=False)
+
+ def _parse_nova(self, path):
+
+ _BASE_URL = "https://www.deutschlandfunknova.de/podcast/"
+
+ # download html site with podcast overview
+ _data, _cookies = http_request(self.addon, self.URL_PODCASTS_NOVA)
+
+ # parse site and read podcast meta data kindly provided as js
+ soup = BeautifulSoup(_data, 'html.parser')
+ _casts = soup.select('li.item')
+
+ for _cast in _casts:
+
+ _href = _cast.a.get("href")
+ _path = _href.replace("/podcasts/download/", "")
+ _img = _cast.img
+
+ entry = {
+ "path": _path,
+ "name": _img.get("alt"),
+ "icon": _img.get("src"),
+ "params": [
+ {
+ "rss": _BASE_URL + _path
+ }
+ ],
+ "node": []
+ }
+ self.add_list_item(entry, path)
+
+ xbmcplugin.addSortMethod(
+ self.addon_handle, xbmcplugin.SORT_METHOD_LABEL)
+
+ xbmcplugin.endOfDirectory(self.addon_handle, updateListing=False)
+
+ def _parse_dlf(self, path, url):
+
+ # download html site with podcast overview
+ _data, _cookies = http_request(self.addon, url)
+
+ soup = BeautifulSoup(_data, "html.parser")
+ main = soup.find("main")
+ _script_js_client_queries = main.find_all(
+ "script", class_="js-client-queries")
+
+ entries = list()
+ _img_src = None
+
+ for _script in _script_js_client_queries:
+
+ if not _script.has_attr("data-json"):
+ continue
+
+ try:
+ _data_json = json.loads(_script["data-json"])
+ if "value" not in _data_json or "__typename" not in _data_json["value"]:
+ continue
+
+ if _data_json["value"]["__typename"] == "Image":
+ _img_src = _data_json["value"]["src"]
+
+ elif _data_json["value"]["__typename"] == "Teaser" and "pathPodcast" in _data_json["value"]:
+ if not _data_json["value"]["pathPodcast"] or not _data_json["value"]["pathPodcast"].endswith(".xml"):
+ continue
+
+ entry = {
+ "path": _data_json["value"]["sophoraId"],
+ "name": _data_json["value"]["title"],
+ "icon": _img_src,
+ "params": [
+ {
+ "rss": _data_json["value"]["pathPodcast"]
+ }
+ ],
+ "node": []
+ }
+ entries.append(entry)
+
+ except:
+ _img_src = None
+
+ uniq_entries = {entries[i]["params"][0]["rss"]: entries[i] for i in range(len(entries))}
+ uniq_entries = [uniq_entries[e] for e in uniq_entries]
+ uniq_entries.sort(key=lambda e: e["name"])
+
+ for entry in uniq_entries:
+ self.add_list_item(entry, path)
+
+ xbmcplugin.addSortMethod(
+ self.addon_handle, xbmcplugin.SORT_METHOD_LABEL)
+
+ xbmcplugin.endOfDirectory(self.addon_handle, updateListing=False)
+
+ def route(self, path, url_params):
+
+ splitted_path = [n for n in path.split("/") if n != ""]
+ if len(splitted_path) == 2 and splitted_path[1] == DeutschlandfunkAddon.PATH_PODCASTS:
+
+ if splitted_path[0] == DeutschlandfunkAddon.PATH_DLF:
+ self._parse_dlf(path, self.URL_PODCASTS_DLF)
+
+ elif splitted_path[0] == DeutschlandfunkAddon.PATH_DLK:
+ self._parse_dlf(path, self.URL_PODCASTS_DFK)
+
+ elif splitted_path[0] == DeutschlandfunkAddon.PATH_NOVA:
+ self._parse_nova(path)
+
+ elif len(splitted_path) == 1 and splitted_path[0] in [DeutschlandfunkAddon.PATH_DLF, DeutschlandfunkAddon.PATH_DLK, DeutschlandfunkAddon.PATH_NOVA]:
+ self._make_station_menu(splitted_path[0])
+
+ else:
+ self._make_root_menu()
diff --git a/plugin.audio.deutschlandfunk/resources/lib/rssaddon/abstract_rss_addon.py b/plugin.audio.deutschlandfunk/resources/lib/rssaddon/abstract_rss_addon.py
new file mode 100644
index 0000000000..3525df1daa
--- /dev/null
+++ b/plugin.audio.deutschlandfunk/resources/lib/rssaddon/abstract_rss_addon.py
@@ -0,0 +1,316 @@
+import base64
+import os
+import re
+import urllib.parse
+from datetime import datetime
+from io import StringIO
+from xml.etree.ElementTree import iterparse
+
+import xbmc
+import xbmcaddon
+import xbmcgui
+import xbmcplugin
+import xbmcvfs
+from resources.lib.rssaddon.http_client import http_request
+from resources.lib.rssaddon.http_status_error import HttpStatusError
+
+# see https://forum.kodi.tv/showthread.php?tid=112916
+_MONTHS = ["Jan", "Feb", "Mar", "Apr", "May",
+ "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+
+
+class AbstractRssAddon:
+
+ addon = None
+ addon_handle = None
+ addon_dir = None
+ anchor_for_latest = True
+
+ def __init__(self, addon_handle):
+
+ self.addon = xbmcaddon.Addon()
+ self.addon_handle = addon_handle
+ self.addon_dir = xbmcvfs.translatePath(self.addon.getAddonInfo('path'))
+
+ def handle(self, argv: 'list[str]') -> None:
+
+ path = urllib.parse.urlparse(argv[0]).path.replace("//", "/")
+ url_params = urllib.parse.parse_qs(argv[2][1:])
+
+ if not self.check_disclaimer():
+ path = "/"
+ url_params = list()
+
+ if "rss" in url_params:
+ url = self.decode_param(url_params["rss"][0])
+ limit = int(self.decode_param(
+ url_params["limit"][0])) if "limit" in url_params else 0
+ offset = int(self.decode_param(
+ url_params["offset"][0])) if "offset" in url_params else 0
+ self.render_rss(path, url, limit=limit, offset=offset)
+
+ elif "play_latest" in url_params:
+ url = self.decode_param(url_params["play_latest"][0])
+ self.play_latest(url)
+ else:
+ self.route(path, url_params)
+
+ def decode_param(self, encoded_param: str) -> str:
+
+ return base64.urlsafe_b64decode(encoded_param).decode("utf-8")
+
+ def check_disclaimer(self) -> bool:
+
+ return True
+
+ def route(self, path: str, url_params):
+
+ pass
+
+ def is_force_http(self) -> bool:
+
+ return False
+
+ def _load_rss(self, url: str) -> 'tuple[str,str,str,list[dict]]':
+
+ def parse_rss_feed(xml: str) -> 'tuple[str,str,str,list[dict]]':
+
+ path = list()
+
+ title = None
+ description = ""
+ image = None
+ items = list()
+
+ for event, elem in iterparse(StringIO(xml), ("start", "end")):
+
+ if event == "start":
+ path.append(elem.tag)
+
+ if path == ["rss", "channel", "item"]:
+ item = dict()
+
+ elif event == "end":
+
+ if path == ["rss", "channel"]:
+ pass
+
+ elif path == ["rss", "channel", "title"] and elem.text:
+ title = elem.text.strip()
+
+ elif path == ["rss", "channel", "description"] and elem.text:
+ description = elem.text.strip()
+
+ elif path == ["rss", "channel", "image", "url"] and elem.text:
+ image = elem.text.strip()
+
+ elif (path == ["rss", "channel", "{http://www.itunes.com/dtds/podcast-1.0.dtd}image"]
+ and "href" in elem.attrib and not image):
+ image = elem.attrib["href"]
+
+ elif path == ["rss", "channel", "item", "title"] and elem.text:
+ item["name"] = elem.text.strip()
+
+ elif path == ["rss", "channel", "item", "description"] and elem.text:
+ item["description"] = elem.text.strip()
+
+ elif path == ["rss", "channel", "item", "enclosure"]:
+ item["stream_url"] = elem.attrib["url"] if not self.is_force_http(
+ ) else elem.attrib["url"].replace("https://", "http://")
+ item["type"] = "video" if elem.attrib["type"].split(
+ "/")[0] == "video" else "music"
+
+ elif (path == ["rss", "channel", "item", "{http://www.itunes.com/dtds/podcast-1.0.dtd}image"]
+ and elem.attrib["href"]):
+ item["icon"] = elem.attrib["href"]
+
+ elif path == ["rss", "channel", "item", "pubDate"] and elem.text:
+ _f = re.findall(
+ "(\d{1,2}) (\w{3}) (\d{4}) (\d{2}):(\d{2}):(\d{2})", elem.text)
+
+ if _f:
+ _m = _MONTHS.index(_f[0][1]) + 1
+ item["date"] = datetime(year=int(_f[0][2]), month=_m, day=int(_f[0][0]), hour=int(
+ _f[0][3]), minute=int(_f[0][4]), second=int(_f[0][5]))
+
+ elif path == ["rss", "channel", "item", "{http://www.itunes.com/dtds/podcast-1.0.dtd}duration"] and elem.text:
+ try:
+ duration = 0
+ for i, s in enumerate(reversed(elem.text.split(":"))):
+ duration += 60**i * int(s)
+
+ item["duration"] = duration
+
+ except:
+ pass
+
+ elif path == ["rss", "channel", "item"]:
+
+ if "description" not in item:
+ item["description"] = ""
+
+ if "icon" not in item:
+ item["icon"] = image
+
+ if "stream_url" in item and item["stream_url"]:
+ items.append(item)
+
+ elem.clear()
+ path.pop()
+
+ return title, description, image, items
+
+ xml, cookies = http_request(self.addon, url)
+
+ if not xml.startswith(" None:
+
+ pass
+
+ def _create_list_item(self, item: dict) -> xbmcgui.ListItem:
+
+ li = xbmcgui.ListItem(label=item["name"])
+
+ if "description" in item:
+ li.setProperty("label2", item["description"])
+
+ if "stream_url" in item:
+ li.setPath(item["stream_url"])
+
+ if "type" in item:
+ infos = {
+ "title": item["name"]
+ }
+
+ if item["type"] == "video":
+ infos["plot"] = item["description"] if "description" in item else ""
+
+ if "duration" in item and item["duration"] >= 0:
+ infos["duration"] = item["duration"]
+
+ li.setInfo(item["type"], infos)
+
+ if "icon" in item and item["icon"]:
+ li.setArt({"thumb": item["icon"]})
+ else:
+ addon_dir = xbmcvfs.translatePath(self.addon.getAddonInfo('path'))
+ li.setArt({"icon": os.path.join(
+ addon_dir, "resources", "assets", "icon.png")}
+ )
+
+ if "date" in item and item["date"]:
+ if "setDateTime" in dir(li): # available since Kodi v20
+ li.setDateTime(item["date"].strftime("%Y-%m-%dT%H:%M:%SZ"))
+ else:
+ pass
+
+ if "specialsort" in item:
+ li.setProperty("SpecialSort", item["specialsort"])
+
+ return li
+
+ def add_list_item(self, entry: dict, path: str) -> None:
+
+ def _build_param_string(params: 'list[str]', current="") -> str:
+
+ if params == None:
+ return current
+
+ for obj in params:
+ for name in obj:
+ enc_value = base64.urlsafe_b64encode(
+ obj[name].encode("utf-8"))
+ current += "?" if len(current) == 0 else "&"
+ current += name + "=" + str(enc_value, "utf-8")
+
+ return current
+
+ if path == "/":
+ path = ""
+
+ item_path = path + "/" + entry["path"]
+
+ param_string = ""
+ if "params" in entry:
+ param_string = _build_param_string(entry["params"],
+ current=param_string)
+
+ li = self._create_list_item(entry)
+
+ if "stream_url" in entry:
+ url = entry["stream_url"]
+
+ else:
+ url = "".join(
+ ["plugin://", self.addon.getAddonInfo("id"), item_path, param_string])
+
+ is_folder = "node" in entry
+ li.setProperty("IsPlayable", "false" if is_folder else "true")
+
+ xbmcplugin.addDirectoryItem(handle=self.addon_handle,
+ listitem=li,
+ url=url,
+ isFolder=is_folder)
+
+ def render_rss(self, path: str, url: str, limit=0, offset=0) -> None:
+
+ try:
+ title, description, image, items = self._load_rss(url)
+
+ except HttpStatusError as error:
+ xbmc.log("HTTP Status Error: %s, path=%s" %
+ (error.message, path), xbmc.LOGERROR)
+ xbmcgui.Dialog().notification(self.addon.getLocalizedString(32151), error.message)
+
+ else:
+ if len(items) > 0 and self.anchor_for_latest:
+ entry = {
+ "path": "latest",
+ "name": "%s (%s)" % (title, self.addon.getLocalizedString(32101)),
+ "description": description,
+ "icon": image,
+ "date": datetime.now(),
+ "specialsort": "top",
+ "type": items[0]["type"],
+ "params": [
+ {
+ "play_latest": url
+ }
+ ]
+ }
+ self.add_list_item(entry, path)
+
+ li = None
+ for i, item in enumerate(items):
+ if i >= offset and (not limit or i < offset + limit):
+ li = self._create_list_item(item)
+ xbmcplugin.addDirectoryItem(handle=self.addon_handle,
+ listitem=li,
+ url=item["stream_url"],
+ isFolder=False)
+
+ if li and "setDateTime" in dir(li): # available since Kodi v20
+ xbmcplugin.addSortMethod(
+ self.addon_handle, xbmcplugin.SORT_METHOD_DATE)
+ xbmcplugin.endOfDirectory(self.addon_handle)
+
+ def play_latest(self, url: str) -> None:
+
+ try:
+ title, description, image, items = self._load_rss(url)
+ item = items[0]
+ li = self._create_list_item(item)
+ xbmcplugin.setResolvedUrl(self.addon_handle, True, li)
+
+ except HttpStatusError as error:
+
+ xbmcgui.Dialog().notification(self.addon.getLocalizedString(32151), error.message)
diff --git a/plugin.audio.deutschlandfunk/resources/lib/rssaddon/http_client.py b/plugin.audio.deutschlandfunk/resources/lib/rssaddon/http_client.py
new file mode 100644
index 0000000000..2f34fd4cf1
--- /dev/null
+++ b/plugin.audio.deutschlandfunk/resources/lib/rssaddon/http_client.py
@@ -0,0 +1,36 @@
+from resources.lib.rssaddon.http_status_error import HttpStatusError
+
+import requests
+
+import xbmc
+
+def http_request(addon, url, headers=dict(), method="GET"):
+
+ useragent = f"{addon.getAddonInfo('id')}/{addon.getAddonInfo('version')} (Kodi/{xbmc.getInfoLabel('System.BuildVersionShort')})"
+ headers["User-Agent"] = useragent
+
+ if method == "GET":
+ req = requests.get
+ elif method == "POST":
+ req = requests.post
+ else:
+ raise HttpStatusError(
+ addon.getLocalizedString(32152) % method)
+
+ try:
+ res = req(url, headers=headers)
+ except requests.exceptions.RequestException as error:
+ xbmc.log("Request Exception: %s" % str(error), xbmc.LOGERROR)
+ raise HttpStatusError(addon.getLocalizedString(32153))
+
+ if res.status_code == 200:
+ if res.encoding and res.encoding != "utf-8":
+ rv = res.text.encode(res.encoding).decode("utf-8")
+ else:
+ rv = res.text
+
+ return rv, res.cookies
+
+ else:
+ raise HttpStatusError(addon.getLocalizedString(
+ 32154) % (res.status_code, url))
\ No newline at end of file
diff --git a/plugin.audio.deutschlandfunk/resources/lib/rssaddon/http_status_error.py b/plugin.audio.deutschlandfunk/resources/lib/rssaddon/http_status_error.py
new file mode 100644
index 0000000000..ae3185f615
--- /dev/null
+++ b/plugin.audio.deutschlandfunk/resources/lib/rssaddon/http_status_error.py
@@ -0,0 +1,7 @@
+class HttpStatusError(Exception):
+
+ message = ""
+
+ def __init__(self, msg):
+
+ self.message = msg
\ No newline at end of file
diff --git a/plugin.audio.eco99music/LICENSE.txt b/plugin.audio.eco99music/LICENSE.txt
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/plugin.audio.eco99music/LICENSE.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/plugin.audio.eco99music/addon.py b/plugin.audio.eco99music/addon.py
new file mode 100644
index 0000000000..5b83417a6f
--- /dev/null
+++ b/plugin.audio.eco99music/addon.py
@@ -0,0 +1,164 @@
+# coding=utf-8
+
+import sys
+import os
+import xbmcaddon
+import xbmcgui
+import xbmcplugin
+import requests
+import re
+import xmltodict
+from six.moves.urllib.parse import parse_qs, urlencode
+
+
+def build_url(query):
+ """Build route url
+
+ :param query: Dictionary to create URL for.
+ :type query: dict
+ :return: Complete route URL.
+ :rtype: str
+ """
+ base_url = sys.argv[0]
+ return base_url + '?' + urlencode(query)
+
+
+def get_rss(url):
+ """Download the source XML for given RSS URL using requests
+ and parse the page using xmltodict.
+
+ :param url: URL of RSS page.
+ :type url: str
+ :return: Dictionary of parsed XML RSS page.
+ :rtype: dict
+ """
+ return xmltodict.parse(requests.get(url).text)
+
+
+def get_channels():
+ """Extract channels from rss.
+
+ :return: Return dictionary of received channels.
+ :rtype: dict
+ """
+ rss = get_rss('http://eco99fm.maariv.co.il/RSS_MusicChannels_Index/')
+ channels = {}
+ index = 1
+
+ for item in rss["rss"]["channel"]["item"]:
+ channels.update({
+ index: {
+ 'album_cover':
+ re.search("src='([^']+)'", item['description']).group(1),
+ 'title':
+ item['title'],
+ 'description':
+ item['itunes:summary'],
+ 'url':
+ build_url({
+ 'mode': 'playlist',
+ 'url': item['link']
+ })
+ }
+ })
+ index += 1
+ return channels
+
+
+def get_playlists(url):
+ """Get playlists of a channel.
+
+ :param url: Channel rss url.
+ :type url: str
+ :return: Dictionary containing playlist items.
+ :rtype: dict
+ """
+ rss = get_rss(url)
+ playlists = {}
+ index = 1
+ for item in rss["rss"]["channel"]["item"]:
+ playlists.update({
+ index: {
+ 'album_cover':
+ re.search("src='([^']+)'", item['description']).group(1),
+ 'title':
+ item['title'],
+ 'description':
+ item['itunes:summary'],
+ 'url':
+ build_url({
+ 'mode': 'stream',
+ 'url': item['enclosure']['@url']
+ })
+ }
+ })
+ index += 1
+ return playlists
+
+
+def build_menu(items, is_folder):
+ """Build menu control
+
+ :param items: List of items, can be channels or playlist.
+ :type items: list
+ :param is_folder: If True the item is channel else a playlist.
+ :type is_folder: bool
+ """
+ items_list = []
+
+ for item in items:
+ # create a list item using the song filename for the label
+ li = xbmcgui.ListItem(label=items[item]['title'])
+ # set the fanart to the album cover
+ if not is_folder:
+ li.setProperty('IsPlayable', 'true')
+ li.setProperty('PlotOutline', items[item]['description'])
+ li.setInfo(
+ 'video', {
+ 'title': items[item]['title'],
+ 'genre': 'Podcast',
+ 'plot': items[item]['description']
+ })
+ li.setArt({
+ 'thumb':
+ items[item]['album_cover'],
+ 'poster':
+ items[item]['album_cover'],
+ 'fanart':
+ os.path.join(ADDON_FOLDER, 'resources', 'media', 'fanart.jpg')
+ })
+ url = items[item]['url']
+ items_list.append((url, li, is_folder))
+ xbmcplugin.addDirectoryItems(ADDON_HANDLE, items_list, len(items_list))
+ xbmcplugin.setContent(ADDON_HANDLE, 'songs')
+ xbmcplugin.endOfDirectory(ADDON_HANDLE)
+
+
+def play(url):
+ """Play playlist by URL.
+
+ :param url: URL of playlist.
+ :type url: str
+ """
+ play_item = xbmcgui.ListItem(path=url)
+ xbmcplugin.setResolvedUrl(ADDON_HANDLE, True, listitem=play_item)
+
+
+def main():
+ """Main method."""
+ args = parse_qs(sys.argv[2][1:])
+ mode = args.get('mode', None)
+ if mode is None:
+ items = get_channels()
+ build_menu(items, True)
+ elif mode[0] == 'playlist':
+ items = get_playlists(args['url'][0])
+ build_menu(items, False)
+ elif mode[0] == 'stream':
+ play(args['url'][0].replace('/playlist.m3u8', ''))
+
+
+if __name__ == '__main__':
+ ADDON_FOLDER = xbmcaddon.Addon().getAddonInfo('path')
+ ADDON_HANDLE = int(sys.argv[1])
+ main()
diff --git a/plugin.audio.eco99music/addon.xml b/plugin.audio.eco99music/addon.xml
new file mode 100644
index 0000000000..36e64995c7
--- /dev/null
+++ b/plugin.audio.eco99music/addon.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+ audio
+
+
+ אלפי פלייליסטים וסטים מוזיקליים, ערוכים ומוכנים להאזנה, ומותאמים לכל מצב, בכל מקום ובכל שעה
+ Thousands of premium music playlists for any mood, activity and time!
+
+ ברוכים הבאים לתוסף eco99music מבית תחנת הרדיו eco99fm. מחכים לכם כאן אלפי פלייליסטים וסטים מוזיקליים, ערוכים ומוכנים להאזנה, ומותאמים לכל מצב, בכל מקום ובכל שעה.
+תהנו, האזנה נעימה!
+לתשומת לבכם- התוכן זמין ופעיל בישראל בלבד.
+
+
+ Welcome to eco99music, an innovative music application based on the consumer possibilities of the listener and browser, and adapts edited music playlists for the current and relevant status of each listener. The app offers the consumers
+a unique and personalizes service, based on their location, activity, time of the day and their mood.
+Enjoy!
+…
+FYI - our content is available only in Israel.
+
+ This is an official ECO 99FM Kodi add-on
+ he
+ all
+ GPL-3.0-only
+ http://eco99fm.maariv.co.il/
+ radio@eco99.fm
+ https://github.com/noamsto/plugin.audio.eco99music
+
+ resources/media/icon.png
+ resources/media/fanart.jpg
+ resources/media/screenshot-01.jpg
+ resources/media/screenshot-02.jpg
+ resources/media/screenshot-03.jpg
+
+
+
diff --git a/plugin.audio.eco99music/changelog.txt b/plugin.audio.eco99music/changelog.txt
new file mode 100644
index 0000000000..ea4823d54e
--- /dev/null
+++ b/plugin.audio.eco99music/changelog.txt
@@ -0,0 +1,19 @@
+v0.0.1
+- Initial version
+
+v0.0.2
+- Use different link to play playlist
+
+v0.0.3
+- Update description & fanart
+
+v0.0.4
+- Fix the XML parsing errors
+
+v0.0.5
+- Fixed playlist not playing bug
+- Ready for python3
+
+v0.0.6
+- Bumped Python requirement to version 3.0.0
+- Kodi 19 compatible
diff --git a/plugin.audio.eco99music/resources/media/fanart.jpg b/plugin.audio.eco99music/resources/media/fanart.jpg
new file mode 100644
index 0000000000..7591546e23
Binary files /dev/null and b/plugin.audio.eco99music/resources/media/fanart.jpg differ
diff --git a/plugin.audio.eco99music/resources/media/icon.png b/plugin.audio.eco99music/resources/media/icon.png
new file mode 100644
index 0000000000..268fe64b63
Binary files /dev/null and b/plugin.audio.eco99music/resources/media/icon.png differ
diff --git a/plugin.audio.eco99music/resources/media/screenshot-01.jpg b/plugin.audio.eco99music/resources/media/screenshot-01.jpg
new file mode 100644
index 0000000000..55934930d0
Binary files /dev/null and b/plugin.audio.eco99music/resources/media/screenshot-01.jpg differ
diff --git a/plugin.audio.eco99music/resources/media/screenshot-02.jpg b/plugin.audio.eco99music/resources/media/screenshot-02.jpg
new file mode 100644
index 0000000000..4f928ef80e
Binary files /dev/null and b/plugin.audio.eco99music/resources/media/screenshot-02.jpg differ
diff --git a/plugin.audio.eco99music/resources/media/screenshot-03.jpg b/plugin.audio.eco99music/resources/media/screenshot-03.jpg
new file mode 100644
index 0000000000..7635435d61
Binary files /dev/null and b/plugin.audio.eco99music/resources/media/screenshot-03.jpg differ
diff --git a/plugin.audio.indigitube/LICENSE.txt b/plugin.audio.indigitube/LICENSE.txt
new file mode 100644
index 0000000000..a625e2a375
--- /dev/null
+++ b/plugin.audio.indigitube/LICENSE.txt
@@ -0,0 +1,19 @@
+This plugin is Copyright (c) 2023 Simon Mollema.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/plugin.audio.indigitube/addon.xml b/plugin.audio.indigitube/addon.xml
new file mode 100644
index 0000000000..1a3ccbf9d9
--- /dev/null
+++ b/plugin.audio.indigitube/addon.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+ audio
+
+
+ Unofficial plugin for indigiTUBE: the Australian national media platform by and for First Nations people.
+ MIT
+ indigiTUBE (www.indigitube.com.au) connects and supports the preservation of language and culture for our future generations. A living modern midden where technology and culture are woven together. indigiTUBE gathers First Nations stories from the desert to the sea, connecting and sharing culture from our extremely remote to urban regions; from our fresh new talent to archived histories. indigiTUBE is a digital meeting place for First Nations song, dance, language and lore; creating a unified space to share our evolving and living culture. It also live streams 27 different radio stations to hear what's going on around the country. The visually stunning media platform reflects the rich culture of our First Nations people, and the vibrant colours represent ochre, land and sea.
+ all
+ xbmc@molzy.com
+ https://github.com/molzy/plugin.audio.indigitube
+ v1.0.0
+- Initial release, with content from the homepage including radio, albums, videos and more
+
+
+ resources/icon.png
+ resources/fanart.jpg
+ resources/screenshots/home.jpg
+ resources/screenshots/radio.jpg
+ resources/screenshots/album.jpg
+
+
+
diff --git a/plugin.audio.indigitube/default.py b/plugin.audio.indigitube/default.py
new file mode 100644
index 0000000000..521303d240
--- /dev/null
+++ b/plugin.audio.indigitube/default.py
@@ -0,0 +1,136 @@
+import sys, json, re
+from urllib.request import Request, urlopen
+from urllib.parse import parse_qs, urlencode
+
+import xbmcgui
+import xbmcplugin
+import xbmcaddon
+import xbmc
+from resources.lib.ListItems import ListItems
+
+try:
+ import StorageServer
+except:
+ from resources.lib.cache import storageserverdummy as StorageServer
+cache = StorageServer.StorageServer('plugin.audio.indigitube', 24) # (Your plugin name, Cache time in hours)
+
+USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
+CONTENT_QUERY = 'https://api.appbooks.com/content/_query/{}'
+CONTENT_CHANNEL = 'https://api.appbooks.com/content/channel/{}'
+CONTENT_ALBUM = 'https://api.appbooks.com/content/album/{}'
+CONTENT_PAGE = 'https://api.appbooks.com/content/pageConfiguration/{}'
+
+PAGE_HOME = '5b5abc2bf6b4d90e6deeaabb'
+QUERY_RADIO = '5d1aeac759dd785afe88ec0b'
+CHANNEL_RADIO = '5b5ac73df6b4d90e6deeabd1'
+
+def urlopen_ua(url):
+ headers = {
+ 'User-Agent': USER_AGENT,
+ 'Authorization': 'Bearer $2a$10$x2Zy/TgIAOC0UUMi3NPKc.KY49e/ZLUJFOpBCNYAs8D72UUnlI526',
+ }
+ return urlopen(Request(url, headers=headers), timeout=5)
+
+def get_json(url):
+ return urlopen_ua(url).read().decode()
+
+def get_json_obj(url):
+ return json.loads(get_json(url))
+
+def get_query_content(media_id):
+ return get_json_obj(CONTENT_QUERY.format(media_id))
+
+def get_channel_content(media_id):
+ return get_json_obj(CONTENT_CHANNEL.format(media_id))
+
+def get_album_content(media_id):
+ return get_json_obj(CONTENT_ALBUM.format(media_id))
+
+def get_page_content(page_id):
+ return get_json_obj(CONTENT_PAGE.format(page_id))
+
+
+def build_main_menu():
+ home_json = get_page_content(PAGE_HOME)
+ root_items = list_items.get_root_items(home_json)
+ if len(root_items) > 0:
+ xbmcplugin.setPluginCategory(addon_handle, home_json.get('title', ''))
+ xbmcplugin.addDirectoryItems(addon_handle, root_items, len(root_items))
+ xbmcplugin.endOfDirectory(addon_handle)
+
+def build_query_list(query_id, title=''):
+ query_json = get_query_content(query_id)
+ query_items = list_items.get_query_items(query_json)
+ if len(query_items) > 0:
+ xbmcplugin.setPluginCategory(addon_handle, title)
+ xbmcplugin.addDirectoryItems(addon_handle, query_items, len(query_items))
+ xbmcplugin.endOfDirectory(addon_handle)
+
+def build_channel_list(channel_id):
+ channel_json = get_channel_content(channel_id)
+ channel_data = channel_json.get('data', {})
+ if len(channel_data.get('items', [])) > 1:
+ channel_items = list_items.get_channel_items(channel_json)
+ if len(channel_items) > 0:
+ xbmcplugin.setPluginCategory(addon_handle, channel_json.get('title', ''))
+ xbmcplugin.addDirectoryItems(addon_handle, channel_items, len(channel_items))
+ xbmcplugin.endOfDirectory(addon_handle)
+ else:
+ query = channel_data.get('query', {}).get('_id')
+ return build_query_list(query, title=channel_json.get('title'))
+
+def build_song_list(album_id):
+ album_json = get_album_content(album_id)
+ album_items = list_items.get_track_items(album_json)
+ if len(album_items) > 0:
+ xbmcplugin.setPluginCategory(addon_handle, album_json.get('title', ''))
+ xbmcplugin.addSortMethod(addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
+ xbmcplugin.setContent(addon_handle, 'songs')
+ xbmcplugin.addDirectoryItems(addon_handle, album_items, len(album_items))
+ xbmcplugin.endOfDirectory(addon_handle)
+
+def play_song(url):
+ play_item = xbmcgui.ListItem(path=url)
+ xbmcplugin.setResolvedUrl(addon_handle, True, listitem=play_item)
+
+
+def main():
+ if addon.getSettingBool('first_run'):
+ xbmcgui.Dialog().textviewer(get_string(30098), get_string(30099))
+ if not list_items.matrix:
+ explicit = xbmcgui.Dialog().yesno(get_string(30030), get_string(30032), defaultbutton=xbmcgui.DLG_YESNO_YES_BTN)
+ else:
+ explicit = xbmcgui.Dialog().yesno(get_string(30030), get_string(30032))
+ # deceased = xbmcgui.Dialog().yesno(get_string(30040), get_string(30042), defaultbutton=xbmcgui.DLG_YESNO_YES_BTN)
+ addon.setSettingBool('allow_explicit', explicit)
+ # addon.setSettingBool('allow_deceased', deceased)
+ addon.setSettingBool('first_run', False)
+ args = parse_qs(sys.argv[2][1:])
+ mode = args.get('mode', None)
+ if mode is None:
+ build_main_menu()
+ elif mode[0] == 'explicit':
+ xbmcgui.Dialog().notification(get_string(30033), get_string(30034))
+ elif mode[0] == 'stream':
+ play_song(args.get('url', [''])[0])
+ elif mode[0] == 'list_radio':
+ build_query_list(QUERY_RADIO)
+ elif mode[0] == 'list_query':
+ query_id = args.get('query_id', [''])[0]
+ build_query_list(query_id)
+ elif mode[0] == 'list_channel':
+ channel_id = args.get('channel_id', [''])[0]
+ build_channel_list(channel_id)
+ elif mode[0] == 'list_songs':
+ album_id = args.get('album_id', [''])[0]
+ build_song_list(album_id)
+
+def get_string(string_id):
+ return addon.getLocalizedString(string_id)
+
+if __name__ == '__main__':
+ xbmc.log("indigiTUBE plugin called: " + str(sys.argv), xbmc.LOGDEBUG)
+ addon = xbmcaddon.Addon()
+ list_items = ListItems(addon)
+ addon_handle = int(sys.argv[1])
+ main()
diff --git a/plugin.audio.indigitube/resources/fanart.jpg b/plugin.audio.indigitube/resources/fanart.jpg
new file mode 100644
index 0000000000..083b617005
Binary files /dev/null and b/plugin.audio.indigitube/resources/fanart.jpg differ
diff --git a/plugin.audio.indigitube/resources/icon.png b/plugin.audio.indigitube/resources/icon.png
new file mode 100644
index 0000000000..92ce6a9cfa
Binary files /dev/null and b/plugin.audio.indigitube/resources/icon.png differ
diff --git a/plugin.audio.indigitube/resources/language/resource.language.en_gb/strings.po b/plugin.audio.indigitube/resources/language/resource.language.en_gb/strings.po
new file mode 100644
index 0000000000..5aec25ed31
--- /dev/null
+++ b/plugin.audio.indigitube/resources/language/resource.language.en_gb/strings.po
@@ -0,0 +1,96 @@
+# Kodi Media Center language file
+# Addon Name: Indigitube
+# Addon id: plugin.audio.indigitube
+# Addon Provider: Simon Mollema
+
+msgid ""
+msgstr ""
+"Project-Id-Version: Kodi Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@kodi.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+# Settings - General
+msgctxt "#30001"
+msgid "General"
+msgstr ""
+
+msgctxt "#30011"
+msgid "General settings for the indigiTUBE addon"
+msgstr ""
+
+msgctxt "#30020"
+msgid "Image Quality"
+msgstr ""
+
+msgctxt "#30021"
+msgid "Set the desired quality level for album art and band images"
+msgstr ""
+
+msgctxt "#30022"
+msgid "High"
+msgstr ""
+
+msgctxt "#30023"
+msgid "Medium"
+msgstr ""
+
+msgctxt "#30024"
+msgid "Low"
+msgstr ""
+
+msgctxt "#30030"
+msgid "Allow Explicit Language"
+msgstr ""
+
+msgctxt "#30031"
+msgid "When turned off, content containing explicit language will be filtered."
+msgstr ""
+
+msgctxt "#30032"
+msgid "WARNING: Some content contains explicit language.\nAllow explicit language?\nYour selection can be changed later in the add-on settings."
+msgstr ""
+
+msgctxt "#30033"
+msgid "Contains Explicit Language"
+msgstr ""
+
+msgctxt "#30034"
+msgid "Explicit language is disabled in the add-on settings."
+msgstr ""
+
+msgctxt "#30040"
+msgid "Allow Content Containing Deceased People"
+msgstr ""
+
+msgctxt "#30041"
+msgid "When turned off, content containing deceased people will be filtered."
+msgstr ""
+
+msgctxt "#30032"
+msgid "WARNING: Some content contains images, voices and names of deceased people.\nAllow this content?\nYour selection can be changed later in the add-on settings."
+msgstr ""
+
+msgctxt "#30098"
+msgid "Acknowledgement Of Country"
+msgstr ""
+
+msgctxt "#30099"
+msgid "indigiTUBE acknowledges our traditional custodians of country where we live and work.\n\nWe pay respect to Elders past, present and emerging, and acknowledge our continuous connection and contribution to land, sea and community.\n\nWARNING: This add-on contains images, voices and names of deceased people."
+msgstr ""
+
+# GUI main menu
+msgctxt "#30101"
+msgid "Listen To Live Radio"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Search"
+msgstr ""
diff --git a/plugin.audio.indigitube/resources/lib/ListItems.py b/plugin.audio.indigitube/resources/lib/ListItems.py
new file mode 100644
index 0000000000..dc064558f2
--- /dev/null
+++ b/plugin.audio.indigitube/resources/lib/ListItems.py
@@ -0,0 +1,310 @@
+import sys, re, os
+import xbmc
+import xbmcgui
+from urllib.parse import urlencode
+
+class ListItems:
+ INDIGITUBE_ACCESS_KEY = 'access_token=%242a%2410%24x2Zy%2FTgIAOC0UUMi3NPKc.KY49e%2FZLUJFOpBCNYAs8D72UUnlI526'
+ INDIGITUBE_ALBUM_URL = 'https://api.appbooks.com/content/album/{}?' + INDIGITUBE_ACCESS_KEY
+ INDIGITUBE_TRACK_URL = 'https://api.appbooks.com/get/{}?' + INDIGITUBE_ACCESS_KEY
+ INDIGITUBE_VIDEO_URL = 'https://api.appbooks.com/get/{}?variant=720&' + INDIGITUBE_ACCESS_KEY
+ INDIGITUBE_ALBUM_ART_URL = 'https://api.appbooks.com/get/{}/file/file.jpg?w={}&quality=90&' + INDIGITUBE_ACCESS_KEY + '&ext=.jpg'
+ QUERY_RADIO = '5b5ac73df6b4d90e6deeabd1'
+ NOWHERE = 'plugin://plugin.audio.indigitube/?mode=explicit'
+
+ def __init__(self, addon):
+ self.matrix = '19.' in xbmc.getInfoLabel('System.BuildVersion')
+ self.addon = addon
+ self.allow_explicit = self.addon.getSettingBool('allow_explicit')
+ self.allow_deceased = self.addon.getSettingBool('allow_deceased')
+ self._respath = os.path.join(self.addon.getAddonInfo('path'), 'resources')
+ self.fanart = os.path.join(self._respath, 'fanart.jpg')
+ quality = self.addon.getSetting('image_quality')
+ self.quality = int(quality) if quality else 1
+
+ def _album_quality(self):
+ if self.quality == 0:
+ return '700'
+ if self.quality == 1:
+ return '350'
+ if self.quality == 2:
+ return '200'
+
+ def _build_url(self, query):
+ base_url = sys.argv[0]
+ return base_url + '?' + urlencode(query)
+
+ def get_item(self, item_json, args={}):
+ definition = item_json.get('definition', item_json.get('file', {}).get('definition'))
+ if definition == 'radioStation':
+ return self.get_radio_station_item(item_json)
+ elif definition == 'channel':
+ if item_json.get('_id') == self.QUERY_RADIO:
+ return self.get_channel_item(item_json, query=True)
+ else:
+ return self.get_channel_item(item_json)
+ elif definition == 'audioContent':
+ return self.get_track_item(item_json, args)
+ elif definition == 'videoContent':
+ return self.get_video_item(item_json)
+ elif definition == 'album':
+ return self.get_album_item(item_json)
+
+ def get_radio_station_item(self, item_json):
+ item_data = item_json.get('data', {})
+ title = item_json.get('title', '')
+ if not self.allow_deceased:
+ if item_data.get('deceasedContent', 'no') != "no":
+ return
+ artist = item_json.get('realms', [{}])[0].get('title')
+ url = item_data.get('feedSource')
+ desc = item_data.get('description')
+ textbody = re.compile(r'<[^>]+>').sub('', desc)
+ art_id = item_data.get('coverImage', '')
+ if not isinstance(art_id, str):
+ art_id = art_id.get('_id')
+ art_url = self.INDIGITUBE_ALBUM_ART_URL.format(art_id, self._album_quality())
+
+ if item_data.get('explicit'):
+ title += ' (Explicit)'
+ if not self.allow_explicit:
+ url = self.NOWHERE
+ if artist:
+ title = title + ' - ' + artist
+ li = xbmcgui.ListItem(label=title, offscreen=True)
+ if not self.matrix:
+ vi = li.getVideoInfoTag()
+ vi.setTitle(title)
+ vi.setPlot(textbody)
+ else: # Matrix v19.0
+ vi = {
+ 'title': title,
+ 'plot': textbody,
+ }
+ li.setInfo('video', vi)
+ li.setArt({'thumb': art_url, 'fanart': self.fanart})
+ li.setProperty('IsPlayable', 'true')
+ li.setPath(url)
+ return (url, li, False)
+
+ def get_channel_item(self, item_json, query=False):
+ item_data = item_json.get('data', {})
+ title = item_json.get('title', '')
+ if not self.allow_deceased:
+ if item_data.get('deceasedContent', 'no') != "no":
+ return
+ if query:
+ mode = 'list_query'
+ key_id = 'query_id'
+ item_id = item_json.get('data', {}).get('query')
+ else:
+ mode = 'list_channel'
+ key_id = 'channel_id'
+ item_id = item_json.get('_id')
+ desc = item_data.get('description', '')
+ textbody = re.compile(r'<[^>]+>').sub('', desc)
+ url = self._build_url({'mode': mode, key_id: item_id})
+
+ if item_data.get('allExplicit'):
+ title += ' (Explicit)'
+ if not self.allow_explicit:
+ url = self.NOWHERE
+ li = xbmcgui.ListItem(label=title, offscreen=True)
+ if not self.matrix:
+ vi = li.getVideoInfoTag()
+ vi.setTitle(title)
+ vi.setPlot(textbody)
+ else: # Matrix v19.0
+ vi = {
+ 'title': title,
+ 'plot': textbody,
+ }
+ li.setInfo('video', vi)
+ li.setArt({'fanart': self.fanart})
+ li.setPath(url)
+ return (url, li, True)
+
+ def get_album_item(self, item_json):
+ item_data = item_json.get('data', {})
+ if not self.allow_deceased:
+ if item_data.get('deceasedContent', 'no') != "no":
+ return
+ title = item_json.get('title', '')
+ artist = item_data.get('artist', '')
+ desc = item_data.get('description', '')
+ textbody = re.compile(r'<[^>]+>').sub('', desc)
+ art_id = item_data.get('coverImage', '')
+ if not isinstance(art_id, str):
+ art_id = art_id.get('_id')
+ art_url = self.INDIGITUBE_ALBUM_ART_URL.format(art_id, self._album_quality())
+
+ if item_data.get('allExplicit') or item_data.get('explicit'):
+ title += ' (Explicit)'
+ if artist:
+ title = title + ' - ' + artist
+ folder = False
+
+ if len(item_data.get('items', [])) > 1:
+ url = self._build_url({'mode': 'list_songs', 'album_id': item_json.get('_id')})
+
+ folder = True
+ if item_data.get('allExplicit'):
+ if not self.allow_explicit:
+ folder = False
+ url = self.NOWHERE
+ li = xbmcgui.ListItem(label=title, offscreen=True)
+ else:
+ item = item_data.get('items', [])[0]
+ file = item.get('file', '')
+ if not isinstance(file, str):
+ file = file.get('_id')
+ url = self.INDIGITUBE_TRACK_URL.format(file)
+
+ li = xbmcgui.ListItem(label=title, offscreen=True)
+ li.setProperty('IsPlayable', 'true')
+ if item_data.get('explicit'):
+ if not self.allow_explicit:
+ li.setProperty('IsPlayable', 'false')
+ url = self.NOWHERE
+
+ li.setArt({'thumb': art_url, 'fanart': self.fanart})
+ if not self.matrix:
+ vi = li.getVideoInfoTag()
+ vi.setTitle(title)
+ vi.setPlot(textbody)
+ else: # Matrix v19.0
+ vi = {
+ 'title': title,
+ 'plot': textbody,
+ }
+ li.setInfo('video', vi)
+ li.setPath(url)
+ return (url, li, folder)
+
+ def get_track_item(self, item_json, args):
+ title = item_json.get('title', '')
+ artist = item_json.get('artist', '')
+ file = item_json.get('file', '')
+ if not isinstance(file, str):
+ file = file.get('_id')
+ url = self.INDIGITUBE_TRACK_URL.format(file)
+
+ if item_json.get('explicit'):
+ title += ' (Explicit)'
+ if not self.allow_explicit:
+ url = self.NOWHERE
+ li = xbmcgui.ListItem(label=title, offscreen=True)
+ # if not self.matrix:
+ if False:
+ mi = li.getMusicInfoTag()
+ mi.setTitle(title)
+ mi.setArtist(artist)
+ mi.setMediaType('song')
+ if args.get('album'):
+ mi.setAlbum(args.get('album'))
+ if args.get('album_artist'):
+ mi.setAlbumArtist(args.get('album_artist'))
+ # setTrack doesn't work in v20. Bypassed
+ mi.setTrack(args.get('track_number'))
+ else: # Matrix v19.0
+ mi = {
+ 'title': title,
+ 'artist': artist,
+ 'mediatype': 'song',
+ 'album': args.get('album'),
+ 'albumartist': args.get('album_artist'),
+ 'tracknumber': args.get('track_number'),
+ }
+ li.setInfo('music', mi)
+ li.setArt({'thumb': args.get('art_url'), 'fanart': self.fanart})
+ li.setProperty('IsPlayable', 'true')
+ li.setPath(url)
+ return (url, li, False)
+
+ def get_video_item(self, item_json):
+ item_data = item_json.get('data', {})
+ title = item_json.get('title', '')
+ duration = int(item_json.get('duration', 0))
+ url = self.INDIGITUBE_VIDEO_URL.format(item_json.get('_id'))
+ desc = item_data.get('description', '')
+ textbody = re.compile(r'<[^>]+>').sub('', desc)
+ art_id = item_json.get('poster', '')
+ if not isinstance(art_id, str) and len(art_id) > 0:
+ art_id = art_id[0]
+
+ if item_data.get('explicit'):
+ title += ' (Explicit)'
+ if not self.allow_explicit:
+ url = self.NOWHERE
+ li = xbmcgui.ListItem(label=title, offscreen=True)
+ if not self.matrix:
+ vi = li.getVideoInfoTag()
+ vi.setTitle(title)
+ vi.setDuration(duration)
+ if textbody:
+ vi.setPlot(textbody)
+ else:
+ vi = {
+ 'title': title,
+ 'duration': duration,
+ 'plot': textbody,
+ }
+ li.setInfo('video', vi)
+ if art_id:
+ art_url = self.INDIGITUBE_ALBUM_ART_URL.format(art_id, self._album_quality())
+ li.setArt({'thumb': art_url, 'fanart': self.fanart})
+ li.setProperty('IsPlayable', 'true')
+ if item_data.get('explicit'):
+ if not self.allow_explicit:
+ url = self.NOWHERE
+ li.setProperty('IsPlayable', 'false')
+ li.setPath(url)
+ return (url, li, False)
+
+
+ def get_root_items(self, page_json):
+ page_data = page_json.get('data', {})
+ items = []
+ for carousel in page_data.get('carousels', []):
+ item = self.get_item(carousel)
+ if item:
+ items.append(item)
+ return items
+
+ def get_query_items(self, query_json):
+ items = []
+ for station in query_json:
+ item = self.get_item(station)
+ if item:
+ items.append(item)
+ # items.sort(key=lambda x: x[1].getLabel())
+ return items
+
+ def get_channel_items(self, channel_json):
+ channel_data = channel_json.get('data', {})
+ channel_items = channel_data.get('items', {})
+ items = []
+ for channel in channel_items:
+ if channel.get('item'):
+ item = self.get_item(channel.get('item'))
+ if item:
+ items.append(item)
+ return items
+
+ def get_track_items(self, album_json):
+ items = []
+ album_title = album_json.get('title', '')
+ album_artist = album_json.get('realms', [{}])[0].get('title', '')
+ album_data = album_json.get('data', {})
+ art_id = album_data.get('coverImage', {}).get('_id')
+ album_art_url = self.INDIGITUBE_ALBUM_ART_URL.format(art_id, self._album_quality())
+ args = {
+ 'track_number': 1,
+ 'album': album_title,
+ 'album_artist': album_artist,
+ 'art_url': album_art_url,
+ }
+ for track in album_data.get('items', []):
+ items.append(self.get_item(track, args))
+ args['track_number'] += 1
+ return items
diff --git a/plugin.audio.indigitube/resources/screenshots/album.jpg b/plugin.audio.indigitube/resources/screenshots/album.jpg
new file mode 100644
index 0000000000..8f2dd100d7
Binary files /dev/null and b/plugin.audio.indigitube/resources/screenshots/album.jpg differ
diff --git a/plugin.audio.indigitube/resources/screenshots/home.jpg b/plugin.audio.indigitube/resources/screenshots/home.jpg
new file mode 100644
index 0000000000..c5ccdaee77
Binary files /dev/null and b/plugin.audio.indigitube/resources/screenshots/home.jpg differ
diff --git a/plugin.audio.indigitube/resources/screenshots/radio.jpg b/plugin.audio.indigitube/resources/screenshots/radio.jpg
new file mode 100644
index 0000000000..0a6fd4366c
Binary files /dev/null and b/plugin.audio.indigitube/resources/screenshots/radio.jpg differ
diff --git a/plugin.audio.indigitube/resources/settings.xml b/plugin.audio.indigitube/resources/settings.xml
new file mode 100644
index 0000000000..1f6da85d8c
--- /dev/null
+++ b/plugin.audio.indigitube/resources/settings.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+ 4
+ true
+
+
+ 0
+ true
+
+
+
+ 4
+ true
+
+
+ 0
+ 1
+
+
+
+
+
+
+
+
+ 30020
+
+
+
+
+
+
diff --git a/plugin.audio.kvartal/LICENSE.txt b/plugin.audio.kvartal/LICENSE.txt
new file mode 100644
index 0000000000..d159169d10
--- /dev/null
+++ b/plugin.audio.kvartal/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/plugin.audio.kvartal/README.md b/plugin.audio.kvartal/README.md
new file mode 100644
index 0000000000..d18761d2e2
--- /dev/null
+++ b/plugin.audio.kvartal/README.md
@@ -0,0 +1,20 @@
+# Kvartal
+Kodi add-on for listening to podcasts published by Kvartal: https://kvartal.se.
+
+## Screenshots
+
", "\n")
+ reg = re.compile('<.*?>')
+ return re.sub(reg, '', html)
+ return ""
+
+
+def get_time_format(timestamp, offset=False, timeOnly=False):
+ start_timestamp = int(str(timestamp)[:-3])
+ if offset:
+ start_timestamp = start_timestamp + int(str(offset)[:-3])
+ start_time = datetime.fromtimestamp(start_timestamp)
+ if not timeOnly:
+ return start_time.strftime("%d.%m.%Y, %H:%M")
+ else:
+ return start_time.strftime("%H:%M")
+
+
+def get_date_format(datestring):
+ try:
+ if datestring:
+ date = datetime.strptime(str(datestring), '%Y%m%d')
+ if date:
+ return date.strftime("%d.%m.%Y")
+ return ""
+ except Exception as e:
+ str_dat = str(datestring)
+ return "%s.%s.%s" % (str_dat[6:8], str_dat[4:6], str_dat[0:4])
+
+
+def get_images(image_versions, thumbnail=False, strict=False):
+ images = []
+ for image_info in image_versions:
+ image_width = int(image_info['width'])
+ if thumbnail and 400 <= image_width <= 500:
+ return image_info['path']
+ else:
+ images.append(image_info['path'])
+ if thumbnail and strict:
+ return ""
+ if thumbnail:
+ images.reverse()
+ return images[0]
+ if images:
+ images.reverse()
+ return images
+
+ if thumbnail:
+ return ""
+ return []
diff --git a/plugin.audio.radiothek/resources/lib/RadioThek.py b/plugin.audio.radiothek/resources/lib/RadioThek.py
new file mode 100644
index 0000000000..7ebda976d7
--- /dev/null
+++ b/plugin.audio.radiothek/resources/lib/RadioThek.py
@@ -0,0 +1,620 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+from resources.lib.Helpers import *
+from resources.lib.Directory import Directory
+from resources.lib.Episode import Episode
+
+import json
+import os
+
+try:
+ from urllib.parse import urlencode
+ from urllib.request import urlopen, Request
+except ImportError:
+ from urllib2 import urlopen, Request
+ from urllib import urlencode
+
+
+class RadioThek:
+ local_resource_path = "./"
+ api_ref = "https://orf.at/app-infos/sound/web/1.0/bundle.json?_o=sound.orf.at"
+ api_base = "https://audioapi.orf.at"
+ tag_url = "/radiothek/api/tags/%s"
+ broadcast_url = "/%s/json/4.0/broadcasts"
+ broadcast_detail_url = "/%s/json/4.0/broadcasts/%s"
+ search_url = "/radiothek/api/search"
+ staple_url = "/radiothek/stapled.json?_o=radiothek.orf.at"
+ user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'
+ api_reference = False
+ stapled_content = False
+ channel_icons = {
+ 'bgl': 'bgl.png',
+ 'vbg': 'vbg.png',
+ 'ooe': 'ooe.png',
+ 'noe': 'noe.png',
+ 'ktn': 'ktn.png',
+ 'sbg': 'sbg.png',
+ 'stm': 'stm.png',
+ 'tir': 'tir.png',
+ 'wie': 'wie.png',
+ 'fm4': 'fm4.png',
+ 'oe1': 'oe1.png',
+ 'oe3': 'oe3.png'
+ }
+ station_nice = {
+ 'sbg': 'Radio Salzburg',
+ 'ooe': 'Radio Oberösterreich',
+ 'wie': 'Radio Wien',
+ 'vgrp': 'ORF Volksgruppen',
+ 'vbg': 'Radio Vorarlberg',
+ 'oe3': 'Hitradio Ö3',
+ 'fm4': 'FM4',
+ 'stm': 'Radio Steiermark',
+ 'noe': 'Radio Niederösterreich',
+ 'oe1': 'Ö1',
+ 'ktn': 'Radio Kärnten',
+ 'bgl': 'Radio Burgenland',
+ 'slo': 'Slovenski spored',
+ 'campus': 'Ö1 Campus',
+ 'tir': 'Radio Tirol'
+ }
+ livestream_dd = {
+ 'oe1': 'https://oe1dd.mdn.ors.at/out/u/oe1dd/manifest.m3u8'
+ }
+ livestream_qualities_shoutcast = {
+ 'q1a': 'https://orf-live.ors-shoutcast.at/%s-q1a',
+ 'q2a': 'https://orf-live.ors-shoutcast.at/%s-q2a',
+ }
+ livestream_qualities_hls_aac = {
+ 'q1a': 'https://orf-live-%s.mdn.ors.at/out/u/%s/q1a/manifest.m3u8',
+ 'q2a': 'https://orf-live-%s.mdn.ors.at/out/u/%s/q2a/manifest.m3u8',
+ 'q3a': 'https://orf-live-%s.mdn.ors.at/out/u/%s/q3a/manifest.m3u8',
+ 'q4a': 'https://orf-live-%s.mdn.ors.at/out/u/%s/q4a/manifest.m3u8',
+ 'qxa': 'https://orf-live-%s.mdn.ors.at/out/u/%s/qxa/manifest.m3u8',
+ }
+
+ def __init__(self, local_resource_path, translation, stream_proto, stream_quality):
+ self.log("RadioThek API loaded")
+ self.local_resource_path = local_resource_path
+ self.translation = translation
+ # hls or shoutcast
+ self.stream_proto = stream_proto
+ # shoutcast: q1a, q2a
+ # hls: q1a, q2a, q3a, q4a, qxa
+ self.stream_quality = stream_quality
+
+ if self.stream_proto == 'shoutcast':
+ self.log("Using shoutcast streaming protocol")
+ if self.stream_quality not in self.livestream_qualities_shoutcast:
+ # Default bad quality settings to highest.
+ self.stream_quality = 'q2a'
+ self.livestream_recipe = self.livestream_qualities_shoutcast[self.stream_quality]
+ else:
+ self.log("Using hls streaming protocol")
+ if self.stream_quality not in self.livestream_qualities_hls_aac:
+ # Default bad quality settings to adaptive.
+ self.stream_quality = 'qxa'
+ self.livestream_recipe = self.livestream_qualities_hls_aac[self.stream_quality]
+ self.log("Using quality setting %s" % self.stream_quality)
+
+ @staticmethod
+ def build_stream_url(host_station, loop_stream_id, offset):
+ return {'channel': host_station,
+ 'id': loop_stream_id,
+ 'shoutcast': 0,
+ 'player': 'radiothek_v1',
+ 'referer': 'radiothek.orf.at',
+ 'offset': offset}
+
+ def get_translation(self, msgid, default=False):
+ try:
+ if not self.translation(msgid+100) and default:
+ return default
+ return self.translation(msgid+100)
+ except:
+ return self.translation(msgid+100).encode('utf-8')
+
+ def get_stream_base(self, station, start, loopStreamIds):
+ loopstream_path = ""
+ loopstream_offset = 0
+ for stream in loopStreamIds:
+ if start >= stream['start']:
+ loopstream_path = stream['loopStreamId']
+ loopstream_offset = start - stream['start']
+
+ api_reference = self.get_api_reference()
+ channel_infos = api_reference['stations'][station]
+ host = channel_infos['loopstream']['host']
+ host_channel = channel_infos['loopstream']['channel']
+
+ return host, host_channel, loopstream_path, loopstream_offset
+
+ def get_stream_url(self, json_item, start=False):
+ station = self.get_station_name(json_item, True)
+ if start:
+ (host, host_channel, loopStreamId, offset) = self.get_stream_base(station, start, json_item['streams'])
+ else:
+ (host, host_channel, loopStreamId, offset) = self.get_stream_base(station, json_item['start'], json_item['streams'])
+ parameters = {'channel': host_channel,
+ 'id': loopStreamId,
+ 'shoutcast': 0,
+ 'player': 'radiothek_v1',
+ 'referer': 'radiothek.orf.at',
+ 'offset': offset}
+ get_params = url_encoder(parameters)
+ return "https://%s/?%s" % (host, get_params)
+
+ def format_title(self, json_item, show_station=False, unformatted=False):
+ subtitle_max_len = 20
+ format_title = ""
+ raw_title = ""
+ station_name = self.get_station_name(json_item)
+ if station_name and show_station:
+ format_title += "[%s] " % station_name
+
+ if 'title' in json_item and json_item['title'] is not None:
+ raw_title = json_item['title']
+ format_title += json_item['title']
+ elif 'name' in json_item and json_item['name'] is not None:
+ raw_title = json_item['name']
+ format_title += json_item['name']
+ else:
+ format_title = "[ -- %s -- ]" % station_name
+
+ if unformatted:
+ return raw_title
+
+ if 'programmTitle' in json_item and json_item['programmTitle'] is not None:
+ format_title += " %s" % json_item['programmTitle']
+ if 'broadcastDay' in json_item and json_item['broadcastDay'] is not None:
+ day = get_date_format(json_item['broadcastDay'])
+ else:
+ day = ""
+ if 'subtitle' in json_item and json_item['subtitle'] is not None:
+ subtitle = clean_html(json_item['subtitle'])
+ else:
+ subtitle = ""
+
+ if subtitle and len(subtitle) < subtitle_max_len:
+ format_title += " - %s" % subtitle
+ if day:
+ format_title += " - %s" % day
+
+ return format_title
+
+ def format_description(self, json_item):
+ format_description = ""
+ station_name = self.get_station_name(json_item)
+ if 'broadcastDay' in json_item and json_item['broadcastDay'] is not None:
+ day = get_date_format(json_item['broadcastDay'])
+ else:
+ if 'published' in json_item and json_item['published'] is not None:
+ day = get_time_format(json_item['published'])
+ else:
+ day = ""
+ if 'subtitle' in json_item and json_item['subtitle'] is not None:
+ subtitle = clean_html(json_item['subtitle'])
+ else:
+ subtitle = ""
+ if 'description' in json_item and json_item['description'] is not None:
+ description = clean_html(json_item['description'], True)
+ elif 'text' in json_item and json_item['text'] is not None:
+ description = clean_html(json_item['text'], True)
+ else:
+ description = ""
+
+ broadcasted = ""
+ if 'scheduledStart' in json_item and json_item['scheduledStart'] is not None:
+ broadcasted = get_time_format(json_item['scheduledStart'], False, True)
+
+ if 'scheduledEnd' in json_item and json_item['scheduledEnd'] is not None:
+ broadcasted += " - %s" % get_time_format(json_item['scheduledEnd'], False, True)
+
+ if station_name:
+ format_description += "%s: %s\n" % (self.get_translation(30001, 'Broadcasted'), station_name)
+ if day or broadcasted:
+ format_description += "%s: %s %s\n" % (self.get_translation(30002, 'Broadcasted'), day, broadcasted)
+ if subtitle:
+ format_description += "%s\n" % subtitle
+ if description:
+ format_description += "%s\n" % description
+ return format_description.strip()
+
+ def get_station_name(self, json_item, raw=False):
+ if 'station' in json_item and json_item['station'] is not None:
+ if raw:
+ return json_item['station']
+ if json_item['station'] in self.station_nice:
+ try:
+ return self.station_nice[str(json_item['station'])].decode('utf-8')
+ except:
+ return self.station_nice[str(json_item['station'])]
+ else:
+ return json_item['station']
+ if 'group' in json_item and json_item['group'] is not None:
+ return json_item['group']
+ return ""
+
+ def get_directory_image(self, json_item, image_type):
+ try:
+ station = self.get_station_name(json_item, True)
+ if station and station in self.channel_icons:
+ if image_type == 'logo':
+ return os.path.join(self.local_resource_path, self.channel_icons[station])
+
+ if 'image' in json_item and json_item["image"] and len(json_item['image']) and 'versions' in json_item['image']:
+ image_arr = json_item['image']['versions']
+ if image_type == 'thumbnail':
+ return get_images(image_arr, True)
+ elif image_type == 'backdrop':
+ images = get_images(image_arr)
+ if images and len(images):
+ return images[0]
+
+ if 'image' in json_item:
+ if image_type == 'thumbnail':
+ return json_item['image']
+
+ if 'images' in json_item and json_item["images"] and len(json_item['images']):
+ image_arr = json_item['images'][0]['versions']
+ if image_type == 'thumbnail':
+ return get_images(image_arr, True)
+ elif image_type == 'backdrop':
+ images = get_images(image_arr)
+ if images and len(images):
+ return images[0]
+ except:
+ self.log("Error loading image %s" % image_type)
+ return ""
+
+ @staticmethod
+ def get_files(json_item):
+ files = []
+ if 'enclosures' in json_item:
+ for audio_file in json_item['enclosures']:
+ files.append(audio_file['url'])
+ return files
+
+ @staticmethod
+ def get_link(json_item):
+ if 'href' in json_item and json_item['href'] is not None:
+ return json_item['href']
+ elif 'target' in json_item and json_item['target']:
+ return json_item['target']
+
+ def get_tag_link(self, json_item):
+ rel_link = self.tag_url % json_item['key']
+ return "%s%s" % (self.api_base, rel_link)
+
+ def get_day_selection(self, station):
+ url = self.broadcast_url % station
+ self.log("Loading url %s" % url)
+ try:
+ days_json = self.request_url(url)
+ list_items = []
+ for day in days_json:
+ if 'broadcasts' in day and day['broadcasts'] and len(day['broadcasts']):
+ station_name = self.get_station_name(day['broadcasts'][0])
+ directory_title = "%s - %s" % (station_name, get_date_format(day['day']))
+ directory_description = ""
+ thumbnail = ""
+ backdrop = ""
+ logo = os.path.join(self.local_resource_path, self.channel_icons[station])
+ link = self.broadcast_detail_url % (station, day['day'])
+ day_directory = Directory(directory_title, directory_description, link, thumbnail, backdrop, station, logo)
+ list_items.append(day_directory)
+ return list_items
+ except:
+ self.log("This station has no 'missed a show?' feature.")
+
+ def get_day_selection_details(self, url):
+ show_json = self.request_url(url)
+ list_items = []
+ for show in show_json:
+ station = self.get_station_name(show)
+ title = show['title']
+ time_start = "%s | " % get_time_format(show['start'], False, True)
+ directory_title = "%s%s" % (time_start, title)
+ directory_description = self.format_description(show)
+ thumbnail = self.get_directory_image(show, 'thumbnail')
+ backdrop = self.get_directory_image(show, 'backdrop')
+ logo = self.get_directory_image(show, 'logo')
+ link = self.get_link(show)
+
+ broadcast_directory = Directory(directory_title, directory_description, link, thumbnail, backdrop, station, logo)
+ list_items.append(broadcast_directory)
+ return list_items
+
+ def get_tags(self):
+ staple = self.get_stapled()
+ list_items = []
+ if 'tags' in staple:
+ for tag_item in staple['tags']:
+ station = self.get_station_name(tag_item)
+ directory_title = self.format_title(tag_item)
+ directory_description = self.format_description(tag_item)
+ thumbnail = self.get_directory_image(tag_item, 'thumbnail')
+ backdrop = self.get_directory_image(tag_item, 'backdrop')
+ logo = self.get_directory_image(tag_item, 'logo')
+ link = self.get_tag_link(tag_item)
+
+ tag_directory = Directory(directory_title, directory_description, link, thumbnail, backdrop, station, logo)
+ list_items.append(tag_directory)
+
+ return list_items
+
+ def get_tag_details(self, url):
+ items = []
+ self.log("Getting Tag Details from %s" % url)
+ tag_json = self.request_url(url, True)
+ if tag_json['items']:
+ for tag_item in tag_json['items']:
+ station = self.get_station_name(tag_item)
+ directory_title = self.format_title(tag_item)
+ directory_description = self.format_description(tag_item)
+ thumbnail = self.get_directory_image(tag_item, 'thumbnail')
+ backdrop = self.get_directory_image(tag_item, 'backdrop')
+ logo = self.get_directory_image(tag_item, 'logo')
+ link = self.get_link(tag_item)
+ tag_directory = Directory(directory_title, directory_description, link, thumbnail, backdrop, station, logo)
+ items.append(tag_directory)
+ return items
+
+ def get_highlights(self):
+ staple = self.get_stapled()
+ list_items = []
+ if 'stations' in staple:
+ for station in staple['stations']:
+ if 'highlights' in staple['stations'][station]['data']:
+ broadcast_items = staple['stations'][station]['data']['highlights']
+ for broadcast_item in broadcast_items:
+ broadcast_item['station'] = station
+ directory_title = self.format_title(broadcast_item)
+ directory_description = self.format_description(broadcast_item)
+ thumbnail = self.get_directory_image(broadcast_item, 'thumbnail')
+ backdrop = self.get_directory_image(broadcast_item, 'backdrop')
+ logo = self.get_directory_image(broadcast_item, 'logo')
+ link = self.get_link(broadcast_item)
+ broadcast_directory = Directory(directory_title, directory_description, link, thumbnail, backdrop, station, logo)
+ list_items.append(broadcast_directory)
+ return list_items
+
+ def get_archive(self):
+ staple = self.get_stapled()
+ list_items = []
+ if 'archive' in staple:
+ for archive_item_container in staple['archive']:
+ archive_item = archive_item_container['data']
+
+ station = self.get_station_name(archive_item)
+ directory_title = self.format_title(archive_item)
+ directory_description = self.format_description(archive_item)
+ thumbnail = self.get_directory_image(archive_item, 'thumbnail')
+ backdrop = self.get_directory_image(archive_item, 'backdrop')
+ logo = self.get_directory_image(archive_item, 'logo')
+ link = self.get_link(archive_item_container)
+
+ archive_directory = Directory(directory_title, directory_description, link, thumbnail, backdrop, station, logo)
+ list_items.append(archive_directory)
+ return list_items
+
+ def get_podcasts(self):
+ staple = self.get_stapled()
+ list_items = []
+ if 'podcasts' in staple:
+ for station in staple['podcasts']:
+ for podcast_item_container in staple['podcasts'][station]:
+ podcast_item = podcast_item_container['data']
+ podcast_item['station'] = station
+
+ station = self.get_station_name(podcast_item)
+ directory_title = self.format_title(podcast_item)
+ directory_description = self.format_description(podcast_item)
+ thumbnail = self.get_directory_image(podcast_item, 'thumbnail')
+ backdrop = self.get_directory_image(podcast_item, 'backdrop')
+ logo = self.get_directory_image(podcast_item, 'logo')
+ link = self.get_link(podcast_item_container)
+
+ podcast_directory = Directory(directory_title, directory_description, link, thumbnail, backdrop, station, logo)
+ list_items.append(podcast_directory)
+ return list_items
+
+ def get_podcast_details(self, url):
+ episodes = []
+ self.log("Getting Podcast Details from %s" % url)
+ detail_json = self.request_url(url, True)
+ item_type = 'Podcast'
+ if 'data' in detail_json and 'episodes' in detail_json['data']:
+ data_json = detail_json['data']
+ show_title = self.format_title(data_json, True, True)
+ for episode_json in data_json['episodes']:
+ cms_id = detail_json['slug']
+ station = self.get_station_name(detail_json)
+ raw_episode_title = self.format_title(episode_json, True, True)
+ episode_title = self.format_title(episode_json)
+ if raw_episode_title != show_title:
+ episode_title = "%s - %s" % (show_title, episode_title)
+ episode_description = self.format_description(episode_json)
+ thumbnail = self.get_directory_image(data_json, 'thumbnail')
+ backdrop = self.get_directory_image(data_json, 'backdrop')
+ logo = self.get_directory_image(detail_json, 'logo')
+ files = self.get_files(episode_json)
+ meta = self.get_broadcast_meta(episode_json)
+ episode = Episode(cms_id, episode_title, episode_description, files, item_type, thumbnail, backdrop, station, logo, 0, meta)
+ episodes.append(episode)
+ return episodes
+
+ def get_broadcast_details(self, url):
+ list_items = []
+ broadcast_details = self.request_url(url, True)
+ if broadcast_details and 'entity' in broadcast_details and broadcast_details['entity'] == 'Broadcast':
+ cms_id = broadcast_details['id']
+ item_type = broadcast_details['entity']
+ station = self.get_station_name(broadcast_details)
+ directory_title = self.format_title(broadcast_details, True, True)
+ directory_description = self.format_description(broadcast_details)
+ thumbnail = self.get_directory_image(broadcast_details, 'thumbnail')
+ backdrop = self.get_directory_image(broadcast_details, 'backdrop')
+ logo = self.get_directory_image(broadcast_details, 'logo')
+ stream_url = self.get_stream_url(broadcast_details)
+ meta = self.get_broadcast_meta(broadcast_details)
+ broadcast_episode = Episode(cms_id, directory_title, directory_description, [stream_url], item_type, thumbnail, backdrop, station, logo, 0, meta)
+ list_items.append(broadcast_episode)
+ if 'items' in broadcast_details:
+ for broadcast_detail_item in broadcast_details['items']:
+ broadcast_episode = self.get_broadcast_items(broadcast_detail_item, broadcast_details)
+ list_items.append(broadcast_episode)
+ return list_items
+
+ def get_broadcast_items(self, broadcast_json, parent_broadcast_json):
+ if broadcast_json and 'entity' in broadcast_json and broadcast_json['entity'] == 'BroadcastItem':
+ hidden = 0
+ cms_id = broadcast_json['id']
+ item_type = broadcast_json['entity']
+ station = self.get_station_name(parent_broadcast_json)
+ parent_directory_title = self.format_title(parent_broadcast_json, True, True)
+ directory_title = "%s - %s" % (parent_directory_title, self.format_title(broadcast_json, True, True))
+ directory_description = self.format_description(broadcast_json)
+ thumbnail = self.get_directory_image(broadcast_json, 'thumbnail')
+ if not thumbnail:
+ thumbnail = self.get_directory_image(parent_broadcast_json, 'thumbnail')
+ backdrop = self.get_directory_image(broadcast_json, 'backdrop')
+ if not backdrop:
+ backdrop = self.get_directory_image(parent_broadcast_json, 'backdrop')
+ logo = self.get_directory_image(parent_broadcast_json, 'logo')
+ if not logo:
+ logo = self.get_directory_image(parent_broadcast_json, 'logo')
+
+ stream_url = self.get_stream_url(parent_broadcast_json, broadcast_json['start'])
+ meta = self.get_broadcast_meta(broadcast_json)
+ if not self.format_title(broadcast_json, True, True):
+ hidden = 1
+ return Episode(cms_id, directory_title, directory_description, [stream_url], item_type, thumbnail, backdrop, station, logo, hidden, meta)
+
+ @staticmethod
+ def get_broadcast_meta(broadcast_json):
+ meta = {}
+ if 'interpreter' in broadcast_json:
+ meta['artist'] = broadcast_json['interpreter']
+ meta['trackname'] = broadcast_json['title']
+ if 'duration' in broadcast_json:
+ meta['duration'] = broadcast_json['duration']
+ elif 'start' in broadcast_json and 'end' in broadcast_json:
+ meta['duration'] = broadcast_json['end'] - broadcast_json['start']
+ if 'start' in broadcast_json:
+ meta['start'] = get_time_format(broadcast_json['start'], False, True)
+ if 'end' in broadcast_json:
+ meta['end'] = get_time_format(broadcast_json['end'], False, True)
+ return meta
+
+ def get_broadcast(self):
+ staple = self.get_stapled()
+ list_items = []
+ if 'stations' in staple:
+ for station in staple['stations']:
+ if 'broadcast' in staple['stations'][station]['data']:
+ broadcast_item = staple['stations'][station]['data']['broadcast']
+
+ station = self.get_station_name(broadcast_item)
+ directory_title = self.format_title(broadcast_item)
+ directory_description = self.format_description(broadcast_item)
+ thumbnail = self.get_directory_image(broadcast_item, 'thumbnail')
+ backdrop = self.get_directory_image(broadcast_item, 'backdrop')
+ logo = self.get_directory_image(broadcast_item, 'logo')
+ link = self.get_link(broadcast_item)
+
+ broadcast_directory = Directory(directory_title, directory_description, link, thumbnail, backdrop, station, logo)
+ list_items.append(broadcast_directory)
+ return list_items
+
+ def get_livestream(self):
+ self.get_api_reference()
+ list_items = []
+
+ for station in self.api_reference['stations']:
+ item = self.api_reference['stations'][station]
+ title = item['name']
+ description = "%s Livestream" % title
+
+ if station in self.livestream_dd:
+ link = self.livestream_dd[station]
+ thumbnail = ""
+ backdrop = ""
+ logo = self.get_directory_image({'station': station}, 'logo')
+ if link:
+ episode = Episode(station, "%s (5.1 DD)" % title, description, [link], 'Livestream', thumbnail, backdrop, station,
+ logo)
+ list_items.append(episode)
+
+ if 'liveStreamUrlTemplate' in item:
+ link = item['liveStreamUrlTemplate'].format(quality = self.stream_quality)
+ thumbnail = ""
+ backdrop = ""
+ logo = self.get_directory_image({'station': station}, 'logo')
+ if link:
+ episode = Episode(station, title, description, [link], 'Livestream', thumbnail, backdrop, station, logo)
+ list_items.append(episode)
+ return list_items
+
+ def build_livestream_url(self, channel):
+ if self.stream_proto == 'hls':
+ return self.livestream_recipe % (channel, channel)
+ else:
+ return self.livestream_recipe % channel
+
+ def get_search(self, query):
+ list_items = []
+ parameters = {
+ 'q': query,
+ 'offset': 0,
+ 'limit': 20,
+ '_o': 'radiothek.orf.at'
+ }
+ get_parameters = url_encoder(parameters)
+ url = "%s?%s" % (self.search_url, get_parameters)
+ search_json = self.request_url(url, False)
+ if search_json['length'] > 0 and search_json['total'] > 0:
+ for search_item_container in search_json['hits']:
+ search_item = search_item_container['data']
+ station = self.get_station_name(search_item)
+ directory_title = self.format_title(search_item)
+ directory_description = self.format_description(search_item)
+ thumbnail = self.get_directory_image(search_item, 'thumbnail')
+ backdrop = self.get_directory_image(search_item, 'backdrop')
+ logo = self.get_directory_image(search_item, 'logo')
+ link = self.get_link(search_item)
+ search_directory = Directory(directory_title, directory_description, link, thumbnail, backdrop, station, logo)
+ list_items.append(search_directory)
+ return list_items
+
+ def get_api_reference(self):
+ if not self.api_reference:
+ content = self.request_url(self.api_ref, True, False)
+ self.api_reference = json.loads(content)
+ return self.api_reference
+
+ def get_stapled(self):
+ if not self.stapled_content:
+ self.stapled_content = self.request_url(self.staple_url)
+ return self.stapled_content
+
+ def request_url(self, url, absolute_url=False, parse_json=True):
+ if not absolute_url:
+ request_url = "%s%s" % (self.api_base, url)
+ else:
+ request_url = url
+ self.log("Loading from %s" % request_url)
+ request = urlopen(Request(request_url, headers={'User-Agent': self.user_agent}))
+ request_data = request.read()
+ if parse_json:
+ return json.loads(request_data)
+ else:
+ return request_data
+
+ @staticmethod
+ def log(msg):
+ try:
+ radiothek_log(msg, True)
+ except Exception as e:
+ radiothek_log(msg.encode('utf-8'))
diff --git a/plugin.audio.radiothek/resources/media/bgl.png b/plugin.audio.radiothek/resources/media/bgl.png
new file mode 100644
index 0000000000..f154ad1575
Binary files /dev/null and b/plugin.audio.radiothek/resources/media/bgl.png differ
diff --git a/plugin.audio.radiothek/resources/media/fm4.png b/plugin.audio.radiothek/resources/media/fm4.png
new file mode 100644
index 0000000000..eb65b33b5d
Binary files /dev/null and b/plugin.audio.radiothek/resources/media/fm4.png differ
diff --git a/plugin.audio.radiothek/resources/media/ktn.png b/plugin.audio.radiothek/resources/media/ktn.png
new file mode 100644
index 0000000000..4f43a6c806
Binary files /dev/null and b/plugin.audio.radiothek/resources/media/ktn.png differ
diff --git a/plugin.audio.radiothek/resources/media/noe.png b/plugin.audio.radiothek/resources/media/noe.png
new file mode 100644
index 0000000000..358eded1d5
Binary files /dev/null and b/plugin.audio.radiothek/resources/media/noe.png differ
diff --git a/plugin.audio.radiothek/resources/media/oe1.png b/plugin.audio.radiothek/resources/media/oe1.png
new file mode 100644
index 0000000000..60fbb89105
Binary files /dev/null and b/plugin.audio.radiothek/resources/media/oe1.png differ
diff --git a/plugin.audio.radiothek/resources/media/oe3.png b/plugin.audio.radiothek/resources/media/oe3.png
new file mode 100644
index 0000000000..d69842698c
Binary files /dev/null and b/plugin.audio.radiothek/resources/media/oe3.png differ
diff --git a/plugin.audio.radiothek/resources/media/ooe.png b/plugin.audio.radiothek/resources/media/ooe.png
new file mode 100644
index 0000000000..27d35f7b48
Binary files /dev/null and b/plugin.audio.radiothek/resources/media/ooe.png differ
diff --git a/plugin.audio.radiothek/resources/media/sbg.png b/plugin.audio.radiothek/resources/media/sbg.png
new file mode 100644
index 0000000000..7ac776b371
Binary files /dev/null and b/plugin.audio.radiothek/resources/media/sbg.png differ
diff --git a/plugin.audio.radiothek/resources/media/stm.png b/plugin.audio.radiothek/resources/media/stm.png
new file mode 100644
index 0000000000..65df3287e8
Binary files /dev/null and b/plugin.audio.radiothek/resources/media/stm.png differ
diff --git a/plugin.audio.radiothek/resources/media/tir.png b/plugin.audio.radiothek/resources/media/tir.png
new file mode 100644
index 0000000000..a922b4dc48
Binary files /dev/null and b/plugin.audio.radiothek/resources/media/tir.png differ
diff --git a/plugin.audio.radiothek/resources/media/vbg.png b/plugin.audio.radiothek/resources/media/vbg.png
new file mode 100644
index 0000000000..6a637e21d4
Binary files /dev/null and b/plugin.audio.radiothek/resources/media/vbg.png differ
diff --git a/plugin.audio.radiothek/resources/media/wie.png b/plugin.audio.radiothek/resources/media/wie.png
new file mode 100644
index 0000000000..8b39117b22
Binary files /dev/null and b/plugin.audio.radiothek/resources/media/wie.png differ
diff --git a/plugin.audio.radiothek/resources/settings.xml b/plugin.audio.radiothek/resources/settings.xml
new file mode 100644
index 0000000000..1eb84a2454
--- /dev/null
+++ b/plugin.audio.radiothek/resources/settings.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugin.audio.rne/LICENSE.txt b/plugin.audio.rne/LICENSE.txt
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/plugin.audio.rne/LICENSE.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/plugin.audio.rne/README.md b/plugin.audio.rne/README.md
new file mode 100644
index 0000000000..02f95a787a
--- /dev/null
+++ b/plugin.audio.rne/README.md
@@ -0,0 +1,37 @@
+RNE Podcasts: KODI audio add-on to listen to the podcasts from *RNE* web site.
+
+ Copyright (C) 2015 Jose Antonio Montes (jamontes)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+
+Initial release 1.0.0
+
+[](http://travis-ci.org/jamontes/plugin.audio.rne)
+
+
+This KODI add-on allows you to listen to all the podcast of the emission programmes from the spanish national radio *RNE* website.
+
+From the add-on settings menu, it can be configured to navigate through all the programmes, or just currently on emission (default).
+
+To install it simply download the zip file, and then proceed as with any other plugin from KODI:
+ System->Settings->Add-ons->Install from zip file.
+
+Any issues detected can be reported using this forum thread for official support: https://forum.kodi.tv/showthread.php?tid=224126
+
+Enjoy it!
+
+jamontes
+
+P.S: Dedicated to the memory of Juan Claudio Cifuentes *Cifu* (April 20, 1941 - March 17, 2015) and his 44 years "Jazz Porque Sí" program.
diff --git a/plugin.audio.rne/addon.xml b/plugin.audio.rne/addon.xml
new file mode 100644
index 0000000000..1a858fef3f
--- /dev/null
+++ b/plugin.audio.rne/addon.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+ audio
+
+
+ all
+ Listen to all the podcast from RNE.
+ This add-on allows you to listen to all the podcast of the emission programmes from the spanish national radio RNE website.[CR]From the add-on settings menu, it can be configured to navigate through all the programmes, or just currently on emission (default).
+ Escucha todos los podcasts de RNE.
+ Este add-on te permite escuchar todos los podcasts de las emisiones de los programas de RNE mostrados en su página web.[CR]Desde el menú de ajustes del add-on, se puede elegir entre la opción de mostrar todos los programas, o sólo aquellos actualmente en emisión (valor por defecto).
+ Hören sie alle podcast von RNE.
+ Dieses add-on ermöglicht es ihnen alle podcast von der spanischen nationalen radio RNE website erneut zu hören.[CR]Im add-on einstellungs-menü, kann man verschiedene sendungen auswählen oder sich das aktuelle live streaming anhören (default).
+ Ecoute tous les podcasts de RNE.
+ Cette add-on permet d'écouter tous les podcasts de les émissions des programmes de la Radio National Espagnole RNE présents sur sa page web.[CR]Depuis le menu de préférences du add-on, on peut choisir entre l´option de montrer tous les programmes ou seulement ceux en émission (valeur par défault).
+ es
+ GPL-3.0-or-later
+ https://forum.kodi.tv/showthread.php?tid=224126
+ https://github.com/jamontes/plugin.audio.rne
+ https://www.rtve.es/alacarta/rne/
+
+ Version 1.3.3+matrix.1
+ * Quick fix for direct channels due to website changes (thanks to vichman).
+
+
+ icon.png
+
+
+
diff --git a/plugin.audio.rne/changelog.txt b/plugin.audio.rne/changelog.txt
new file mode 100644
index 0000000000..63e1571de8
--- /dev/null
+++ b/plugin.audio.rne/changelog.txt
@@ -0,0 +1,71 @@
+1.3.3+matrix.1 (2022.11.07)
+- Quick fix for direct channels due to website changes (thanks to vichman).
+1.3.2+matrix.1 (2022.01.15)
+- Quick fix for direct channels due to website changes (credits and great thanks to vicglez).
+1.3.1+matrix.1 (2020.08.29)
+- Quick fix due to loglevel deprecation on matrix release.
+1.3.0+matrix.1 (2020.05.03)
+- Code updated for matrix onwards.
+- Several code fixes due to website changes.
+1.0.13 (2020.05.01)
+- Several code fixes due to website changes.
+1.0.12 (2019.05.02)
+- Quick fix for direct channels due to website changes (full credits and great thanks to vicglez).
+1.0.11 (2018.12.22)
+- Recover podcast playback due to website changes (thanks to cgg1969).
+- Updated search feature due to website changes.
+- Updated integration tests due to code changes.
+1.0.10 (2018.03.25)
+- Quick fix for direct channels.
+- Fixed playback and search podcasts.
+1.0.9 (2018.01.30)
+- Quick fix for direct channels.
+- Added search option for each program (thanks to crashillo for his suggestion).
+- Updated integration tests due to code changes.
+- Minor changes and fixes.
+1.0.8 (2017.10.10)
+- Quick fix for direct channels due to website changes (credits and great thanks to vicglez).
+- Quick fix for media URL (thanks to crashillo and vicglez).
+- Updated search feature due to website changes.
+- Updated integration tests due to code changes.
+1.0.7 (2016.05.02)
+- Quick fix for direct channels due to website changes (credits: vicglez).
+1.0.6 (2016.04.11)
+- Got the direct channels back to their original source (credits: vicglez).
+1.0.5 (2016.02.14)
+- Quick fix for direct channels due to website changes.
+1.0.4 (2016.01.21)
+- Updated the direct channels sources (great thanks to vicglez).
+1.0.3 (2016.01.17)
+- Recover all of the direct channels play (credits: vicglez).
+1.0.2 (2015.12.31)
+- Fixed half of the direct channels play (credits: vicglez).
+- Search code vamped due to website changes.
+1.0.1
+- Quick fix for direct channels play detected in some platforms.
+1.0.0
+- Updated version number for official repo bump.
+- Updated french translations (credits: Emil).
+0.0.9
+- Added forum thread to addon and README files.
+- Added Travis-CI integration support for scraper API testing.
+0.0.8
+- Cleaned up code and documentation.
+- prepared everything for first bump into git repo.
+0.0.7
+- Added Search option for audios.
+0.0.6
+- Improved info tags presentation into audios lists.
+- Improved main index generation.
+0.0.5
+- Added support for direct emissions.
+0.0.4
+- Added filter option for All The Programmes or Only On Emission from settings menu,
+0.0.3
+- Improved audio list parser function.
+- Added suport for info tags in audios and programs.
+0.0.2
+- Added multilevel menu options for "Podcast por cadena" and "Podcast por género".
+- Added internationalization support.
+0.0.1
+- First Try.
diff --git a/plugin.audio.rne/default.py b/plugin.audio.rne/default.py
new file mode 100644
index 0000000000..8db7ce8d22
--- /dev/null
+++ b/plugin.audio.rne/default.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+
+import resources.lib.rne_addon as addon
+
+# Runs the add-on from here.
+if __name__ == "__main__":
+ addon.run()
diff --git a/plugin.audio.rne/icon.png b/plugin.audio.rne/icon.png
new file mode 100644
index 0000000000..d2ec49bdc7
Binary files /dev/null and b/plugin.audio.rne/icon.png differ
diff --git a/plugin.audio.rne/icon_credits.txt b/plugin.audio.rne/icon_credits.txt
new file mode 100644
index 0000000000..3b5561d1d8
--- /dev/null
+++ b/plugin.audio.rne/icon_credits.txt
@@ -0,0 +1 @@
+icon credits: Jose Antonio Rodrigo.
diff --git a/plugin.audio.rne/resources/__init__.py b/plugin.audio.rne/resources/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugin.audio.rne/resources/language/resource.language.de_de/strings.po b/plugin.audio.rne/resources/language/resource.language.de_de/strings.po
new file mode 100644
index 0000000000..44cde7e9aa
--- /dev/null
+++ b/plugin.audio.rne/resources/language/resource.language.de_de/strings.po
@@ -0,0 +1,60 @@
+# XBMC Media Center language file
+# Addon Name: RNE Podcasts
+# Addon id: plugin.audio.rne
+# Addon Provider: Jose Antonio Montes (jamontes)
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Gabriel Lorenzo\n"
+"Language-Team: German (http://www.transifex.com/projects/p/xbmc-addons/language/de/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: de\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#30010"
+msgid "Next page"
+msgstr "Nächste seite"
+
+msgctxt "#30011"
+msgid "Audio type not supported"
+msgstr "Audiotype nicht unterstützt"
+
+msgctxt "#30012"
+msgid "Couldn't locate audio url"
+msgstr "Konnte audio-URL nicht finden"
+
+msgctxt "#30013"
+msgid "Previous page"
+msgstr "Vorherige seite"
+
+msgctxt "#30014"
+msgid "Search"
+msgstr "Suchen"
+
+msgctxt "#30015"
+msgid "Search result"
+msgstr "Suchergebnis"
+
+# Empty strings from id 30016 to 30100
+
+msgctxt "#30101"
+msgid "Debug (enable logs)"
+msgstr "Debug (logs aktivieren)"
+
+msgctxt "#30102"
+msgid "Filter"
+msgstr "Filter"
+
+msgctxt "#30103"
+msgid "Show only on emission programmes"
+msgstr "Nur emissonsprogramme anzeigen"
+
+msgctxt "#30104"
+msgid "Show all the programmes"
+msgstr "Alle sendungen anzeigen"
+
diff --git a/plugin.audio.rne/resources/language/resource.language.en_gb/strings.po b/plugin.audio.rne/resources/language/resource.language.en_gb/strings.po
new file mode 100644
index 0000000000..1c4cdd9a99
--- /dev/null
+++ b/plugin.audio.rne/resources/language/resource.language.en_gb/strings.po
@@ -0,0 +1,59 @@
+# XBMC Media Center language file
+# Addon Name: RNE Podcasts
+# Addon id: plugin.audio.rne
+# Addon Provider: Jose Antonio Montes (jamontes)
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Jose Antonio Montes (jamontes)\n"
+"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#30010"
+msgid "Next page"
+msgstr ""
+
+msgctxt "#30011"
+msgid "Audio type not supported"
+msgstr ""
+
+msgctxt "#30012"
+msgid "Couldn't locate audio url"
+msgstr ""
+
+msgctxt "#30013"
+msgid "Previous page"
+msgstr ""
+
+msgctxt "#30014"
+msgid "Search"
+msgstr ""
+
+msgctxt "#30015"
+msgid "Search result"
+msgstr ""
+
+# Empty strings from id 30016 to 30100
+
+msgctxt "#30101"
+msgid "Debug (enable logs)"
+msgstr ""
+
+msgctxt "#30102"
+msgid "Filter"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Show only on emission programmes"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Show all the programmes"
+msgstr ""
diff --git a/plugin.audio.rne/resources/language/resource.language.es_es/strings.po b/plugin.audio.rne/resources/language/resource.language.es_es/strings.po
new file mode 100644
index 0000000000..f5793373a7
--- /dev/null
+++ b/plugin.audio.rne/resources/language/resource.language.es_es/strings.po
@@ -0,0 +1,59 @@
+# XBMC Media Center language file
+# Addon Name: RNE Podcasts
+# Addon id: plugin.audio.rne
+# Addon Provider: Jose Antonio Montes (jamontes)
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Jose Antonio Montes (jamontes)\n"
+"Language-Team: Spanish (http://www.transifex.com/projects/p/xbmc-addons/language/es/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: es\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#30010"
+msgid "Next page"
+msgstr "Siguiente página"
+
+msgctxt "#30011"
+msgid "Audio type not supported"
+msgstr "Tipo de audio no soportado"
+
+msgctxt "#30012"
+msgid "Couldn't locate audio url"
+msgstr "No se ha podido localizar la url de audio"
+
+msgctxt "#30013"
+msgid "Previous page"
+msgstr "Página anterior"
+
+msgctxt "#30014"
+msgid "Search"
+msgstr "Buscar"
+
+msgctxt "#30015"
+msgid "Search result"
+msgstr "Resultado de la búsqueda"
+
+# Empty strings from id 30016 to 30100
+
+msgctxt "#30101"
+msgid "Debug (enable logs)"
+msgstr "Debug (activa los logs)"
+
+msgctxt "#30102"
+msgid "Filter"
+msgstr "Filtro"
+
+msgctxt "#30103"
+msgid "Show only on emission programmes"
+msgstr "Mostrar sólo los programas en emisión"
+
+msgctxt "#30104"
+msgid "Show all the programmes"
+msgstr "Mostrar todos los programas"
diff --git a/plugin.audio.rne/resources/language/resource.language.fr_fr/strings.po b/plugin.audio.rne/resources/language/resource.language.fr_fr/strings.po
new file mode 100644
index 0000000000..a602d81693
--- /dev/null
+++ b/plugin.audio.rne/resources/language/resource.language.fr_fr/strings.po
@@ -0,0 +1,59 @@
+# XBMC Media Center language file
+# Addon Name: RNE Podcasts
+# Addon id: plugin.audio.rne
+# Addon Provider: Jose Antonio Montes (jamontes)
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Jose Antonio Montes (jamontes)\n"
+"Language-Team: French (http://www.transifex.com/projects/p/xbmc-addons/language/fr/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: fr\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+msgctxt "#30010"
+msgid "Next page"
+msgstr "Page suivante"
+
+msgctxt "#30011"
+msgid "Audio type not supported"
+msgstr "Ce type audio n'est pas pris en charge"
+
+msgctxt "#30012"
+msgid "Couldn't locate audio url"
+msgstr "Impossible de trouver le lien URL de l'audio"
+
+msgctxt "#30013"
+msgid "Previous page"
+msgstr "Page précédente"
+
+msgctxt "#30014"
+msgid "Search"
+msgstr "Recherchez"
+
+msgctxt "#30015"
+msgid "Search result"
+msgstr "Résultat de la recherche"
+
+# Empty strings from id 30016 to 30100
+
+msgctxt "#30101"
+msgid "Debug (enable logs)"
+msgstr "Débogage (activer la journalisation)"
+
+msgctxt "#30102"
+msgid "Filter"
+msgstr "Filtre"
+
+msgctxt "#30103"
+msgid "Show only on emission programmes"
+msgstr "Montrer seulement les programmes en diffusion"
+
+msgctxt "#30104"
+msgid "Show all the programmes"
+msgstr "Montrer tous les programmes"
diff --git a/plugin.audio.rne/resources/lib/__init__.py b/plugin.audio.rne/resources/lib/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugin.audio.rne/resources/lib/lutil.py b/plugin.audio.rne/resources/lib/lutil.py
new file mode 100644
index 0000000000..d420275863
--- /dev/null
+++ b/plugin.audio.rne/resources/lib/lutil.py
@@ -0,0 +1,195 @@
+# _*_ coding: utf-8 _*_
+
+'''
+ lutil: library functions for KODI media add-ons.
+ Copyright (C) 2015 José Antonio Montes (jamontes)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+ Description:
+ These funtions are called from the main plugin module, aimed to ease
+ and simplify the plugin development process.
+ Release 0.1.9
+'''
+
+# First of all We must import all the libraries used for plugin development.
+import re, html
+from urllib.parse import urlencode, quote_plus, unquote_plus
+from urllib.request import urlopen, Request
+from datetime import date
+
+debug_enable = False # The debug logs are disabled by default.
+
+
+def local_log(message):
+ """This function logs the debug messages under development and testing process.
+ It is never invoked when the add-on is run under KODI.
+ Called from the library modules by other functions."""
+
+ if debug_enable:
+ print("%s" % message)
+
+
+log = local_log # Use local log function by default.
+
+
+def set_debug_mode(debug_flag, func_log=local_log):
+ """This function sets the debug_enable var to log everything if debug option is true."""
+
+ global debug_enable
+ global log
+ debug_enable = debug_flag in ("true", True)
+ log = func_log
+
+
+def get_url_decoded(url):
+ """This function returns the URL decoded."""
+
+ log('get_url_decoded URL: "%s"' % url)
+ return unquote_plus(url)
+
+
+def get_url_encoded(url):
+ """This function returns the URL encoded."""
+
+ log('get_url_encoded URL: "%s"' % url)
+ return quote_plus(url)
+
+
+def get_parms_encoded(**kwars):
+ """This function returns the params encoded to form an URL or data post."""
+
+ param_list = urlencode(kwars)
+ log('get_parms_encoded params: "%s"' % param_list)
+ return param_list
+
+
+def carga_web(url):
+ """This function loads the html code from a webserver and returns it into a string."""
+
+ log('carga_web URL: "%s"' % url)
+ MiReq = Request(url) # We use the Request method because we need to add some headers into the HTTP GET to the web site.
+ # We have to tell the web site we are using a real browser.
+ MiReq.add_header('User-Agent', 'Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0')
+ MiReq.add_header('Accept', '*/*')
+ MiReq.add_header('Accept-Language', 'es-ES,es;q=0.8,en-US;q=0.7,en;q=0.5,zh-TW;q=0.3,zh-CN;q=0.2')
+ MiReq.add_header('DNT', '1')
+ MiReq.add_header('Connection', 'keep-alive')
+ MiConex = urlopen(MiReq) # We open the HTTP connection to the URL.
+ encoding = MiConex.info().get_param('charset', 'utf8')
+ MiHTML = MiConex.read().decode(encoding, 'ignore') # We load all the HTML contents from the web page and store it into a var.
+ MiConex.close() # We close the HTTP connection as we have all the info required.
+
+ return MiHTML
+
+
+def carga_web_cookies(url, headers=''):
+ """This function loads the html code from a webserver passsing the headers into the GET message
+ and returns it into a string along with the cookies collected from the website."""
+
+ log('carga_web_cookies URL: "%s"' % url)
+ MiReq = Request(url) # We use the Request method because we need to add some headers into the HTTP GET to the web site.
+ # We have to tell the web site we are using a real browser.
+ MiReq.add_header('User-Agent', 'Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0')
+ for key in headers:
+ MiReq.add_header(key, headers[key])
+ MiConex = urlopen(MiReq) # We open the HTTP connection to the URL.
+ encoding = MiConex.info().get_param('charset', 'utf8')
+ MiHTML = MiConex.read().decode(encoding, 'ignore') # We load all the HTML contents from the web page and store it into a var.
+ server_info = "%s" % MiConex.info().decode(encoding, 'ignore')
+ my_cookie_pattern = re.compile('Set-Cookie: ([^;]+);')
+ my_cookies = ''
+ pcookie = ''
+ for lcookie in my_cookie_pattern.findall(server_info):
+ if (lcookie != pcookie):
+ my_cookies = "%s %s;" % (my_cookies, lcookie)
+ pcookie = lcookie
+
+ MiConex.close() # We close the HTTP connection as we have all the info required.
+
+ log('carga_web Cookie: "%s"' % my_cookies)
+ return MiHTML, my_cookies
+
+
+def send_post_data(url, headers='', data=''):
+ """This function sends an HTTP POST request with theirr corresponding headers and data to a webserver
+ and returns the html code into a string along with the cookies collected from the website."""
+
+ log("send_post_data " + url)
+ MiReq = Request(url, data) # We use the Request method because we need to send a HTTP POST to the web site.
+ # We have to tell the web site we are using a real browser.
+ MiReq.add_header('User-Agent', 'Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0')
+ for key in headers:
+ MiReq.add_header(key, headers[key])
+ MiConex = urlopen(MiReq) # We open the HTTP connection to the URL.
+ encoding = MiConex.info().get_param('charset', 'utf8')
+ MiHTML = MiConex.read().decode(encoding, 'ignore') # We load all the HTML contents from the web page and store it into a var.
+ server_info = "%s" % MiConex.info().decode(encoding, 'ignore')
+ my_cookie_pattern = re.compile('Set-Cookie: ([^;]+);')
+ my_cookies = ''
+ pcookie = ''
+ for lcookie in my_cookie_pattern.findall(server_info):
+ if (lcookie != pcookie):
+ my_cookies = "%s %s;" % (my_cookies, lcookie)
+ pcookie = lcookie
+
+ MiConex.close() # We close the HTTP connection as we have all the info required.
+
+ log('send_post_data Cookie: "%s"' % my_cookies)
+ return MiHTML, my_cookies
+
+
+def get_redirect(url):
+ """This function returns the redirected URL from a 30X response received from the webserver."""
+
+ log('get_redirect URL: "%s"' % url)
+ MiConex = urlopen(url) # Opens the http connection to the URL.
+ MiHTML = MiConex.geturl() # Gets the URL redirect link and stores it into MiHTML.
+ MiConex.close() # Close the http connection as we get what we need.
+
+ return MiHTML.decode('utf8', 'ignore')
+
+
+def find_multiple(buffer_text, pattern):
+ """This function allows us to find multiples matches from a regexp into a string."""
+
+ pat_url_par = re.compile(pattern, re.DOTALL)
+
+ return pat_url_par.findall(buffer_text)
+
+
+def find_first(buffer_text, pattern):
+ """This function gets back the first match from a regexp into a string."""
+
+ pat_url_par = re.compile(pattern, re.DOTALL)
+ try:
+ return pat_url_par.findall(buffer_text)[0]
+ except:
+ return ""
+
+
+def clean_title(title):
+ """This function cleans all the HTML special chars from the title."""
+
+ title_clean = re.sub('(<.+?>)', '', title.strip())
+
+ return html.unescape(title_clean)
+
+
+def get_this_year():
+ """This function gets the current year. Useful to fill the Year infolabel whenever it isn't available"""
+
+ return date.today().year
+
+
diff --git a/plugin.audio.rne/resources/lib/plugin.py b/plugin.audio.rne/resources/lib/plugin.py
new file mode 100644
index 0000000000..adf716c193
--- /dev/null
+++ b/plugin.audio.rne/resources/lib/plugin.py
@@ -0,0 +1,182 @@
+# _*_ coding: utf-8 _*_
+
+'''
+ plugin: library class for KODI media add-ons.
+ Copyright (C) 2015 José Antonio Montes (jamontes)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+ Description:
+ These class methods are called from the main add-on module, aimed to ease
+ and simplify the add-on development process.
+ Release 0.1.5
+'''
+
+# First of all We must import all the libraries used for plugin development.
+import sys, re, os
+from urllib.parse import urlencode, quote_plus, unquote_plus
+import xbmcplugin, xbmcaddon, xbmcgui, xbmcaddon, xbmc
+
+class Plugin():
+
+ def __init__(self, plugin_id='', show_thumb_as_fanart=False):
+ self.pluginpath = sys.argv[0]
+ self.pluginhandle = int(sys.argv[1])
+ self.pluginparams = sys.argv[2]
+ self.plugin_id = plugin_id
+ self.plugin_type = 'Video' if 'video' in plugin_id else 'Music'
+ self.debug_enable = False # The debug logs are disabled by default.
+ self.plugin_settings = xbmcaddon.Addon(id=self.plugin_id)
+ self.translation = self.plugin_settings.getLocalizedString
+ self.root_path = self.plugin_settings.getAddonInfo('path')
+ self.fanart_file = os.path.join(self.root_path, "fanart.jpg")
+ self.show_thumb_as_fanart = show_thumb_as_fanart
+
+
+ def get_plugin_settings(self):
+ """This is a getter method to return the settings method reference."""
+ return self.plugin_settings
+
+
+ def get_plugin_translation(self):
+ """This is a getter method to return the translation method reference."""
+ return self.translation
+
+
+ def get_system_language(self):
+ """This method returns the GUI language."""
+ return xbmc.getLanguage()
+
+
+ def set_debug_mode(self, debug_flag=""):
+ """This method sets the debug_enable flag to log everything if debug option within add-on settings is activated."""
+ self.debug_enable = debug_flag in ("true", True)
+
+
+ def set_fanart(self):
+ """This method setup the file and global plugin fanart."""
+ xbmcplugin.setPluginFanart(self.pluginhandle, self.fanart_file)
+
+
+ def log(self, message):
+ """This method logs the messages into the main XBMC log file, only if debug option is activated from the add-on settings.
+ This method is called from the main add-on module."""
+ if self.debug_enable:
+ try:
+ xbmc.log(msg=message, level=xbmc.LOGINFO)
+ except:
+ xbmc.log('%s: log this line is not possible due to encoding string problems' % self.plugin_id, level=xbmc.LOGINFO)
+
+
+ def _log(self, message):
+ """This method logs the messages into the main XBMC log file, only if debug option is activated from the add-on settings.
+ This method is privated and only called from other methods within the class."""
+ if self.debug_enable:
+ try:
+ xbmc.log(msg=message, level=xbmc.LOGINFO)
+ except:
+ xbmc.log('%s: _log this line is not possible due to encoding string problems' % self.plugin_id, level=xbmc.LOGINFO)
+
+
+ def get_plugin_parms(self):
+ """This method gets all the parameters passed to the plugin from KODI API and retuns a dictionary.
+ Example: plugin://plugin.video.atactv/?parametro1=valor1¶metro2=valor2¶metro3"""
+ params = sys.argv[2]
+
+ pattern_params = re.compile('[?&]([^=&]+)=?([^&]*)')
+ options = dict((parameter, unquote_plus(value)) for (parameter, value) in pattern_params.findall(params))
+ self._log("get_plugin_parms " + repr(options))
+ return options
+
+
+ def get_plugin_path(self, **kwars):
+ """This method returns the add-on path URL encoded along with all its parameters."""
+ return sys.argv[0] + '?' + urlencode(kwars)
+
+
+ def get_url_decoded(self, url):
+ """This method returns the URL decoded."""
+ self._log('get_url_decoded URL: "%s"' % url)
+ return unquote_plus(url)
+
+
+ def get_url_encoded(self, url):
+ """This method returns the URL encoded."""
+ self._log('get_url_encoded URL: "%s"' % url)
+ return quote_plus(url)
+
+
+ def set_view_mode(self, viewid):
+ """This method sets the view mode into the media list."""
+ self._log("set_view_mode mode: " + viewid)
+ xbmc.executebuiltin('Container.SetViewMode('+viewid+')')
+
+
+ def set_content_list(self, contents="episodes"):
+ """This method sets the media contents for the media list."""
+ self._log("set_content_list contents: " + contents)
+ xbmcplugin.setContent(self.pluginhandle, contents)
+
+
+ def set_plugin_category(self, genre=''):
+ """This method sets the plugin genre for the media list."""
+ xbmcplugin.setPluginCategory(self.pluginhandle, genre)
+
+
+ def get_keyboard_text(self, prompt):
+ """This method gets an input text from the keyboard."""
+ self._log('get_keyboard_text prompt: "%s"' % prompt)
+
+ keyboard = xbmc.Keyboard('', prompt)
+ keyboard.doModal()
+ if keyboard.isConfirmed() and keyboard.getText():
+ self._log("get_keyboard_text input text: '%s'" % keyboard.getText())
+ return keyboard.getText()
+ else:
+ # Close directory as empty result.
+ xbmcplugin.endOfDirectory(self.pluginhandle, succeeded=True, updateListing=False, cacheToDisc=False)
+ return ""
+
+
+ def add_items(self, items, updateListing=False):
+ """This method adds the list of items (links and folders) to the add-on media list."""
+ item_list = []
+ for item in items:
+ link_item = xbmcgui.ListItem(item.get('info').get('title'))
+ thumbnailImage = item.get('thumbnail', '')
+ if thumbnailImage:
+ link_item.setArt({ 'thumb': thumbnailImage })
+ if item.get('IsPlayable', False):
+ link_item.setProperty('IsPlayable', 'true')
+ link_item.setLabel(item.get('label', item.get('info').get('title')))
+ link_item.setLabel2(item.get('label', item.get('info').get('title')))
+ link_item.setInfo(type = self.plugin_type, infoLabels = item.get('info'))
+ item_list.append((item.get('path'), link_item, not item.get('IsPlayable', False)))
+ xbmcplugin.addDirectoryItems(self.pluginhandle, item_list, len(item_list))
+ xbmcplugin.endOfDirectory(self.pluginhandle, succeeded=True, updateListing=updateListing, cacheToDisc=True)
+
+
+ def showWarning(self, message):
+ """This method shows a popup window with a notices message through the XBMC GUI during 5 secs."""
+ self._log("showWarning message: %s" % message)
+ xbmc.executebuiltin('XBMC.Notification(Info:,' + message + ',6000)')
+
+
+ def play_resolved_url(self, url = ""):
+ """This method plays the media file pointed by the URL passed as argument."""
+ self._log("play_resolved_url pluginhandle = [%s] url = [%s]" % (self.pluginhandle, url))
+ listitem = xbmcgui.ListItem(path=url)
+ return xbmcplugin.setResolvedUrl(self.pluginhandle, True, listitem)
+
+
diff --git a/plugin.audio.rne/resources/lib/rne_addon.py b/plugin.audio.rne/resources/lib/rne_addon.py
new file mode 100644
index 0000000000..f566248e5e
--- /dev/null
+++ b/plugin.audio.rne/resources/lib/rne_addon.py
@@ -0,0 +1,287 @@
+# -*- coding: utf-8 -*-
+
+'''
+ KODI RNE Podcasts audio add-on.
+ Copyright (C) 2015 José Antonio Montes (jamontes)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+ This is the first trial of RNE Podcasts add-on for KODI.
+ This add-on gets the audio podcasts from RNE web site and shows
+ them properly ordered.
+ This add-on depends on the lutil and plugin library functions.
+'''
+
+from resources.lib.plugin import Plugin
+import resources.lib.rne_api as api
+
+plugin_id = 'plugin.audio.rne'
+
+localized_strings = {
+ 'Next page' : 30010,
+ 'Type not suported' : 30011,
+ 'Audio not located' : 30012,
+ 'Previous page' : 30013,
+ 'Search' : 30014,
+ 'Search result' : 30015,
+ }
+
+p = Plugin(plugin_id)
+
+settings = p.get_plugin_settings()
+translation = p.get_plugin_translation()
+
+debug_flag = settings.getSetting("debug") == "true"
+all_programmes_flag = settings.getSetting("all_programmes") == "1"
+
+p.set_debug_mode(debug_flag)
+api.set_debug(debug_flag, p.log)
+
+p.log("rne %s flag is set" % {True : 'all_the_programmes', False : 'only_emission'}[all_programmes_flag])
+
+
+def get_located_string(string_name):
+ """This function returns the localized string if it is available."""
+ return translation(localized_strings.get(string_name)) or string_name if string_name in localized_strings else string_name
+
+
+# Entry point
+def run():
+ """This function is the entry point to the add-on.
+ It gets the add-on parameters and call the 'action' function.
+ """
+ p.log("rne.run")
+
+ # Get the params
+ params = p.get_plugin_parms()
+
+ action = params.get("action", '')
+ if action:
+ eval("%s(params)" % action)
+ else:
+ menu_direct(params)
+
+
+# Main menu
+def create_index(params):
+ """This function generates the main add-on menu, based on the website sections."""
+ p.log("rne.create_index "+repr(params))
+
+ menu_index = api.get_create_index()
+
+ main_menu = [ {
+ 'thumbnail' : '',
+ 'info': {
+ 'title' : item.get('title'),
+ 'genre' : item.get('title'),
+ },
+ 'path' : p.get_plugin_path(
+ action = item.get('action'),
+ url = item.get('args'),
+ genre = item.get('title'),
+ ),
+ 'IsPlayable': False,
+ } for item in menu_index ]
+
+ search_title = get_located_string('Search')
+ search_menu = {
+ 'thumbnail' : '',
+ 'info': {
+ 'title' : search_title,
+ 'genre' : search_title,
+ },
+ 'path' : p.get_plugin_path(
+ action = 'search_audio',
+ ),
+ 'IsPlayable': False,
+ }
+ main_menu.append(search_menu)
+
+ p.add_items(main_menu)
+
+
+def program_list(params):
+ """This function generates the programmes list, based on the upper menu selection."""
+ p.log("rne.program_list "+repr(params))
+
+ menu_url = params.get('url')
+ programs = api.get_program_list(menu_url, all_programmes_flag, get_located_string)
+ reset_cache = 'yes' if params.get('reset_cache') == 'yes' or programs.get('reset_cache') else 'no'
+
+ main_menu = [ {
+ 'thumbnail' : item.get('thumbnail', ''),
+ 'info': {
+ 'title' : item.get('title', ''),
+ 'genre' : item.get('genre', ''),
+ 'comment' : item.get('comment', ''),
+ },
+ 'path' : p.get_plugin_path(
+ action = item.get('action'),
+ url = item.get('url'),
+ program = item.get('program', ''),
+ canal = item.get('canal', ''),
+ genre = item.get('genre', ''),
+ reset_cache = reset_cache,
+ ),
+ 'IsPlayable' : False,
+ } for item in programs.get('program_list') ]
+
+ p.add_items(main_menu, reset_cache == 'yes')
+
+
+def audio_list(params):
+ """This function generates the podcasts list of the program emissions."""
+ p.log("rne.audio_list "+repr(params))
+
+ program = params.get('program', '')
+ canal = params.get('canal', '')
+ genre = params.get('genre', '')
+
+ audios = api.get_audio_list(params.get('url'), get_located_string)
+ reset_cache = 'yes' if params.get('reset_cache') == 'yes' or audios.get('reset_cache') else 'no'
+
+ audio_items = [ {
+ 'thumbnail' : '',
+ 'label' : audio_entry.get('title', ''),
+ 'info' : {
+ 'title' : audio_entry.get('title', ''),
+ 'genre' : genre,
+ 'album' : canal,
+ 'artist' : program,
+ 'comment' : audio_entry.get('comment', ''),
+ 'year' : audio_entry.get('year', 0),
+ 'duration' : audio_entry.get('duration', 1),
+ 'rating' : audio_entry.get('rating', 0),
+ },
+ 'path' : p.get_plugin_path(
+ url = audio_entry.get('url'),
+ action = audio_entry.get('action'),
+ ) if audio_entry.get('IsPlayable') else p.get_plugin_path(
+ url = audio_entry.get('url'),
+ action = audio_entry.get('action'),
+ program = program,
+ canal = canal,
+ genre = genre,
+ reset_cache = reset_cache,
+ ),
+ 'IsPlayable' : audio_entry.get('IsPlayable'),
+ } for audio_entry in audios.get('audio_list') ]
+
+ p.add_items(audio_items, reset_cache == 'yes')
+
+
+def search_program(params):
+ """This function gets the URL for search the podcast by title inside the program list."""
+ p.log("rne.search_program "+repr(params))
+
+ search_string = p.get_keyboard_text(get_located_string('Search'))
+ if search_string:
+ params['url'] = params.get('url') % search_string.replace(' ', '%20')
+ params['search'] = search_string
+ p.log("rne.search_program Value of search url: %s" % params['url'])
+ return audio_list(params)
+
+
+def menu_direct(params):
+ """This funcion creates the menu for the direct channels play."""
+ p.log("rne.menu_direct "+repr(params))
+
+ channels = [ {
+ 'thumbnail' : '',
+ 'label' : channel.get('title', ''),
+ 'info' : {
+ 'title' : channel.get('title', ''),
+ },
+ 'path' : p.get_plugin_path(
+ url = channel.get('url'),
+ action = channel.get('action'),
+ ),
+ 'IsPlayable' : True,
+ } for channel in api.get_direct_channels() ]
+
+ p.add_items(channels)
+
+
+def search_audio(params):
+ """This function gets the URL for search the podcast emissions and list them if something is found on the website archives."""
+ p.log("rne.search_audio "+repr(params))
+
+ search_string = p.get_keyboard_text(get_located_string('Search'))
+ if search_string:
+ params['url'] = api.get_search_url(search_string)
+ params['search'] = search_string
+ p.log("rne.search Value of search url: %s" % params['url'])
+ return search_list(params)
+
+
+def search_list(params):
+ """This function list the programs found by the website search engine"""
+ p.log("rne.search_list "+repr(params))
+
+ audios = api.get_search_list(params.get('url'), get_located_string)
+ reset_cache = 'yes' if params.get('reset_cache') == 'yes' or audios.get('reset_cache') else 'no'
+
+ audio_items = [ {
+ 'thumbnail' : '',
+ 'label' : audio_entry.get('title', ''),
+ 'info' : {
+ 'title' : audio_entry.get('title', ''),
+ 'album' : '"%s"' % params.get('search'),
+ 'artist' : get_located_string('Search result'),
+ 'comment' : audio_entry.get('comment', ''),
+ 'year' : audio_entry.get('year', 0),
+ 'rating' : 0,
+ },
+ 'path' : p.get_plugin_path(
+ url = audio_entry.get('url'),
+ action = audio_entry.get('action'),
+ ) if audio_entry.get('IsPlayable') else p.get_plugin_path(
+ url = audio_entry.get('url'),
+ action = audio_entry.get('action'),
+ search = params.get('search'),
+ reset_cache = reset_cache,
+ ),
+ 'IsPlayable' : audio_entry.get('IsPlayable'),
+ } for audio_entry in audios.get('search_list') ]
+
+ p.add_items(audio_items, reset_cache == 'yes')
+
+
+def play_search(params):
+ """This function plays the audio source from the search url link."""
+ p.log("rne.play_search "+repr(params))
+
+ url = api.get_playable_search_url(params.get("url"))
+
+ if url:
+ return p.play_resolved_url(url)
+ else:
+ p.showWarning(get_located_string('Audio not located'))
+
+
+def play_audio(params):
+ """This function plays the audio source."""
+ p.log("rne.play_audio "+repr(params))
+
+ url = params.get("url")
+
+ if url.endswith('m3u'):
+ url = api.get_playable_url(url)
+
+ if url:
+ return p.play_resolved_url(url)
+ else:
+ p.showWarning(get_located_string('Audio not located'))
+
+
diff --git a/plugin.audio.rne/resources/lib/rne_api.py b/plugin.audio.rne/resources/lib/rne_api.py
new file mode 100644
index 0000000000..0d4d9b5acf
--- /dev/null
+++ b/plugin.audio.rne/resources/lib/rne_api.py
@@ -0,0 +1,373 @@
+# _*_ coding: utf-8 _*_
+
+'''
+ RNE Podcasts API lib: library functions for RNE Podcast audio add-on.
+ Copyright (C) 2015 José Antonio Montes (jamontes)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+ Description:
+ These funtions are called from the main plugin module, aimed to ease
+ and simplify the add-on development process.
+ Release 0.1.6
+'''
+
+import resources.lib.lutil as l
+
+root_url = 'se.evtr.www//:sptth'[::-1]
+
+def set_debug(debug_flag, func_log=l.local_log):
+ """This function is a wrapper to setup the debug flag into the lutil module"""
+ l.set_debug_mode(debug_flag, func_log)
+
+
+def get_channels_menu(html_channels):
+ """This function makes the program menu parammeters data structure for all the channel menus."""
+
+ channel_pattern = '
', re.I), '- '),
+ (re.compile(r'?(li|ul)(|\s[^>]+)>', re.I), '\n'),
+ (re.compile(r'?(div|p|span)(|\s[^>]+)>', re.I), ''),
+ (re.compile(' \n{0,1}', re.I), ' '), # This appears to be specific formatting for VRT NU, but unwanted by us
+ (re.compile('( \n){2,}', re.I), '\n'), # Remove repeating non-blocking spaced newlines
+]
+
+
+def to_unicode(text, encoding='utf-8', errors='strict'):
+ """Force text to unicode"""
+ if isinstance(text, bytes):
+ return text.decode(encoding, errors=errors)
+ return text
+
+
+def from_unicode(text, encoding='utf-8', errors='strict'):
+ """Force unicode to text"""
+ import sys
+ if sys.version_info.major == 2 and isinstance(text, unicode): # noqa: F821; pylint: disable=undefined-variable
+ return text.encode(encoding, errors)
+ return text
+
+
+def capitalize(string):
+ """Ensure the first character is uppercase"""
+ string = string.strip()
+ return string[0].upper() + string[1:]
+
+
+def strip_newlines(text):
+ """Strip newlines and trailing whitespaces"""
+ return text.replace('\n', '').strip()
+
+
+def html_to_kodi(text):
+ """Convert VRT HTML content into Kodit formatted text"""
+ for key, val in HTML_MAPPING:
+ text = key.sub(val, text)
+ return unescape(text).strip()
+
+
+def add_https_proto(url):
+ """Add HTTPS protocol to URL that lacks it"""
+ if url.startswith('//'):
+ return 'https:' + url
+ if url.startswith('/'):
+ return 'https://www.vrt.be' + url
+ return url
+
+
+def find_entry(dlist, key, value, default=None):
+ """Find (the first) dictionary in a list where key matches value"""
+ return next((entry for entry in dlist if entry.get(key) == value), default)
+
+
+def youtube_to_plugin_url(url):
+ """Convert a YouTube URL to a Kodi plugin URL"""
+ url = url.replace('https://www.youtube.com/', 'plugin://plugin.video.youtube/')
+ if not url.endswith('/'):
+ url += '/'
+ return url
diff --git a/plugin.audio.vrt.radio/resources/media/fanart.jpg b/plugin.audio.vrt.radio/resources/media/fanart.jpg
new file mode 100644
index 0000000000..02739084ec
Binary files /dev/null and b/plugin.audio.vrt.radio/resources/media/fanart.jpg differ
diff --git a/plugin.audio.vrt.radio/resources/media/icon.png b/plugin.audio.vrt.radio/resources/media/icon.png
new file mode 100644
index 0000000000..cf6ac8f9c9
Binary files /dev/null and b/plugin.audio.vrt.radio/resources/media/icon.png differ
diff --git a/plugin.audio.vrt.radio/resources/settings.xml b/plugin.audio.vrt.radio/resources/settings.xml
new file mode 100644
index 0000000000..4965de8ae6
--- /dev/null
+++ b/plugin.audio.vrt.radio/resources/settings.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugin.audio.wdr3konzert/LICENSE.txt b/plugin.audio.wdr3konzert/LICENSE.txt
new file mode 100644
index 0000000000..23cb790338
--- /dev/null
+++ b/plugin.audio.wdr3konzert/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/plugin.audio.wdr3konzert/addon.xml b/plugin.audio.wdr3konzert/addon.xml
new file mode 100644
index 0000000000..90f18f88eb
--- /dev/null
+++ b/plugin.audio.wdr3konzert/addon.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+ audio
+
+
+ all
+ de
+ GPL-2.0-only
+ https://github.com/sarbes/plugin.audio.wdr3konzert
+ https://forum.kodi.tv/showthread.php?tid=353899
+ https://www1.wdr.de/radio/wdr3/konzertplayer/index.html
+ Play concert content from the German tv broadcaster "WDR3".
+ This add-on lists the concert content of the WD3.
+ Die Konzerte des WDR3.
+ Dieses Add-on bietet Zugriff auf Konzerte des WDR3.
+ Dieses Add-on wird von keiner Sendeanstalt unterstützt und ist daher inoffiziell.
+
+ resources/icon.png
+ resources/fanart.jpg
+
+
+
diff --git a/plugin.audio.wdr3konzert/default.py b/plugin.audio.wdr3konzert/default.py
new file mode 100644
index 0000000000..cc598d4a36
--- /dev/null
+++ b/plugin.audio.wdr3konzert/default.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+import libwdr
+
+
+class wdrkonzert(libwdr.libwdr):
+ def libWdrListMain(self):
+ l = []
+ l.append({'metadata':{'name':self.translation(30000)}, 'params':{'mode':'libWdrListId', 'id':'konzertplayer-uebersicht-100'}, 'type':'dir'})
+ l.append({'metadata':{'name':self.translation(30001)}, 'params':{'mode':'libWdrListId', 'id':'konzertplayer-uebersicht-klassik-100'}, 'type':'dir'})
+ l.append({'metadata':{'name':self.translation(30002)}, 'params':{'mode':'libWdrListId', 'id':'konzertplayer-uebersicht-jazz-100'}, 'type':'dir'})
+ #l.append({'metadata':{'name':self.translation(30000)}, 'params':{'mode':'listItems','uri':'/kalender/'}, 'type':'dir'})#'New'
+ #l.append({'metadata':{'name':self.translation(30001)}, 'params':{'mode':'listItems','uri':'/klassische-musik/'}, 'type':'dir'})#'Klassische Musik'
+ #l.append({'metadata':{'name':self.translation(30002)}, 'params':{'mode':'listItems','uri':'/jazz-and-more/'}, 'type':'dir'})#'Jazz and More'
+ return {'items':l,'name':'root'}
+
+
+
+wdr = wdrkonzert()
+wdr.action()
\ No newline at end of file
diff --git a/plugin.audio.wdr3konzert/resources/fanart.jpg b/plugin.audio.wdr3konzert/resources/fanart.jpg
new file mode 100644
index 0000000000..a57757fc13
Binary files /dev/null and b/plugin.audio.wdr3konzert/resources/fanart.jpg differ
diff --git a/plugin.audio.wdr3konzert/resources/icon.png b/plugin.audio.wdr3konzert/resources/icon.png
new file mode 100644
index 0000000000..b13d6b420b
Binary files /dev/null and b/plugin.audio.wdr3konzert/resources/icon.png differ
diff --git a/plugin.audio.wdr3konzert/resources/language/resource.language.de_de/strings.po b/plugin.audio.wdr3konzert/resources/language/resource.language.de_de/strings.po
new file mode 100644
index 0000000000..fa5ad52592
--- /dev/null
+++ b/plugin.audio.wdr3konzert/resources/language/resource.language.de_de/strings.po
@@ -0,0 +1,25 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.2.4\n"
+"Last-Translator: \n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"Language: de_DE\n"
+
+msgctxt "#30000"
+msgid "New"
+msgstr "Neu"
+
+msgctxt "#30001"
+msgid "Classical Music"
+msgstr "Klassische Musik"
+
+msgctxt "#30002"
+msgid "Jazz and More"
+msgstr "Klassische Musik"
diff --git a/plugin.audio.wdr3konzert/resources/language/resource.language.en_gb/strings.po b/plugin.audio.wdr3konzert/resources/language/resource.language.en_gb/strings.po
new file mode 100644
index 0000000000..c39d2193c6
--- /dev/null
+++ b/plugin.audio.wdr3konzert/resources/language/resource.language.en_gb/strings.po
@@ -0,0 +1,25 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.2.4\n"
+"Last-Translator: \n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"Language: en_GB\n"
+
+msgctxt "#30000"
+msgid "New"
+msgstr "New"
+
+msgctxt "#30001"
+msgid "Classical Music"
+msgstr "Classical Music"
+
+msgctxt "#30002"
+msgid "Jazz and More"
+msgstr "Jazz and More"
diff --git a/plugin.audio.wdraudiothek/LICENSE.txt b/plugin.audio.wdraudiothek/LICENSE.txt
new file mode 100644
index 0000000000..23cb790338
--- /dev/null
+++ b/plugin.audio.wdraudiothek/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/plugin.audio.wdraudiothek/addon.xml b/plugin.audio.wdraudiothek/addon.xml
new file mode 100644
index 0000000000..1de4aaa7b8
--- /dev/null
+++ b/plugin.audio.wdraudiothek/addon.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+ audio
+
+
+ all
+ de
+ GPL-2.0-only
+ https://github.com/sarbes/plugin.video.wdraudiothek
+ https://forum.kodi.tv/showthread.php?tid=353899
+ https://www1.wdr.de/mediathek/audio/index.html
+ Play audio content from the German tv broadcaster "WDR".
+ This add-on lists the audio content of the WDR Mediathek.
+ Die Audiothek des WDR.
+ Dieses Add-on bietet Zugriff auf die Audiothek des WDR. Hier gibt es Nachrichten und andere Beiträge.
+ Dieses Add-on wird von keiner Sendeanstalt unterstützt und ist daher inoffiziell.
+
+ resources/icon.png
+ resources/fanart.jpg
+
+
+
diff --git a/plugin.audio.wdraudiothek/default.py b/plugin.audio.wdraudiothek/default.py
new file mode 100644
index 0000000000..1179a80084
--- /dev/null
+++ b/plugin.audio.wdraudiothek/default.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+import libwdr
+
+class wdraudio(libwdr.libwdr):
+
+ def libWdrListMain(self):
+ l = []
+ l.append({'metadata':{'name':self.translation(32030)}, 'params':{'mode':'libWdrListId', 'id':'audio-uebersicht-100'}, 'type':'dir'})
+ l.append({'metadata':{'name':self.translation(32132)}, 'params':{'mode':'libMediathekListLetters','ignore':'#,x', 'subParams':'{"mode":"libWdrListLetter"}'}, 'type':'dir'})
+ l.append({'metadata':{'name':self.translation(32133)}, 'params':{'mode':'libMediathekListDate', 'subParams':'{"mode":"libWdrListDateVideos"}'}, 'type':'dir'})
+ return {'items':l,'name':'root'}
+
+ def libWdrListLetter(self):
+ import libwdrrssandroidparser
+ if self.params["letter"] == 'c':
+ return libwdrrssandroidparser.parseShows(f'sendungen-{self.params["letter"]}-102')
+ else:
+ return libwdrrssandroidparser.parseShows(f'sendungenabisz-{self.params["letter"]}-100')
+
+ def libWdrListDateVideos(self):
+ self.params['id'] = f'sendung-verpasst-audios-100~_tag-{self.params["ddmmyyyy"]}'
+ return self.libWdrListId()
+
+
+wdr = wdraudio()
+wdr.action()
\ No newline at end of file
diff --git a/plugin.audio.wdraudiothek/resources/fanart.jpg b/plugin.audio.wdraudiothek/resources/fanart.jpg
new file mode 100644
index 0000000000..a57757fc13
Binary files /dev/null and b/plugin.audio.wdraudiothek/resources/fanart.jpg differ
diff --git a/plugin.audio.wdraudiothek/resources/icon.png b/plugin.audio.wdraudiothek/resources/icon.png
new file mode 100644
index 0000000000..b13d6b420b
Binary files /dev/null and b/plugin.audio.wdraudiothek/resources/icon.png differ
diff --git a/plugin.audio.wdrrockpalast/LICENSE.txt b/plugin.audio.wdrrockpalast/LICENSE.txt
new file mode 100644
index 0000000000..23cb790338
--- /dev/null
+++ b/plugin.audio.wdrrockpalast/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/plugin.audio.wdrrockpalast/addon.xml b/plugin.audio.wdrrockpalast/addon.xml
new file mode 100644
index 0000000000..f6ad782172
--- /dev/null
+++ b/plugin.audio.wdrrockpalast/addon.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+ audio
+
+
+ all
+ de
+ GPL-2.0-only
+ https://github.com/sarbes/plugin.video.wdrrockpalast
+ https://forum.kodi.tv/showthread.php?tid=353899
+ https://www1.wdr.de/fernsehen/rockpalast/startseite/index.html
+ This add-on lists all videos of the WDR Rockpalast.
+ This is a music add-on for the WDR Rockpalast. The content may be geolocked.
+ Dies ist ein Music Add-on für den WDR Rockpalast.
+ Dieses Add-on listet alle Videos vom WDR Rockpalast. Die Inhalte sind unter Umständen nur aus Deutschland erreichbar.
+ Dieses Add-on wird von keiner Sendeanstalt unterstützt und ist daher inoffiziell.
+
+ resources/icon.png
+ resources/fanart.jpg
+
+
+
diff --git a/plugin.audio.wdrrockpalast/default.py b/plugin.audio.wdrrockpalast/default.py
new file mode 100644
index 0000000000..1ad0c98678
--- /dev/null
+++ b/plugin.audio.wdrrockpalast/default.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+import libwdr
+
+class rockpalast(libwdr.libwdr):
+ def __init__(self):
+ libwdr.libwdr.__init__(self)
+
+ def libWdrListMain(self):
+ d = {'items':[]}
+ d['items'].append({'metadata':{'name':self.translation(32030)}, 'params':{'mode':'libWdrListFeed', 'url':'http://www1.wdr.de/mediathek/video/sendungen/rockpalast/rockpalast-100~_format-mp111_type-rss.feed'}, 'type':'dir'})
+ d['items'].append({'metadata':{'name':'2019'}, 'params':{'mode':'libWdrListFeed', 'url':'http://www1.wdr.de/mediathek/video/sendungen/rockpalast/rockpalast264~_format-mp111_type-rss.feed'}, 'type':'dir'})
+ d['items'].append({'metadata':{'name':'2018'}, 'params':{'mode':'libWdrListFeed', 'url':'http://www1.wdr.de/mediathek/video/sendungen/rockpalast/rockpalast-156~_format-mp111_type-rss.feed'}, 'type':'dir'})
+ d['items'].append({'metadata':{'name':'2017'}, 'params':{'mode':'libWdrListFeed', 'url':'http://www1.wdr.de/mediathek/video/sendungen/rockpalast/rockpalast-136~_format-mp111_type-rss.feed'}, 'type':'dir'})
+ d['items'].append({'metadata':{'name':'2016'}, 'params':{'mode':'libWdrListFeed', 'url':'http://www1.wdr.de/mediathek/video/sendungen/rockpalast/rockpalast-106~_format-mp111_type-rss.feed'}, 'type':'dir'})
+ d['items'].append({'metadata':{'name':'2015'}, 'params':{'mode':'libWdrListFeed', 'url':'http://www1.wdr.de/mediathek/video/sendungen/rockpalast/rockpalast-108~_format-mp111_type-rss.feed'}, 'type':'dir'})
+ d['items'].append({'metadata':{'name':'2014'}, 'params':{'mode':'libWdrListFeed', 'url':'http://www1.wdr.de/mediathek/video/sendungen/rockpalast/rockpalast-110~_format-mp111_type-rss.feed'}, 'type':'dir'})
+ d['items'].append({'metadata':{'name':'2013'}, 'params':{'mode':'libWdrListFeed', 'url':'http://www1.wdr.de/mediathek/video/sendungen/rockpalast/rockpalast-112~_format-mp111_type-rss.feed'}, 'type':'dir'})
+ d['items'].append({'metadata':{'name':'2012'}, 'params':{'mode':'libWdrListFeed', 'url':'http://www1.wdr.de/mediathek/video/sendungen/rockpalast/rockpalast-114~_format-mp111_type-rss.feed'}, 'type':'dir'})
+ d['items'].append({'metadata':{'name':'2011'}, 'params':{'mode':'libWdrListFeed', 'url':'http://www1.wdr.de/mediathek/video/sendungen/rockpalast/rockpalast-116~_format-mp111_type-rss.feed'}, 'type':'dir'})
+ d['items'].append({'metadata':{'name':'2010'}, 'params':{'mode':'libWdrListFeed', 'url':'http://www1.wdr.de/mediathek/video/sendungen/rockpalast/rockpalast-118~_format-mp111_type-rss.feed'}, 'type':'dir'})
+ d['items'].append({'metadata':{'name':'2009'}, 'params':{'mode':'libWdrListFeed', 'url':'http://www1.wdr.de/mediathek/video/sendungen/rockpalast/rockpalast-120~_format-mp111_type-rss.feed'}, 'type':'dir'})
+ d['items'].append({'metadata':{'name':'2008'}, 'params':{'mode':'libWdrListFeed', 'url':'http://www1.wdr.de/mediathek/video/sendungen/rockpalast/rockpalast-122~_format-mp111_type-rss.feed'}, 'type':'dir'})
+ d['items'].append({'metadata':{'name':'2007'}, 'params':{'mode':'libWdrListFeed', 'url':'http://www1.wdr.de/mediathek/video/sendungen/rockpalast/rockpalast-124~_format-mp111_type-rss.feed'}, 'type':'dir'})
+ d['items'].append({'metadata':{'name':'2006'}, 'params':{'mode':'libWdrListFeed', 'url':'http://www1.wdr.de/mediathek/video/sendungen/rockpalast/rockpalast-126~_format-mp111_type-rss.feed'}, 'type':'dir'})
+ d['items'].append({'metadata':{'name':'2005'}, 'params':{'mode':'libWdrListFeed', 'url':'http://www1.wdr.de/mediathek/video/sendungen/rockpalast/rockpalast-128~_format-mp111_type-rss.feed'}, 'type':'dir'})
+ d['items'].append({'metadata':{'name':'2004'}, 'params':{'mode':'libWdrListFeed', 'url':'http://www1.wdr.de/mediathek/video/sendungen/rockpalast/rockpalast-132~_format-mp111_type-rss.feed'}, 'type':'dir'})
+ return d
+
+r = rockpalast()
+r.action()
\ No newline at end of file
diff --git a/plugin.audio.wdrrockpalast/resources/fanart.jpg b/plugin.audio.wdrrockpalast/resources/fanart.jpg
new file mode 100644
index 0000000000..d316c3e3ca
Binary files /dev/null and b/plugin.audio.wdrrockpalast/resources/fanart.jpg differ
diff --git a/plugin.audio.wdrrockpalast/resources/icon.png b/plugin.audio.wdrrockpalast/resources/icon.png
new file mode 100644
index 0000000000..1d6761d110
Binary files /dev/null and b/plugin.audio.wdrrockpalast/resources/icon.png differ
diff --git a/plugin.googledrive/LICENSE.txt b/plugin.googledrive/LICENSE.txt
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/plugin.googledrive/LICENSE.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/plugin.googledrive/README.md b/plugin.googledrive/README.md
new file mode 100644
index 0000000000..345a9d1792
--- /dev/null
+++ b/plugin.googledrive/README.md
@@ -0,0 +1,24 @@
+# Google Drive KODI Addon
+
+Play all your media from Google Drive including Videos, Music and Pictures (including Google Photos).
+* Unlimited accounts
+* Team Drives support
+* Google Photos support
+* Playback your music and videos. Listing of videos with thumbnails.
+* ~~Use Google Drive as a source.~~ (Not currently working due to changes in the Google Drive API)
+* Subtitles can be assigned automatically if a .str file exists with the same name as the video.
+* Export your videos to your library (.strm files). You can export your music too, but kodi won't support it yet. It's a Kodi issue for now.
+* Show your photos individually or run a slideshow of them. Listing of pictures with thumbnails.
+* Auto-Refreshed slideshow.
+* Use of OAuth 2 login. You don't have to write your user/password within the add-on. Use the login process in your browser.
+* Extremely fast. Using the Google Drive API
+
+This program is not affiliated with or sponsored by Google.
+
+
+### Installation
+
+* From the **Kodi Add-on repository**
+* From **[my repository](https://github.com/cguZZman/repository.plugins)** if for any reason the latest version is still not in the Kodi Add-on repository.
+* Manual, by downloading the source code and creating your zip.
+If your installation is manual, you **must install first** the latest version of the **[common module](https://github.com/cguZZman/script.module.clouddrive.common)**.
\ No newline at end of file
diff --git a/plugin.googledrive/addon.xml b/plugin.googledrive/addon.xml
new file mode 100644
index 0000000000..89192738e6
--- /dev/null
+++ b/plugin.googledrive/addon.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+ image audio video
+
+
+
+ all
+ Google Drive for KODI
+
+Play all your media from Google Drive including Videos, Music and Pictures (including Google Photos).
+ - Unlimited number of accounts.
+ - Team Drives support
+ - Google Photos support
+ - Search over your drive.
+ - Auto-Refreshed slideshow.
+ - Export your videos to your library (.strm files)
+ - Use Google Drive as a source. **(Not currently working due to changes in the Google Drive API)
+ - This program is not affiliated with or sponsored by Google.
+
+ כונן Google Drive של מיקרוסופט עבור קודי
+
+הפעל את כל המדיה שלך מ- Google Drive כולל וידאו, מוסיקה ותמונות.
+ - מספר בלתי מוגבל של חשבונות.
+ - חיפוש ומציאת הכונן שלך.
+ - ריענון מצגת אוטומטית.
+ - ייצוא קטעי הווידאו לספרייה שלך (קבצי .strm)
+ - תוכנית זו אינה קשורה או ממומנת על ידי Google.
+
+ GPL-3.0-or-later
+ https://github.com/cguZZman/plugin.googledrive
+ https://github.com/cguZZman/plugin.googledrive/issues
+ https://addons.kodi.tv/show/plugin.googledrive
+
+ icon.png
+ fanart.jpg
+
+
+v1.5.0 released Jan 21, 2023:
+- Kodi 20 fix
+
+
+This cloud drive addon uses a third-party authentication mechanism commonly known as OAuth 2.0.
+If you want to know more about OAuth 2.0 you can visit the following pages:
+- https://oauth.net/2/
+- https://developers.google.com/identity/protocols/OAuth2
+- https://docs.microsoft.com/en-us/onedrive/developer/rest-api/getting-started/msa-oauth
+
+Kodi and myself take no responsibility or liability.
+
+The authentication server URL is specified in Settings / Advanced / Sign-in Server. The Sign-in Server implements the OAuth 2.0 protocol.
+The complete source code of the Sign-in Server can be download here: https://github.com/cguZZman/drive-login
+You can clone the project and host it in your own server.
+
+
+
diff --git a/plugin.googledrive/entrypoint.py b/plugin.googledrive/entrypoint.py
new file mode 100644
index 0000000000..17c5c2b1cd
--- /dev/null
+++ b/plugin.googledrive/entrypoint.py
@@ -0,0 +1,21 @@
+#-------------------------------------------------------------------------------
+# Copyright (C) 2017 Carlos Guzman (cguZZman) carlosguzmang@protonmail.com
+#
+# This file is part of Google Drive for Kodi
+#
+# Google Drive for Kodi is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Cloud Drive Common Module for Kodi is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#-------------------------------------------------------------------------------
+
+from resources.lib.addon import GoogleDriveAddon
+GoogleDriveAddon().route()
diff --git a/plugin.googledrive/fanart.jpg b/plugin.googledrive/fanart.jpg
new file mode 100644
index 0000000000..7130be98b1
Binary files /dev/null and b/plugin.googledrive/fanart.jpg differ
diff --git a/plugin.googledrive/icon.png b/plugin.googledrive/icon.png
new file mode 100644
index 0000000000..e29a50263d
Binary files /dev/null and b/plugin.googledrive/icon.png differ
diff --git a/plugin.googledrive/resources/__init__.py b/plugin.googledrive/resources/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugin.googledrive/resources/language/resource.language.en_gb/strings.po b/plugin.googledrive/resources/language/resource.language.en_gb/strings.po
new file mode 100644
index 0000000000..624de9cb63
--- /dev/null
+++ b/plugin.googledrive/resources/language/resource.language.en_gb/strings.po
@@ -0,0 +1,165 @@
+# Kodi Media Center language file
+# Addon Name: Google Drive
+# Addon id: plugin.googledrive
+# Addon Provider: Carlos Guzman (cguZZman)
+msgid ""
+msgstr ""
+"Project-Id-Version: Kodi Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#32000"
+msgid "Player Service"
+msgstr ""
+
+msgctxt "#32001"
+msgid "Export Service"
+msgstr ""
+
+msgctxt "#32002"
+msgid "Source Service"
+msgstr ""
+
+msgctxt "#32003"
+msgid "Before full export, remove previous local files and folders"
+msgstr ""
+
+msgctxt "#32004"
+msgid "Automatically set subtitles from cloud drive"
+msgstr ""
+
+msgctxt "#32005"
+msgid "Auto-Refreshed slideshow"
+msgstr ""
+
+msgctxt "#32006"
+msgid "Refresh interval in minutes"
+msgstr ""
+
+msgctxt "#32007"
+msgid "Google Photos"
+msgstr ""
+
+msgctxt "#32008"
+msgid "Always ask for the stream format before play"
+msgstr ""
+
+msgctxt "#32009"
+msgid "Loading stream formats..."
+msgstr ""
+
+msgctxt "#32010"
+msgid "Recursive auto-refreshed slideshow"
+msgstr ""
+
+msgctxt "#32011"
+msgid "Resume playing when resume point exists in library"
+msgstr ""
+
+msgctxt "#32012"
+msgid "Open Cloud Drive Common Settings..."
+msgstr ""
+
+msgctxt "#32013"
+msgid "My Drive"
+msgstr ""
+
+msgctxt "#32014"
+msgid "Starred"
+msgstr ""
+
+msgctxt "#32015"
+msgid "Original format"
+msgstr ""
+
+msgctxt "#32016"
+msgid "Select the stream format"
+msgstr ""
+
+msgctxt "#32017"
+msgid "Ask to resume if resume point exists in library"
+msgstr ""
+
+msgctxt "#32018"
+msgid "Save resume points and watched status in library"
+msgstr ""
+
+msgctxt "#32019"
+msgid "Do not include filename extension in a .strm"
+msgstr ""
+
+msgctxt "#32020"
+msgid "Hide exporting progress dialog"
+msgstr ""
+
+msgctxt "#32030"
+msgid "Collaboration"
+msgstr ""
+
+msgctxt "#32031"
+msgid "Report errors automatically to help resolve them quickly"
+msgstr ""
+
+msgctxt "#32032"
+msgid "Advanced"
+msgstr ""
+
+msgctxt "#32033"
+msgid "Sign-in Server"
+msgstr ""
+
+msgctxt "#32034"
+msgid "Cache expiration time (in minutes)"
+msgstr ""
+
+msgctxt "#32035"
+msgid "Clear cache now"
+msgstr ""
+
+msgctxt "#32067"
+msgid "Services"
+msgstr ""
+
+msgctxt "#32068"
+msgid "Allow using Google Drive as a source"
+msgstr ""
+
+msgctxt "#32069"
+msgid " Source server port - http://localhost:/source"
+msgstr ""
+
+msgctxt "#32070"
+msgid "Default stream quality"
+msgstr ""
+
+msgctxt "#32071"
+msgid "Check for Google ban"
+msgstr ""
+
+msgctxt "#32072"
+msgid "Banned: %s"
+msgstr ""
+
+msgctxt "#32073"
+msgid "Response code: %s"
+msgstr ""
+
+msgctxt "#32074"
+msgid "Download metadata files (.nfo, .strm)"
+msgstr ""
+
+msgctxt "#32075"
+msgid "Skip unmodified files"
+msgstr ""
+
+msgctxt "#32076"
+msgid "Play choosing stream format"
+msgstr ""
\ No newline at end of file
diff --git a/plugin.googledrive/resources/language/resource.language.es_es/strings.po b/plugin.googledrive/resources/language/resource.language.es_es/strings.po
new file mode 100644
index 0000000000..133d50640f
--- /dev/null
+++ b/plugin.googledrive/resources/language/resource.language.es_es/strings.po
@@ -0,0 +1,85 @@
+# Kodi Media Center language file
+# Addon Name: Google Drive
+# Addon id: plugin.googledrive
+# Addon Provider: Carlos Guzman (cguZZman)
+msgid ""
+msgstr ""
+"Project-Id-Version: Kodi Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#32000"
+msgid "Account"
+msgstr "Cuenta"
+
+msgctxt "#32001"
+msgid "Video Library Export Folder"
+msgstr "Directorio de exportación de la biblioteca de vídeos"
+
+msgctxt "#32002"
+msgid "Music Library Export Folder"
+msgstr "Directorio de exportación de la biblioteca de música"
+
+msgctxt "#32003"
+msgid "Before export to library, remove previous files and folders"
+msgstr "Antes de exportar a biblioteca, eliminar ficheros y directorios anteriores"
+
+msgctxt "#32004"
+msgid "When playing videos, set the subtitle file located next to the video (.srt)"
+msgstr "Durante la reproducción de vídeo, establecer el fichero de subítulos ubicado junto al vídeo (.srt)"
+
+msgctxt "#32005"
+msgid "Auto-Refreshed slideshow"
+msgstr "Actualizar automaticamente las diapositivas"
+
+msgctxt "#32006"
+msgid "Refresh interval in minutes"
+msgstr "Intervalo de actualización en minutos"
+
+msgctxt "#32007"
+msgid "Google Photos"
+msgstr "Google Photos"
+
+msgctxt "#32008"
+msgid "Always ask for the stream format before play"
+msgstr "Preguntar siempre por el formato de la reproducción"
+
+msgctxt "#32009"
+msgid "Loading stream formats..."
+msgstr "Cargando formatos de reproducción"
+
+msgctxt "#32010"
+msgid "Recursive auto-refreshed slideshow"
+msgstr "Auto-Actualizar recursivamente las diapositivas"
+
+msgctxt "#32011"
+msgid "Common Settings"
+msgstr "Ajustes"
+
+msgctxt "#32012"
+msgid "Open Cloud Drive Common Settings..."
+msgstr "Configuración de Drive..."
+
+msgctxt "#32013"
+msgid "My Drive"
+msgstr "Mi Disco"
+
+msgctxt "#32014"
+msgid "Starred"
+msgstr "Seleccionados"
+
+msgctxt "#32015"
+msgid "Original format"
+msgstr "Formato original"
+
+msgctxt "#32016"
+msgid "Select the stream format"
+msgstr "Seleccionar formato de la reproducción"
diff --git a/plugin.googledrive/resources/language/resource.language.he_il/strings.po b/plugin.googledrive/resources/language/resource.language.he_il/strings.po
new file mode 100644
index 0000000000..83986dc499
--- /dev/null
+++ b/plugin.googledrive/resources/language/resource.language.he_il/strings.po
@@ -0,0 +1,77 @@
+# Kodi Media Center language file
+# Addon Name: Google Drive
+# Addon id: plugin.googledrive
+# Addon Provider: Carlos Guzman (cguZZman)
+msgid ""
+msgstr ""
+"Project-Id-Version: Kodi Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2017-10-19 11:41+0300\n"
+"Last-Translator: A. Dambledore\n"
+"Language-Team: Eng2Heb\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: he_IL\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#32000"
+msgid "Account"
+msgstr "חשבון"
+
+msgctxt "#32001"
+msgid "Video Library Export Folder"
+msgstr "תיקיית ייצוא ספריית וידאו"
+
+msgctxt "#32002"
+msgid "Music Library Export Folder"
+msgstr "תיקיית ייצוא ספריית מוזיקה"
+
+msgctxt "#32003"
+msgid "Before export to library, remove previous files and folders"
+msgstr "לפני ייצוא לספריה, הסר את הקבצים והתיקיות הקודמים"
+
+msgctxt "#32004"
+msgid "When playing videos, set the subtitle file located next to the video (.srt)"
+msgstr "בעת ניגון וידאו, הגדר את קובץ הכתוביות הממוקם ליד הווידאו (.srt)"
+
+msgctxt "#32005"
+msgid "Auto-Refreshed slideshow"
+msgstr "מצגת עם רענון אוטומטי"
+
+msgctxt "#32006"
+msgid "Refresh interval in minutes"
+msgstr "מרווח זמן לרענון בדקות"
+
+msgctxt "#32007"
+msgid "Google Photos"
+msgstr "אלבומי Google"
+
+msgctxt "#32008"
+msgid "-unused-Special: Camera Roll"
+msgstr "-לא בשימוש-מיוחד: סרט צילום"
+
+msgctxt "#32009"
+msgid "-unused-Special: Music"
+msgstr "-לא בשימוש-מיוחד: מוזיקה"
+
+msgctxt "#32010"
+msgid "Recursive auto-refreshed slideshow"
+msgstr "רקורסיבית אוטומטי לרענן שקופיות"
+
+msgctxt "#32011"
+msgid "Common Settings"
+msgstr "הגדרות נפוצות"
+
+msgctxt "#32012"
+msgid "Open Cloud Drive Common Settings..."
+msgstr "פתח הגדרות נפוצות של כונן ענן ..."
+
+msgctxt "#32013"
+msgid "My Drive"
+msgstr "הכונן שלי"
+
+msgctxt "#32014"
+msgid "Starred"
+msgstr "כיכב"
diff --git a/plugin.googledrive/resources/language/resource.language.it_it/strings.po b/plugin.googledrive/resources/language/resource.language.it_it/strings.po
new file mode 100644
index 0000000000..f889d53d22
--- /dev/null
+++ b/plugin.googledrive/resources/language/resource.language.it_it/strings.po
@@ -0,0 +1,77 @@
+# Kodi Media Center language file
+# Addon Name: Google Drive
+# Addon id: plugin.googledrive
+# Addon Provider: Carlos Guzman (cguZZman)
+msgid ""
+msgstr ""
+"Project-Id-Version: Kodi Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#32000"
+msgid "Account"
+msgstr "Account"
+
+msgctxt "#32001"
+msgid "Video Library Export Folder"
+msgstr "Cartella di export della Libreria Video"
+
+msgctxt "#32002"
+msgid "Music Library Export Folder"
+msgstr "Cartella di export della Libreria Musica"
+
+msgctxt "#32003"
+msgid "Before export to library, remove previous files and folders"
+msgstr "Prima di esportare la libreria, cancella i file e cartelle precedenti"
+
+msgctxt "#32004"
+msgid "When playing videos, set the subtitle file located next to the video (.srt)"
+msgstr "Quando riproduci video, imposta il file dei sottotitoli del video situato accanto al video (.srt)"
+
+msgctxt "#32005"
+msgid "Auto-Refreshed slideshow"
+msgstr "Auto aggiorna slideshow"
+
+msgctxt "#32006"
+msgid "Refresh interval in minutes"
+msgstr "Intervallo in minuti per l'aggiornamento"
+
+msgctxt "#32007"
+msgid "Google Photos"
+msgstr "Google Photos"
+
+msgctxt "#32008"
+msgid "-unused-Special: Camera Roll"
+msgstr "-unused-Special: Rullino fotografico"
+
+msgctxt "#32009"
+msgid "-unused-Special: Music"
+msgstr "-unused-Special: Musica"
+
+msgctxt "#32010"
+msgid "Recursive auto-refreshed slideshow"
+msgstr "Auto aggiornamento ricursivo slideshow"
+
+msgctxt "#32011"
+msgid "Common Settings"
+msgstr "Impostazioni comuni"
+
+msgctxt "#32012"
+msgid "Open Cloud Drive Common Settings..."
+msgstr "Impostazioni comuni di Open Cloud..."
+
+msgctxt "#32013"
+msgid "My Drive"
+msgstr "Mio Drive"
+
+msgctxt "#32014"
+msgid "Starred"
+msgstr "Recitato"
diff --git a/plugin.googledrive/resources/language/resource.language.pt_br/strings.po b/plugin.googledrive/resources/language/resource.language.pt_br/strings.po
new file mode 100644
index 0000000000..abcea64f30
--- /dev/null
+++ b/plugin.googledrive/resources/language/resource.language.pt_br/strings.po
@@ -0,0 +1,77 @@
+# Kodi Media Center language file
+# Addon Name: Google Drive
+# Addon id: plugin.googledrive
+# Addon Provider: Carlos Guzman (cguZZman)
+msgid ""
+msgstr ""
+"Project-Id-Version: Kodi Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#32000"
+msgid "Account"
+msgstr "Conta"
+
+msgctxt "#32001"
+msgid "Video Library Export Folder"
+msgstr "Diretório de exportação da biblioteca de videos"
+
+msgctxt "#32002"
+msgid "Music Library Export Folder"
+msgstr "Diretório de exportação da biblioteca de música"
+
+msgctxt "#32003"
+msgid "Before export to library, remove previous files and folders"
+msgstr "Antes de exportar a biblioteca, remover arquivos e diretórios anteriores"
+
+msgctxt "#32004"
+msgid "When playing videos, set the subtitle file located next to the video (.srt)"
+msgstr "Quando tocar os videos, usar as legendas que estão no mesmo diretorio do video (.srt)"
+
+msgctxt "#32005"
+msgid "Auto-Refreshed slideshow"
+msgstr "Slideshow atualizado automaticamente"
+
+msgctxt "#32006"
+msgid "Refresh interval in minutes"
+msgstr "Tempo de atualização em minutos"
+
+msgctxt "#32007"
+msgid "Google Photos"
+msgstr "Google Fotos"
+
+msgctxt "#32008"
+msgid "-unused-Special: Camera Roll"
+msgstr "-unused-Special: Rolo de Camera"
+
+msgctxt "#32009"
+msgid "-unused-Special: Music"
+msgstr "-unused-Special: Musica"
+
+msgctxt "#32010"
+msgid "Recursive auto-refreshed slideshow"
+msgstr "Slideshow autalizado automaticamente de forma recursiva"
+
+msgctxt "#32011"
+msgid "Common Settings"
+msgstr "Configurações comuns"
+
+msgctxt "#32012"
+msgid "Open Cloud Drive Common Settings..."
+msgstr "Abrir configurações comuns doCloud Drive..."
+
+msgctxt "#32013"
+msgid "My Drive"
+msgstr "Meu Drive"
+
+msgctxt "#32014"
+msgid "Starred"
+msgstr "Com estrela"
diff --git a/plugin.googledrive/resources/lib/__init__.py b/plugin.googledrive/resources/lib/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugin.googledrive/resources/lib/addon.py b/plugin.googledrive/resources/lib/addon.py
new file mode 100644
index 0000000000..02812ff58d
--- /dev/null
+++ b/plugin.googledrive/resources/lib/addon.py
@@ -0,0 +1,199 @@
+#-------------------------------------------------------------------------------
+# Copyright (C) 2017 Carlos Guzman (cguZZman) carlosguzmang@protonmail.com
+#
+# This file is part of Google Drive for Kodi
+#
+# Google Drive for Kodi is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Cloud Drive Common Module for Kodi is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#-------------------------------------------------------------------------------
+
+import urllib
+
+from clouddrive.common.remote.request import Request
+from clouddrive.common.ui.addon import CloudDriveAddon
+from clouddrive.common.ui.utils import KodiUtils
+from clouddrive.common.utils import Utils
+from resources.lib.provider.googledrive import GoogleDrive
+from clouddrive.common.ui.logger import Logger
+
+
+class GoogleDriveAddon(CloudDriveAddon):
+ _provider = GoogleDrive()
+ _change_token = None
+ choose_stream_format = False
+
+ def __init__(self):
+ self._child_count_supported = False
+ super(GoogleDriveAddon, self).__init__()
+
+ def get_provider(self):
+ return self._provider
+
+ def get_my_files_menu_name(self):
+ return self._addon.getLocalizedString(32013)
+
+ def get_custom_drive_folders(self, driveid):
+ drive_folders = []
+ self._provider.configure(self._account_manager, driveid)
+ if not self._provider._is_shared_drive:
+ drive_folders.append({'name' : self._common_addon.getLocalizedString(32058), 'path' : 'sharedWithMe'})
+ if self._content_type == 'image':
+ drive_folders.append({'name' : self._addon.getLocalizedString(32007), 'path' : 'photos'})
+ drive_folders.append({'name' : self._addon.getLocalizedString(32014), 'path' : 'starred'})
+ return drive_folders
+
+ def new_change_token_slideshow(self, change_token, driveid, item_driveid=None, item_id=None, path=None):
+ self._provider.configure(self._account_manager, driveid)
+ if not change_token:
+ response = self._provider.get('/changes/startPageToken', parameters = self._provider._parameters)
+ self._change_token = Utils.get_safe_value(response, 'startPageToken')
+ change_token = 1
+ else:
+ page_token = self._change_token
+ while page_token:
+ self._provider._parameters['pageToken'] = page_token
+ self._provider._parameters['fields'] = 'nextPageToken,newStartPageToken,changes(file(id,name,parents))'
+ response = self._provider.get('/changes', parameters = self._provider._parameters)
+ if self.cancel_operation():
+ return
+ self._change_token = Utils.get_safe_value(response, 'newStartPageToken', self._change_token)
+ changes = Utils.get_safe_value(response, 'changes', [])
+ for change in changes:
+ f = Utils.get_safe_value(change, 'file', {})
+ parents = Utils.get_safe_value(f, 'parents', [])
+ parents.append(f['id'])
+ if item_id in parents:
+ return change_token + 1
+ page_token = Utils.get_safe_value(response, 'nextPageToken')
+ return change_token
+
+ def _get_url_original(self, driveid, item_driveid=None, item_id=None):
+ self._provider.configure(self._account_manager, driveid)
+ item = self._provider.get_item(item_driveid=item_driveid, item_id=item_id, include_download_info = True)
+ url = item['download_info']['url']
+ url += "|Authorization=%s" % urllib.parse.quote("Bearer %s" % self._provider.get_access_tokens()['access_token'])
+ return url
+
+ def _get_item_play_url(self, file_name, driveid, item_driveid=None, item_id=None, is_subtitle=False):
+ url = None
+ if self._content_type == 'video' and not is_subtitle:
+ if KodiUtils.get_addon_setting('ask_stream_format') == 'false' and not self.choose_stream_format:
+ if KodiUtils.get_addon_setting('default_stream_quality') == 'Original':
+ url = self._get_url_original(driveid, item_driveid, item_id)
+ else:
+ url = self._select_stream_format(driveid, item_driveid, item_id, True)
+ else:
+ url = self._select_stream_format(driveid, item_driveid, item_id, False)
+ if not url:
+ url = self._get_url_original(driveid, item_driveid, item_id)
+ return url
+
+ def _select_stream_format(self, driveid, item_driveid=None, item_id=None, auto=False):
+ url = None
+ if not auto:
+ self._progress_dialog.update(0, self._addon.getLocalizedString(32009))
+ self._provider.configure(self._account_manager, driveid)
+ self._provider.get_item(item_driveid, item_id)
+ request = Request('https://drive.google.com/get_video_info', urllib.parse.urlencode({'docid' : item_id}), {'authorization': 'Bearer %s' % self._provider.get_access_tokens()['access_token']})
+ response_text = request.request()
+ response_params = dict(urllib.parse.parse_qsl(response_text))
+ if not auto:
+ self._progress_dialog.close()
+ if Utils.get_safe_value(response_params, 'status', '') == 'ok':
+ fmt_list = Utils.get_safe_value(response_params, 'fmt_list', '').split(',')
+ stream_formats = []
+ for fmt in fmt_list:
+ data = fmt.split('/')
+ stream_formats.append(data[1])
+ stream_formats.append(self._addon.getLocalizedString(32015))
+ Logger.debug('Stream formats: %s' % Utils.str(stream_formats))
+ select = -1
+ if auto:
+ select = self._auto_select_stream(stream_formats)
+ else:
+ select = self._dialog.select(self._addon.getLocalizedString(32016), stream_formats, 8000, 0)
+ Logger.debug('Selected: %s' % Utils.str(select))
+ if select == -1:
+ self._cancel_operation = True
+ elif select != len(stream_formats) - 1:
+ data = fmt_list[select].split('/')
+ fmt_stream_map = Utils.get_safe_value(response_params, 'fmt_stream_map', '').split(',')
+
+ for fmt in fmt_stream_map:
+ stream_data = fmt.split('|')
+ if stream_data[0] == data[0]:
+ url = stream_data[1]
+ break
+ if url:
+ cookie_header = ''
+ for cookie in request.response_cookies:
+ if cookie_header: cookie_header += ';'
+ cookie_header += cookie.name + '=' + cookie.value;
+ url += '|cookie=' + urllib.parse.quote(cookie_header)
+ return url
+
+ def _auto_select_stream(self, streams):
+ select = -1
+ allowedQualitied = ['Original format','1920x1080','1280x720','854x480','640x360']
+ max_qual = KodiUtils.get_addon_setting('default_stream_quality')
+ if max_qual == '1080p':
+ allowedQualitied = ['1920x1080','1280x720','854x480','640x360','Original format']
+ elif max_qual == '720p':
+ allowedQualitied = ['1280x720','854x480','640x360','Original format']
+ elif max_qual == '480p':
+ allowedQualitied = ['854x480','640x360','Original format']
+ elif max_qual == '360p':
+ allowedQualitied = ['640x360','Original format']
+ for q in allowedQualitied:
+ if q in streams:
+ select = streams.index(q)
+ break
+ return select
+
+ def play_stream_format(self, driveid, item_driveid=None, item_id=None):
+ self.choose_stream_format = True
+ self.play(driveid, item_driveid, item_id)
+
+ def get_context_options(self, list_item, params, is_folder):
+ context_options = []
+ if Utils.get_safe_value(params, 'action', '') == 'play':
+ p = params.copy()
+ p['action'] = 'check_google_ban'
+ context_options.append((self._addon.getLocalizedString(32071), 'RunPlugin('+self._addon_url + '?' + urllib.parse.urlencode(p)+')'))
+ p['action'] = 'play_stream_format'
+ cmd = 'PlayMedia(%s?%s)' % (self._addon_url, urllib.parse.urlencode(p),)
+ context_options.append((self._addon.getLocalizedString(32076), cmd))
+ return context_options
+
+ def check_google_ban(self, driveid, item_driveid=None, item_id=None):
+ self._provider.configure(self._account_manager, driveid)
+ self._progress_dialog.update(0, '')
+ item = self._provider.get_item(item_driveid=item_driveid, item_id=item_id, include_download_info = True)
+ url = item['download_info']['url']
+ request_params = {
+ 'read_content' : False,
+ 'on_complete': lambda request: self.display_google_ban_result(request),
+ }
+ self._provider.prepare_request('get', url, request_params = request_params).request()
+
+ def display_google_ban_result(self, request):
+ self._progress_dialog.close()
+ color = 'lime'
+ ban = self._common_addon.getLocalizedString(32013)
+ if request.response_code == 403 or request.response_code == 429:
+ color = 'red'
+ ban = self._common_addon.getLocalizedString(32033)
+ msg = self._addon.getLocalizedString(32072) % '[B][COLOR %s]%s[/COLOR][/B]' % (color, ban,)
+ msg += '\n' + self._addon.getLocalizedString(32073) % '[B]%s[/B]' % Utils.str(request.response_code)
+ msg += '\n' + request.response_text
+ self._dialog.ok(self._addon_name, msg)
diff --git a/plugin.googledrive/resources/lib/provider/__init__.py b/plugin.googledrive/resources/lib/provider/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugin.googledrive/resources/lib/provider/googledrive.py b/plugin.googledrive/resources/lib/provider/googledrive.py
new file mode 100644
index 0000000000..7201673adb
--- /dev/null
+++ b/plugin.googledrive/resources/lib/provider/googledrive.py
@@ -0,0 +1,399 @@
+#-------------------------------------------------------------------------------
+# Copyright (C) 2017 Carlos Guzman (cguZZman) carlosguzmang@protonmail.com
+#
+# This file is part of Google Drive for Kodi
+#
+# Google Drive for Kodi is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Cloud Drive Common Module for Kodi is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#-------------------------------------------------------------------------------
+
+import urllib
+import datetime
+import copy
+
+from clouddrive.common.remote.provider import Provider
+from clouddrive.common.utils import Utils
+from clouddrive.common.ui.logger import Logger
+from clouddrive.common.ui.utils import KodiUtils
+from clouddrive.common.exception import RequestException, ExceptionUtils
+from clouddrive.common.cache.cache import Cache
+try:
+ from urllib.error import HTTPError
+except ImportError:
+ from urllib2 import HTTPError
+
+class GoogleDrive(Provider):
+ _default_parameters = {'spaces': 'drive', 'supportsAllDrives': 'true', 'prettyPrint': 'false'}
+ _is_shared_drive = False
+ _shared_drive_parameters = {'includeItemsFromAllDrives': 'true', 'corpora': 'drive', 'driveId': ''}
+ _user = None
+
+ def __init__(self, source_mode = False):
+ super(GoogleDrive, self).__init__('googledrive', source_mode)
+ self.download_requires_auth = True
+ self._items_cache = Cache(KodiUtils.get_addon_info('id'), 'items', datetime.timedelta(minutes=KodiUtils.get_cache_expiration_time()))
+
+ def configure(self, account_manager, driveid):
+ super(GoogleDrive, self).configure(account_manager, driveid)
+ drive = account_manager.get_by_driveid('drive', driveid)
+ self._is_shared_drive = drive and 'type' in drive and (drive['type'] == 'drive#drive' or drive['type'] == 'drive#teamDrive')
+
+ def _get_api_url(self):
+ return 'https://www.googleapis.com/drive/v3'
+
+ def _get_request_headers(self):
+ return None
+
+ def get_account(self, request_params=None, access_tokens=None):
+ me = self.get('/about', parameters={'fields':'user'}, request_params=request_params, access_tokens=access_tokens)
+ if not me or not 'user' in me:
+ raise Exception('NoAccountInfo')
+ self._user = me['user']
+ return { 'id' : self._user['permissionId'], 'name' : self._user['displayName']}
+
+ def get_drives(self, request_params=None, access_tokens=None):
+ drives = [{
+ 'id' : self._user['permissionId'],
+ 'name' : '',
+ 'type' : ''
+ }]
+ try:
+ all_shareddrives_fetch = False
+ page_token = None
+ parameters = {'pageSize': 100}
+ while not all_shareddrives_fetch:
+ if page_token:
+ parameters['pageToken'] = page_token
+ response = self.get('/drives', parameters=parameters, request_params=request_params, access_tokens=access_tokens)
+ if response and 'drives' in response:
+ for drive in response['drives']:
+ drives.append({
+ 'id' : drive['id'],
+ 'name' : Utils.get_safe_value(drive, 'name', drive['id']),
+ 'type' : drive['kind']
+ })
+ if response and 'nextPageToken' in response:
+ page_token = response['nextPageToken']
+ else:
+ all_shareddrives_fetch = True
+ except RequestException as ex:
+ httpex = ExceptionUtils.extract_exception(ex, HTTPError)
+ if not httpex or httpex.code != 403:
+ raise ex
+ return drives
+
+ def get_drive_type_name(self, drive_type):
+ if drive_type == 'drive#drive' or drive_type == 'drive#teamDrive':
+ return 'Shared Drive'
+ return drive_type
+
+ def prepare_parameters(self):
+ parameters = copy.deepcopy(self._default_parameters)
+ if self._is_shared_drive:
+ parameters.update(self._shared_drive_parameters)
+ parameters['driveId'] = self._driveid
+ return parameters
+
+ def _get_field_parameters(self):
+ file_fileds = 'id,name,modifiedTime,size,mimeType'
+ if not self.source_mode:
+ file_fileds = file_fileds + ',description,hasThumbnail,thumbnailLink,owners(permissionId),parents,trashed,imageMediaMetadata(width),videoMediaMetadata,shortcutDetails'
+ return file_fileds
+
+ def get_folder_items(self, item_driveid=None, item_id=None, path=None, on_items_page_completed=None, include_download_info=False, on_before_add_item=None):
+ item_driveid = Utils.default(item_driveid, self._driveid)
+ is_album = item_id and item_id[:6] == 'album-'
+ if is_album:
+ item_id = item_id[6:]
+ parameters = self.prepare_parameters()
+ if item_id:
+ parameters['q'] = '\'%s\' in parents' % item_id
+ elif path == 'sharedWithMe' or path == 'starred':
+ parameters['q'] = path
+ elif path != 'photos':
+ if path == '/':
+ parent = self._driveid if self._is_shared_drive else 'root'
+ parameters['q'] = '\'%s\' in parents' % parent
+ elif not is_album:
+ item = self.get_item_by_path(path, include_download_info)
+ parameters['q'] = '\'%s\' in parents' % item['id']
+
+ parameters['fields'] = 'files(%s),kind,nextPageToken' % self._get_field_parameters()
+ if 'q' in parameters:
+ parameters['q'] += ' and not trashed'
+
+ self.configure(self._account_manager, self._driveid)
+ provider_method = self.get
+ url = '/files'
+ parameters['pageSize'] = 1000
+ items = []
+ if path == 'photos':
+ self._photos_provider = GooglePhotos()
+ self._photos_provider.configure(self._account_manager, self._driveid)
+ parameters = {}
+ provider_method = self._photos_provider.get
+ url = '/albums'
+ items.append(self._extract_item({'id': 'photos', 'title': 'Photos', 'kind': 'album'}))
+ elif is_album:
+ self._photos_provider = GooglePhotos()
+ self._photos_provider.configure(self._account_manager, self._driveid)
+ if item_id == 'photos':
+ parameters = {}
+ else:
+ parameters = {'albumId': item_id}
+ provider_method = self._photos_provider.post
+ url = '/mediaItems:search'
+
+ files = provider_method(url, parameters = parameters)
+ if self.cancel_operation():
+ return
+ items.extend(self.process_files(files, parameters, on_items_page_completed, include_download_info, on_before_add_item=on_before_add_item))
+ return items
+
+ def search(self, query, item_driveid=None, item_id=None, on_items_page_completed=None):
+ item_driveid = Utils.default(item_driveid, self._driveid)
+ parameters = self.prepare_parameters()
+ parameters['fields'] = 'files(%s),kind,nextPageToken' % self._get_field_parameters()
+ query = 'fullText contains \'%s\'' % Utils.str(query)
+ if item_id:
+ query += ' and \'%s\' in parents' % item_id
+ parameters['q'] = query + ' and not trashed'
+ parameters['pageSize'] = 1000
+ files = self.get('/files', parameters = parameters)
+ if self.cancel_operation():
+ return
+ return self.process_files(files, parameters, on_items_page_completed)
+
+ def process_files(self, files, parameters, on_items_page_completed=None, include_download_info=False, extra_info=None, on_before_add_item=None):
+ items = []
+ if files:
+ kind = Utils.get_safe_value(files, 'kind', '')
+ collection = []
+ if kind == 'drive#fileList':
+ collection = files['files']
+ elif kind == 'drive#changeList':
+ collection = files['changes']
+ elif 'albums' in files:
+ kind = 'album'
+ collection = files['albums']
+ elif 'mediaItems' in files:
+ kind = 'media_item'
+ collection = files['mediaItems']
+ if collection:
+ for f in collection:
+ f['kind'] = Utils.get_safe_value(f, 'kind', kind)
+ item = self._extract_item(f, include_download_info)
+ if item:
+ if on_before_add_item:
+ on_before_add_item(item)
+ items.append(item)
+ if on_items_page_completed:
+ on_items_page_completed(items)
+ if type(extra_info) is dict:
+ if 'newStartPageToken' in files:
+ extra_info['change_token'] = files['newStartPageToken']
+ if 'nextPageToken' in files:
+ parameters['pageToken'] = files['nextPageToken']
+ url = '/files'
+ provider_method = self.get
+ if kind == 'drive#changeList':
+ url = '/changes'
+ elif kind == 'album':
+ url = '/albums'
+ provider_method = self._photos_provider.get
+ elif kind == 'media_item':
+ url = '/mediaItems:search'
+ provider_method = self._photos_provider.post
+ next_files = provider_method(url, parameters = parameters)
+ if self.cancel_operation():
+ return
+ items.extend(self.process_files(next_files, parameters, on_items_page_completed, include_download_info, extra_info, on_before_add_item))
+ return items
+
+ def _extract_item(self, f, include_download_info=False):
+ kind = Utils.get_safe_value(f, 'kind', '')
+ if kind == 'drive#change':
+ change_type = Utils.get_safe_value(f, 'changeType', '')
+ if change_type == 'file':
+ if 'file' in f:
+ f = f['file']
+ else:
+ f['id'] = Utils.get_safe_value(f, 'fileId')
+ f['trashed'] = Utils.get_safe_value(f, 'removed')
+ f['modifiedTime'] = Utils.get_safe_value(f, 'time')
+ else:
+ return {}
+ size = int('%s' % Utils.get_safe_value(f, 'size', 0))
+ is_album = kind == 'album'
+ is_media_items = kind == 'media_item'
+ item_id = f['id']
+ if is_album:
+ mimetype = 'application/vnd.google-apps.folder'
+ name = Utils.get_safe_value(f, 'title', item_id)
+ else:
+ mimetype = Utils.get_safe_value(f, 'mimeType', '')
+ name = Utils.get_safe_value(f, 'name', '')
+ if mimetype == 'application/vnd.google-apps.shortcut':
+ shortcut = Utils.get_safe_value(f, 'shortcutDetails')
+ item_id = Utils.get_safe_value(shortcut, 'targetId', item_id)
+ mimetype = Utils.get_safe_value(shortcut, 'targetMimeType', mimetype)
+ if is_media_items:
+ name = Utils.get_safe_value(f, 'filename', item_id)
+ item = {
+ 'id': item_id,
+ 'name': name,
+ 'name_extension' : Utils.get_extension(name),
+ 'parent': Utils.get_safe_value(f, 'parents', ['root'])[0],
+ 'drive_id' : Utils.get_safe_value(Utils.get_safe_value(f, 'owners', [{}])[0], 'permissionId'),
+ 'mimetype' : mimetype,
+ 'last_modified_date' : Utils.get_safe_value(f,'modifiedTime'),
+ 'size': size,
+ 'description': Utils.get_safe_value(f, 'description', ''),
+ 'deleted' : Utils.get_safe_value(f, 'trashed', False)
+ }
+ if item['mimetype'] == 'application/vnd.google-apps.folder':
+ item['folder'] = {
+ 'child_count' : 0
+ }
+ if is_media_items:
+ item['url'] = f['baseUrl'] + '=d'
+ item['thumbnail'] = f['baseUrl'] + '=w100-h100'
+ if 'mediaMetadata' in f:
+
+ metadata = f['mediaMetadata']
+ if 'video' in metadata:
+ item['url'] += 'v'
+ item['video'] = {
+ 'width' : Utils.get_safe_value(metadata, 'width'),
+ 'height' : Utils.get_safe_value(metadata, 'height')
+ }
+ item['last_modified_date'] = Utils.get_safe_value(metadata, 'creationTime')
+ if 'videoMediaMetadata' in f:
+ video = f['videoMediaMetadata']
+ item['video'] = {
+ 'width' : Utils.get_safe_value(video, 'width'),
+ 'height' : Utils.get_safe_value(video, 'height'),
+ 'duration' : int('%s' % Utils.get_safe_value(video, 'durationMillis', 0)) / 1000
+ }
+ if 'imageMediaMetadata' in f or 'mediaMetadata' in f:
+ item['image'] = {
+ 'size' : size
+ }
+ if 'hasThumbnail' in f and f['hasThumbnail']:
+ item['thumbnail'] = Utils.get_safe_value(f, 'thumbnailLink')
+ if is_album:
+ item['thumbnail'] = Utils.get_safe_value(f, 'coverPhotoBaseUrl')
+ item['id'] = 'album-' + item['id']
+ if include_download_info:
+ if is_media_items:
+ item['download_info'] = {
+ 'url' : item['url']
+ }
+ else:
+ parameters = {
+ 'alt': 'media',
+ }
+ url = self._get_api_url() + '/files/%s' % item['id']
+ if 'size' not in f and item['mimetype'] == 'application/vnd.google-apps.document':
+ url += '/export'
+ parameters['mimeType'] = Utils.default(Utils.get_mimetype_by_extension(item['name_extension']), Utils.get_mimetype_by_extension('pdf'))
+ url += '?%s' % urllib.parse.urlencode(parameters)
+ item['download_info'] = {
+ 'url' : url
+ }
+ return item
+
+ def get_item_by_path(self, path, include_download_info=False):
+ parameters = self.prepare_parameters()
+ if path[-1:] == '/':
+ path = path[:-1]
+ Logger.debug(path + ' <- Target')
+ key = '%s%s' % (self._driveid, path,)
+ Logger.debug('Testing item from cache: %s' % key)
+ item = self._items_cache.get(key)
+ if not item:
+ parameters['fields'] = 'files(%s)' % self._get_field_parameters()
+ index = path.rfind('/')
+ filename = urllib.parse.unquote(path[index+1:])
+ parent = path[0:index]
+ if not parent:
+ parent = 'root'
+ else:
+ parent = self.get_item_by_path(parent, include_download_info)['id']
+ item = None
+ parameters['q'] = '\'%s\' in parents and name = \'%s\'' % (Utils.str(parent), Utils.str(filename).replace("'","\\'"))
+ parameters['pageSize'] = 1000
+ files = self.get('/files', parameters = parameters)
+ if (len(files['files']) > 0):
+ for f in files['files']:
+ item = self._extract_item(f, include_download_info)
+ break
+ else:
+ Logger.debug('Found in cache.')
+ if not item:
+ raise RequestException('Not found by path', HTTPError(path, 404, 'Not found', None, None), 'Request URL: %s' % path, None)
+
+ else:
+ self._items_cache.set(key, item)
+ return item
+
+ def get_subtitles(self, parent, name, item_driveid=None, include_download_info=False):
+ parameters = self.prepare_parameters()
+ item_driveid = Utils.default(item_driveid, self._driveid)
+ subtitles = []
+ parameters['fields'] = 'files(' + self._get_field_parameters() + ')'
+ parameters['q'] = 'name contains \'%s\'' % Utils.str(Utils.remove_extension(name)).replace("'","\\'")
+ parameters['pageSize'] = 1000
+ files = self.get('/files', parameters = parameters)
+ for f in files['files']:
+ subtitle = self._extract_item(f, include_download_info)
+ if subtitle['name_extension'].lower() in ('srt','idx','sub','sbv','ass','ssa','smi'):
+ subtitles.append(subtitle)
+ return subtitles
+
+ def get_item(self, item_driveid=None, item_id=None, path=None, find_subtitles=False, include_download_info=False):
+ parameters = self.prepare_parameters()
+ item_driveid = Utils.default(item_driveid, self._driveid)
+ parameters['fields'] = self._get_field_parameters()
+ if not item_id and path == '/':
+ item_id = 'root'
+ if item_id:
+ f = self.get('/files/%s' % item_id, parameters = parameters)
+ item = self._extract_item(f, include_download_info)
+ else:
+ item = self.get_item_by_path(path, include_download_info)
+
+ if find_subtitles:
+ subtitles = self.get_subtitles(item['parent'], item['name'], item_driveid, include_download_info)
+ if subtitles:
+ item['subtitles'] = subtitles
+ return item
+
+ def changes(self):
+ change_token = self.get_change_token()
+ if not change_token:
+ change_token = Utils.get_safe_value(self.get('/changes/startPageToken', parameters = self.prepare_parameters()), 'startPageToken')
+ extra_info = {}
+ parameters = self.prepare_parameters()
+ parameters['pageToken'] = change_token
+ parameters['pageSize'] = 1000
+ parameters['fields'] = 'kind,nextPageToken,newStartPageToken,changes(kind,fileId,removed,changeType,time,file(%s))' % self._get_field_parameters()
+ f = self.get('/changes', parameters = parameters)
+ changes = self.process_files(f, parameters, include_download_info=True, extra_info=extra_info)
+ self.persist_change_token(Utils.get_safe_value(extra_info, 'change_token'))
+ return changes
+
+class GooglePhotos(GoogleDrive):
+ def _get_api_url(self):
+ return 'https://photoslibrary.googleapis.com/v1'
+
diff --git a/plugin.googledrive/resources/settings.xml b/plugin.googledrive/resources/settings.xml
new file mode 100644
index 0000000000..236634de0c
--- /dev/null
+++ b/plugin.googledrive/resources/settings.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugin.googledrive/service.py b/plugin.googledrive/service.py
new file mode 100644
index 0000000000..ee7d45a571
--- /dev/null
+++ b/plugin.googledrive/service.py
@@ -0,0 +1,30 @@
+#-------------------------------------------------------------------------------
+# Copyright (C) 2017 Carlos Guzman (cguZZman) carlosguzmang@protonmail.com
+#
+# This file is part of Google Drive for Kodi
+#
+# Google Drive for Kodi is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Cloud Drive Common Module for Kodi is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#-------------------------------------------------------------------------------
+
+from clouddrive.common.service.download import DownloadService
+from clouddrive.common.service.source import SourceService
+from clouddrive.common.service.utils import ServiceUtil
+from resources.lib.provider.googledrive import GoogleDrive
+from clouddrive.common.service.export import ExportService
+from clouddrive.common.service.player import PlayerService
+
+
+if __name__ == '__main__':
+ ServiceUtil.run([DownloadService(GoogleDrive), SourceService(GoogleDrive),
+ ExportService(GoogleDrive), PlayerService(GoogleDrive)])
\ No newline at end of file
diff --git a/plugin.image.bancasapo/LICENSE b/plugin.image.bancasapo/LICENSE
new file mode 100644
index 0000000000..b616f5b958
--- /dev/null
+++ b/plugin.image.bancasapo/LICENSE
@@ -0,0 +1,692 @@
+Copyright (c) 2017 enen92 (http://24.sapo.pt/jornais)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see
+
+
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {one line to give the program's name and a brief idea of what it does.}
+ Copyright (C) {year} {name of author}
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ {project} Copyright (C) {year} {fullname}
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
+
diff --git a/plugin.image.bancasapo/README.md b/plugin.image.bancasapo/README.md
new file mode 100644
index 0000000000..3d9ad0e657
--- /dev/null
+++ b/plugin.image.bancasapo/README.md
@@ -0,0 +1,16 @@
+# plugin.image.bancasapo
+
+/badge.svg)
+/badge.svg)
+
+Daily portuguese newspapper covers in Kodi
+
+* Screenshots
+
+
+
+
+
+
+
+
diff --git a/plugin.image.bancasapo/addon.xml b/plugin.image.bancasapo/addon.xml
new file mode 100644
index 0000000000..39faf2a91d
--- /dev/null
+++ b/plugin.image.bancasapo/addon.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+ image
+
+
+ Daily portuguese newspapper covers in Kodi
+ Capas diárias dos jornais portugueses no Kodi
+ A Kodi image plugin for Banca Sapo
+ Um plugin para o Kodi para aceder à Banca Sapo
+ pt
+ all
+ GPL-3.0
+ https://24.sapo.pt/jornais
+ enen92@kodi.tv
+ https://github.com/enen92/plugin.image.bancasapo
+
+ 3.0.2:
+ - Fix regex
+
+ Addon not endorsed by SAPO
+ Addon não desenvolvido pelo SAPO
+
+ resources/images/icon.png
+ resources/images/fanart.jpg
+ resources/images/screenshot-01.jpg
+ resources/images/screenshot-02.jpg
+ resources/images/screenshot-03.jpg
+ resources/images/screenshot-04.jpg
+
+
+
diff --git a/plugin.image.bancasapo/changelog.txt b/plugin.image.bancasapo/changelog.txt
new file mode 100644
index 0000000000..353f05d43b
--- /dev/null
+++ b/plugin.image.bancasapo/changelog.txt
@@ -0,0 +1,10 @@
+3.0.2:
+- Fix regex
+
+3.0.1:
+- Automatic submissions to krypton and matrix
+- Fix languages
+- Addon-check fixes
+
+v2.0.0
+- Old XBMC addon fixed to work for krypton
\ No newline at end of file
diff --git a/plugin.image.bancasapo/main.py b/plugin.image.bancasapo/main.py
new file mode 100644
index 0000000000..e95eb4af2b
--- /dev/null
+++ b/plugin.image.bancasapo/main.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+
+from resources.lib import kodilogging
+from resources.lib import plugin
+
+import logging
+import xbmcaddon
+
+# Keep this file to a minimum, as Kodi
+# doesn't keep a compiled copy of this
+ADDON = xbmcaddon.Addon()
+kodilogging.config()
+
+plugin.run()
diff --git a/plugin.image.bancasapo/resources/__init__.py b/plugin.image.bancasapo/resources/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugin.image.bancasapo/resources/images/fanart.jpg b/plugin.image.bancasapo/resources/images/fanart.jpg
new file mode 100644
index 0000000000..8cb5119aaf
Binary files /dev/null and b/plugin.image.bancasapo/resources/images/fanart.jpg differ
diff --git a/plugin.image.bancasapo/resources/images/icon.png b/plugin.image.bancasapo/resources/images/icon.png
new file mode 100644
index 0000000000..dfc417d208
Binary files /dev/null and b/plugin.image.bancasapo/resources/images/icon.png differ
diff --git a/plugin.image.bancasapo/resources/images/newspapericon.png b/plugin.image.bancasapo/resources/images/newspapericon.png
new file mode 100644
index 0000000000..ee0dacb3b3
Binary files /dev/null and b/plugin.image.bancasapo/resources/images/newspapericon.png differ
diff --git a/plugin.image.bancasapo/resources/images/screenshot-01.jpg b/plugin.image.bancasapo/resources/images/screenshot-01.jpg
new file mode 100644
index 0000000000..4938a1f352
Binary files /dev/null and b/plugin.image.bancasapo/resources/images/screenshot-01.jpg differ
diff --git a/plugin.image.bancasapo/resources/images/screenshot-02.jpg b/plugin.image.bancasapo/resources/images/screenshot-02.jpg
new file mode 100644
index 0000000000..2560ad7d65
Binary files /dev/null and b/plugin.image.bancasapo/resources/images/screenshot-02.jpg differ
diff --git a/plugin.image.bancasapo/resources/images/screenshot-03.jpg b/plugin.image.bancasapo/resources/images/screenshot-03.jpg
new file mode 100644
index 0000000000..a10231ab15
Binary files /dev/null and b/plugin.image.bancasapo/resources/images/screenshot-03.jpg differ
diff --git a/plugin.image.bancasapo/resources/images/screenshot-04.jpg b/plugin.image.bancasapo/resources/images/screenshot-04.jpg
new file mode 100644
index 0000000000..4e9541af81
Binary files /dev/null and b/plugin.image.bancasapo/resources/images/screenshot-04.jpg differ
diff --git a/plugin.image.bancasapo/resources/language/resource.language.en_gb/strings.po b/plugin.image.bancasapo/resources/language/resource.language.en_gb/strings.po
new file mode 100644
index 0000000000..47d88c3b66
--- /dev/null
+++ b/plugin.image.bancasapo/resources/language/resource.language.en_gb/strings.po
@@ -0,0 +1,31 @@
+# Kodi Media Center language file
+# Addon Name: Banca Sapo
+# Addon id: plugin.image.bancasapo
+# Addon Provider: enen92
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: This is a comment
+
+msgctxt "#32000"
+msgid "Banca Sapo"
+msgstr ""
+
+msgctxt "#32001"
+msgid "Unable to access website. Please try again later or contact the addon author."
+msgstr ""
+
+msgctxt "#32002"
+msgid "Debug mode"
+msgstr ""
\ No newline at end of file
diff --git a/plugin.image.bancasapo/resources/language/resource.language.pt_pt/strings.po b/plugin.image.bancasapo/resources/language/resource.language.pt_pt/strings.po
new file mode 100644
index 0000000000..a8c7e21f4e
--- /dev/null
+++ b/plugin.image.bancasapo/resources/language/resource.language.pt_pt/strings.po
@@ -0,0 +1,31 @@
+# Kodi Media Center language file
+# Addon Name: Banca Sapo
+# Addon id: plugin.image.bancasapo
+# Addon Provider: enen92
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Portuguese (http://www.transifex.com/projects/p/xbmc-addons/language/pt/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: pt\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: This is a comment
+
+msgctxt "#32000"
+msgid "Banca Sapo"
+msgstr "Banca Sapo"
+
+msgctxt "#32001"
+msgid "Unable to access website. Please try again later or contact the addon author."
+msgstr "Não foi possível aceder ao website. Por favor tente novamente mais tarde ou contacte o autor do addon"
+
+msgctxt "#32002"
+msgid "Modo de depuração"
+msgstr ""
\ No newline at end of file
diff --git a/plugin.image.bancasapo/resources/lib/__init__.py b/plugin.image.bancasapo/resources/lib/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugin.image.bancasapo/resources/lib/kodilogging.py b/plugin.image.bancasapo/resources/lib/kodilogging.py
new file mode 100644
index 0000000000..06f00d3bf2
--- /dev/null
+++ b/plugin.image.bancasapo/resources/lib/kodilogging.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import unicode_literals
+from resources.lib.kodiutils import get_setting_as_bool
+
+import logging
+import xbmc
+import xbmcaddon
+
+
+class KodiLogHandler(logging.StreamHandler):
+
+ def __init__(self):
+ logging.StreamHandler.__init__(self)
+ addon_id = xbmcaddon.Addon().getAddonInfo('id')
+ prefix = "[%s] " % addon_id
+ formatter = logging.Formatter(prefix + '%(name)s: %(message)s')
+ self.setFormatter(formatter)
+
+ def emit(self, record):
+ levels = {
+ logging.CRITICAL: xbmc.LOGFATAL,
+ logging.ERROR: xbmc.LOGERROR,
+ logging.WARNING: xbmc.LOGWARNING,
+ logging.INFO: xbmc.LOGINFO,
+ logging.DEBUG: xbmc.LOGDEBUG,
+ logging.NOTSET: xbmc.LOGNONE,
+ }
+ if get_setting_as_bool('debug'):
+ try:
+ xbmc.log(self.format(record), levels[record.levelno])
+ except UnicodeEncodeError:
+ xbmc.log(self.format(record).encode(
+ 'utf-8', 'ignore'), levels[record.levelno])
+
+ def flush(self):
+ pass
+
+
+def config():
+ logger = logging.getLogger()
+ logger.addHandler(KodiLogHandler())
+ logger.setLevel(logging.DEBUG)
diff --git a/plugin.image.bancasapo/resources/lib/kodiutils.py b/plugin.image.bancasapo/resources/lib/kodiutils.py
new file mode 100644
index 0000000000..9b1ccb4891
--- /dev/null
+++ b/plugin.image.bancasapo/resources/lib/kodiutils.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+import xbmcaddon
+from sys import version_info
+
+PY3 = version_info > (3, 0)
+ADDON = xbmcaddon.Addon()
+
+
+def translate(string_id):
+ if PY3:
+ return ADDON.getLocalizedString(string_id)
+ return ADDON.getLocalizedString(string_id).encode('utf-8')
+
+
+def get_setting(setting):
+ if PY3:
+ return ADDON.getSetting(setting).strip()
+ return ADDON.getSetting(setting).strip().decode('utf-8')
+
+
+def get_setting_as_bool(setting):
+ return get_setting(setting).lower() == "true"
\ No newline at end of file
diff --git a/plugin.image.bancasapo/resources/lib/plugin.py b/plugin.image.bancasapo/resources/lib/plugin.py
new file mode 100644
index 0000000000..a5baaf833e
--- /dev/null
+++ b/plugin.image.bancasapo/resources/lib/plugin.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+import requests
+import re
+import os
+import sys
+import routing
+import logging
+import xbmcaddon
+from resources.lib import kodilogging
+from resources.lib.kodiutils import translate
+from xbmcgui import Dialog,ListItem
+from xbmcplugin import addDirectoryItem, endOfDirectory
+
+
+ADDON = xbmcaddon.Addon()
+logger = logging.getLogger(ADDON.getAddonInfo('id'))
+kodilogging.config()
+plugin = routing.Plugin()
+
+GLOBAL_FANART = os.path.join(
+ ADDON.getAddonInfo('path'),
+ 'resources',
+ 'images',
+ 'fanart.jpg'
+)
+GLOBAL_NEWSPAPPER_ICON = os.path.join(
+ ADDON.getAddonInfo('path'),
+ 'resources',
+ 'images',
+ 'newspapericon.png'
+)
+BANCA_SAPO_URL = "http://24.sapo.pt/jornais"
+
+
+@plugin.route('/')
+def categories():
+ try:
+ req = requests.get(BANCA_SAPO_URL).text
+ except:
+ raise_notification()
+
+ categories_regex = re.findall(r'(.+?)', req)
+ for uri, category in categories_regex:
+ liz = ListItem(cleanup(category))
+ liz.setArt({
+ "thumb":GLOBAL_NEWSPAPPER_ICON,
+ "fanart": GLOBAL_FANART
+ })
+ addDirectoryItem(plugin.handle,
+ plugin.url_for(show_category, uri),
+ liz,
+ True
+ )
+ endOfDirectory(plugin.handle)
+
+
+@plugin.route('/category/')
+def show_category(category_id):
+ try:
+ req = requests.get('{}/{}'.format(BANCA_SAPO_URL, category_id)).text
+ except:
+ raise_notification()
+
+ match = re.findall(r'data-data-extrameta="newspaper-id.+?data-original-src="(.+?)".+?data-share-url=.+?title="(.+?)".+?source data-srcset="(.+?)" srcset', req, re.DOTALL)
+ for cover, newspapper, thumb in match:
+ if thumb.startswith('//'): thumb = '{}{}'.format('http:', thumb)
+ newspapper = cleanup(newspapper)
+ liz = ListItem(newspapper)
+ liz.setArt({
+ "thumb":thumb,
+ "fanart": GLOBAL_FANART
+ })
+ addDirectoryItem(plugin.handle, cover, liz)
+ endOfDirectory(plugin.handle)
+
+
+def cleanup(field):
+ return field.replace("", "").replace("", "")
+
+
+def raise_notification():
+ Dialog().ok(translate(32000), translate(32001))
+ sys.exit(0)
+
+
+def run():
+ plugin.run()
diff --git a/plugin.image.bancasapo/resources/settings.xml b/plugin.image.bancasapo/resources/settings.xml
new file mode 100644
index 0000000000..96f7359af4
--- /dev/null
+++ b/plugin.image.bancasapo/resources/settings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/plugin.image.dumpert/LICENSE.txt b/plugin.image.dumpert/LICENSE.txt
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/plugin.image.dumpert/LICENSE.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/plugin.image.dumpert/__init__.py b/plugin.image.dumpert/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugin.image.dumpert/addon.py b/plugin.image.dumpert/addon.py
new file mode 100644
index 0000000000..4b98fe476d
--- /dev/null
+++ b/plugin.image.dumpert/addon.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+#
+# Imports
+#
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
+import sys
+import urllib.parse
+import xbmc
+
+from resources.lib.dumpert_const import ADDON, DATE, VERSION, SETTINGS
+
+# Parse parameters...
+if len(sys.argv[2]) == 0:
+ #
+ # Main menu
+ #
+ xbmc.log("[ADDON] %s, Python Version %s" % (ADDON, str(sys.version)), xbmc.LOGDEBUG)
+ xbmc.log("[ADDON] %s v%s (%s) is starting, ARGV = %s" % (ADDON, VERSION, DATE, repr(sys.argv)),
+ xbmc.LOGDEBUG)
+
+ if SETTINGS.getSetting('onlyshownewimagescategory') == 'true':
+ import resources.lib.dumpert_json as plugin
+ else:
+ import resources.lib.dumpert_main as plugin
+else:
+ action = urllib.parse.parse_qs(urllib.parse.urlparse(sys.argv[2]).query)['action'][0]
+ #
+ # Search
+ #
+ if action == 'search':
+ import resources.lib.dumpert_search as plugin
+ #
+ # Timemachine
+ #
+ elif action == 'timemachine':
+ import resources.lib.dumpert_timemachine as plugin
+ #
+ # JSON
+ #
+ if action == 'json':
+ import resources.lib.dumpert_json as plugin
+
+plugin.Main()
diff --git a/plugin.image.dumpert/addon.xml b/plugin.image.dumpert/addon.xml
new file mode 100644
index 0000000000..f0740d1246
--- /dev/null
+++ b/plugin.image.dumpert/addon.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+ image
+
+
+ Watch funny pictures from Dumpert.nl (dutch)
+ Watch funny pictures from Dumpert.nl (dutch)
+ For bugs, requests or general questions visit the Dumpert.nl thread on the Kodi forum.
+ Bekijk grappige plaatjes van Dumpert.nl (dutch)
+ Bekijk grappige plaatjes van Dumpert.nl (dutch)
+ Bugs of andere feedback op deze plugin kan geplaatst worden in de Dumpert.nl thread op het Kodi forum.
+ nl
+ all
+ GNU General Public License v3.0 only
+ https://forum.kodi.tv/showthread.php?tid=315439
+ https://dumpert.nl
+ https://github.com/skipmodea1/plugin.image.dumpert
+ v1.0.4 (2019-09-21)
+ - MAJOR changes in the addon due to redesigned website
+ - moved to Krypton
+ - updated logos
+
+
+ resources/icon.png
+ resources/fanart.jpg
+
+
+
\ No newline at end of file
diff --git a/plugin.image.dumpert/resources/__init__.py b/plugin.image.dumpert/resources/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugin.image.dumpert/resources/fanart-blur.jpg b/plugin.image.dumpert/resources/fanart-blur.jpg
new file mode 100644
index 0000000000..c6fa0a3bce
Binary files /dev/null and b/plugin.image.dumpert/resources/fanart-blur.jpg differ
diff --git a/plugin.image.dumpert/resources/fanart.jpg b/plugin.image.dumpert/resources/fanart.jpg
new file mode 100644
index 0000000000..fa2c3350e3
Binary files /dev/null and b/plugin.image.dumpert/resources/fanart.jpg differ
diff --git a/plugin.image.dumpert/resources/icon.png b/plugin.image.dumpert/resources/icon.png
new file mode 100644
index 0000000000..fbe7a92763
Binary files /dev/null and b/plugin.image.dumpert/resources/icon.png differ
diff --git a/plugin.image.dumpert/resources/language/resource.language.en_gb/strings.po b/plugin.image.dumpert/resources/language/resource.language.en_gb/strings.po
new file mode 100644
index 0000000000..c4e10fa16a
--- /dev/null
+++ b/plugin.image.dumpert/resources/language/resource.language.en_gb/strings.po
@@ -0,0 +1,164 @@
+# XBMC Media Center language file
+# Addon Name: Dumpert
+# Addon id: plugin.image.dumpert
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC-Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: 2016-06-04 07:34+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+msgctxt "Addon Summary"
+msgid "Watch funny images from Dumpert.nl (dutch)"
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Watch funny images from Dumpert.nl (dutch)"
+msgstr ""
+
+msgctxt "Addon Disclaimer"
+msgid "For bugs, requests or general questions visit the Dumpert.nl thread on the XBMC forum."
+msgstr ""
+
+msgctxt "#30000"
+msgid "Top"
+msgstr ""
+
+msgctxt "#30001"
+msgid "New"
+msgstr ""
+
+msgctxt "#30004"
+msgid "Search"
+msgstr ""
+
+msgctxt "#30005"
+msgid "Top Time machine (daily/weekly/montly Toppers)"
+msgstr ""
+
+msgctxt "#30007"
+msgid "Dumpert TV"
+msgstr ""
+
+msgctxt "#30008"
+msgid "Daily Top"
+msgstr ""
+
+msgctxt "#30009"
+msgid "Weekly Top"
+msgstr ""
+
+msgctxt "#30010"
+msgid "Monthly Top"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Version"
+msgstr ""
+
+msgctxt "#30101"
+msgid "Author"
+msgstr ""
+
+msgctxt "#30102"
+msgid "Website"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Video player"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Auto"
+msgstr ""
+
+msgctxt "#30202"
+msgid "DVDPlayer"
+msgstr ""
+
+msgctxt "#30203"
+msgid "MPlayer"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Maximum Image quality"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Low"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Medium"
+msgstr ""
+
+msgctxt "#30303"
+msgid "High"
+msgstr ""
+
+msgctxt "#30501"
+msgid "%s to %s"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Page %u"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Next page"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Getting image location..."
+msgstr ""
+
+msgctxt "#30505"
+msgid "No image found."
+msgstr ""
+
+msgctxt "#30506"
+msgid "Error showing image file."
+msgstr ""
+
+msgctxt "#30507"
+msgid "Error getting page: %s"
+msgstr ""
+
+msgctxt "#30508"
+msgid "What are you looking for?"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Pick a date"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Day top for day %s"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Week top for week %s %s"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Month top for month %s"
+msgstr ""
+
+msgctxt "#30600"
+msgid "Show NSFW content"
+msgstr ""
+
+msgctxt "#30610"
+msgid "Only show New image category"
+msgstr ""
+
+msgctxt "#30620"
+msgid "Refresh page"
+msgstr ""
\ No newline at end of file
diff --git a/plugin.image.dumpert/resources/language/resource.language.nl_nl/strings.po b/plugin.image.dumpert/resources/language/resource.language.nl_nl/strings.po
new file mode 100644
index 0000000000..9a9208e165
--- /dev/null
+++ b/plugin.image.dumpert/resources/language/resource.language.nl_nl/strings.po
@@ -0,0 +1,168 @@
+# XBMC Media Center language file
+# Addon Name: Dumpert
+# Addon id: plugin.image.dumpert
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC-Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: 2016-06-04 07:34+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: nl\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+msgctxt "Addon Summary"
+msgid "Watch funny images from Dumpert.nl (dutch)"
+msgstr "Bekijk grappige plaatjes van Dumpert.nl (dutch)"
+
+msgctxt "Addon Description"
+msgid "Watch funny photos from Dumpert.nl (dutch)"
+msgstr "Bekijk grappige plaatjes van Dumpert.nl (dutch)"
+
+msgctxt "Addon Disclaimer"
+msgid "For bugs, requests or general questions visit the Dumpert.nl thread on the Kodi forum."
+msgstr "Bugs of andere feedback op deze plugin kan geplaatst worden in de Dumpert.nl thread op het Kodi forum."
+
+msgctxt "#30000"
+msgid "Top"
+msgstr "Toppers"
+
+msgctxt "#30001"
+msgid "New"
+msgstr "Images"
+
+msgctxt "#30004"
+msgid "Search"
+msgstr "Zoeken"
+
+msgctxt "#30005"
+msgid "Top Time machine (daily/weekly/montly Toppers)"
+msgstr "Toppers Tijdmachine: Toppers per dag/week/maand"
+
+msgctxt "#30007"
+msgid "Dumpert TV"
+msgstr "Dumpert TV"
+
+msgctxt "#30008"
+msgid "Daily Top"
+msgstr "Dagelijkse Toppers"
+
+msgctxt "#30009"
+msgid "Weekly Top"
+msgstr "Wekelijkse Toppers"
+
+msgctxt "#30010"
+msgid "Monthly Top"
+msgstr "Maandelijkse Toppers"
+
+msgctxt "#30100"
+msgid "Version"
+msgstr "Versie"
+
+msgctxt "#30101"
+msgid "Author"
+msgstr "Auteur"
+
+msgctxt "#30102"
+msgid "Website"
+msgstr "Website"
+
+msgctxt "#30200"
+msgid "Video player"
+msgstr "Video player"
+
+msgctxt "#30201"
+msgid "Auto"
+msgstr "Auto"
+
+msgctxt "#30202"
+msgid "DVDPlayer"
+msgstr "DVDPlayer"
+
+msgctxt "#30203"
+msgid "MPlayer"
+msgstr "MPlayer"
+
+msgctxt "#30300"
+msgid "Image quality"
+msgstr "Plaatjes kwaliteit"
+
+msgctxt "#30301"
+msgid "Low"
+msgstr "Laag"
+
+msgctxt "#30302"
+msgid "Medium"
+msgstr "Midden"
+
+msgctxt "#30303"
+msgid "High"
+msgstr "Hoog"
+
+msgctxt "#30400"
+msgid "Debug"
+msgstr "Debug"
+
+msgctxt "#30501"
+msgid "%s to %s"
+msgstr "%s tot %s"
+
+msgctxt "#30502"
+msgid "Page %u"
+msgstr "Pagina %u"
+
+msgctxt "#30503"
+msgid "Next page"
+msgstr "Volgende pagina"
+
+msgctxt "#30504"
+msgid "Getting image location..."
+msgstr "Plaatje wordt opgehaald..."
+
+msgctxt "#30505"
+msgid "No image found."
+msgstr "Plaatje niet gevonden."
+
+msgctxt "#30506"
+msgid "Error showing image file."
+msgstr "Fout bij het laten zien van het plaatje."
+
+msgctxt "#30507"
+msgid "Error getting page: %s"
+msgstr "Fout bij ophalen van pagina %s"
+
+msgctxt "#30508"
+msgid "What are you looking for?"
+msgstr "Wat zoek je?"
+
+msgctxt "#30509"
+msgid "Pick a date"
+msgstr "Kies een datum"
+
+msgctxt "#30510"
+msgid "Day top for day %s"
+msgstr "Dagtop voor dag %s"
+
+msgctxt "#30511"
+msgid "Week top for week %s %s"
+msgstr "Weektop voor week %s %s"
+
+msgctxt "#30512"
+msgid "Month top for month %s"
+msgstr "Maandtop voor maand %s"
+
+msgctxt "#30600"
+msgid "Show NSFW content"
+msgstr "Toon NSFW filmpjes"
+
+msgctxt "#30610"
+msgid "Only show New image category"
+msgstr "Toon alleen Nieuw plaatje categorie"
+
+msgctxt "#30620"
+msgid "Refresh page"
+msgstr "Ververs pagina"
\ No newline at end of file
diff --git a/plugin.image.dumpert/resources/lib/__init__.py b/plugin.image.dumpert/resources/lib/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugin.image.dumpert/resources/lib/dumpert_const.py b/plugin.image.dumpert/resources/lib/dumpert_const.py
new file mode 100644
index 0000000000..255f8aa6f4
--- /dev/null
+++ b/plugin.image.dumpert/resources/lib/dumpert_const.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+import sys
+import os
+import xbmc
+import xbmcaddon
+from bs4 import BeautifulSoup
+
+#
+# Constants
+#
+ADDON = "plugin.image.dumpert"
+SETTINGS = xbmcaddon.Addon(id=ADDON)
+LANGUAGE = SETTINGS.getLocalizedString
+IMAGES_PATH = os.path.join(xbmcaddon.Addon(id=ADDON).getAddonInfo('path'), 'resources')
+LATEST_URL = "https://api-live.dumpert.nl/mobile_api/json/foto/latest/0/"
+TOPPERS_URL = "https://api-live.dumpert.nl/mobile_api/json/foto/toppers/0/"
+SEARCH_URL = "https://api-live.dumpert.nl/mobile_api/json/search/"
+DAY_TOPPERS_URL = "https://api-live.dumpert.nl/mobile_api/json/foto/top5/dag/"
+WEEK_TOPPERS_URL = "https://api-live.dumpert.nl/mobile_api/json/foto/top5/week/"
+MONTH_TOPPERS_URL = "https://api-live.dumpert.nl/mobile_api/json/foto/top5/maand/"
+SFW_HEADERS = {'X-Dumpert-NSFW': '0'}
+NSFW_HEADERS = {'X-Dumpert-NSFW': '1'}
+DAY = "day"
+WEEK = "week"
+MONTH = "month"
+DATE = "2019-09-21"
+VERSION = "1.0.4"
+
+
+if sys.version_info[0] > 2:
+ unicode = str
+
+
+def convertToUnicodeString(s, encoding='utf-8'):
+ """Safe decode byte strings to Unicode"""
+ if isinstance(s, bytes): # This works in Python 2.7 and 3+
+ s = s.decode(encoding)
+ return s
+
+
+def convertToByteString(s, encoding='utf-8'):
+ """Safe encode Unicode strings to bytes"""
+ if isinstance(s, unicode):
+ s = s.encode(encoding)
+ return s
+
+
+def log(name_object, object):
+ try:
+ # Let's try and remove any non-ascii stuff first
+ object = object.encode('ascii', 'ignore')
+ except:
+ pass
+
+ try:
+ xbmc.log("[ADDON] %s v%s (%s) debug mode, %s = %s" % (
+ ADDON, VERSION, DATE, name_object, convertToUnicodeString(object)), xbmc.LOGDEBUG)
+ except:
+ xbmc.log("[ADDON] %s v%s (%s) debug mode, %s = %s" % (
+ ADDON, VERSION, DATE, name_object,
+ "Unable to log the object due to an error while converting it to an unicode string"), xbmc.LOGDEBUG)
+
+
+def getSoup(html,default_parser="html5lib"):
+ soup = BeautifulSoup(html, default_parser)
+ return soup
\ No newline at end of file
diff --git a/plugin.image.dumpert/resources/lib/dumpert_json.py b/plugin.image.dumpert/resources/lib/dumpert_json.py
new file mode 100644
index 0000000000..5f00a91479
--- /dev/null
+++ b/plugin.image.dumpert/resources/lib/dumpert_json.py
@@ -0,0 +1,308 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+#
+# Imports
+#
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
+from builtins import object
+from datetime import datetime, timedelta
+import os
+import requests
+import sys
+import urllib.request, urllib.parse, urllib.error
+import xbmcgui
+import xbmcplugin
+import json
+
+from resources.lib.dumpert_const import LANGUAGE, IMAGES_PATH, SETTINGS, convertToUnicodeString, log, SFW_HEADERS, NSFW_HEADERS, \
+ DAY, WEEK, MONTH, DAY_TOPPERS_URL, WEEK_TOPPERS_URL, MONTH_TOPPERS_URL, LATEST_URL
+
+#
+# Main class
+#
+class Main(object):
+ #
+ # Init
+ #
+ def __init__(self):
+ # Get the command line arguments
+ # Get the plugin url in plugin:// notation
+ self.plugin_url = sys.argv[0]
+ # Get the plugin handle as an integer number
+ self.plugin_handle = int(sys.argv[1])
+
+ # log("ARGV", repr(sys.argv))
+
+ # Parse parameters
+ try:
+ self.plugin_category = urllib.parse.parse_qs(urllib.parse.urlparse(sys.argv[2]).query)['plugin_category'][0]
+ self.next_page_possible = urllib.parse.parse_qs(urllib.parse.urlparse(sys.argv[2]).query)['next_page_possible'][0]
+ except KeyError:
+ self.plugin_category = LANGUAGE(30001)
+ self.next_page_possible = "True"
+ try:
+ self.period = urllib.parse.parse_qs(urllib.parse.urlparse(sys.argv[2]).query)['period'][0]
+ except KeyError:
+ self.period = ""
+ try:
+ self.days_deducted_from_today = urllib.parse.parse_qs(urllib.parse.urlparse(sys.argv[2]).query)['days_deducted_from_today'][0]
+ except KeyError:
+ self.days_deducted_from_today = "0"
+ try:
+ self.foto_list_page_url = urllib.parse.parse_qs(urllib.parse.urlparse(sys.argv[2]).query)['url'][0]
+ except KeyError:
+ # If the only-show-new-images-category switch is turned on, this will be empty the first time
+ if self.period == "":
+ self.foto_list_page_url = LATEST_URL
+ # If period is filled in we will construct the url
+ else:
+ self.foto_list_page_url = ""
+
+ log("self.foto_list_page_url", self.foto_list_page_url)
+
+ self.next_url = ""
+
+ # Constuct the next url based on days_deducted_from_today
+ if self.period == DAY or self.period == WEEK or self.period == MONTH:
+ # For some strange reason converting a string to a datetime object does NOT work here :(
+ # Thus we have to do this silly stuff to be able to determine the next_url
+ current_url_datetime_object = datetime.now()
+ next_url_datetime_object = datetime.now()
+
+ # log("self.days_deducted_from_today", self.days_deducted_from_today)
+
+ self.days_deducted_from_today = int(self.days_deducted_from_today)
+ if self.period == DAY:
+ # If we don't have a current foto list page url, lets construct it
+ if self.foto_list_page_url == "":
+ current_url_datetime_object = current_url_datetime_object - timedelta(days=self.days_deducted_from_today)
+ # https://api-live.dumpert.nl/mobile_api/json/foto/top5/dag/2019-09-19/
+ self.foto_list_page_url = DAY_TOPPERS_URL + current_url_datetime_object.strftime('%Y-%m-%d')
+
+ # log("Generated self.foto_list_page_url day", self.foto_list_page_url)
+
+ self.days_deducted_from_today = self.days_deducted_from_today + 1
+ # Let's deduct all the cumulated days
+ next_url_datetime_object = next_url_datetime_object - timedelta(days=self.days_deducted_from_today)
+ self.days_deducted_from_today = str(self.days_deducted_from_today)
+ # https://api-live.dumpert.nl/mobile_api/json/foto/top5/dag/2019-09-18/
+ # This should be an url of the day before the date of the current foto url
+ self.next_url = DAY_TOPPERS_URL + next_url_datetime_object.strftime('%Y-%m-%d')
+
+ elif self.period == WEEK:
+ # If we don't have a current foto list page url, lets construct it
+ if self.foto_list_page_url == "":
+ current_url_datetime_object = current_url_datetime_object - timedelta(days=self.days_deducted_from_today)
+ # For some reason date.strftime('%Y%W') will now contain the weeknumber that is 1 below the weeknumber should be for the site
+ # Let's add a week to fix that
+ current_url_datetime_object = current_url_datetime_object + timedelta(days=7)
+ # https://api-live.dumpert.nl/mobile_api/json/foto/top5/week/201938/
+ self.foto_list_page_url = WEEK_TOPPERS_URL + current_url_datetime_object.strftime('%Y%W')
+
+ # log("Generated self.foto_list_page_url week", self.foto_list_page_url)
+
+ # For some reason date.strftime('%Y%W') will now contain the weeknumber that is 1 below the weeknumber should be for the site
+ # Let's add a week to fix that
+ next_url_datetime_object = next_url_datetime_object + timedelta(days=7)
+ # Let's deduct all the cumulated days
+ self.days_deducted_from_today = self.days_deducted_from_today + 7
+ next_url_datetime_object = next_url_datetime_object - timedelta(days=self.days_deducted_from_today)
+
+ # Let's skip week "00"
+ if next_url_datetime_object.strftime('%W') == "00":
+
+ # log("skipping week 00", "skipping week 00")
+
+ self.days_deducted_from_today = self.days_deducted_from_today + 7
+ next_url_datetime_object = next_url_datetime_object - timedelta(days=7)
+
+ self.days_deducted_from_today = str(self.days_deducted_from_today)
+ # https://api-live.dumpert.nl/mobile_api/json/foto/top5/week/201937/
+ self.next_url = WEEK_TOPPERS_URL + next_url_datetime_object.strftime('%Y%W')
+
+ elif self.period == MONTH:
+ # If we don't have a current foto list page url, lets construct it
+ if self.foto_list_page_url == "":
+ current_url_datetime_object = current_url_datetime_object - timedelta(days=self.days_deducted_from_today)
+ # https://api-live.dumpert.nl/mobile_api/json/foto/top5/maand/201909/
+ self.foto_list_page_url = MONTH_TOPPERS_URL + current_url_datetime_object.strftime('%Y%m')
+
+ # log("Generated self.foto_list_page_url month", self.foto_list_page_url)
+
+ current_url_datetime_object = current_url_datetime_object - timedelta(days=self.days_deducted_from_today)
+
+ self.days_deducted_from_today = self.days_deducted_from_today + 27
+ # Let's deduct all the cumulated days
+ next_url_datetime_object = next_url_datetime_object - timedelta(days=self.days_deducted_from_today)
+
+ # If the year/month didn't change, up the days deducted some more
+ if current_url_datetime_object.strftime('%Y%m') == next_url_datetime_object.strftime('%Y%m'):
+
+ # log("forcing next month", "forcing next month")
+
+ self.days_deducted_from_today = self.days_deducted_from_today + 5
+ # Let's deduct 5 more days
+ next_url_datetime_object = next_url_datetime_object - timedelta(days=5)
+
+ self.days_deducted_from_today = str(self.days_deducted_from_today)
+ # https://api-live.dumpert.nl/mobile_api/json/foto/top5/maand/201908/
+ self.next_url = MONTH_TOPPERS_URL + next_url_datetime_object.strftime('%Y%m')
+
+ log("self.next_url", self.next_url)
+
+ # "https://api-live.dumpert.nl/mobile_api/json/foto/latest/0/"
+ else:
+ # Determine current page number and base_url
+ # find last slash
+ pos_of_last_slash = self.foto_list_page_url.rfind('/')
+ # remove last slash
+ self.foto_list_page_url = self.foto_list_page_url[0: pos_of_last_slash]
+ pos_of_last_slash = self.foto_list_page_url.rfind('/')
+ self.base_url = self.foto_list_page_url[0: pos_of_last_slash + 1]
+ self.current_page = self.foto_list_page_url[pos_of_last_slash + 1:]
+ self.current_page = int(self.current_page)
+ # add last slash
+ self.foto_list_page_url = str(self.foto_list_page_url) + "/"
+
+ log("self.foto_list_page_url", self.foto_list_page_url)
+
+ #
+ # Get the fotos...
+ #
+ self.getFotos()
+
+ #
+ # Get fotos...
+ #
+ def getFotos(self):
+ #
+ # Init
+ #
+ listing = []
+
+ if SETTINGS.getSetting('nsfw') == 'true':
+ response = requests.get(self.foto_list_page_url, headers=NSFW_HEADERS)
+ else:
+ response = requests.get(self.foto_list_page_url, headers=SFW_HEADERS)
+
+ # response.status
+ json_source = response.text
+ json_source = convertToUnicodeString(json_source)
+ data = json.loads(json_source)
+ if not data['success']:
+ xbmcplugin.endOfDirectory(self.plugin_handle)
+ return
+
+ # max_foto_quality = SETTINGS.getSetting('foto')
+
+ total_items = len(data['items'])
+
+ # log("total_items", total_items)
+
+ for item in data['items']:
+ title = item['title']
+ title = convertToUnicodeString(title)
+ description = item['description']
+ description = convertToUnicodeString(description)
+ thumbnail_url = item['stills']['still-large']
+ for i in item['media']:
+ duration = i.get('duration',False)
+
+ # {"gentime":1569050758,"items":[{"date":"2019-09-21T07:22:27+02:00","description":"Politie deelt vijftien prenten uit aan trouwturken","id":"7759341_b7ad3577","media":[{"description":"","duration":0,"mediatype":"FOTO","variants":[{"uri":"https://media.dumpert.nl/foto/b7ad3577_20190920_230634.jpg.jpg","version":"foto"}]}],"nopreroll":false,"nsfw":false,"stats":{"kudos_today":1592,"kudos_total":1592,"views_today":32673,"views_total":32673},"still":"https://media.dumpert.nl/stills/7759341_b7ad3577.jpg","stills":{"still":"https://media.dumpert.nl/stills/7759341_b7ad3577.jpg","still-large":"https://media.dumpert.nl/stills/large/7759341_b7ad3577.jpg","still-medium":"https://media.dumpert.nl/stills/medium/7759341_b7ad3577.jpg","thumb":"https://media.dumpert.nl/sq_thumbs/7759341_b7ad3577.jpg","thumb-medium":"https://media.dumpert.nl/sq_thumbs/medium/7759341_b7ad3577.jpg"},"tags":"auto politie bekeuring rotterdam trouwen stoet asociaal","thumbnail":"https://media.dumpert.nl/sq_thumbs/7759341_b7ad3577.jpg","title":"Trouwstoet mag dokken","upload_id":""},{"date":"2019-09-21T07:21:31+02:00","description":"Gooi jij hem ff vol?","id":"7759365_e4593f40","media":[{"description":"","duration":0,"med
+
+ file = ""
+
+ # Only process foto items
+ try:
+ foto_type = item['media'][0]['mediatype']
+ if foto_type == 'FOTO':
+ process_item = True
+ else:
+ process_item = False
+ except IndexError:
+ process_item = False
+
+ if process_item:
+ file = self.find_image(file, item)
+
+ # log("title", title)
+
+ log("json file", file)
+
+ list_item = xbmcgui.ListItem(label=title)
+ list_item.setInfo(
+ type='pictures',
+ infoLabels={
+ "title": title,
+ "picturepath": file,
+ "exif:path": file
+ })
+ list_item.setArt({'thumb': thumbnail_url, 'icon': thumbnail_url,
+ 'fanart': os.path.join(IMAGES_PATH, 'fanart-blur.jpg')})
+ list_item.setProperty('IsPlayable', 'true')
+
+ is_folder = False
+ # Add refresh option to context menu
+ list_item.addContextMenuItems([(LANGUAGE(30620), 'Container.Refresh')])
+ # Add the item
+ xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
+ url=file,
+ listitem=list_item,
+ isFolder=is_folder,
+ totalItems=total_items)
+
+ # Next page entry
+ if self.next_page_possible == 'True':
+ thumbnail_url = os.path.join(IMAGES_PATH, 'next-page.png')
+ list_item = xbmcgui.ListItem(LANGUAGE(30503))
+ list_item.setArt({'thumb': thumbnail_url, 'icon': thumbnail_url,
+ 'fanart': os.path.join(IMAGES_PATH, 'fanart-blur.jpg')})
+ list_item.setProperty('IsPlayable', 'false')
+ # If the next url is still empty, we have to make one
+ # "https://api-live.dumpert.nl/mobile_api/json/foto/latest/1/"
+ if self.next_url == "":
+ next_page = self.current_page + 1
+ self.next_url = str(self.base_url) + str(next_page) + '/'
+ parameters = {"action": "json",
+ "plugin_category": self.plugin_category,
+ "url": self.next_url,
+ "period": self.period,
+ "next_page_possible": self.next_page_possible,
+ "days_deducted_from_today": self.days_deducted_from_today}
+ url = self.plugin_url + '?' + urllib.parse.urlencode(parameters)
+ is_folder = True
+ # Add refresh option to context menu
+ list_item.addContextMenuItems([(LANGUAGE(30620), 'Container.Refresh')])
+ # Add our item to the listing as a 3-element tuple.
+ listing.append((url, list_item, is_folder))
+
+ # Add our listing to Kodi.
+ # Large lists and/or slower systems benefit from adding all items at once via addDirectoryItems
+ # instead of adding one by one via addDirectoryItem.
+ xbmcplugin.addDirectoryItems(self.plugin_handle, listing, len(listing))
+ # Disable sorting
+ xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_NONE)
+ # Finish creating a virtual folder.
+ xbmcplugin.endOfDirectory(self.plugin_handle)
+
+
+ def find_image(self, file, item):
+ if file == "":
+ try:
+ file = item['media'][0]['variants'][0]['uri']
+ except IndexError:
+ pass
+ if file == "":
+ try:
+ file = item['media'][0]['variants'][1]['uri']
+ except IndexError:
+ pass
+ if file == "":
+ try:
+ file = item['media'][0]['variants'][2]['uri']
+ except IndexError:
+ pass
+ return file
\ No newline at end of file
diff --git a/plugin.image.dumpert/resources/lib/dumpert_main.py b/plugin.image.dumpert/resources/lib/dumpert_main.py
new file mode 100644
index 0000000000..6171249c94
--- /dev/null
+++ b/plugin.image.dumpert/resources/lib/dumpert_main.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+#
+# Imports
+#
+from future import standard_library
+
+standard_library.install_aliases()
+from builtins import object
+import os
+import sys
+import urllib.request, urllib.parse, urllib.error
+import xbmcgui
+import xbmcplugin
+
+from resources.lib.dumpert_const import LANGUAGE, IMAGES_PATH, DAY, WEEK, MONTH, LATEST_URL, TOPPERS_URL, SEARCH_URL
+
+#
+# Main class
+#
+class Main(object):
+ def __init__(self):
+ # Get the command line arguments
+ # Get the plugin url in plugin:// notation
+ self.plugin_url = sys.argv[0]
+ # Get the plugin handle as an integer number
+ self.plugin_handle = int(sys.argv[1])
+
+ #
+ # Nieuw
+ #
+ title = LANGUAGE(30001)
+ parameters = {"action": "json",
+ "plugin_category": title,
+ "url": LATEST_URL,
+ "next_page_possible": "True"}
+ self.add_dir(parameters, title)
+
+ #
+ # Toppers
+ #
+ title = LANGUAGE(30000)
+ parameters = {"action": "json",
+ "plugin_category": title,
+ "url": TOPPERS_URL,
+ "next_page_possible": "True"}
+ self.add_dir(parameters, title)
+
+
+ #
+ # Dag Toppers
+ #
+ title = LANGUAGE(30008)
+ parameters = {"action": "json",
+ "plugin_category": title,
+ "period": DAY,
+ "days_deducted_from_today": "0",
+ "next_page_possible": "True"}
+ self.add_dir(parameters, title)
+
+ #
+ # Week Toppers
+ #
+ title = LANGUAGE(30009)
+ parameters = {"action": "json",
+ "plugin_category": title,
+ "period": WEEK,
+ "days_deducted_from_today": "0",
+ "next_page_possible": "True"}
+ self.add_dir(parameters, title)
+
+ #
+ # Maand Toppers
+ #
+ title = LANGUAGE(30010)
+ parameters = {"action": "json",
+ "plugin_category": title,
+ "period": MONTH,
+ "days_deducted_from_today": "0",
+ "next_page_possible": "True"}
+ self.add_dir(parameters, title)
+
+ #
+ # Timemachine: Toppers for a given date
+ #
+ title = LANGUAGE(30005)
+ parameters = {"action": "timemachine",
+ "plugin_category": title,
+ "next_page_possible": "True"}
+ self.add_dir(parameters, title)
+
+ #
+ # Search
+ #
+ title = LANGUAGE(30004)
+ parameters = {"action": "search",
+ "plugin_category": title,
+ "url": SEARCH_URL,
+ "next_page_possible": "True"}
+ self.add_dir(parameters, title)
+
+ # Disable sorting
+ xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_NONE)
+ # Finish creating a virtual folder.
+ xbmcplugin.endOfDirectory(self.plugin_handle)
+
+ def add_dir(self, parameters, title):
+ url = self.plugin_url + '?' + urllib.parse.urlencode(parameters)
+ list_item = xbmcgui.ListItem(title)
+ thumbnail_url = 'DefaultFolder.png'
+ list_item.setArt({'thumb': thumbnail_url, 'icon': thumbnail_url,
+ 'fanart': os.path.join(IMAGES_PATH, 'fanart-blur.jpg')})
+ is_folder = True
+ list_item.setProperty('IsPlayable', 'false')
+ xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=list_item, isFolder=is_folder)
diff --git a/plugin.image.dumpert/resources/lib/dumpert_search.py b/plugin.image.dumpert/resources/lib/dumpert_search.py
new file mode 100644
index 0000000000..ef33603bb6
--- /dev/null
+++ b/plugin.image.dumpert/resources/lib/dumpert_search.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+#
+# Imports
+#
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
+from builtins import object
+import sys
+import xbmc
+
+from resources.lib.dumpert_const import LANGUAGE, log, convertToUnicodeString
+
+
+#
+# Main class
+#
+class Main(object):
+ #
+ # Init
+ #
+ def __init__(self):
+ # Get the command line arguments
+ # Get the plugin url in plugin:// notation
+ self.plugin_url = sys.argv[0]
+ # Get the plugin handle as an integer number
+ self.plugin_handle = int(sys.argv[1])
+
+ log("ARGV", repr(sys.argv))
+
+ # Get search term from user
+ keyboard = xbmc.Keyboard('', LANGUAGE(30508))
+ keyboard.doModal()
+
+ if keyboard.isConfirmed():
+ search_term = keyboard.getText()
+ # If the user has entered nothing, we stop
+ if search_term == "":
+ sys.exit(0)
+ else:
+ # If the user cancels the input box, we stop
+ sys.exit(0)
+
+ sys.argv[2] = convertToUnicodeString(sys.argv[2])
+
+ # Converting URL argument to proper query string like 'https://api-live.dumpert.nl/mobile_api/json/search/fiets/0/'
+ sys.argv[2] = sys.argv[2] + search_term + "/0/"
+
+ log("sys.argv[2]", sys.argv[2])
+
+ import resources.lib.dumpert_json as plugin
+
+ plugin.Main()
diff --git a/plugin.image.dumpert/resources/lib/dumpert_timemachine.py b/plugin.image.dumpert/resources/lib/dumpert_timemachine.py
new file mode 100644
index 0000000000..366677d4d4
--- /dev/null
+++ b/plugin.image.dumpert/resources/lib/dumpert_timemachine.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+#
+# Imports
+#
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
+from builtins import object
+import os
+import sys
+import urllib.request, urllib.parse, urllib.error
+import xbmcgui
+import xbmcplugin
+from datetime import datetime, timedelta
+import time
+
+from resources.lib.dumpert_const import LANGUAGE, IMAGES_PATH, log, DAY, WEEK, MONTH, DAY_TOPPERS_URL, WEEK_TOPPERS_URL, \
+ MONTH_TOPPERS_URL
+
+
+#
+# Main class
+#
+class Main(object):
+ #
+ # Init
+ #
+ def __init__(self):
+ # Get the command line arguments
+ # Get the plugin url in plugin:// notation
+ self.plugin_url = sys.argv[0]
+ # Get the plugin handle as an integer number
+ self.plugin_handle = int(sys.argv[1])
+
+ log("ARGV", repr(sys.argv))
+
+ self.plugin_category = urllib.parse.parse_qs(urllib.parse.urlparse(sys.argv[2]).query)['plugin_category'][0]
+ self.next_page_possible = urllib.parse.parse_qs(urllib.parse.urlparse(sys.argv[2]).query)['next_page_possible'][0]
+
+ # Ask the user for a date
+ date = xbmcgui.Dialog().numeric(1, LANGUAGE(30509))
+ if date is None:
+ date = datetime.now()
+ else:
+ date = date.replace(' ', '')
+ try:
+ try:
+ date = datetime.strptime(date, '%d/%m/%Y')
+ except TypeError:
+ date = datetime(*(time.strptime(date, '%d/%m/%Y')[0:6]))
+ except ValueError:
+ date = datetime.now()
+
+ # If the date is in the future or too old, set it to the current date
+ if date > datetime.now() or date < datetime(2006, 1, 1):
+ date = datetime.now()
+
+ date_now = datetime.now()
+
+ # days
+ # https://api-live.dumpert.nl/mobile_api/json/foto/top5/dag/2019-09-18/
+ daily_toppers_url = DAY_TOPPERS_URL + date.strftime('%Y-%m-%d')
+
+ delta = date_now - date
+ days_deducted_from_today = delta.days
+
+ log("days_deducted_from_today for days", str(days_deducted_from_today))
+
+ title = LANGUAGE(30510) % (date.strftime('%d %b %Y'))
+ parameters = {"action": "json",
+ "plugin_category": self.plugin_category,
+ "url": daily_toppers_url,
+ "period": DAY,
+ "next_page_possible": self.next_page_possible,
+ "days_deducted_from_today": days_deducted_from_today}
+ self.add_folder(parameters, title)
+
+ # weeks.
+ # Here we do something a bit odd.
+ # For some reason date.strftime('%Y%W') will now contain the weeknumber of last (!) week and not this week
+ # Let's add a week to fix that for the url and the title
+ date_plus_a_week = date + timedelta(days=7)
+ # https://api-live.dumpert.nl/mobile_api/json/foto/top5/week/201938/
+ weekly_toppers_url = WEEK_TOPPERS_URL + date_plus_a_week.strftime('%Y%W')
+ title = LANGUAGE(30511) % (date_plus_a_week.strftime('%W'), date_plus_a_week.strftime('%Y'))
+
+ delta = date_now - date
+ days_deducted_from_today = delta.days
+
+ log("days_deducted_from_today for months", str(days_deducted_from_today))
+
+ parameters = {"action": "json",
+ "plugin_category": self.plugin_category,
+ "url": weekly_toppers_url,
+ "period": WEEK,
+ "next_page_possible": self.next_page_possible,
+ "days_deducted_from_today": days_deducted_from_today}
+ self.add_folder(parameters, title)
+
+ # months
+ # https://api-live.dumpert.nl/mobile_api/json/foto/top5/maand/201909/
+ monthly_toppers_url = MONTH_TOPPERS_URL + date.strftime('%Y%m')
+
+ delta = date_now - date
+ days_deducted_from_today = delta.days
+
+ log("days_deducted_from_today for weeks", str(days_deducted_from_today))
+
+ title = LANGUAGE(30512) % (date.strftime('%b %Y'))
+ parameters = {"action": "json",
+ "plugin_category": self.plugin_category,
+ "url": monthly_toppers_url,
+ "period": MONTH,
+ "next_page_possible": self.next_page_possible,
+ "days_deducted_from_today": days_deducted_from_today}
+ self.add_folder(parameters, title)
+
+ # Disable sorting
+ xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_NONE)
+ # Finish creating a virtual folder.
+ xbmcplugin.endOfDirectory(self.plugin_handle)
+
+ def add_folder(self, parameters, title):
+ url = self.plugin_url + '?' + urllib.parse.urlencode(parameters)
+ list_item = xbmcgui.ListItem(title)
+ thumbnail_url = 'DefaultFolder.png'
+ list_item.setArt({'thumb': thumbnail_url, 'icon': thumbnail_url,
+ 'fanart': os.path.join(IMAGES_PATH, 'fanart-blur.jpg')})
+ is_folder = True
+ list_item.setProperty('IsPlayable', 'false')
+ xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=list_item, isFolder=is_folder)
\ No newline at end of file
diff --git a/plugin.image.dumpert/resources/next-page.png b/plugin.image.dumpert/resources/next-page.png
new file mode 100644
index 0000000000..b12e695539
Binary files /dev/null and b/plugin.image.dumpert/resources/next-page.png differ
diff --git a/plugin.image.dumpert/resources/settings.xml b/plugin.image.dumpert/resources/settings.xml
new file mode 100644
index 0000000000..d04db621b7
--- /dev/null
+++ b/plugin.image.dumpert/resources/settings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/plugin.library.node.editor/LICENSE.txt b/plugin.library.node.editor/LICENSE.txt
new file mode 100644
index 0000000000..d159169d10
--- /dev/null
+++ b/plugin.library.node.editor/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/plugin.library.node.editor/addon.xml b/plugin.library.node.editor/addon.xml
new file mode 100644
index 0000000000..3eb5802251
--- /dev/null
+++ b/plugin.library.node.editor/addon.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+ executable
+
+
+ all
+ GPL-2.0-only
+ https://forum.kodi.tv/showthread.php?tid=224512
+ https://github.com/XBMC-Addons/plugin.library.node.editor
+
+ icon.png
+
+
+ 2.0.5 (31/12/2021)
+ - Sync translations
+
+ Administrer brugerdefinerede biblioteksnoder.
+ Benutzerdefinierte Bibliotheksknoten verwalten.
+ Manage custom library nodes.
+ Manage custom library nodes.
+ Manage custom library nodes.
+ Gérer des nœuds de médiathèque personnalisés.
+ Gére des nœuds de médiathèque personnalisés.
+ Gestisci nodi libreria personalizzati
+ 사용자 정의 라이브러리 노드를 관리합니다.
+ Tvarkykite savus bibliotekos mazgus.
+ Beheer aangepaste bibliotheek nodes.
+ Zarządzanie niestandardowymi węzłami biblioteki.
+ Gerenciar nós customizados da biblioteca.
+ Administrați noduri mediatecă personalizate
+ Управление узлами пользовательских медиатек.
+ Hantera anpassade biblioteksnoder.
+ Quản lý các nút thư viện tùy chỉnh.
+ 管理自定义资料库节点。
+ Opret og rediger brugerdefinerede biblioteksnoder.
+ Benutzerdefinierte Bibliotheksknoten erstellen und bearbeiten.
+ Create and edit custom library nodes.
+ Create and edit custom library nodes.
+ Create and edit custom library nodes.
+ Créer et modifier des nœuds de médiathèque personnalisés.
+ Crée et modifie des nœuds de médiathèque personnalisés.
+ Crea e modifica nodi libreria personalizzati.
+ 사용자 라이브러리 노드를 생성하고 편집합니다.
+ Sukurkite ir keiskite savus bibliotekos mazgus.
+ Creëer en wijzig aangepaste bibliotheek nodes.
+ Tworzenie i edytowanie niestandardowych węzłów biblioteki.
+ Crie e edite nós customizados na biblioteca.
+ Создание и редактирование пользовательских узлов медиатеки.
+ Skapa och redigera anpassade biblioteksnoder.
+ 创建和编辑自定义资料库节点。
+
+
diff --git a/plugin.library.node.editor/icon.png b/plugin.library.node.editor/icon.png
new file mode 100644
index 0000000000..4a62edfbc1
Binary files /dev/null and b/plugin.library.node.editor/icon.png differ
diff --git a/plugin.library.node.editor/plugin.py b/plugin.library.node.editor/plugin.py
new file mode 100644
index 0000000000..acff29b64a
--- /dev/null
+++ b/plugin.library.node.editor/plugin.py
@@ -0,0 +1,6 @@
+import sys
+from resources.lib import addon
+
+
+if __name__ == "__main__":
+ addon.run(sys.argv)
diff --git a/plugin.library.node.editor/resources/__init__.py b/plugin.library.node.editor/resources/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugin.library.node.editor/resources/language/resource.language.af_ZA/strings.po b/plugin.library.node.editor/resources/language/resource.language.af_ZA/strings.po
new file mode 100644
index 0000000000..149b81a346
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.af_ZA/strings.po
@@ -0,0 +1,323 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-05-07 12:40+0000\n"
+"Last-Translator: Christian Gade \n"
+"Language-Team: Afrikaans (South Africa) \n"
+"Language: af_ZA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.6.1\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Video Biblioteek"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Musiek Biblioteek"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Wis uit"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Redigeer etiket"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Soek vir ikoon"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Orde"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Stop"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Groep"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Stop"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Orden"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.am_ET/strings.po b/plugin.library.node.editor/resources/language/resource.language.am_ET/strings.po
new file mode 100644
index 0000000000..369fc3f363
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.am_ET/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Amharic (http://www.transifex.com/projects/p/xbmc-addons/language/am/)\n"
+"Language: am\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "ማጥፊያ"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "ምልክት ማረሚያ"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "ማቆሚያ"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "ቡድን"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "ማቆሚያ"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "ምልክት"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr ""
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.ar_SA/strings.po b/plugin.library.node.editor/resources/language/resource.language.ar_SA/strings.po
new file mode 100644
index 0000000000..81ded229b2
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.ar_SA/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Arabic (http://www.transifex.com/projects/p/xbmc-addons/language/ar/)\n"
+"Language: ar\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "حذف"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "تحرير العلامة"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "مسار"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "مجموعة"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "مسار"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "ترتيب"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.az_AZ/strings.po b/plugin.library.node.editor/resources/language/resource.language.az_AZ/strings.po
new file mode 100644
index 0000000000..c0bd8f6a5a
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.az_AZ/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Azerbaijani (http://www.transifex.com/projects/p/xbmc-addons/language/az/)\n"
+"Language: az\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Sil"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr ""
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Məkan"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr ""
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Məkan"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr ""
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.be_BY/strings.po b/plugin.library.node.editor/resources/language/resource.language.be_BY/strings.po
new file mode 100644
index 0000000000..8524afdfd2
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.be_BY/strings.po
@@ -0,0 +1,323 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-05-07 12:40+0000\n"
+"Last-Translator: Christian Gade \n"
+"Language-Team: Belarusian \n"
+"Language: be_BY\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: Weblate 4.6.1\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Відэабібліятэка"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Музычная бібліятэка"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Выдаліць"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Рэдагаваць адмеціну"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Пошук значка"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Чарга"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Шлях"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Група"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Шлях"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Icon"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Упарадкавана па"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.bg_BG/strings.po b/plugin.library.node.editor/resources/language/resource.language.bg_BG/strings.po
new file mode 100644
index 0000000000..0a6b0893f7
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.bg_BG/strings.po
@@ -0,0 +1,325 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Bulgarian (http://www.transifex.com/projects/p/xbmc-addons/language/bg/)\n"
+"Language: bg\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Добави съдържание..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Добави път..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Добави подредба..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "Добави ограничение..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "Добави групиране..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Добави правило..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Видео библиотека"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Музикална библиотека"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Изтрий"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Редактирай етикета"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Редактирай иконата"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Преглед за икона"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Редактирай видимостта"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Добави към главното меню"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Преглед за сойност"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Съдържание"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Ред"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Групиране"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Ограничение"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Път"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Правило"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Задайте име"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "Задайте условие за видимост"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "Начало със стандартните"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Поле"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Оператор"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Стойност"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Тип съдържание"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Съдържание"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Група"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "Ограничение"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Път"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Икона"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Подреди по"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Ред"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "Наистина ли желаете да го изтриете?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "Наистина ли желаете да изтриете правилото?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr "Редът изисква параметър \"Съдържание\""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
+
+#~ msgctxt "#30104"
+#~ msgid "Edit order"
+#~ msgstr "Редактирай реда"
diff --git a/plugin.library.node.editor/resources/language/resource.language.bs_BA/strings.po b/plugin.library.node.editor/resources/language/resource.language.bs_BA/strings.po
new file mode 100644
index 0000000000..bf876de17f
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.bs_BA/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Bosnian (http://www.transifex.com/projects/p/xbmc-addons/language/bs/)\n"
+"Language: bs\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Izbriši"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Uredi natpis"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Putanja"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grupa"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Putanja"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Složi po"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.ca_ES/strings.po b/plugin.library.node.editor/resources/language/resource.language.ca_ES/strings.po
new file mode 100644
index 0000000000..6e647d31ee
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.ca_ES/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Catalan (http://www.transifex.com/projects/p/xbmc-addons/language/ca/)\n"
+"Language: ca\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Biblioteca de vídeo"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Biblioteca de música"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Elimina"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Edita l'etiqueta"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Cerca una icona"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Camí"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Valor"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grup"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Camí"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Icona"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Ordena per"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.cs_CZ/strings.po b/plugin.library.node.editor/resources/language/resource.language.cs_CZ/strings.po
new file mode 100644
index 0000000000..744580421f
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.cs_CZ/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Czech (http://www.transifex.com/projects/p/xbmc-addons/language/cs/)\n"
+"Language: cs\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Hudební knihovna"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Smazat"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Upravit popisek"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Vyhledej ikonku"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Cesta"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Pole"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operátor"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Hodnota"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Skupina"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Cesta"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Ikona"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Řadit"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.cy_GB/strings.po b/plugin.library.node.editor/resources/language/resource.language.cy_GB/strings.po
new file mode 100644
index 0000000000..5173ab4a6d
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.cy_GB/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Welsh (http://www.transifex.com/projects/p/xbmc-addons/language/cy/)\n"
+"Language: cy\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Dileu"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Golygu labeli"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Pori am eiconau"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Llwybr"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Rheol"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grŵp"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Llwybr"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Trefnu yn ôl"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.da_DK/strings.po b/plugin.library.node.editor/resources/language/resource.language.da_DK/strings.po
new file mode 100644
index 0000000000..b28491534f
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.da_DK/strings.po
@@ -0,0 +1,323 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: translations@kodi.tv\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-11-09 11:30+0000\n"
+"Last-Translator: Christian Gade \n"
+"Language-Team: Danish \n"
+"Language: da_DK\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.8.1\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr "Administrer brugerdefinerede biblioteksnoder."
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr "Opret og rediger brugerdefinerede biblioteksnoder."
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Tilføj indhold..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Tilføj sti..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Tilføj rækkefølge..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "Tilføj grænse..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "Tilføj gruppering..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Tilføj regel..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "Ny node..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "Ny overordnet node..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr "Nulstil biblioteksnoder til standard..."
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr "Tilføj komponent..."
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr "Rediger manuelt..."
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Videobibliotek"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Musikbibliotek"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Slet"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Rediger navn"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Rediger ikon"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Se efter ikon"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr "Flyt node"
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Rediger synlighed"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Tilføj til hovedmenu"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Søg efter værdi"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Indhold"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Rækkefølge"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Gruppering"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Grænse"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Sti"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Regel"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr "Match"
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Indstil navn"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "Indstil synlighed"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr "Indstil indeks for rækkefølge"
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr "Navn på ny overordnet node"
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "Start med standarder"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Felt"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operatør"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Værdi"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Indholdstype"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Indhold"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Gruppe"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "Grænse"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Sti"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Ikon"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Sorter efter"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Retning"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "Navn på ny node"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr "Henter værdier..."
+
+msgctxt "#30318"
+msgid "Property"
+msgstr "Egenskab"
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr "Kan ikke kopiere standard biblioteksnoder"
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr "Er du sikker på at du vil slette denne node?"
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr "Er du sikker på at du vil slette alle noder?"
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "Er du sikker på at du vil slette denne egenskab?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr "Indholdsparameter kan ikke slettes, mens denne node stadig har et parameter for rækkefølge, begrænsning eller regel."
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "Er du sikker på at du vil slette denne regel?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr "Rækkefølge kræver et parameter for indhold"
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr "Er du sikker på at du vil slette denne komponent?"
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr "Brugerdefineret egenskab..."
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr "Der blev ikke fundet nogen muligheder at vælge imellem"
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr "Henter indhold for plugin..."
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr "Link stien hertil"
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr "Genre-ID"
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr "ID for land"
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr "Studio-ID"
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr "ID for instruktør"
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr "ID for skuespiller"
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr "ID for sæt"
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr "ID for mærkater"
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr "ID for tv-serie"
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr "ID for kunstner"
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr "ID for album"
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr "ID for rolle"
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr "ID for sang"
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr "Kun albumkunstnere"
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr "Vis singler"
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr "Plugin..."
diff --git a/plugin.library.node.editor/resources/language/resource.language.de_DE/strings.po b/plugin.library.node.editor/resources/language/resource.language.de_DE/strings.po
new file mode 100644
index 0000000000..8ed32555a0
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.de_DE/strings.po
@@ -0,0 +1,326 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: translations@kodi.tv\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-07-23 14:15+0000\n"
+"Last-Translator: Kai Sommerfeld \n"
+"Language-Team: German \n"
+"Language: de_DE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.7.2\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr "Benutzerdefinierte Bibliotheksknoten verwalten."
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr "Benutzerdefinierte Bibliotheksknoten erstellen und bearbeiten."
+
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Inhalt hinzufügen ..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Pfad hinzufügen ..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Sortierung hinzufügen ..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "Beschränkung hinzufügen ..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "Gruppierung hinzufügen ..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Regel hinzufügen ..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "Neuer Knoten ..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "Neuer übergeordneter Knoten ..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr "Bibliotheksknoten auf Standard zurücksetzen ..."
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr "Komponente hinzufügen ..."
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr "Manuell bearbeiten ..."
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Videobibliothek"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Musikbibliothek"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Löschen"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Beschriftung ändern"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Symbol bearbeiten"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Nach Symbol suchen"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr "Knoten verschieben"
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Sichtbarkeit bearbeiten"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Zum Hauptmenü hinzufügen"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Nach Wert suchen"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Inhalt"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Sortierung"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Gruppierung"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Beschränkung"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Pfad"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Regel"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr "Übereinstimmung"
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Name festlegen"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "Sichtbarkeitsbedingung festlegen"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr "Sortierungsindex festlegen"
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr "Name des neuen übergeordneten Knotens"
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "Mit Standardeinstellungen starten"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Feld"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operator"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Wert"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Inhaltstyp"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Inhalt"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Gruppe"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "Beschränkung"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Pfad"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Symbol"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Sortieren nach"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Richtung"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "Name des neuen Knotens"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr "Werte werden abgerufen ..."
+
+msgctxt "#30318"
+msgid "Property"
+msgstr "Eigenschaft"
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr "Standardbibliotheksknoten können nicht kopiert werden"
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr "Diesen Knoten wirklich löschen?"
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr "Wirklich alle Knoten zurücksetzen?"
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "Diese Eigenschaft wirklich löschen?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr "Inhaltsparameter kann nicht gelöscht werden, solange dieser Knoten noch eine Sortierung, Beschränkung oder Regel enthält."
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "Diese Regel wirklich löschen?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr "Sortierung benötigt einen Inhaltsparameter"
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr "Diese Komponente wirklich löschen?"
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr "Benutzerdefinierte Eigenschaft ..."
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr "Keine Auswahlmöglichkeiten gefunden"
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr "Plugin-Auflistungen werden abgerufen ..."
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr "Pfad hierauf verlinken"
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr "Genre-ID"
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr "Landes-ID"
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr "Studio-ID"
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr "Direktor-ID"
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr "Darsteller-ID"
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr "Zusammenstellungs-ID"
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr "Tag-ID"
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr "TV-Show-ID"
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr "Interpreten-ID"
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr "Album-ID"
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr "Rollen-ID"
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr "Titel-ID"
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr "Nur Albumsinterpreten"
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr "Singles anzeigen"
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr "Plugin ..."
+
+#~ msgctxt "#30104"
+#~ msgid "Edit order"
+#~ msgstr "Sortierung bearbeiten"
diff --git a/plugin.library.node.editor/resources/language/resource.language.el_GR/strings.po b/plugin.library.node.editor/resources/language/resource.language.el_GR/strings.po
new file mode 100644
index 0000000000..7c9f383163
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.el_GR/strings.po
@@ -0,0 +1,326 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Greek (http://www.transifex.com/projects/p/xbmc-addons/language/el/)\n"
+"Language: el\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Συλλογή Βίντεο"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Μουσική Συλλογή"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Διαγραφή"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Επεξεργασία ετικέτας"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Επεξεργασία εικονιδίου"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Αναζήτηση εικονιδίου"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Επεξεργασία Ορατότητας"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Προσθήκη στο κεντρικό μενού"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Περιεχόμενο"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Σειρά"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Ομαδοποίηση"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Όριο"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Διαδρομή"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Κανόνας"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Ορισμός Ονόματος"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Πεδίο"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Χειριστής"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Τιμή"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Περιεχόμενο"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Γκρουπ"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "Όριο"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Διαδρομή"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Εικονίδιο"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Σειρά κατά"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Κατεύθυνση"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
+
+#~ msgctxt "#30104"
+#~ msgid "Edit order"
+#~ msgstr "Επεξεργασία Σειράς"
diff --git a/plugin.library.node.editor/resources/language/resource.language.en_AU/strings.po b/plugin.library.node.editor/resources/language/resource.language.en_AU/strings.po
new file mode 100644
index 0000000000..2bc28e6982
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.en_AU/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: English (Australia) (http://www.transifex.com/projects/p/xbmc-addons/language/en_AU/)\n"
+"Language: en_AU\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Video Library"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Music Library"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Delete"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Edit label"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Edit icon"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Browse for icon"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Path"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Rule"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Group"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Path"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Icon"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Order by"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.en_GB/strings.po b/plugin.library.node.editor/resources/language/resource.language.en_GB/strings.po
new file mode 100644
index 0000000000..d735b4fe4c
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.en_GB/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr ""
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr ""
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr ""
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr ""
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr ""
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr ""
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.en_NZ/strings.po b/plugin.library.node.editor/resources/language/resource.language.en_NZ/strings.po
new file mode 100644
index 0000000000..88cdb3df64
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.en_NZ/strings.po
@@ -0,0 +1,325 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: English (New Zealand) (http://www.transifex.com/projects/p/xbmc-addons/language/en_NZ/)\n"
+"Language: en_NZ\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr "Manage custom library nodes."
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr "Create and edit custom library nodes."
+
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Add content..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Add path..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Add order..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "Add limit..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "Add grouping..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Add rule..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "New node..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "New parent node..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr "Reset library nodes to default..."
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Video Library"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Music Library"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Delete"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Edit label"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Edit icon"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Browse for icon"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Edit visibility"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Add to main menu"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Browse for value"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Content"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Order"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Grouping"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Limit"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Path"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Rule"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Set name"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "Set visibility condition"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr "Set order index"
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr "Name of new parent node"
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "Start with defaults"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Field"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operator"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Value"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Content type"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Content"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Group"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "Limit"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Path"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Icon"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Order by"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Direction"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "Name of new node"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr "Unable to copy default library nodes"
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr "Are you sure you want to delete this node?"
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr "Are you sure you want to reset all nodes?"
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "Are you sure you want to delete this property?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "Are you sure you want to delete this rule?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr "Order requires a Content parameter"
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
+
+#~ msgctxt "#30104"
+#~ msgid "Edit order"
+#~ msgstr "Edit order"
diff --git a/plugin.library.node.editor/resources/language/resource.language.en_US/strings.po b/plugin.library.node.editor/resources/language/resource.language.en_US/strings.po
new file mode 100644
index 0000000000..8fd0b458fa
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.en_US/strings.po
@@ -0,0 +1,325 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: English (US) (http://www.transifex.com/projects/p/xbmc-addons/language/en_US/)\n"
+"Language: en_US\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr "Manage custom library nodes."
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr "Create and edit custom library nodes."
+
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Add content..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Add path..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Add order..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "Add limit..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "Add grouping..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Add rule..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "New node..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "New parent node..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr "Reset library nodes to default..."
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Video Library"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Music Library"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Delete"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Edit label"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Edit icon"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Browse for icon"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Edit visibility"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Add to main menu"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Browse for value"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Content"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Order"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Grouping"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Limit"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Path"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Rule"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Set name"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "Set visibility condition"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr "Set order index"
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr "Name of new parent node"
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "Start with defaults"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Field"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operator"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Value"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Content type"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Content"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Group"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "Limit"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Path"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Icon"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Order by"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Direction"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "Name of new node"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr "Unable to copy default library nodes"
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr "Are you sure you want to delete this node?"
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr "Are you sure you want to reset all nodes?"
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "Are you sure you want to delete this property?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "Are you sure you want to delete this rule?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr "Order requires a Content parameter"
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
+
+#~ msgctxt "#30104"
+#~ msgid "Edit order"
+#~ msgstr "Edit order"
diff --git a/plugin.library.node.editor/resources/language/resource.language.eo/strings.po b/plugin.library.node.editor/resources/language/resource.language.eo/strings.po
new file mode 100644
index 0000000000..c8d0d4de8a
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.eo/strings.po
@@ -0,0 +1,323 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: translations@kodi.tv\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-08-04 13:29+0000\n"
+"Last-Translator: Christian Gade \n"
+"Language-Team: Esperanto \n"
+"Language: eo\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.7.2\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Aldoni enhavon..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Aldoni dosierindikon..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Aldoni ordigon..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "Aldoni limon..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "Aldoni grupigon..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Aldoni regulon..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "Nova nodo..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "Nova parenta nodo..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr "Restaŭri nodojn de biblioteko al defaŭlto..."
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr "Mane redakti..."
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Videaĵa biblioteko"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Muzika biblioteko"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Forigi"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Editii Label"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Redakti piktogramon"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Elekti piktogramon"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr "Movi nodon"
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Redakti videblecon"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Aldoni al ĉefmenuo"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Elekti valoron"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Enhavo"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Ordo"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Grupigado"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Limo"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Dosierindiko"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Regulo"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Agordi nomon"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr "Nomo de nova parenta nodo"
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "Komenci kun defaŭltaj"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Kampo"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operatoro"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Valoro"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Enhava speco"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Enhavo"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grupo"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "Limo"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Dosierindiko"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Piktogramo"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Ordigi laŭ"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Direkto"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "Nomo de nova nodo"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr "Atribuo"
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr "Neeblas kopii defaŭltajn nodojn de biblioteko"
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr "Ĉu vi certe volas forigi ĉi tiun nodon?"
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr "Ĉu vi certe volas restaŭri ĉiujn nodojn?"
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "Ĉu vi certe volas forigi ĉi tiun atribuon?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "Ĉu vi certe volas forigi ĉi tiun regulon?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr "Ĉu vi certe volas forigi ĉi tiun komponenton?"
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr "Propra atribuo..."
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr "Neniu elekteblaj opcioj trovitaj"
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr "Montri singlaĵojn"
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr "Kromprogramo..."
diff --git a/plugin.library.node.editor/resources/language/resource.language.es_AR/strings.po b/plugin.library.node.editor/resources/language/resource.language.es_AR/strings.po
new file mode 100644
index 0000000000..bffe5b46c0
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.es_AR/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Spanish (Argentina) (http://www.transifex.com/projects/p/xbmc-addons/language/es_AR/)\n"
+"Language: es_AR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Colección de Video"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Colección de Música"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Eliminar"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Editar etiqueta"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Buscar un icono"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Ruta"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grupo"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Ruta"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Ícono"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Ordenar por"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.es_ES/strings.po b/plugin.library.node.editor/resources/language/resource.language.es_ES/strings.po
new file mode 100644
index 0000000000..3f0badadde
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.es_ES/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Spanish (http://www.transifex.com/projects/p/xbmc-addons/language/es/)\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Vídeoteca"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Biblioteca de Música"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Eliminar"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Editar etiqueta"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Buscar por icono"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Contenido"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Ruta"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Campo"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operador"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Valor"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Contenido"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grupo"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Ruta"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Icono"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Ordenar por"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Dirección"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.es_MX/strings.po b/plugin.library.node.editor/resources/language/resource.language.es_MX/strings.po
new file mode 100644
index 0000000000..b5e1cacff6
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.es_MX/strings.po
@@ -0,0 +1,323 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-03-26 06:28+0000\n"
+"Last-Translator: Edson Armando \n"
+"Language-Team: Spanish (Mexico) \n"
+"Language: es_MX\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.5.1\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Agregar contenido..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Agregar ruta..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Agregar orden..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "Agregar límite..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "Agregar agrupación..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Agregar regla..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "Nuevo nodo..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "Nuevo nodo padre..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr "Reiniciar nodos de biblioteca a valores por defecto..."
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr "Agregar componente..."
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr "Editar manualmente..."
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Biblioteca Vídeo"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Biblioteca de Música"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Eliminar"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Editar etiqueta"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Editar ícono"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Buscar ícono"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr "Mover nodo"
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Editar visibilidad"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Agregar al menú principal"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Buscar valor"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Contenido"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Orden"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Agrupación"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Límite"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Ruta"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Regla"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr "Coincidencia"
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Establecer nombre"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "Establecer condición de visibilidad"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr "Establecer índice de orden"
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr "Nombre del nuevo nodo padre"
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "Iniciar con valores por defecto"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Campo"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operador"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Valor"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Tipo de contenido"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Contenido"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grupo"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "Límite"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Ruta"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Ícono"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Ordenar por"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Dirección"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "Nombre del nuevo nodo"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr "Obteniendo valores..."
+
+msgctxt "#30318"
+msgid "Property"
+msgstr "Propiedad"
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr "No se pudieron copiar los nodos por defecto"
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr "¿Estás seguro que quieres borrar este nodo?"
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr "¿Estás seguro que quieres reiniciar todos los nodos?"
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "¿Estás seguro que quieres borrar esta propiedad?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr "El parámetro contenido no se puede borrar mientras el nodo aún tenga un parámetro de orden, límite o regla."
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "¿Estás seguro que quieres borrar esta regla?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr "Orden requiere el parámetro contenido"
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr "¿Estás seguro que quieres borrar este componente?"
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr "Propiedad personalizada..."
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr "No se encontraron opciones para la selección"
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr "Obteniendo listado del plugin..."
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr "Enlazar directorio aquí"
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr "ID de género"
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr "ID de país"
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr "ID de estudio"
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr "ID de director"
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr "ID de actor"
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr "ID de set"
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr "ID de etiqueta"
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr "ID de serie"
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr "ID de artista"
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr "ID de álbum"
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr "ID de rol"
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr "ID de canción"
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr "Solo artistas del álbum"
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr "Mostrar sencillos"
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr "Plugin..."
diff --git a/plugin.library.node.editor/resources/language/resource.language.et_EE/strings.po b/plugin.library.node.editor/resources/language/resource.language.et_EE/strings.po
new file mode 100644
index 0000000000..872085bd47
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.et_EE/strings.po
@@ -0,0 +1,323 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-07-19 09:21+0000\n"
+"Last-Translator: rimasx \n"
+"Language-Team: Estonian \n"
+"Language: et_EE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.7.1\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Lisa sisu..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Lisa rada..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Lisa järjestus..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "Lisa limiit..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Lisa reegel..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "Uus sõlm..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "Uus ülemsõlm..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr "Taasta meediakogu sõlmede vaikeväärtused..."
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr "Lisa komponent..."
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr "Muuda käsitsi..."
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Videokogu"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Muusikakogu"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Kustuta"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Muuda silti"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Muuda ikooni"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Sirvi ikoone"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr "Teisalda sõlm"
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Muuda nähtavust"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Lisa peamenüüsse"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Sirvi väärtust"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Sisu"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Järjestus"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Rühmitamine"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Piirid"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Rada"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Reegel"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr "Vaste"
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Anna nimi"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "Määra nähtavuse tingimused"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr "Sea järjestuse indeks"
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr "Uue ülemsõlme nimi"
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "Alusta vaikeväärtustega"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Väli"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operaator"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Väärtus"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Sisu tüüp"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Sisu"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grupp"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Rada"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Ikoon"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Järjesta"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Suund"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "Uue sõlme nimi"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr "Väärtuste toomine..."
+
+msgctxt "#30318"
+msgid "Property"
+msgstr "Omadus"
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr "Meediakogu vaikesõlmede kopeerimine nurjus"
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr "Kas soovid selle sõlme kustutada?"
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr "Kas soovid kõik sõlmed lähtestada?"
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "Kas soovid selle omaduse kustutada?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "Kas soovid selle reegli kustutada?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr "Järjestus nõuab sisu parameetrit"
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr "Kas soovid selle komponendi kustutada?"
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr "Kohandatud omadus..."
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr "Ühtegi valikut ei leitud"
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr "Pistikprogrammide loendite toomine..."
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr "Lingi rada siia"
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr "Žanri ID"
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr "Riigi ID"
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr "Stuudio ID"
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr "Lavastaja ID"
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr "Näitleja ID"
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr "Määra ID"
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr "Sildi ID"
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr "Seriaali ID"
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr "Esitaja ID"
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr "Albumi ID"
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr "Rolli ID"
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr "Loo ID"
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr "Ainult albumi esitajad"
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.eu_ES/strings.po b/plugin.library.node.editor/resources/language/resource.language.eu_ES/strings.po
new file mode 100644
index 0000000000..118e139f51
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.eu_ES/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Basque (http://www.transifex.com/projects/p/xbmc-addons/language/eu/)\n"
+"Language: eu\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Ezabatu"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Editatu etiketa"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Bidea"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Taldea"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Bidea"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Ordenatu"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.fa_AF/strings.po b/plugin.library.node.editor/resources/language/resource.language.fa_AF/strings.po
new file mode 100644
index 0000000000..51a6c3672c
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.fa_AF/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Persian (http://www.transifex.com/projects/p/xbmc-addons/language/fa/)\n"
+"Language: fa\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "حذف"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr ""
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr ""
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr ""
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr ""
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr ""
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.fa_IR/strings.po b/plugin.library.node.editor/resources/language/resource.language.fa_IR/strings.po
new file mode 100644
index 0000000000..54ec7d934d
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.fa_IR/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Persian (Iran) (http://www.transifex.com/projects/p/xbmc-addons/language/fa_IR/)\n"
+"Language: fa_IR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "کتابخانه ی ویدیو"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "کتابخانه ی موسیقی"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "حذف"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "ویرایش برچسب"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "مسیر"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "گروه"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "مسیر"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr ""
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.fi_FI/strings.po b/plugin.library.node.editor/resources/language/resource.language.fi_FI/strings.po
new file mode 100644
index 0000000000..a051ab36df
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.fi_FI/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Finnish (http://www.transifex.com/projects/p/xbmc-addons/language/fi/)\n"
+"Language: fi\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Videokirjasto"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Musiikkikirjasto"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Poista"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Muuta nimeä"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Hae kuvake"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Polku"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Ryhmä"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Polku"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Kuvake"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Järjestys"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.fo_FO/strings.po b/plugin.library.node.editor/resources/language/resource.language.fo_FO/strings.po
new file mode 100644
index 0000000000..b0e0535a52
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.fo_FO/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Faroese (http://www.transifex.com/projects/p/xbmc-addons/language/fo/)\n"
+"Language: fo\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Strika"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Broyt heiti"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Stíggi"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Bólkur"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Stíggi"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Skipa eftir"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.fr_CA/strings.po b/plugin.library.node.editor/resources/language/resource.language.fr_CA/strings.po
new file mode 100644
index 0000000000..d4d8a6ecc0
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.fr_CA/strings.po
@@ -0,0 +1,325 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: French (Canada) (http://www.transifex.com/projects/p/xbmc-addons/language/fr_CA/)\n"
+"Language: fr_CA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr "Gérer des nœuds de médiathèque personnalisés."
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr "Créer et modifier des nœuds de médiathèque personnalisés."
+
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Ajouter du contenu..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Ajouter un chemin..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Ajouter un ordre..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "Ajouter une limite..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "Ajouter un regroupement..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Ajouter une règle..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "Nouveau nœud..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "Nouveau nœud parent..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr "Réinitialiser les nœuds de médiathèque à leurs valeurs par défaut..."
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Vidéothèque"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Audiothèque"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Supprimer"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Modifier l'étiquette"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Modifier l'icône"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Rechercher des icônes"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Modifier la visibilité"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Ajouter au menu principal"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Rechercher une valeur"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Contenu"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Ordre"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Regroupement"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Limite"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Chemin"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Règle"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Définir un nom"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "Définir une condition de visibilité"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr "Définir l'index d'ordre"
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr "Nom du nouveau nœud parent"
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "Démarrer avec les valeurs par défaut"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Champ"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Opérateur"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Valeur"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Type de contenu"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Contenu"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Groupe"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "Limite"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Chemin"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Icône"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Ranger par"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Direction"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "Nom du nouveau nœud"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr "Impossible de copier les nœuds de médiathèque par défaut"
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr "Voulez-vous vraiment supprimer ce nœud?"
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr "Voulez-vous vraiment réinitialiser tous les nœuds?"
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "Voulez-vous vraiment supprimer cette propriété?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr "Le paramètre de contenu ne peut pas être supprimé tant que le nœud à une paramètre d'Ordre, de Limite ou de Règle."
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "Voulez-vous vraiment supprimer cette règle?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr "L'Ordre exige un paramètre de Contenu"
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
+
+#~ msgctxt "#30104"
+#~ msgid "Edit order"
+#~ msgstr "Modifier l'ordre"
diff --git a/plugin.library.node.editor/resources/language/resource.language.fr_FR/strings.po b/plugin.library.node.editor/resources/language/resource.language.fr_FR/strings.po
new file mode 100644
index 0000000000..a4ebd38cb4
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.fr_FR/strings.po
@@ -0,0 +1,325 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: French (http://www.transifex.com/projects/p/xbmc-addons/language/fr/)\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr "Gére des nœuds de médiathèque personnalisés."
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr "Crée et modifie des nœuds de médiathèque personnalisés."
+
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Ajouter du contenu…"
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Ajouter un chemin…"
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Ajouter un ordre…"
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "Ajouter une limite…"
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "Ajouter un regroupement…"
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Ajouter une règle…"
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "Nouveau nœud…"
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "Nouveau nœud parent…"
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr "Réinitialiser les nœuds de médiathèque aux valeurs par défaut…"
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Vidéothèque"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Audiothèque"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Supprimer"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Éditer le nom"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Éditer l'icône"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Rechercher des icônes"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Éditer la visibilité"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Ajouter au menu principal"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Chercher une valeur"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Contenu"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Ordre"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Regroupement"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Limite"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Chemin"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Règle"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Définir un nom"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "Définir une condition de visibilité"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr "Définir un index d'ordre"
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr "Nom du nouveau nœud parent"
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "Commencer avec les réglages par défaut"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Champ"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Opérateur"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Valeur"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Type de contenu"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Contenu"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Groupe"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "Limite"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Chemin"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Icône"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Trier par"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Direction"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "Nom du nouveau nœud"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr "Impossible de copier les nœuds de médiathèque par défaut"
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr "Faut-il vraiment supprimer ce nœud ?"
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr "Faut-il vraiment réinitialiser tous les nœuds ?"
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "Faut-il vraiment supprimer cette propriété ?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr "Le paramètre de contenu ne peut pas être supprimé tant que le nœud possède un paramètre d'ordre, de limite ou de règle."
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "Faut-il vraiment supprimer cette règle ?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr "L'ordre requiert un paramètre de Contenu"
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
+
+#~ msgctxt "#30104"
+#~ msgid "Edit order"
+#~ msgstr "Éditer l'ordre"
diff --git a/plugin.library.node.editor/resources/language/resource.language.gl_ES/strings.po b/plugin.library.node.editor/resources/language/resource.language.gl_ES/strings.po
new file mode 100644
index 0000000000..78bf1bcc5f
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.gl_ES/strings.po
@@ -0,0 +1,325 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Galician (http://www.transifex.com/projects/p/xbmc-addons/language/gl/)\n"
+"Language: gl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Engadir contido..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Engadir ruta..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Engadir orde..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "Engadir limite..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Engadir regra..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "Novo nodo..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "Novo nodo pai..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Biblioteca de Vídeo"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Biblioteca de Música"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Eliminar"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Editar etiqueta"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Editar icona"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Buscar icona"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Editar visibilidade"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Engadir ó menú principal"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Buscar un valor"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Contido"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Orde"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Límite"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Ruta"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Regra"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Estabelecer nome"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "Estabelecer condición de visibilidade"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Campo"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operador"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Valor"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Tipo de contido"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Contido"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grupo"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "Límite"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Ruta"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Icona"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Ordear por"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Dirección"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "Nome do novo nodo"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr "Tes a certeza de querer eliminar este nodo?"
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr "Tes a certeza de querer restabelecer todos os nodos?"
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "Tes a certeza de querer eliminar esta propiedade?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "Tes a certeza de querer eliminar esta regra?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
+
+#~ msgctxt "#30104"
+#~ msgid "Edit order"
+#~ msgstr "Editar orde"
diff --git a/plugin.library.node.editor/resources/language/resource.language.he_IL/strings.po b/plugin.library.node.editor/resources/language/resource.language.he_IL/strings.po
new file mode 100644
index 0000000000..b699115db9
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.he_IL/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-07-19 09:21+0000\n"
+"Last-Translator: Yaron Shahrabani \n"
+"Language-Team: Hebrew (Israel) \n"
+"Language: he_IL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Weblate 4.7.1\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "הוספת תוכן ..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "הוספת נתיב ..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "הוספת סדר…"
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "הוספת גבול ..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "הוספת הקבצה ..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "הוספת כלל ..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "ספריית וידאו"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "ספריית מוזיקה"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "מחיקה"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "עריכת תווית"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "עריכת סמל"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "בחירת סמל"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "הוספה לתפריט הראשי"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "איתור ערך"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "תוכן"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "סדר"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "קיבוץ"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "מגבלה"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "נתיב"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "כלל"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr "התאמה"
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "הגדרת שם"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "הגדרת תנאי חשיפה"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr "הגדרת מפתח סידור"
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "התחלה עם ברירת מחדל"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "שדה"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "אופרטור"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "ערך"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "סוג תוכן"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "תוכן"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "קבוצה"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "מגבלה"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "נתיב"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "אייקון"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "סדר לפי"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "כיוון"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "האם אתה בטוח שאתה רוצה למחוק את הנכס הזה?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "האם אתה בטוח שאתה רוצה למחוק את הכלל הזה?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr "הצו מחייב פרמטר תוכן"
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.hi_IN/strings.po b/plugin.library.node.editor/resources/language/resource.language.hi_IN/strings.po
new file mode 100644
index 0000000000..6209c32a91
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.hi_IN/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Hindi (Devanagiri) (http://www.transifex.com/projects/p/xbmc-addons/language/hi/)\n"
+"Language: hi\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "मिटाना"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr ""
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "पथ"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr ""
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "पथ"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr ""
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.hr_HR/strings.po b/plugin.library.node.editor/resources/language/resource.language.hr_HR/strings.po
new file mode 100644
index 0000000000..44c720f7b7
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.hr_HR/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Croatian (http://www.transifex.com/projects/p/xbmc-addons/language/hr/)\n"
+"Language: hr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Videoteka"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Fonoteka"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Obriši"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Uredi oznaku"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Potraži ikonu"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Putanja"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Polje"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operator"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Vrijednost"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grupa"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Putanja"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Ikona"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Razvrstaj po"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.hu_HU/strings.po b/plugin.library.node.editor/resources/language/resource.language.hu_HU/strings.po
new file mode 100644
index 0000000000..8601f04d09
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.hu_HU/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Hungarian (http://www.transifex.com/projects/p/xbmc-addons/language/hu/)\n"
+"Language: hu\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Videó könytár"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Zene könyvtár"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Törlés"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Cím szerkesztése"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Ikon keresése"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Tartalom"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Útvonal"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Mező"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Üzemeltető"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Érték"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Tartalom"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Csoport"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Útvonal"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Ikon"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Rendezés"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Irány"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.hy_AM/strings.po b/plugin.library.node.editor/resources/language/resource.language.hy_AM/strings.po
new file mode 100644
index 0000000000..51ec89bbed
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.hy_AM/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Armenian (http://www.transifex.com/projects/p/xbmc-addons/language/hy/)\n"
+"Language: hy\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Ջնջել"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr ""
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Ուղի"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr ""
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Ուղի"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr ""
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.id_ID/strings.po b/plugin.library.node.editor/resources/language/resource.language.id_ID/strings.po
new file mode 100644
index 0000000000..9b394c3def
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.id_ID/strings.po
@@ -0,0 +1,323 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-07-19 09:21+0000\n"
+"Last-Translator: liimee \n"
+"Language-Team: Indonesian \n"
+"Language: id_ID\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Weblate 4.7.1\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Hapus"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Sunting label"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Sunting ikon"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Cari ikon"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Path"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Atur nama"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grup"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Path"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Ikon"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Order berdasar"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr "Apakah Anda ingin menghapus komponen ini?"
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.is_IS/strings.po b/plugin.library.node.editor/resources/language/resource.language.is_IS/strings.po
new file mode 100644
index 0000000000..4b129e1227
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.is_IS/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Icelandic (http://www.transifex.com/projects/p/xbmc-addons/language/is/)\n"
+"Language: is\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Myndbandasafn"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Tónlistarsafn"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Eyða"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Breyta nafni"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Fletta eftir táknmynd"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Slóð"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Gildi"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Hópur"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Slóð"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Tákn"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Raða eftir"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.it_IT/strings.po b/plugin.library.node.editor/resources/language/resource.language.it_IT/strings.po
new file mode 100644
index 0000000000..907121c289
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.it_IT/strings.po
@@ -0,0 +1,325 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Italian (http://www.transifex.com/projects/p/xbmc-addons/language/it/)\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr "Gestisci nodi libreria personalizzati"
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr "Crea e modifica nodi libreria personalizzati."
+
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Aggiungi contenuto..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Aggiungi percorso..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Aggiungi ordine..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "Aggiungi limite..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "Aggiungi raggruppamento..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Aggiungi regola..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "Nuovo nodo..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "Nuovo nodo superiore..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr "Ripristina nodi libreria a predefinito..."
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Libreria Video"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Libreria Musicale"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Elimina"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Modifica etichetta"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Modifica icona"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Cerca icona"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Modifica visibilita'"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Aggiungi a menu' principale"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Cerca valore"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Contenuto"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Ordine"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Raggruppamento"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Limite"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Percorso"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Regola"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Imposta nome"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "Imposta condizione visibilita'"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr "Imposta ordine indice"
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr "Nome del nuovo nodo superiore"
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "Parti con predefiniti"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Campo"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operatore"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Valore"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Tipo contenuto"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Contenuto"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Gruppo"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "Limite"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Percorso"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Icona"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Ordina per"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Direzione"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "Nome del nuovo nodo"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr "Impossibile copiare i nodi libreria predefiniti"
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr "Sei sicuro di voler eliminare questo nodo?"
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr "Sei sicuro di voler resettare tutti i nodi?"
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "Sei sicuro di voler cancellare questa proprieta'?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr "Il parametro del contenuto non può essere cancellato finche' questo nodo ha ancora un parametro Ordine, Limite o Regola."
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "Sei sicuro di voler cancellare questa regola?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr "L'Ordine richiede un parametro Contenuto"
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
+
+#~ msgctxt "#30104"
+#~ msgid "Edit order"
+#~ msgstr "Modifica ordine"
diff --git a/plugin.library.node.editor/resources/language/resource.language.ja_JP/strings.po b/plugin.library.node.editor/resources/language/resource.language.ja_JP/strings.po
new file mode 100644
index 0000000000..6e6dc1ee48
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.ja_JP/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Japanese (http://www.transifex.com/projects/p/xbmc-addons/language/ja/)\n"
+"Language: ja\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "ビデオライブラリ"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "ミュージックライブラリ"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "削除"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "ラベル編集"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "アイコンをブラウズ"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "パス"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "グループ"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "パス"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "並べ替え"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.ko_KR/strings.po b/plugin.library.node.editor/resources/language/resource.language.ko_KR/strings.po
new file mode 100644
index 0000000000..b4649b863f
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.ko_KR/strings.po
@@ -0,0 +1,326 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: translations@kodi.tv\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-08-20 04:51+0000\n"
+"Last-Translator: Minho Park \n"
+"Language-Team: Korean \n"
+"Language: ko_KR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Weblate 4.7.2\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr "사용자 정의 라이브러리 노드를 관리합니다."
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr "사용자 라이브러리 노드를 생성하고 편집합니다."
+
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "콘텐츠 추가..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "경로 추가..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "순서 추가..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "한도 추가..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "그룹화 추가..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "규칙 추가..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "새 노드..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "새 상위 노드..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr "라이브러리 노드를 기본값으로 초기화..."
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr "구성요소 추가..."
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr "수동으로 편집..."
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "비디오 라이브러리"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "음악 라이브러리"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "삭제"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "레이블 수정"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "아이콘 편집"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "아이콘 찾기"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr "노드 이동"
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "가시성 수정"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "메인 메뉴에 추가"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "값 찾아보기"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "콘텐츠"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "순서"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "그룹화"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "한계"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "경로"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "규칙"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr "일치"
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "이름 설정"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "가시성 조건 설정"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr "순서 인덱스 설정"
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr "새 상위 노드 이름"
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "기본값으로 시작"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "항목"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "운영자"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "값"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "콘텐츠 종류"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "콘텐츠"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "그룹"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "한계"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "경로"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "아이콘"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "정렬순서"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "방향"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "새 노느 이름"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr "값 검색 중..."
+
+msgctxt "#30318"
+msgid "Property"
+msgstr "속성"
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr "기본 라이브러리 노드를 복사할 수 없습니다"
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr "이 노드를 삭제하겠습니까?"
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr "모든 노드를 초기화 하겠습니까?"
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "이 속성을 삭제할까요?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr "이 노드에 아직 순서, 한계 또는 통제 매개변수가 있는 동안에는 컨텐트 매개변수를 삭제할 수 없습니다."
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "이 규칙을 삭제하겠습니까?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr "순서에는 컨텐트 매개변수가 필요합니다"
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr "이 구성 요소를 삭제할까요?"
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr "사용자 정의 속성..."
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr "선택할 수 있는 옵션이 없습니다"
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr "플러그인 목록을 가져오는 중..."
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr "연결 경로"
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr "장르 ID"
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr "국가 ID"
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr "스튜디오 ID"
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr "감독 ID"
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr "연기자 ID"
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr "세트 ID"
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr "태그 ID"
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr "TV 쇼 ID"
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr "아티스트 ID"
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr "앨범 ID"
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr "역할 ID"
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr "노래 ID"
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr "앨범 아티스트만"
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr "싱글 보기"
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr "플러그인..."
+
+#~ msgctxt "#30104"
+#~ msgid "Edit order"
+#~ msgstr "순서 편집"
diff --git a/plugin.library.node.editor/resources/language/resource.language.lt_LT/strings.po b/plugin.library.node.editor/resources/language/resource.language.lt_LT/strings.po
new file mode 100644
index 0000000000..e1adcd753b
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.lt_LT/strings.po
@@ -0,0 +1,325 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Lithuanian (http://www.transifex.com/projects/p/xbmc-addons/language/lt/)\n"
+"Language: lt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr "Tvarkykite savus bibliotekos mazgus."
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr "Sukurkite ir keiskite savus bibliotekos mazgus."
+
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Pridėti turinį..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Pridėti kelią..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Pridėti rikiavimą..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "Pridėti apribojimą..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "ridėti grupavimą..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Pridėti taisyklę..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "Naujas mazgą..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "Naujas pagrindinis mazgas..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr "Atstatyti numatytuosius bibliotekos mazgus..."
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Vaizdo biblioteka"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Muzikos biblioteka"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Šalinti"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Keisti etiketę"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Keisti piktogramą"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Parinkti piktogramą"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Keisti matomumą"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Pridėti į pagrindinį meniu"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Parinkti reikšmę"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Turinys"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Rikiavimas"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Grupavimas"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Apribojimas"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Kelias"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Taisyklė"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Nustatyti vardą"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "Nustatyti matomumo sąlygą"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr "Nustatyti rikiavimo indeksą"
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr "Naujo pagrindinio mazgo vardas"
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "Pradėti su numatytomis reikšmėmis"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Laukas"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operatorius"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Reikšmė"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Turinio tipas"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Turinys"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grupė"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "Apribojimas"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Kelias"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Piktograma"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Rikiuoti pagal"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Kryptis"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "Naujo mazgo vardas"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr "Neįmanoma nukopijuoti numatytųjų bibliotekos mazgų"
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr "Ar tikrai norite pašalinti šį mazgą?"
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr "Ar tikrai norite atstatyti visus mazgus?"
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "Ar tikrai norite pašalinti šią savybę?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr "Turinio parametras negali būti pašalintas, kol mazgas vis dar turi rikiavimo, apribojimo ar taisyklės parametrą."
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "Ar tikrai norite pašalinti šią taisyklę?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr "Rikiavimui reikalingas turinio parametras"
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
+
+#~ msgctxt "#30104"
+#~ msgid "Edit order"
+#~ msgstr "Keisti rikiavimą"
diff --git a/plugin.library.node.editor/resources/language/resource.language.lv_LV/strings.po b/plugin.library.node.editor/resources/language/resource.language.lv_LV/strings.po
new file mode 100644
index 0000000000..518bccc936
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.lv_LV/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Latvian (http://www.transifex.com/projects/p/xbmc-addons/language/lv/)\n"
+"Language: lv\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Dzēst"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Labot etiķeti"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Pārlūkot ikonu"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Ceļš"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grupa"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Ceļš"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Sakārto pēc"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.mk_MK/strings.po b/plugin.library.node.editor/resources/language/resource.language.mk_MK/strings.po
new file mode 100644
index 0000000000..6f070ac8a0
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.mk_MK/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Macedonian (http://www.transifex.com/projects/p/xbmc-addons/language/mk/)\n"
+"Language: mk\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Избриши"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Уреди натпис"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Барај икона"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Патека"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Група"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Патека"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Подреди по"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.ml_IN/strings.po b/plugin.library.node.editor/resources/language/resource.language.ml_IN/strings.po
new file mode 100644
index 0000000000..84cf0bdd8e
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.ml_IN/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Malayalam (http://www.transifex.com/projects/p/xbmc-addons/language/ml/)\n"
+"Language: ml\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "കളയുക"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr ""
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr ""
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr ""
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr ""
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr ""
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.mn_MN/strings.po b/plugin.library.node.editor/resources/language/resource.language.mn_MN/strings.po
new file mode 100644
index 0000000000..5fde5ff1b3
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.mn_MN/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Mongolian (Mongolia) (http://www.transifex.com/projects/p/xbmc-addons/language/mn_MN/)\n"
+"Language: mn_MN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Устгах"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr ""
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr ""
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Групп"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr ""
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr ""
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.ms_MY/strings.po b/plugin.library.node.editor/resources/language/resource.language.ms_MY/strings.po
new file mode 100644
index 0000000000..79523b4cc6
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.ms_MY/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Malay (http://www.transifex.com/projects/p/xbmc-addons/language/ms/)\n"
+"Language: ms\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Pustaka Video"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Pustaka Muzik"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Hapus"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Sunting label"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Sunting ikon"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Kandungan"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Laluan"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Peraturan"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Kandungan"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Kumpulan"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Laluan"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Ikon"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr ""
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Arah"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.mt_MT/strings.po b/plugin.library.node.editor/resources/language/resource.language.mt_MT/strings.po
new file mode 100644
index 0000000000..f25d643efb
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.mt_MT/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Maltese (http://www.transifex.com/projects/p/xbmc-addons/language/mt/)\n"
+"Language: mt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : n==0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 3);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Ħassar"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Biddel l-Label"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr ""
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr ""
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr ""
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Issortja"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.my_MM/strings.po b/plugin.library.node.editor/resources/language/resource.language.my_MM/strings.po
new file mode 100644
index 0000000000..b88aa914d6
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.my_MM/strings.po
@@ -0,0 +1,323 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-05-07 12:40+0000\n"
+"Last-Translator: Christian Gade \n"
+"Language-Team: Burmese \n"
+"Language: my_MM\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Weblate 4.6.1\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "ဖျက်ရန်"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr ""
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "လမ်းကြောင်း"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "အုပ်စု"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "လမ်းကြောင်း"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr ""
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.nb_NO/strings.po b/plugin.library.node.editor/resources/language/resource.language.nb_NO/strings.po
new file mode 100644
index 0000000000..927b8c037f
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.nb_NO/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Norwegian (http://www.transifex.com/projects/p/xbmc-addons/language/no/)\n"
+"Language: no\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Videobibliotek"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Musikk Bibliotek"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Slett"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Rediger plateselskap"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Bla etter ikon"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Sti"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Verdi"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Gruppe"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Sti"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Ikon"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Sorter etter"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Retning"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.nl_NL/strings.po b/plugin.library.node.editor/resources/language/resource.language.nl_NL/strings.po
new file mode 100644
index 0000000000..1e9b081be8
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.nl_NL/strings.po
@@ -0,0 +1,326 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: translations@kodi.tv\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-08-04 13:29+0000\n"
+"Last-Translator: Christian Gade \n"
+"Language-Team: Dutch \n"
+"Language: nl_NL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.7.2\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr "Beheer aangepaste bibliotheek nodes."
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr "Creëer en wijzig aangepaste bibliotheek nodes."
+
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Toevoegen inhoud..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Toevoegen pad..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Toevoegen volgorde..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "toevoegen limiet..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "toevoegen groepering..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Toevoegen regel..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "Nieuwe node..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "Nieuw hoofd node..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr "Herstel bibliotheek nodes naar standaardwaarden..."
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Videobibliotheek"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Muziekbibliotheek"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Verwijderen"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Label aanpassen"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Wijzig icoon"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Zoek naar icoon"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Aanpassen zichtbaarheid"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Toevoegen aan het startmenu"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Zoek naar waarde"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Inhoud"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Volgorde"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Groepering"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Limiet"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Locatie"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Regel"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Instellen naam"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "Instellen zichtbaarheid conditie"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr "Instellen volgorde index"
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr "Naam van nieuwe hoofd node"
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "Start met standaardwaarden"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Veld"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operator"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Waarde"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Inhoud type"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Inhoud"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Groep"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "Limiet"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Locatie"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Icoon"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Sorteren op"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Richting"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "Naam van nieuwe node"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr "Niet gelukt standaard bibliotheek nodes te kopiëren"
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr "Weet u zeker dat u deze node wilt verwijderen?"
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr "Wat u zeker dat u alle nodes wilt herstellen?"
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "Wet u zeker dat u eigendom wilt verwijderen?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr "Inhoudsparameter kan niet worden verwijder omdat deze node nog steeds een volgorde, limiet of regelparameter heeft."
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "Wet u zeker dat u deze regel wilt verwijderen?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr "Volgorde vereist een inhoudsparameter"
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
+
+#~ msgctxt "#30104"
+#~ msgid "Edit order"
+#~ msgstr "Wijzig volgorde"
diff --git a/plugin.library.node.editor/resources/language/resource.language.pl_PL/strings.po b/plugin.library.node.editor/resources/language/resource.language.pl_PL/strings.po
new file mode 100644
index 0000000000..3658d1f4a1
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.pl_PL/strings.po
@@ -0,0 +1,326 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: translations@kodi.tv\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-08-04 13:29+0000\n"
+"Last-Translator: Marek Adamski \n"
+"Language-Team: Polish \n"
+"Language: pl_PL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"X-Generator: Weblate 4.7.2\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr "Zarządzanie niestandardowymi węzłami biblioteki."
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr "Tworzenie i edytowanie niestandardowych węzłów biblioteki."
+
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Dodaj zawartość..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Dodaj ścieżkę..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Dodaj kolejność..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "Dodaj ograniczenie..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "Dodaj grupowanie..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Dodaj regułę..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "Dodaj węzeł..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "Nowy węzeł nadrzędny..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr "Przywróć domyśle węzły biblioteki..."
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr "Dodaj komponent..."
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr "Edytuj ręcznie..."
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Biblioteka wideo"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Biblioteka muzyki"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Usuń"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Edytuj etykietę"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Edytuj ikonę"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Wybierz ikonę"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr "Przenieś węzeł"
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Edytuj widoczność"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Dodaj do menu startowego"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Wybierz wartość"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Zawartość"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Kolejność"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Grupowanie"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Ograniczenie"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Ścieżka"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Reguła"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr "Dopasowanie"
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Ustaw nazwę"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "Ustaw warunek widoczności"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr "Ustaw indeks kolejności"
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr "Nazwa nowego węzła nadrzędnego"
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "Rozpocznij z domyślnymi"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Pole"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operator"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Wartość"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Typ zawartości"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Zawartość"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grupa"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "Ograniczenie"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Ścieżka"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Ikona"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Sortuj wg"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Kierunek"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "Nazwa nowego węzła"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr "Pobieranie wartości..."
+
+msgctxt "#30318"
+msgid "Property"
+msgstr "Własność"
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr "Nieudane kopiowanie domyślnych węzłów biblioteki"
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr "Jesteś pewien, że chcesz usunąć ten węzeł?"
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr "Jesteś pewien, że chcesz wyczyścić wszystkie węzły?"
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "Jesteś pewien, że chcesz usunąć tę właściwość?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr "Parametr zawartości nie może zostać usunięty, gdy ten węzeł ciągle ma parametr kolejności, ograniczenia lub regułę."
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "Jesteś pewien, że chcesz usunąć tę regułę?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr "Kolejność wymaga parametru zawartości"
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr "Czy na pewno chcesz usunąć ten komponent?"
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr "Własność niestandardowa..."
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr "Nie znaleziono opcji do wyboru"
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr "Pobieranie listy wtyczek..."
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr "Ścieżka łącza tutaj"
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr "Identyfikator gatunku"
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr "Identyfikator kraju"
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr "Identyfikator studia"
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr "Identyfikator reżysera"
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr "Identyfikator aktora"
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr "Identyfikator zestawu"
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr "Identyfikator znacznika"
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr "Identyfikator serialu"
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr "Identyfikator artysty"
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr "Identyfikator albumu"
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr "Identyfikator roli"
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr "Identyfikator utworu"
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr "Tylko wykonawcy albumów"
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr "Pokazuj single"
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr "Wtyczka..."
+
+#~ msgctxt "#30104"
+#~ msgid "Edit order"
+#~ msgstr "Edytuj kolejność"
diff --git a/plugin.library.node.editor/resources/language/resource.language.pt_BR/strings.po b/plugin.library.node.editor/resources/language/resource.language.pt_BR/strings.po
new file mode 100644
index 0000000000..7fc1eba14e
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.pt_BR/strings.po
@@ -0,0 +1,326 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: translations@kodi.tv\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-12-08 00:58+0000\n"
+"Last-Translator: Fabio \n"
+"Language-Team: Portuguese (Brazil) \n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n > 1;\n"
+"X-Generator: Weblate 4.9.1\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr "Gerenciar nós customizados da biblioteca."
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr "Crie e edite nós customizados na biblioteca."
+
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Adicionar conteúdo..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Adicionar caminho..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Adicionar ordem..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "Adicionar limite..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "Adicionar agrupando..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Adicionar regra..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "Novo nó..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "Novo nó principal..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr "Resetar os nós da biblioteca para valores padrões..."
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr "Adicionar componente..."
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr "Editar manualmente..."
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Coleção de Vídeo"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Coleção de Músicas"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Delete"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Editar etiqueta"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Editar ícone"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Procurar por logo do canal"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr "Mover nó"
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Editar visibilidade"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Adicionar ao menu principal"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Procurar pelo valor"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Conteúdo"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Ordem"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Agrupando"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Limite"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Caminho"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Regra"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr "Corresponder"
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Defina nome"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "Defina condição de visibilidade"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr "Definir ordem de indexação"
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr "Nome do novo nó principal"
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "Iniciar com valores padrões"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Campo"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operador"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Valor"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Tipo do Conteúdo"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Conteúdo"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grupo"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "Limite"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Caminho"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Ícone"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Ordenar por"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Direção"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "Nome do novo nó"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr "Obtendo valores..."
+
+msgctxt "#30318"
+msgid "Property"
+msgstr "Propriedades"
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr "Não foi possível copiar os nós de biblioteca padrão"
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr "Tem certeza que deseja deletar este nó?"
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr "Tem certeza que deseja resetar todos os nós?"
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "Tem certeza que deseja deletar esta propriedade?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr "Parâmetro do conteúdo não pode ser excluído enquanto esse nó ainda tiver um parâmetro de ordem, limite ou regra."
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "Tem certeza que deseja deletar esta regra?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr "Ordenar requer um parâmetro de Conteúdo"
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr "Tem certeza de que deseja excluir este componente?"
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr "Propriedade personalizada..."
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr "Não foram encontradas opções para seleção"
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr "Obtendo listas de plug-ins..."
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr "Vincular caminho a este"
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr "ID Gênero"
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr "ID País"
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr "ID Estúdio"
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr "ID Diretor"
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr "ID Ator"
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr "ID Set"
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr "ID Tag"
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr "ID Série"
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr "ID Artista"
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr "ID Álbum"
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr "ID Papel"
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr "ID Música"
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr "Apenas artistas do álbum"
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr "Mostrar singles"
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr "Plugin..."
+
+#~ msgctxt "#30104"
+#~ msgid "Edit order"
+#~ msgstr "Editar ordem"
diff --git a/plugin.library.node.editor/resources/language/resource.language.pt_PT/strings.po b/plugin.library.node.editor/resources/language/resource.language.pt_PT/strings.po
new file mode 100644
index 0000000000..7994fe2c8d
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.pt_PT/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Portuguese (http://www.transifex.com/projects/p/xbmc-addons/language/pt/)\n"
+"Language: pt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Biblioteca de Vídeo"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Biblioteca de Música"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Apagar"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Editar etiqueta"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Procurar ícone"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Caminho"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Campo"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operador"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Valor"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grupo"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Caminho"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Ícone"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Ordenar"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Direcção"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.ro_RO/strings.po b/plugin.library.node.editor/resources/language/resource.language.ro_RO/strings.po
new file mode 100644
index 0000000000..ed05bd9b12
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.ro_RO/strings.po
@@ -0,0 +1,325 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Romanian (http://www.transifex.com/projects/p/xbmc-addons/language/ro/)\n"
+"Language: ro\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr "Administrați noduri mediatecă personalizate"
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Adăugare conținut..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Adăugare cale..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Adăugare ordonare..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "Adăugare limită..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "Adăugare grupare..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Adăugare regulă..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "Nod nou..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "Nod părinte nou..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Mediatecă video"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Mediatecă audio"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Șterge"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Modificare etichetă"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Modificare pictogramă"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Răsfoire după pictogramă"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Modificare vizibilitate"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Adaugă la meniul principal"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Răsfoire după valoare"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Conținut"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Ordonare"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Grupare"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Limită"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Cale"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Regulă"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Definire nume"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr "Numele noului nod părinte"
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "Pornește cu cele implicite"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Câmp"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operator"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Valoare"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Tip conținut"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Conținut"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grup"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "Limită"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Cale"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Pictogramă"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Ordonează după"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Direcție"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "Numele noului nod"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "Sigur doriți să ștergeți această regulă?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
+
+#~ msgctxt "#30104"
+#~ msgid "Edit order"
+#~ msgstr "Modificare ordonare"
diff --git a/plugin.library.node.editor/resources/language/resource.language.ru_RU/strings.po b/plugin.library.node.editor/resources/language/resource.language.ru_RU/strings.po
new file mode 100644
index 0000000000..276dbf0d87
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.ru_RU/strings.po
@@ -0,0 +1,323 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: translations@kodi.tv\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-07-23 14:15+0000\n"
+"Last-Translator: vdkbsd \n"
+"Language-Team: Russian \n"
+"Language: ru_RU\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"X-Generator: Weblate 4.7.2\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr "Управление узлами пользовательских медиатек."
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr "Создание и редактирование пользовательских узлов медиатеки."
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Содержимое..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Путь..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Сортировка..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "Лимит..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "Группирование..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Добавить правило..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "Новый узел..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "Новый родительский узел..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr "Сбросить узлы медиатеки на значения по умолчанию..."
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr "Добавить компонент..."
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr "Редактировать вручную..."
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Видеотека"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Музыкальная медиатека"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Удалить"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Изменить название"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Изменить значок"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Найти значок"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr "Переместить узел"
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Редактировать видимость"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Добавить в главное меню"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Поиск значения"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Содержимое"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Сортировка"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Группирование"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Лимит"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Путь"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Правило"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr "Соответствие"
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Введите имя"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "Введите условие видимости"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr "Установите индекс сортировки"
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr "Имя нового родительского узла"
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "Начните с настроек по умолчанию"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Поле"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Оператор"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Значение"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Тип содержимого"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Содержимое"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Группа"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "Лимит"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Путь"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Иконка"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Упорядочить по"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Направление"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "Имя нового узла"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr "Получение значений..."
+
+msgctxt "#30318"
+msgid "Property"
+msgstr "Свойство"
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr "Невозможно скопировать узлы медиатеки"
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr "Вы действительно хотите удалить этот узел?"
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr "Вы действительно хотите сбросить все узлы?"
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "Вы действительно хотите удалить это свойство?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr "Параметр содержимого не может быть удален, пока в этом узле установлены значения сортировки, лимита или правила."
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "Вы действительно хотите удалить это правило?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr "Необходимо установить значение для содержания"
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr "Вы действительно хотите удалить этот компонент?"
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr "Пользовательское..."
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr "Не найдено ни одного варианта для выбора"
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr "Получение списков плагинов..."
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr "Путь по ссылке здесь"
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr "ID жанра"
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr "ID страны"
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr "ID студии"
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr "ID режиссёра"
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr "ID актёра"
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr "ID киноцикла"
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr "ID метки"
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr "ID сериала"
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr "ID исполнителя"
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr "ID альбома"
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr "ID роли"
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr "ID песни"
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr "Только исполнители альбомов"
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr "Показывать синглы"
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr "Плагин..."
diff --git a/plugin.library.node.editor/resources/language/resource.language.sk_SK/strings.po b/plugin.library.node.editor/resources/language/resource.language.sk_SK/strings.po
new file mode 100644
index 0000000000..4d9f57a4bf
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.sk_SK/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Slovak (http://www.transifex.com/projects/p/xbmc-addons/language/sk/)\n"
+"Language: sk\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Hudobná knižnica"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Vymazať"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Upraviť názov"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Nájsť cestu k logu"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Umiestnenie"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Skupina"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Umiestnenie"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Ikona"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Usporiadať podľa"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.sl_SI/strings.po b/plugin.library.node.editor/resources/language/resource.language.sl_SI/strings.po
new file mode 100644
index 0000000000..8056d99456
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.sl_SI/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Slovenian (http://www.transifex.com/projects/p/xbmc-addons/language/sl/)\n"
+"Language: sl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Izbriši"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Uredi oznako"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Prebrskajte za ikono"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Pot"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Pravilo"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Polje"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Vrednost"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Skupina"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Pot"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Ikona"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Razvrščeno po"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.sq_AL/strings.po b/plugin.library.node.editor/resources/language/resource.language.sq_AL/strings.po
new file mode 100644
index 0000000000..e6af73233f
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.sq_AL/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Albanian (http://www.transifex.com/projects/p/xbmc-addons/language/sq/)\n"
+"Language: sq\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Fshij"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Ndryshoni etiketën"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Vendndodhja"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grup"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Vendndodhja"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Porosi nga"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.sr_RS/strings.po b/plugin.library.node.editor/resources/language/resource.language.sr_RS/strings.po
new file mode 100644
index 0000000000..52952c3172
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.sr_RS/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Serbian (Cyrillic) (http://www.transifex.com/projects/p/xbmc-addons/language/sr_RS/)\n"
+"Language: sr_RS\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Избриши"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Уреди натпис"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Путања"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr ""
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Путања"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Сложи по"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.sr_RS@latin/strings.po b/plugin.library.node.editor/resources/language/resource.language.sr_RS@latin/strings.po
new file mode 100644
index 0000000000..fb25a63ee7
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.sr_RS@latin/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Serbian (http://www.transifex.com/projects/p/xbmc-addons/language/sr/)\n"
+"Language: sr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Izbriši"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Uredi natpis"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Putanja"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr ""
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Putanja"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Ikone"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Složi po"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.sv_SE/strings.po b/plugin.library.node.editor/resources/language/resource.language.sv_SE/strings.po
new file mode 100644
index 0000000000..1c8950d2d3
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.sv_SE/strings.po
@@ -0,0 +1,323 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: translations@kodi.tv\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-10-25 09:53+0000\n"
+"Last-Translator: Prahlis \n"
+"Language-Team: Swedish \n"
+"Language: sv_SE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.8\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr "Hantera anpassade biblioteksnoder."
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr "Skapa och redigera anpassade biblioteksnoder."
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Lägg till innehåll..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Lägg till sökväg..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "Lägg till sortering..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "Lägg till begränsning..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "Lägg till gruppering..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "Lägg till regel..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "Ny nod..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "Ny överliggande nod..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr "Återställ biblioteksnoder till standard..."
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr "Lägg till komponent..."
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr "Redigera manuellt..."
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Videobibliotek"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Musikbibliotek"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Ta bort"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Ändra namn"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Redigera ikon"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Bläddra bland ikoner"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr "Flytta nod"
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "Redigera synlighet"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "Lägg till i huvudmenyn"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "Bläddra efter värde"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "Innehåll"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "Sortera"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "Gruppering"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "Gräns"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Sökväg"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Regel"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr "Matchning"
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Välj namn"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "Välj synlighetsvillkor"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Fält"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "Operatör"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "Värde"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Innehåll"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grupp"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Sökväg"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Ikon"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Sortera efter"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Riktning"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.ta_IN/strings.po b/plugin.library.node.editor/resources/language/resource.language.ta_IN/strings.po
new file mode 100644
index 0000000000..553b9adf01
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.ta_IN/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Tamil (India) (http://www.transifex.com/projects/p/xbmc-addons/language/ta_IN/)\n"
+"Language: ta_IN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "நீக்கு"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "சிட்டை திருத்து"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "உருவத்திற்காக உலாவுக"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "பாதை"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "குழு"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "பாதை"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "வரிசைபடி"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.th_TH/strings.po b/plugin.library.node.editor/resources/language/resource.language.th_TH/strings.po
new file mode 100644
index 0000000000..33dd8b379c
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.th_TH/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Thai (http://www.transifex.com/projects/p/xbmc-addons/language/th/)\n"
+"Language: th\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "แฟ้ม วิดีโอ"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "คลังเพลง"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "ลบ"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "แก้ป้ายชื่อ"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "เรียกดูไอคอน"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "เส้นทาง"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "กลุ่ม"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "เส้นทาง"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "ไอคอน"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "เรียงลำดับตาม"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.tr_TR/strings.po b/plugin.library.node.editor/resources/language/resource.language.tr_TR/strings.po
new file mode 100644
index 0000000000..8797702f2d
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.tr_TR/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Turkish (http://www.transifex.com/projects/p/xbmc-addons/language/tr/)\n"
+"Language: tr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Video Kitaplığı"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Müzik Kitaplığı"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Sil"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Etiketi düzenle"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "Simgeyi Düzenle"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Simgeye gözat"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Yol"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "Kural"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "Alan"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "İşletici"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Grup"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Yol"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "Simge"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Sırala"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "Yön"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.uk_UA/strings.po b/plugin.library.node.editor/resources/language/resource.language.uk_UA/strings.po
new file mode 100644
index 0000000000..bc76328f0f
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.uk_UA/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Ukrainian (http://www.transifex.com/projects/p/xbmc-addons/language/uk/)\n"
+"Language: uk\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "Бібліотека відео"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "Бібліотека музики"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Видалити"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Змінити назву"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "Вибрати значок"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Шлях"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Група"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Шлях"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "Упорядкувати за"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.uz_UZ/strings.po b/plugin.library.node.editor/resources/language/resource.language.uz_UZ/strings.po
new file mode 100644
index 0000000000..eba376e36f
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.uz_UZ/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Uzbek (http://www.transifex.com/projects/p/xbmc-addons/language/uz/)\n"
+"Language: uz\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "O'chirish"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr ""
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Yo'l"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Guruh"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Yo'l"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr ""
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.vi_VN/strings.po b/plugin.library.node.editor/resources/language/resource.language.vi_VN/strings.po
new file mode 100644
index 0000000000..9a2630a236
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.vi_VN/strings.po
@@ -0,0 +1,323 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: translations@kodi.tv\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-08-20 04:51+0000\n"
+"Last-Translator: Nguyễn Trung Hậu \n"
+"Language-Team: Vietnamese \n"
+"Language: vi_VN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Weblate 4.7.2\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr "Quản lý các nút thư viện tùy chỉnh."
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "Thêm nội dung..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "Thêm đường dẫn..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr ""
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "Xóa"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "Nhãn sửa"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "Đường dẫn"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "Đặt tên"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "Bắt đầu theo mặc định"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "Kiểu nội dung"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "Nội dung"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "Nhóm"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "Đường dẫn"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr ""
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/language/resource.language.zh_CN/strings.po b/plugin.library.node.editor/resources/language/resource.language.zh_CN/strings.po
new file mode 100644
index 0000000000..c89ecde1e6
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.zh_CN/strings.po
@@ -0,0 +1,323 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: translations@kodi.tv\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2021-09-17 05:30+0000\n"
+"Last-Translator: taxigps \n"
+"Language-Team: Chinese (China) \n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Weblate 4.8\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr "管理自定义资料库节点。"
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr "创建和编辑自定义资料库节点。"
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr "添加内容..."
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr "添加路径..."
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr "添加顺序..."
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr "添加限制..."
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr "添加组..."
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr "添加规则..."
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr "新节点..."
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr "新父节点..."
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr "将资料库节点重置为默认值..."
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr "添加部件..."
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr "人工编辑..."
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "视频资料库"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "音乐资料库"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "删除"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "编辑标签"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr "编辑图标"
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "浏览图标"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr "移动节点"
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr "编辑可见性"
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr "添加到主菜单"
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr "浏览价值"
+
+msgctxt "#30200"
+msgid "Content"
+msgstr "内容"
+
+msgctxt "#30201"
+msgid "Order"
+msgstr "顺序"
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr "组"
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr "限制"
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "路径"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr "规则"
+
+msgctxt "#30206"
+msgid "Match"
+msgstr "匹配"
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr "设置名称"
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr "设置可见性条件"
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr "设置排序索引"
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr "新父节点名称"
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr "从默认值开始"
+
+msgctxt "#30305"
+msgid "Field"
+msgstr "域"
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr "算子"
+
+msgctxt "#30307"
+msgid "Value"
+msgstr "值"
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr "内容类型"
+
+msgctxt "#30309"
+msgid "Content"
+msgstr "内容"
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "组"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr "限制"
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "路径"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr "图标"
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "排序"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr "方向"
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr "新节点名称"
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr "检索值..."
+
+msgctxt "#30318"
+msgid "Property"
+msgstr "属性"
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr "无法复制默认资料库节点"
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr "确定删除此节点吗?"
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr "确定重置所有节点吗?"
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr "确定删除此属性吗?"
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr "当此节点仍具有排序、限制或规则参数时,无法删除内容参数。"
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr "确定删除此规则吗?"
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr "排序需要一个内容参数"
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr "确定删除此部件吗?"
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr "自定义属性..."
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr "找不到可供选择的选项"
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr "正获取插件列表..."
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr "链接路径到此处"
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr "类型 ID"
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr "国家 ID"
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr "出品人 ID"
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr "导演 ID"
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr "演员 ID"
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr "电影集 ID"
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr "标签 ID"
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr "剧集 ID"
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr "歌手 ID"
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr "专辑 ID"
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr "角色 ID"
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr "歌曲 ID"
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr "仅专辑歌手"
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr "显示单曲"
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr "插件..."
diff --git a/plugin.library.node.editor/resources/language/resource.language.zh_TW/strings.po b/plugin.library.node.editor/resources/language/resource.language.zh_TW/strings.po
new file mode 100644
index 0000000000..e3b8a22cf9
--- /dev/null
+++ b/plugin.library.node.editor/resources/language/resource.language.zh_TW/strings.po
@@ -0,0 +1,322 @@
+# Kodi Media Center language file
+# Addon Name: Library Node Editor
+# Addon id: plugin.library.node.editor
+# Addon Provider: Unfledged, Team-Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: Chinese (Traditional) (http://www.transifex.com/projects/p/xbmc-addons/language/zh_TW/)\n"
+"Language: zh_TW\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgctxt "Addon Summary"
+msgid "Manage custom library nodes."
+msgstr ""
+
+msgctxt "Addon Description"
+msgid "Create and edit custom library nodes."
+msgstr ""
+
+# Menu items
+msgctxt "#30000"
+msgid "Add content..."
+msgstr ""
+
+msgctxt "#30001"
+msgid "Add path..."
+msgstr ""
+
+msgctxt "#30002"
+msgid "Add order..."
+msgstr ""
+
+msgctxt "#30003"
+msgid "Add limit..."
+msgstr ""
+
+msgctxt "#30004"
+msgid "Add grouping..."
+msgstr ""
+
+msgctxt "#30005"
+msgid "Add rule..."
+msgstr ""
+
+msgctxt "#30006"
+msgid "New node..."
+msgstr ""
+
+msgctxt "#30007"
+msgid "New parent node..."
+msgstr ""
+
+msgctxt "#30008"
+msgid "Reset library nodes to default..."
+msgstr ""
+
+msgctxt "#30009"
+msgid "Add component..."
+msgstr ""
+
+msgctxt "#30010"
+msgid "Manually edit..."
+msgstr ""
+
+msgctxt "#30091"
+msgid "Video Library"
+msgstr "影片資料庫"
+
+msgctxt "#30092"
+msgid "Music Library"
+msgstr "音樂資料庫"
+
+msgctxt "#30100"
+msgid "Delete"
+msgstr "刪除"
+
+msgctxt "#30101"
+msgid "Edit label"
+msgstr "編輯標籤"
+
+msgctxt "#30102"
+msgid "Edit icon"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Browse for icon"
+msgstr "瀏覽圖示"
+
+msgctxt "#30104"
+msgid "Move node"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Edit visibility"
+msgstr ""
+
+msgctxt "#30106"
+msgid "Add to main menu"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Browse for value"
+msgstr ""
+
+msgctxt "#30200"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Order"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Grouping"
+msgstr ""
+
+msgctxt "#30203"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Path"
+msgstr "路徑"
+
+msgctxt "#30205"
+msgid "Rule"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Match"
+msgstr ""
+
+msgctxt "#30300"
+msgid "Set name"
+msgstr ""
+
+msgctxt "#30301"
+msgid "Set visibility condition"
+msgstr ""
+
+msgctxt "#30302"
+msgid "Set order index"
+msgstr ""
+
+msgctxt "#30303"
+msgid "Name of new parent node"
+msgstr ""
+
+msgctxt "#30304"
+msgid "Start with defaults"
+msgstr ""
+
+msgctxt "#30305"
+msgid "Field"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Operator"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Value"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Content"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Group"
+msgstr "群組"
+
+msgctxt "#30311"
+msgid "Limit"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Path"
+msgstr "路徑"
+
+msgctxt "#30313"
+msgid "Icon"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Order by"
+msgstr "排序方式"
+
+msgctxt "#30315"
+msgid "Direction"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Name of new node"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Retrieving values..."
+msgstr ""
+
+msgctxt "#30318"
+msgid "Property"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Unable to copy default library nodes"
+msgstr ""
+
+msgctxt "#30401"
+msgid "Are you sure you want to delete this node?"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Are you sure you want to reset all nodes?"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Are you sure you want to delete this property?"
+msgstr ""
+
+msgctxt "#30404"
+msgid "Content parameter can't be deleted whilst this node still has an Order, Limit or Rule parameter."
+msgstr ""
+
+msgctxt "#30405"
+msgid "Are you sure you want to delete this rule?"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Order requires a Content parameter"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Are you sure you want to delete this component?"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Custom property..."
+msgstr ""
+
+msgctxt "#30409"
+msgid "No options to select from were found"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Getting plugin listings..."
+msgstr ""
+
+msgctxt "#30411"
+msgid "Link path to here"
+msgstr ""
+
+msgctxt "#30500"
+msgid "Genre ID"
+msgstr ""
+
+msgctxt "#30501"
+msgid "Country ID"
+msgstr ""
+
+msgctxt "#30502"
+msgid "Studio ID"
+msgstr ""
+
+msgctxt "#30503"
+msgid "Director ID"
+msgstr ""
+
+msgctxt "#30504"
+msgid "Actor ID"
+msgstr ""
+
+msgctxt "#30505"
+msgid "Set ID"
+msgstr ""
+
+msgctxt "#30506"
+msgid "Tag ID"
+msgstr ""
+
+msgctxt "#30507"
+msgid "TV Show ID"
+msgstr ""
+
+msgctxt "#30508"
+msgid "Artist ID"
+msgstr ""
+
+msgctxt "#30509"
+msgid "Album ID"
+msgstr ""
+
+msgctxt "#30510"
+msgid "Role ID"
+msgstr ""
+
+msgctxt "#30511"
+msgid "Song ID"
+msgstr ""
+
+msgctxt "#30512"
+msgid "Album artists only"
+msgstr ""
+
+msgctxt "#30513"
+msgid "Show singles"
+msgstr ""
+
+msgctxt "#30514"
+msgid "Plugin..."
+msgstr ""
diff --git a/plugin.library.node.editor/resources/lib/__init__.py b/plugin.library.node.editor/resources/lib/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugin.library.node.editor/resources/lib/addon.py b/plugin.library.node.editor/resources/lib/addon.py
new file mode 100644
index 0000000000..5b2e1362ad
--- /dev/null
+++ b/plugin.library.node.editor/resources/lib/addon.py
@@ -0,0 +1,836 @@
+# coding=utf-8
+import os, sys, shutil, unicodedata, re, types
+
+from resources.lib.common import *
+
+from html.entities import name2codepoint
+from urllib.parse import parse_qs
+from urllib.parse import quote, unquote
+
+import xbmc, xbmcplugin, xbmcgui, xbmcvfs
+import xml.etree.ElementTree as xmltree
+import urllib
+from unidecode import unidecode
+
+from traceback import print_exc
+import json
+
+from resources.lib import rules, pathrules, viewattrib, orderby, moveNodes
+
+# character entity reference
+CHAR_ENTITY_REXP = re.compile('&(%s);' % '|'.join(name2codepoint))
+
+# decimal character reference
+DECIMAL_REXP = re.compile('(\d+);')
+
+# hexadecimal character reference
+HEX_REXP = re.compile('([\da-fA-F]+);')
+
+REPLACE1_REXP = re.compile(r'[\']+')
+REPLACE2_REXP = re.compile(r'[^-a-z0-9]+')
+REMOVE_REXP = re.compile('-{2,}')
+
+
+
+class Main:
+ # MAIN ENTRY POINT
+ def __init__(self, params, ltype, rule, attrib, pathrule, orderby):
+
+ self._parse_argv()
+ self.ltype = ltype
+ self.PARAMS = params
+ self.RULE = rule
+ self.ATTRIB = attrib
+ self.PATHRULE = pathrule
+ self.ORDERBY = orderby
+ # If there are no custom library nodes in the profile directory, copy them from the Kodi install
+ targetDir = os.path.join( xbmcvfs.translatePath( "special://profile" ), "library", ltype )
+ if True:
+ if not os.path.exists( targetDir ):
+ xbmcvfs.mkdirs( targetDir )
+ originDir = os.path.join( xbmcvfs.translatePath( "special://xbmc" ), "system", "library", ltype )
+ dirs, files = xbmcvfs.listdir( originDir )
+ self.copyNode( dirs, files, targetDir, originDir )
+ else:
+ xbmcgui.Dialog().ok(ADDONNAME, LANGUAGE( 30400 ) )
+ print_exc
+ xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+ return
+ # Create data if not exists
+ if not os.path.exists(DATAPATH):
+ xbmcvfs.mkdir(DATAPATH)
+ if "type" in self.PARAMS:
+ # We're performing a specific action
+ if self.PARAMS[ "type" ] == "delete":
+ message = LANGUAGE( 30401 )
+ actionpath = unquote(self.PARAMS["actionPath"])
+ if self.PARAMS[ "actionPath" ] == targetDir:
+ # Ask the user is they want to reset all nodes
+ message = LANGUAGE( 30402 )
+ result = xbmcgui.Dialog().yesno(ADDONNAME, message )
+ if result:
+ if actionpath.endswith( ".xml" ):
+ # Delete single file
+ xbmcvfs.delete(actionpath)
+ else:
+ # Delete folder
+ self.RULE.deleteAllNodeRules(actionpath)
+ shutil.rmtree(actionpath)
+ else:
+ return
+ elif self.PARAMS[ "type" ] == "deletenode":
+ result = xbmcgui.Dialog().yesno(ADDONNAME, LANGUAGE( 30403 ) )
+ if result:
+ self.changeViewElement( self.PARAMS[ "actionPath" ], self.PARAMS[ "node" ], "" )
+ elif self.PARAMS[ "type" ] == "editlabel":
+ if self.PARAMS[ "label" ].isdigit():
+ label = xbmc.getLocalizedString( int( self.PARAMS[ "label" ] ) )
+ else:
+ label = self.PARAMS[ "label" ]
+ # Get new label from keyboard dialog
+ keyboard = xbmc.Keyboard( label, LANGUAGE( 30300 ), False )
+ keyboard.doModal()
+ if ( keyboard.isConfirmed() ):
+ newlabel = keyboard.getText()
+ if newlabel != "" and newlabel != label:
+ # We've got a new label, update the xml file
+ self.changeViewElement( self.PARAMS[ "actionPath" ], "label", newlabel )
+ else:
+ return
+ elif self.PARAMS[ "type" ] == "editvisibility":
+ currentVisibility = self.getRootAttrib( self.PARAMS[ "actionPath" ], "visible" )
+ # Get new visibility from keyboard dialog
+ keyboard = xbmc.Keyboard( currentVisibility, LANGUAGE( 30301 ), False )
+ keyboard.doModal()
+ if ( keyboard.isConfirmed() ):
+ newVisibility = keyboard.getText()
+ if newVisibility != currentVisibility:
+ # We've got a new label, update the xml file
+ self.changeRootAttrib( self.PARAMS[ "actionPath" ], "visible", newVisibility )
+ else:
+ return
+ elif self.PARAMS[ "type" ] == "moveNode":
+ self.indexCounter = -1
+
+ # Get existing nodes
+ nodes = {}
+ self.listNodes( self.PARAMS[ "actionPath" ], nodes )
+
+ # Get updated order
+ newOrder = moveNodes.getNewOrder( nodes, int( self.PARAMS[ "actionItem" ] ) )
+
+ if newOrder is not None:
+ # Update the orders
+ for i, node in enumerate( newOrder, 1 ):
+ path = unquote( node[ 2 ] )
+ if node[ 3 ] == "folder":
+ path = os.path.join( unquote( node[ 2 ] ), "index.xml" )
+ self.changeRootAttrib( path, "order", str( i * 10 ) )
+
+ elif self.PARAMS[ "type" ] == "newView":
+ # Get new view name from keyboard dialog
+ keyboard = xbmc.Keyboard( "", LANGUAGE( 30316 ), False )
+ keyboard.doModal()
+ if ( keyboard.isConfirmed() ):
+ newView = keyboard.getText()
+ if newView != "":
+ # Ensure filename is unique
+ filename = self.slugify( newView.lower().replace( " ", "" ) )
+ if os.path.exists( os.path.join( self.PARAMS[ "actionPath" ], filename + ".xml" ) ):
+ count = 0
+ while os.path.exists( os.path.join( self.PARAMS[ "actionPath" ], filename + "-" + str( count ) + ".xml" ) ):
+ count += 1
+ filename = filename + "-" + str( count )
+ # Create a new xml file
+ tree = xmltree.ElementTree( xmltree.Element( "node" ) )
+ root = tree.getroot()
+ subtree = xmltree.SubElement( root, "label" ).text = newView
+ # Add any node rules
+ self.RULE.addAllNodeRules( self.PARAMS[ "actionPath" ], root )
+ # Write the xml file
+ self.indent( root )
+ xmlfile = unquote(os.path.join( self.PARAMS[ "actionPath" ], filename + ".xml" ))
+ if not os.path.exists(xmlfile):
+ with open(xmlfile, 'a'):
+ os.utime(xmlfile, None)
+ tree.write( xmlfile, encoding="UTF-8" )
+ else:
+ return
+ elif self.PARAMS[ "type" ] == "newNode":
+ # Get new node name from the keyboard dialog
+ keyboard = xbmc.Keyboard( "", LANGUAGE( 30303 ), False )
+ keyboard.doModal()
+ if ( keyboard.isConfirmed() ):
+ newNode = keyboard.getText()
+ if newNode == "":
+ return
+ # Ensure foldername is unique
+ foldername = self.slugify( newNode.lower().replace( " ", "" ) )
+ if os.path.exists( os.path.join( self.PARAMS[ "actionPath" ], foldername + os.pathsep ) ):
+ count = 0
+ while os.path.exists( os.path.join( self.PARAMS[ "actionPath" ], foldername + "-" + str( count ) + os.pathsep ) ):
+ count += 1
+ foldername = foldername + "-" + str( count )
+ foldername = unquote(os.path.join( self.PARAMS[ "actionPath" ], foldername ))
+ # Create new node folder
+ xbmcvfs.mkdir( foldername )
+ # Create a new xml file
+ tree = xmltree.ElementTree( xmltree.Element( "node" ) )
+ root = tree.getroot()
+ subtree = xmltree.SubElement( root, "label" ).text = newNode
+ # Ask user if they want to import defaults
+ if self.ltype.startswith( "video" ):
+ defaultNames = [ xbmc.getLocalizedString( 231 ), xbmc.getLocalizedString( 342 ), xbmc.getLocalizedString( 20343 ), xbmc.getLocalizedString( 20389 ) ]
+ defaultValues = [ "", "movies", "tvshows", "musicvideos" ]
+ selected = xbmcgui.Dialog().select( LANGUAGE( 30304 ), defaultNames )
+ else:
+ selected = 0
+ # If the user selected some defaults...
+ if selected != -1 and selected != 0:
+ try:
+ # Copy those defaults across
+ originDir = os.path.join( xbmcvfs.translatePath( "special://xbmc" ), "system", "library", self.ltype, defaultValues[ selected ] )
+ dirs, files = xbmcvfs.listdir( originDir )
+ for file in files:
+ if file != "index.xml":
+ xbmcvfs.copy( os.path.join( originDir, file), os.path.join( foldername, file ) )
+ # Open index.xml and copy values across
+ index = xmltree.parse( os.path.join( originDir, "index.xml" ) ).getroot()
+ if "visible" in index.attrib:
+ root.set( "visible", index.attrib.get( "visible" ) )
+ icon = index.find( "icon" )
+ if icon is not None:
+ xmltree.SubElement( root, "icon" ).text = icon.text
+ except:
+ print_exc()
+ # Write the xml file
+ self.indent( root )
+ tree.write( unquote(os.path.join( foldername, "index.xml" )), encoding="UTF-8" )
+ else:
+ return
+ elif self.PARAMS[ "type" ] == "rule":
+ # Display list of all elements of a rule
+ self.RULE.displayRule( self.PARAMS[ "actionPath" ], self.PATH, self.PARAMS[ "rule" ] )
+ return
+ elif self.PARAMS[ "type" ] == "editMatch":
+ # Editing the field the rule is matched against
+ self.RULE.editMatch( self.PARAMS[ "actionPath" ], self.PARAMS[ "rule" ], self.PARAMS[ "content"], self.PARAMS[ "default" ] )
+ elif self.PARAMS[ "type" ] == "editOperator":
+ # Editing the operator of a rule
+ self.RULE.editOperator( self.PARAMS[ "actionPath" ], self.PARAMS[ "rule" ], self.PARAMS[ "group" ], self.PARAMS[ "default" ] )
+ elif self.PARAMS[ "type" ] == "editValue":
+ # Editing the value of a rule
+ self.RULE.editValue(self.PARAMS["actionPath"], self.PARAMS[ "rule" ] )
+ elif self.PARAMS[ "type" ] == "browseValue":
+ # Browse for the new value of a rule
+ self.RULE.browse( self.PARAMS[ "actionPath" ], self.PARAMS[ "rule" ], self.PARAMS[ "match" ], self.PARAMS[ "content" ] )
+ elif self.PARAMS[ "type" ] == "deleteRule":
+ # Delete a rule
+ self.RULE.deleteRule( self.PARAMS[ "actionPath" ], self.PARAMS[ "rule" ] )
+ elif self.PARAMS[ "type" ] == "editRulesMatch":
+ # Editing whether any or all rules must match
+ self.ATTRIB.editMatch( self.PARAMS[ "actionPath" ] )
+ # --- Edit order-by ---
+ elif self.PARAMS[ "type" ] == "orderby":
+ # Display all elements of order by
+ self.ORDERBY.displayOrderBy( self.PARAMS[ "actionPath" ])
+ return
+ elif self.PARAMS[ "type" ] == "editOrderBy":
+ self.ORDERBY.editOrderBy( self.PARAMS[ "actionPath" ], self.PARAMS[ "content" ], self.PARAMS[ "default" ] )
+ elif self.PARAMS[ "type" ] == "editOrderByDirection":
+ self.ORDERBY.editDirection( self.PARAMS[ "actionPath" ], self.PARAMS[ "default" ] )
+ # --- Edit paths ---
+ elif self.PARAMS[ "type" ] == "addPath":
+ self.ATTRIB.addPath( self.PARAMS[ "actionPath" ] )
+ elif self.PARAMS[ "type" ] == "editPath":
+ self.ATTRIB.editPath( self.PARAMS[ "actionPath" ], self.PARAMS[ "value" ] )
+ elif self.PARAMS[ "type" ] == "pathRule":
+ self.PATHRULE.displayRule( self.PARAMS[ "actionPath" ], int( self.PARAMS[ "rule" ] ) )
+ return
+ elif self.PARAMS[ "type" ] == "deletePathRule":
+ self.ATTRIB.deletePathRule( self.PARAMS[ "actionPath" ], int( self.PARAMS[ "rule" ] ) )
+ elif self.PARAMS[ "type" ] == "editPathMatch":
+ # Editing the field the rule is matched against
+ self.PATHRULE.editMatch( self.PARAMS[ "actionPath" ], int( self.PARAMS[ "rule" ] ) )
+ elif self.PARAMS[ "type" ] == "editPathValue":
+ # Editing the value of a rule
+ self.PATHRULE.editValue( self.PARAMS[ "actionPath" ], int( self.PARAMS[ "rule" ] ) )
+ elif self.PARAMS[ "type" ] == "browsePathValue":
+ # Browse for the new value of a rule
+ self.PATHRULE.browse( self.PARAMS[ "actionPath" ], int( self.PARAMS[ "rule" ] ) )
+ # --- Edit other attribute of view ---
+ # > Content
+ elif self.PARAMS[ "type" ] == "editContent":
+ self.ATTRIB.editContent( self.PARAMS[ "actionPath" ], "" ) # No default to pass, yet!
+ # > Grouping
+ elif self.PARAMS[ "type" ] == "editGroup":
+ self.ATTRIB.editGroup( self.PARAMS[ "actionPath" ], self.PARAMS[ "content" ], "" )
+ # > Limit
+ elif self.PARAMS[ "type" ] == "editLimit":
+ self.ATTRIB.editLimit( self.PARAMS[ "actionPath" ], self.PARAMS[ "value" ] )
+ # > Icon (also for node)
+ elif self.PARAMS[ "type" ] == "editIcon":
+ self.ATTRIB.editIcon( self.PARAMS[ "actionPath" ], self.PARAMS[ "value" ] )
+ elif self.PARAMS[ "type" ] == "browseIcon":
+ self.ATTRIB.browseIcon( self.PARAMS[ "actionPath" ] )
+ # Refresh the listings and exit
+ xbmc.executebuiltin("Container.Refresh")
+ return
+ if self.PATH.endswith( ".xml" ):
+ self.RulesList()
+ else:
+ self.NodesList(targetDir)
+
+ def NodesList( self, targetDir ):
+ # List nodes and views
+ nodes = {}
+ self.indexCounter = -1
+ if self.PATH != "":
+ self.listNodes( self.PATH, nodes )
+ else:
+ self.listNodes( targetDir, nodes )
+ self.PATH = quote( self.PATH )
+ for i, key in enumerate( sorted( nodes ) ):
+ # 0 = Label
+ # 1 = Icon
+ # 2 = Path
+ # 3 = Type
+ # 4 = Order
+ # Localize the label
+ if nodes[ key ][ 0 ].isdigit():
+ label = xbmc.getLocalizedString( int( nodes[ key ][ 0 ] ) )
+ else:
+ label = nodes[ key ][ 0 ]
+ # Create the listitem
+ if nodes[ key ][ 3 ] == "folder":
+ listitem = xbmcgui.ListItem( label="%s >" % ( label ), label2=nodes[ key ][ 4 ] )
+ listitem.setArt({"icon": nodes[ key ][ 1 ]})
+ else:
+ listitem = xbmcgui.ListItem( label=label, label2=nodes[ key ][ 4 ] )
+ listitem.setArt({"icon": nodes[ key ][ 1 ]})
+ # Add context menu items
+ commands = []
+ commandsNode = []
+ commandsView = []
+ commandsNode.append( ( LANGUAGE(30101), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=editlabel&actionPath=" % self.ltype + os.path.join( nodes[ key ][ 2 ], "index.xml" ) + "&label=" + nodes[ key ][ 0 ] + ")" ) )
+ commandsNode.append( ( LANGUAGE(30102), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=editIcon&actionPath=" % self.ltype + os.path.join( nodes[ key ][ 2 ], "index.xml" ) + "&value=" + nodes[ key ][ 1 ] + ")" ) )
+ commandsNode.append( ( LANGUAGE(30103), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=browseIcon&actionPath=" % self.ltype + os.path.join( nodes [ key ][ 2 ], "index.xml" ) + ")" ) )
+ if self.PATH == "":
+ commandsNode.append( ( LANGUAGE(30104), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=moveNode&actionPath=" % self.ltype + targetDir + "&actionItem=" + str( i ) + ")" ) )
+ else:
+ commandsNode.append( ( LANGUAGE(30104), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=moveNode&actionPath=" % self.ltype + self.PATH + "&actionItem=" + str( i ) + ")" ) )
+ commandsNode.append( ( LANGUAGE(30105), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=editvisibility&actionPath=" % self.ltype + os.path.join( nodes[ key ][ 2 ], "index.xml" ) + ")" ) )
+ commandsNode.append( ( LANGUAGE(30100), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=delete&actionPath=" % self.ltype + nodes[ key ][ 2 ] + ")" ) )
+
+ commandsView.append( ( LANGUAGE(30101), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=editlabel&actionPath=" % self.ltype + nodes[ key ][ 2 ] + "&label=" + nodes[ key ][ 0 ] + ")" ) )
+ commandsView.append( ( LANGUAGE(30102), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=editIcon&actionPath=" % self.ltype + nodes[ key ][ 2 ] + "&value=" + nodes[ key ][ 1 ] + ")" ) )
+ commandsView.append( ( LANGUAGE(30103), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=browseIcon&actionPath=" % self.ltype + nodes[ key ][ 2 ] + ")" ) )
+ if self.PATH == "":
+ commandsView.append( ( LANGUAGE(30104), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=moveNode&actionPath=" % self.ltype + targetDir + "&actionItem=" + str( i ) + ")" ) )
+ else:
+ commandsView.append( ( LANGUAGE(30104), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=moveNode&actionPath=" % self.ltype + self.PATH + "&actionItem=" + str( i ) + ")" ) )
+ commandsView.append( ( LANGUAGE(30105), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=editvisibility&actionPath=" % self.ltype + nodes[ key ][ 2 ] + ")" ) )
+ commandsView.append( ( LANGUAGE(30100), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=delete&actionPath=" % self.ltype + nodes[ key ][ 2 ] + ")" ) )
+ if nodes[ key ][ 3 ] == "folder":
+ listitem.addContextMenuItems( commandsNode )
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), "plugin://plugin.library.node.editor?ltype=%s&path=" % self.ltype + nodes[ key ][ 2 ], listitem, isFolder=True )
+ else:
+ listitem.addContextMenuItems( commandsView )
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), "plugin://plugin.library.node.editor?ltype=%s&path=" % self.ltype + nodes[ key ][ 2 ], listitem, isFolder=True )
+ if self.PATH != "":
+ # Get any rules from the index.xml
+ rules, nextRule = self.getRules( os.path.join( unquote( self.PATH ), "index.xml" ), True )
+ rulecount = 0
+ if rules is not None:
+ for rule in rules:
+ commands = []
+ if rule[ 0 ] == "rule":
+ # 1 = field
+ # 2 = operator
+ # 3 = value (optional)
+ if len(rule) == 3:
+ translated = self.RULE.translateRule( [ rule[ 1 ], rule[ 2 ] ] )
+ else:
+ translated = self.RULE.translateRule( [ rule[ 1 ], rule[ 2 ], rule[ 3 ] ] )
+ if len(translated) == 2:
+ listitem = xbmcgui.ListItem( label="%s: %s %s" % ( LANGUAGE(30205), translated[ 0 ][ 0 ], translated[ 1 ][ 0 ] ) )
+ else:
+ listitem = xbmcgui.ListItem( label="%s: %s %s %s" % ( LANGUAGE(30205), translated[ 0 ][ 0 ], translated[ 1 ][ 0 ], translated[ 2 ][ 1 ] ) )
+ commands.append( ( LANGUAGE( 30100 ), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=deleteRule&actionPath=" % self.ltype + os.path.join( self.PATH, "index.xml" ) + "&rule=" + str( rulecount ) + ")" ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=rule&actionPath=" % self.ltype + os.path.join( self.PATH, "index.xml" ) + "&rule=" + str( rulecount )
+ rulecount += 1
+ listitem.addContextMenuItems( commands, replaceItems = True )
+ if rule[ 0 ] == "rule" or rule[ 0 ] == "order":
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=True )
+ else:
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
+ # New rule
+ xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=rule&actionPath=" % self.ltype + os.path.join( self.PATH, "index.xml" ) + "&rule=" + str( nextRule), xbmcgui.ListItem( label="* %s" %( LANGUAGE(30005) ) ), isFolder=True )
+ showReset = False
+ if self.PATH == "":
+ self.PATH = quote( targetDir )
+ showReset = True
+ # New view and node
+ xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=newView&actionPath=" % self.ltype + self.PATH, xbmcgui.ListItem( label="* %s" %( LANGUAGE(30006) ) ), isFolder=False )
+ xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=newNode&actionPath=" % self.ltype + self.PATH, xbmcgui.ListItem( label="* %s" %( LANGUAGE(30007) ) ), isFolder=False )
+ if showReset:
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), "plugin://plugin.library.node.editor?ltype=%s&type=delete&actionPath=" % self.ltype + targetDir, xbmcgui.ListItem( label="* %s" %( LANGUAGE(30008) ) ), isFolder=False )
+ xbmcplugin.setContent(int(sys.argv[1]), 'files')
+ xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+
+ def RulesList( self ):
+ # List rules for specific view
+ rules, nextRule = self.getRules( self.PATH )
+ hasContent = False
+ content = ""
+ hasOrder = False
+ hasGroup = False
+ hasLimit = False
+ hasPath = False
+ splitPath = None
+ rulecount = 0
+ if rules is not None:
+ for rule in rules:
+ commands = []
+ if rule[ 0 ] == "content":
+ # 1 = Content
+ listitem = xbmcgui.ListItem( label="%s: %s" % ( LANGUAGE(30200), self.ATTRIB.translateContent( rule[ 1 ] ) ) )
+ commands.append( ( LANGUAGE(30100), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=deletenode&actionPath=" % self.ltype + self.PATH + "&node=content)" ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=editContent&actionPath=" % self.ltype + self.PATH
+ hasContent = True
+ content = rule[ 1 ]
+ elif rule[ 0 ] == "order":
+ # 1 = orderby
+ # 2 = direction (optional?)
+ if len( rule ) == 3:
+ translate = self.ORDERBY.translateOrderBy( [ rule[ 1 ], rule[ 2 ] ] )
+ listitem = xbmcgui.ListItem( label="%s: %s (%s)" % ( LANGUAGE(30201), translate[ 0 ][ 0 ], translate[ 1 ][ 0 ] ) )
+ else:
+ translate = self.ORDERBY.translateOrderBy( [ rule[ 1 ], "" ] )
+ listitem = xbmcgui.ListItem( label="%s: %s" % ( LANGUAGE(30201), translate[ 0 ][ 0 ] ) )
+ commands.append( ( LANGUAGE(30100), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=deletenode&actionPath=" % self.ltype + self.PATH + "&node=order)" ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=orderby&actionPath=" % self.ltype + self.PATH
+ hasOrder = True
+ elif rule[ 0 ] == "group":
+ # 1 = group
+ listitem = xbmcgui.ListItem( label="%s: %s" % ( LANGUAGE(30202), self.ATTRIB.translateGroup( rule[ 1 ] ) ) )
+ commands.append( ( LANGUAGE(30100), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=deletenode&actionPath=" % self.ltype + self.PATH + "&node=group)" ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=editGroup&actionPath=" % self.ltype + self.PATH + "&value=" + rule[ 1 ] + "&content=" + content
+ hasGroup = True
+ elif rule[ 0 ] == "limit":
+ # 1 = limit
+ listitem = xbmcgui.ListItem( label="%s: %s" % ( LANGUAGE(30203), rule[ 1 ] ) )
+ commands.append( ( LANGUAGE(30100), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=deletenode&actionPath=" % self.ltype + self.PATH + "&node=limit)" ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=editLimit&actionPath=" % self.ltype + self.PATH + "&value=" + rule[ 1 ]
+ hasLimit = True
+ elif rule[ 0 ] == "path":
+ # 1 = path
+ # Split the path into components
+ splitPath = self.ATTRIB.splitPath( rule[ 1 ] )
+
+ # Add each element of the path to the list
+ for x, component in enumerate( splitPath ):
+ if x == 0:
+ # library://path/
+ listitem = xbmcgui.ListItem( label="%s: %s" % ( LANGUAGE(30204), self.ATTRIB.translatePath( component ) ) )
+ commands.append( ( LANGUAGE(30100), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=deletenode&actionPath=" % self.ltype + self.PATH + "&node=path)" ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=addPath&actionPath=" % self.ltype + self.PATH
+
+ # Get the rules
+ rules = self.PATHRULE.getRulesForPath( splitPath[ 0 ] )
+ if x != 0:
+ # Specific component
+
+ # Add the listitem generated from the last component we processed
+ listitem.addContextMenuItems( commands, replaceItems = True )
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=True )
+ commands = []
+
+ # Get the rule for this component
+ componentRule = self.PATHRULE.getMatchingRule( component, rules )
+ translatedComponent = self.PATHRULE.translateComponent( componentRule, splitPath[ x ] )
+ translatedValue = self.PATHRULE.translateValue( componentRule, splitPath, x )
+
+ listitem = xbmcgui.ListItem( label="%s: %s" % ( translatedComponent, translatedValue ) )
+ commands.append( ( LANGUAGE(30100), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=deletePathRule&actionPath=%s&rule=%d)" %( self.ltype, self.PATH, x ) ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=pathRule&actionPath=%s&rule=%d" % ( self.ltype, self.PATH, x )
+ hasPath = True
+ elif rule[ 0 ] == "rule":
+ # 1 = field
+ # 2 = operator
+ # 3 = value (optional)
+ # 4 = ruleNum
+ if len(rule) == 3:
+ translated = self.RULE.translateRule( [ rule[ 1 ], rule[ 2 ] ] )
+ else:
+ translated = self.RULE.translateRule( [ rule[ 1 ], rule[ 2 ], rule[ 3 ] ] )
+ if translated[ 2 ][ 0 ] == "|NONE|":
+ listitem = xbmcgui.ListItem( label="%s: %s %s" % ( LANGUAGE(30205), translated[ 0 ][ 0 ], translated[ 1 ][ 0 ] ) )
+ else:
+ listitem = xbmcgui.ListItem( label="%s: %s %s %s" % ( LANGUAGE(30205), translated[ 0 ][ 0 ], translated[ 1 ][ 0 ], translated[ 2 ][ 1 ] ) )
+ commands.append( ( LANGUAGE(30100), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=deleteRule&actionPath=" % self.ltype + self.PATH + "&rule=" + str( rule[ 4 ] ) + ")" ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=rule&actionPath=" % self.ltype + self.PATH + "&rule=" + str( rule[ 4 ] )
+ rulecount += 1
+ elif rule[ 0 ] == "match":
+ # 1 = value
+ listitem = xbmcgui.ListItem( label="%s: %s" % ( LANGUAGE(30206), self.ATTRIB.translateMatch( rule[ 1 ] ) ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=editRulesMatch&actionPath=%s" %( self.ltype, self.PATH )
+ hasGroup = True
+ listitem.addContextMenuItems( commands, replaceItems = True )
+ if rule[ 0 ] == "rule" or rule[ 0 ] == "order" or rule[ 0 ] == "path":
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=True )
+ else:
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
+ if not hasContent and not hasPath:
+ # Add content
+ xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=editContent&actionPath=" % self.ltype + self.PATH, xbmcgui.ListItem( label="* %s" %( LANGUAGE(30000) ) ) )
+ if not hasOrder and hasContent:
+ # Add order
+ xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=orderby&actionPath=" % self.ltype + self.PATH, xbmcgui.ListItem( label="* %s" %( LANGUAGE(30002) ) ), isFolder=True )
+ if not hasGroup and hasContent:
+ # Add group
+ xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=editGroup&actionPath=" % self.ltype + self.PATH + "&content=" + content, xbmcgui.ListItem( label="* %s" %( LANGUAGE(30004) ) ) )
+ if not hasLimit and hasContent:
+ # Add limit
+ xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=editLimit&actionPath=" % self.ltype + self.PATH + "&value=25", xbmcgui.ListItem( label="* %s" %( LANGUAGE(30003) ) ) )
+ if not hasPath and not hasContent:
+ # Add path
+ xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=addPath&actionPath=" % self.ltype + self.PATH, xbmcgui.ListItem( label="* %s" %( LANGUAGE(30001) ) ) )
+ if hasContent:
+ # Add rule
+ xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=rule&actionPath=" % self.ltype + self.PATH + "&rule=" + str( nextRule ), xbmcgui.ListItem( label="* %s" %( LANGUAGE(30005) ) ), isFolder = True )
+ if hasPath:
+ if "plugin://" not in splitPath[0][0]:
+ # Add component
+ xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=pathRule&actionPath=%s&rule=%d" % ( self.ltype, self.PATH, x + 1 ), xbmcgui.ListItem( label="* %s" %( LANGUAGE(30009) ) ), isFolder = True )
+ # Manually edit path
+ xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=editPath&actionPath=" % self.ltype + self.PATH + "&value=" + quote( rule[ 1 ] ), xbmcgui.ListItem( label="* %s" %( LANGUAGE(30010) ) ), isFolder = True )
+ xbmcplugin.setContent(int(sys.argv[1]), 'files')
+ xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+
+ def _parse_argv( self ):
+ try:
+ p = parse_qs(sys.argv[2][1:])
+ for i in p.keys():
+ p[i] = p[i][0]
+ self.PARAMS = p
+ except:
+ p = parse_qs(sys.argv[1])
+ for i in p.keys():
+ p[i] = p[i][0]
+ self.PARAMS = p
+ if "path" in self.PARAMS:
+ self.PATH = self.PARAMS[ "path" ]
+ else:
+ self.PATH = ""
+
+ def getRules( self, actionPath, justRules = False ):
+ returnVal = []
+ try:
+ # Load the xml file
+ tree = xmltree.parse( actionPath )
+ root = tree.getroot()
+ if justRules == False:
+ # Look for a 'content'
+ content = root.find( "content" )
+ if content is not None:
+ returnVal.append( ( "content", content.text ) )
+ # Look for an 'order'
+ order = root.find( "order" )
+ if order is not None:
+ if "direction" in order.attrib:
+ returnVal.append( ( "order", order.text, order.attrib.get( "direction" ) ) )
+ else:
+ returnVal.append( ( "order", order.text ) )
+ # Look for a 'group'
+ group = root.find( "group" )
+ if group is not None:
+ returnVal.append( ( "group", group.text ) )
+ # Look for a 'limit'
+ limit = root.find( "limit" )
+ if limit is not None:
+ returnVal.append( ( "limit", limit.text ) )
+ # Look for a 'path'
+ path = root.find( "path" )
+ if path is not None:
+ returnVal.append( ( "path", path.text ) )
+ # Save the current length of the returnVal - we'll insert Match here if there are two or more rules
+ currentLen = len( returnVal )
+ # Look for any rules
+ ruleNum = 0
+ if actionPath.endswith( "index.xml" ):
+ # Load the rules from RULE module
+ rules = self.RULE.getNodeRules( actionPath )
+ if rules is not None:
+ for rule in rules:
+ returnVal.append( ( "rule", rule[ 0 ], rule[ 1 ], rule[ 2 ], ruleNum ) )
+ ruleNum += 1
+ return returnVal, len( rules )
+ else:
+ return returnVal, 0
+ else:
+ rules = root.findall( "rule" )
+ # Process the rules
+ if rules is not None:
+ for rule in rules:
+ value = rule.find( "value" )
+ if value is not None and value.text is not None:
+ translated = self.RULE.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value.text ] )
+ if not self.RULE.isNodeRule( translated, actionPath ):
+ returnVal.append( ( "rule", rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value.text, ruleNum ) )
+ else:
+ translated = self.RULE.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), "" ] )
+ if not self.RULE.isNodeRule( translated, actionPath ):
+ returnVal.append( ( "rule", rule.attrib.get( "field" ), rule.attrib.get( "operator" ), "", ruleNum ) )
+ ruleNum += 1
+ # Get any current match value if there are more than two rules
+ # (if there's only one, the match value doesn't matter)
+ if ruleNum >= 2:
+ matchRules = "all"
+ match = root.find( "match" )
+ if match is not None:
+ matchRules = match.text
+ returnVal.insert( currentLen, ( "match", matchRules ) )
+ return returnVal, len( rules )
+ return returnVal, 0
+ except:
+ print_exc()
+
+ def listNodes( self, targetDir, nodes ):
+ dirs, files = xbmcvfs.listdir( targetDir )
+ for dir in dirs:
+ self.parseNode( os.path.join( targetDir, dir ), nodes )
+ for file in files:
+ self.parseItem( os.path.join( targetDir, file ), nodes )
+
+ def parseNode( self, node, nodes ):
+ # If the folder we've been passed contains an index.xml, send that file to be processed
+ if os.path.exists( os.path.join( node, "index.xml" ) ):
+ # BETA2 ONLY CODE
+ self.RULE.moveNodeRuleToAppdata( node, os.path.join( node, "index.xml" ) )
+ # /BETA2 ONLY CODE
+ self.parseItem( os.path.join( node, "index.xml" ), nodes, True, node )
+
+ def parseItem( self, file, nodes, isFolder = False, origFolder = None ):
+ if not isFolder and file.endswith( "index.xml" ):
+ return
+ try:
+ # Load the xml file
+ tree = xmltree.parse( file )
+ root = tree.getroot()
+ # Get the item index
+ if "order" in tree.getroot().attrib:
+ index = tree.getroot().attrib.get( "order" )
+ origIndex = index
+ while int( index ) in nodes:
+ index = int( index )
+ index += 1
+ index = str( index )
+ else:
+ self.indexCounter -= 1
+ index = str( self.indexCounter )
+ origIndex = "-"
+ # Get label and icon
+ label = root.find( "label" ).text
+ icon = root.find( "icon" )
+ if icon is not None:
+ icon = icon.text
+ else:
+ icon = ""
+ # Add it to our list of nodes
+ if isFolder:
+ nodes[ int( index ) ] = [ label, icon, quote( origFolder ), "folder", origIndex ]
+ else:
+ nodes[ int( index ) ] = [ label, icon, file, "item", origIndex ]
+ except:
+ print_exc()
+
+ def getViewElement( self, file, element, newvalue ):
+ try:
+ # Load the file
+ tree = xmltree.parse( file )
+ root = tree.getroot()
+ # Change the element
+ node = root.find( element )
+ if node is not None:
+ return node.text
+ else:
+ return ""
+ except:
+ print_exc()
+
+ def changeViewElement( self, file, element, newvalue ):
+ try:
+ # Load the file
+ tree = xmltree.parse( file )
+ root = tree.getroot()
+ # If the element is content, we can only delete this if there are no
+ # rules, limits, orders
+ if element == "content":
+ rule = root.find( "rule" )
+ order = root.find( "order" )
+ limit = root.find( "limit" )
+ if rule is not None or order is not None or limit is not None:
+ xbmcgui.Dialog().ok( ADDONNAME, LANGUAGE( 30404 ) )
+ return
+ # Find the element
+ node = root.find( element )
+ if node is not None:
+ # If we've been passed an empty value, delete the node
+ if newvalue == "":
+ root.remove( node )
+ else:
+ node.text = newvalue
+ else:
+ # Add a new node
+ if newvalue != "":
+ xmltree.SubElement( root, element ).text = newvalue
+ # Pretty print and save
+ self.indent( root )
+ tree.write( file, encoding="UTF-8" )
+ except:
+ print_exc()
+
+ def getRootAttrib( self, file, attrib ):
+ try:
+ # Load the file
+ tree = xmltree.parse( file )
+ root = tree.getroot()
+ # Find the element
+ if attrib in root.attrib:
+ return root.attrib.get( attrib )
+ else:
+ return ""
+ except:
+ print_exc()
+
+ def changeRootAttrib( self, file, attrib, newvalue ):
+ try:
+ # Load the file
+ tree = xmltree.parse( file )
+ root = tree.getroot()
+ # If empty newvalue, delete the attribute
+ if newvalue == "":
+ if attrib in root.attrib:
+ root.attrib.pop( attrib )
+ else:
+ # Change or add the attribute
+ root.set( attrib, newvalue )
+ # Pretty print and save
+ self.indent( root )
+ tree.write( file, encoding="UTF-8" )
+ except:
+ print_exc()
+
+ def copyNode(self, dirs, files, target, origin):
+ for file in files:
+ success = xbmcvfs.copy( os.path.join( origin, file ), os.path.join( target, file ) )
+ for dir in dirs:
+ nextDirs, nextFiles = xbmcvfs.listdir( os.path.join( origin, dir ) )
+ self.copyNode( nextDirs, nextFiles, os.path.join( target, dir ), os.path.join( origin, dir ) )
+
+ # in-place prettyprint formatter
+ def indent( self, elem, level=0 ):
+ i = "\n" + level*"\t"
+ if len(elem):
+ if not elem.text or not elem.text.strip():
+ elem.text = i + "\t"
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ for elem in elem:
+ self.indent(elem, level+1)
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ else:
+ if level and (not elem.tail or not elem.tail.strip()):
+ elem.tail = i
+
+ # Slugify functions
+ def smart_truncate(string, max_length=0, word_boundaries=False, separator=' '):
+ string = string.strip(separator)
+ if not max_length:
+ return string
+ if len(string) < max_length:
+ return string
+ if not word_boundaries:
+ return string[:max_length].strip(separator)
+ if separator not in string:
+ return string[:max_length]
+ truncated = ''
+ for word in string.split(separator):
+ if word:
+ next_len = len(truncated) + len(word) + len(separator)
+ if next_len <= max_length:
+ truncated += '{0}{1}'.format(word, separator)
+ if not truncated:
+ truncated = string[:max_length]
+ return truncated.strip(separator)
+
+ def slugify(self, text, entities=True, decimal=True, hexadecimal=True, max_length=0, word_boundary=False, separator='-', convertInteger=False):
+ # Handle integers
+ if convertInteger and text.isdigit():
+ text = "NUM-" + text
+ # decode unicode ( ??? = Ying Shi Ma)
+ text = unidecode(text)
+ # character entity reference
+ if entities:
+ text = CHAR_ENTITY_REXP.sub(lambda m: unichr(name2codepoint[m.group(1)]), text)
+ # decimal character reference
+ if decimal:
+ try:
+ text = DECIMAL_REXP.sub(lambda m: unichr(int(m.group(1))), text)
+ except:
+ pass
+ # hexadecimal character reference
+ if hexadecimal:
+ try:
+ text = HEX_REXP.sub(lambda m: unichr(int(m.group(1), 16)), text)
+ except:
+ pass
+ # translate
+ text = unicodedata.normalize('NFKD', text)
+ if sys.version_info < (3,):
+ text = text.encode('ascii', 'ignore')
+ # replace unwanted characters
+ text = REPLACE1_REXP.sub('', text.lower()) # replace ' with nothing instead with -
+ text = REPLACE2_REXP.sub('-', text.lower())
+ # remove redundant -
+ text = REMOVE_REXP.sub('-', text).strip('-')
+ # smart truncate if requested
+ if max_length > 0:
+ text = smart_truncate(text, max_length, word_boundary, '-')
+ if separator != '-':
+ text = text.replace('-', separator)
+ return text
+
+def getVideoLibraryLType():
+ json_query = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "id": 0, "method": "Settings.GetSettingValue", "params": {"setting": "myvideos.flatten"}}')
+ json_response = json.loads(json_query)
+
+ if json_response.get('result') and json_response['result'].get('value'):
+ if json_response['result']['value']:
+ return "video_flat"
+
+ return "video"
+
+def run(args):
+ log('script version %s started' % ADDONVERSION)
+ ltype = ''
+ if args[2] == '':
+ videoltype = getVideoLibraryLType()
+ xbmcplugin.addDirectoryItem( int( args[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s" %( videoltype ), xbmcgui.ListItem( label=LANGUAGE(30091) ), isFolder=True )
+ xbmcplugin.addDirectoryItem( int( args[ 1 ] ), "plugin://plugin.library.node.editor?ltype=music", xbmcgui.ListItem( label=LANGUAGE(30092) ), isFolder=True )
+ xbmcplugin.setContent(int(args[1]), 'files')
+ xbmcplugin.endOfDirectory(handle=int(args[1]))
+ else:
+ params = dict( arg.split( "=" ) for arg in args[ 2 ][1:].split( "&" ) )
+ ltype = params['ltype']
+ if ltype != '':
+ RULE = rules.RuleFunctions(ltype)
+ ATTRIB = viewattrib.ViewAttribFunctions(ltype)
+ PATHRULE = pathrules.PathRuleFunctions(ltype)
+ PATHRULE.ATTRIB = ATTRIB
+ ORDERBY = orderby.OrderByFunctions(ltype)
+ Main(params, ltype, RULE, ATTRIB, PATHRULE, ORDERBY)
+
+ log('script stopped')
diff --git a/plugin.library.node.editor/resources/lib/common.py b/plugin.library.node.editor/resources/lib/common.py
new file mode 100644
index 0000000000..fdab1c4869
--- /dev/null
+++ b/plugin.library.node.editor/resources/lib/common.py
@@ -0,0 +1,17 @@
+
+import sys
+import os
+import xbmc, xbmcvfs, xbmcaddon
+
+ADDON = xbmcaddon.Addon()
+ADDONID = ADDON.getAddonInfo('id')
+ADDONVERSION = ADDON.getAddonInfo('version')
+LANGUAGE = ADDON.getLocalizedString
+CWD = ADDON.getAddonInfo('path')
+ADDONNAME = ADDON.getAddonInfo('name')
+DATAPATH = xbmcvfs.translatePath(xbmcaddon.Addon().getAddonInfo('profile'))
+DEFAULTPATH = os.path.join( CWD, 'resources' )
+
+def log(txt):
+ message = u'%s: %s' % (ADDONID, txt)
+ xbmc.log(msg=message, level=xbmc.LOGDEBUG)
diff --git a/plugin.library.node.editor/resources/lib/moveNodes.py b/plugin.library.node.editor/resources/lib/moveNodes.py
new file mode 100644
index 0000000000..e234079dd3
--- /dev/null
+++ b/plugin.library.node.editor/resources/lib/moveNodes.py
@@ -0,0 +1,98 @@
+# coding=utf-8
+import sys
+import xbmc, xbmcaddon, xbmcgui
+
+from resources.lib.common import *
+
+
+def getNewOrder( currentPositions, indexToMove ):
+ # Show select dialog
+ w = ShowDialog( "DialogSelect.xml", CWD, order=currentPositions, focus=indexToMove, windowtitle=LANGUAGE(30104) )
+ w.doModal()
+ newOrder = w.newOrder
+ del w
+
+ return newOrder
+
+class ShowDialog( xbmcgui.WindowXMLDialog ):
+ def __init__( self, *args, **kwargs ):
+ xbmcgui.WindowXMLDialog.__init__( self )
+ self.order = kwargs.get( "order" )
+ self.windowtitle = kwargs.get( "windowtitle" )
+ self.selectedItem = kwargs.get( "focus" )
+ self.newOrder = []
+
+ def onInit(self):
+ self.list = self.getControl(3)
+
+ # Set visibility
+ self.getControl(3).setVisible(True)
+ self.getControl(3).setEnabled(True)
+ self.getControl(5).setVisible(False)
+ self.getControl(6).setVisible(False)
+ self.getControl(1).setLabel(self.windowtitle)
+
+ # Set Cancel label
+ self.getControl(7).setLabel(xbmc.getLocalizedString(222))
+
+ # Add all the items to the list
+ for i, key in enumerate( sorted( self.order ) ):
+ # Get the label and localise if necessary
+ label = self.order[ key ][ 0 ]
+ if label.isdigit():
+ label = xbmc.getLocalizedString( int( label ) )
+ if label == "":
+ label = self.order[ key ][ 0 ]
+ if self.order[ key ][ 3 ] == "folder":
+ label = "%s >" %( label )
+
+ # Create the listitem and add it
+ listitem = xbmcgui.ListItem( label=label )
+ self.list.addItem( listitem )
+
+ # And add it to the list that we'll eventually return
+ self.newOrder.append( self.order[ key ] )
+
+ # If it's the item we're moving, save it separately
+ if i == self.selectedItem:
+ self.itemMoving = self.order[ key ]
+
+ # Set focus
+ self.list.selectItem(self.selectedItem)
+ self.setFocus(self.list)
+
+ def onAction(self, action):
+ # Check if the selected item has changed
+ if self.list.getSelectedPosition() != self.selectedItem:
+ # Remove the item we're moving from the list of items
+ self.newOrder.pop( self.selectedItem )
+
+ # Add the item we're moving at its new location
+ self.newOrder.insert( self.list.getSelectedPosition(), self.itemMoving )
+
+ # Update its current current position
+ self.selectedItem = self.list.getSelectedPosition()
+
+ # Update the labels of all list items
+ for i in range( len( self.newOrder ) ):
+ item = self.list.getListItem( i )
+ label = self.newOrder[ i ][ 0 ]
+ if label.isdigit():
+ label = xbmc.getLocalizedString( int( label ) )
+ if label == "":
+ label = self.newOrder[ i ][ 0 ]
+ if self.newOrder[ i ][ 3 ] == "folder":
+ label = "%s >" %( label )
+ if item.getLabel() != label:
+ item.setLabel( label )
+
+ if action.getId() in ( 9, 10, 92, 216, 247, 257, 275, 61467, 61448, ):
+ self.close()
+ return
+
+ def onClick(self, controlID):
+ if controlID == 7:
+ # Cancel button
+ self.newOrder = None
+
+ self.close()
diff --git a/plugin.library.node.editor/resources/lib/orderby.py b/plugin.library.node.editor/resources/lib/orderby.py
new file mode 100644
index 0000000000..66b882a4bc
--- /dev/null
+++ b/plugin.library.node.editor/resources/lib/orderby.py
@@ -0,0 +1,195 @@
+# coding=utf-8
+import os, sys
+import xbmc, xbmcaddon, xbmcplugin, xbmcgui, xbmcvfs
+import xml.etree.ElementTree as xmltree
+from traceback import print_exc
+from urllib.parse import unquote
+
+from resources.lib.common import *
+
+class OrderByFunctions():
+ def __init__(self, ltype):
+ self.ltype = ltype
+
+ def _load_rules( self ):
+ if self.ltype.startswith('video'):
+ overridepath = os.path.join( DEFAULTPATH , "videorules.xml" )
+ else:
+ overridepath = os.path.join( DEFAULTPATH , "musicrules.xml" )
+ try:
+ tree = xmltree.parse( overridepath )
+ return tree
+ except:
+ return None
+
+ def translateOrderBy( self, rule ):
+ # Load the rules
+ tree = self._load_rules()
+ hasValue = True
+ if rule[ 0 ] == "sorttitle":
+ rule[ 0 ] = "title"
+ if rule[ 0 ] != "random":
+ # Get the field we're ordering by
+ elems = tree.getroot().find( "matches" ).findall( "match" )
+ for elem in elems:
+ if elem.attrib.get( "name" ) == rule[ 0 ]:
+ match = xbmc.getLocalizedString( int( elem.find( "label" ).text ) )
+ else:
+ # We'll manually set for random
+ match = xbmc.getLocalizedString( 590 )
+ # Get localization of direction
+ direction = None
+ elems = tree.getroot().find( "orderby" ).findall( "type" )
+ for elem in elems:
+ if elem.text == rule[ 1 ]:
+ direction = xbmc.getLocalizedString( int( elem.attrib.get( "label" ) ) )
+ directionVal = rule[ 1 ]
+ if direction is None:
+ direction = xbmc.getLocalizedString( int( tree.getroot().find( "orderby" ).find( "type" ).attrib.get( "label" ) ) )
+ directionVal = tree.getroot().find( "orderby" ).find( "type" ).text
+ return [ [ match, rule[ 0 ] ], [ direction, directionVal ] ]
+
+ def displayOrderBy( self, actionPath):
+ try:
+ # Load the xml file
+ tree = xmltree.parse( unquote(actionPath) )
+ root = tree.getroot()
+ # Get the content type
+ content = root.find( "content" ).text
+ # Get the order node
+ orderby = root.find( "order" )
+ if orderby is None:
+ # There is no orderby element, so add one
+ self.newOrderBy( tree, actionPath )
+ orderby = root.find( "order" )
+ match = orderby.text
+ if "direction" in orderby.attrib:
+ direction = orderby.attrib.get( "direction" )
+ else:
+ direction = ""
+ translated = self.translateOrderBy( [match, direction ] )
+ listitem = xbmcgui.ListItem( label="%s" % ( translated[ 0 ][ 0 ] ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=editOrderBy&actionPath=" % self.ltype + actionPath + "&content=" + content + "&default=" + translated[0][1]
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
+ listitem = xbmcgui.ListItem( label="%s" % ( translated[ 1 ][ 0 ] ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=editOrderByDirection&actionPath=" % self.ltype + actionPath + "&default=" + translated[1][1]
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
+ xbmcplugin.setContent(int(sys.argv[1]), 'files')
+ xbmcplugin.endOfDirectory(int(sys.argv[1]))
+ except:
+ print_exc()
+
+ def editOrderBy( self, actionPath, content, default ):
+ # Load all operator groups
+ tree = self._load_rules().getroot()
+ elems = tree.find( "matches" ).findall( "match" )
+ selectName = []
+ selectValue = []
+ # Find the matches for the content we've been passed
+ for elem in elems:
+ contentMatch = elem.find( content )
+ if contentMatch is not None:
+ selectName.append( xbmc.getLocalizedString( int( elem.find( "label" ).text ) ) )
+ selectValue.append( elem.attrib.get( "name" ) )
+ # Add a random element
+ selectName.append( xbmc.getLocalizedString( 590 ) )
+ selectValue.append( "random" )
+ # Let the user select an operator
+ selectedOperator = xbmcgui.Dialog().select( LANGUAGE( 30314 ), selectName )
+ # If the user selected no operator...
+ if selectedOperator == -1:
+ return
+ returnVal = selectValue[ selectedOperator ]
+ if returnVal == "title":
+ returnVal = "sorttitle"
+ self.writeUpdatedOrderBy( actionPath, field = returnVal )
+
+ def editDirection( self, actionPath, direction ):
+ # Load all directions
+ tree = self._load_rules().getroot()
+ elems = tree.find( "orderby" ).findall( "type" )
+ selectName = []
+ selectValue = []
+ # Find the group we've been passed and load its operators
+ for elem in elems:
+ selectName.append( xbmc.getLocalizedString( int( elem.attrib.get( "label" ) ) ) )
+ selectValue.append( elem.text )
+ # Let the user select an operator
+ selectedOperator = xbmcgui.Dialog().select( LANGUAGE( 30315 ), selectName )
+ # If the user selected no operator...
+ if selectedOperator == -1:
+ return
+ self.writeUpdatedOrderBy( actionPath, direction = selectValue[ selectedOperator ] )
+
+ def writeUpdatedOrderBy( self, actionPath, field = None, direction = None ):
+ # This function writes an updated orderby rule
+ try:
+ # Load the xml file
+ tree = xmltree.parse( unquote(unquote(actionPath)) )
+ root = tree.getroot()
+ # Get all the rules
+ orderby = root.find( "order" )
+ if field is not None:
+ orderby.text = field
+ if direction is not None:
+ orderby.set( "direction", direction )
+ # Save the file
+ self.indent( root )
+ tree.write( unquote(actionPath), encoding="UTF-8" )
+ except:
+ print_exc()
+
+ def newOrderBy( self, tree, actionPath ):
+ # This function adds a new OrderBy, with default match and direction
+ try:
+ # Load the xml file
+ #tree = xmltree.parse( actionPath )
+ root = tree.getroot()
+ # Get the content type
+ content = root.find( "content" )
+ if content is None:
+ xbmcgui.Dialog().ok( ADDONNAME, LANGUAGE( 30406 ) )
+ return
+ else:
+ content = content.text
+ # Find the default match for this content type
+ ruleTree = self._load_rules().getroot()
+ elems = ruleTree.find( "matches" ).findall( "match" )
+ match = "title"
+ for elem in elems:
+ contentCheck = elem.find( content )
+ if contentCheck is not None:
+ # We've found the first match for this type
+ match = elem.attrib.get( "name" )
+ break
+ if match == "title":
+ match = "sorttitle"
+ # Find the default direction
+ elem = ruleTree.find( "orderby" ).find( "type" )
+ direction = elem.text
+ # Write the new rule
+ newRule = xmltree.SubElement( root, "order" )
+ newRule.text = match
+ newRule.set( "direction", direction )
+ # Save the file
+ self.indent( root )
+ tree.write( unquote( actionPath ), encoding="UTF-8" )
+ except:
+ print_exc()
+
+ # in-place prettyprint formatter
+ def indent( self, elem, level=0 ):
+ i = "\n" + level*"\t"
+ if len(elem):
+ if not elem.text or not elem.text.strip():
+ elem.text = i + "\t"
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ for elem in elem:
+ self.indent(elem, level+1)
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ else:
+ if level and (not elem.tail or not elem.tail.strip()):
+ elem.tail = i
+
diff --git a/plugin.library.node.editor/resources/lib/pathrules.py b/plugin.library.node.editor/resources/lib/pathrules.py
new file mode 100644
index 0000000000..8d68294347
--- /dev/null
+++ b/plugin.library.node.editor/resources/lib/pathrules.py
@@ -0,0 +1,377 @@
+# coding=utf-8
+import os, sys, shutil
+import xbmc, xbmcaddon, xbmcplugin, xbmcgui, xbmcvfs
+import xml.etree.ElementTree as xmltree
+import json
+from traceback import print_exc
+from urllib.parse import quote, unquote
+
+from resources.lib.common import *
+
+class PathRuleFunctions():
+ def __init__(self, ltype):
+ self.nodeRules = None
+ self.ATTRIB = None
+ self.ltype = ltype
+
+ def _load_rules( self ):
+ if self.ltype.startswith('video'):
+ overridepath = os.path.join( DEFAULTPATH , "videorules.xml" )
+ else:
+ overridepath = os.path.join( DEFAULTPATH , "musicrules.xml" )
+ try:
+ tree = xmltree.parse( overridepath )
+ return tree
+ except:
+ return None
+
+ def translateComponent( self, component, splitPath ):
+ if component[ 0 ] is None:
+ return splitPath[ 0 ]
+ if component[0].isdigit():
+ string = LANGUAGE( int( component[ 0 ] ) )
+ if string != "": return string
+ return xbmc.getLocalizedString( int( component[ 0 ] ) )
+ else:
+ return component[ 0 ]
+
+ def translateValue( self, rule, splitPath, ruleNum ):
+ if splitPath[ ruleNum ][ 1 ] == "":
+ return ""
+
+ if rule[ 1 ] == "year" or rule[ 2 ] != "integer" or rule[ 3 ] is None:
+ return splitPath[ ruleNum ][ 1 ]
+
+ try:
+ value = int( splitPath[ ruleNum ][ 1 ] )
+ except:
+ return splitPath[ ruleNum ][ 1 ]
+
+ json_query = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "id": 0, "method": "Files.GetDirectory", "params": { "properties": ["title"], "directory": "%s", "media": "files" } }' % rule[ 3 ] )
+ json_response = json.loads(json_query)
+ listings = []
+ values = []
+ # Add all directories returned by the json query
+ if json_response.get('result') and json_response['result'].get('files') and json_response['result']['files'] is not None:
+ for item in json_response['result']['files']:
+ if "id" in item and item[ "id" ] == value:
+ return "(%d) %s" %( value, item[ "label" ] )
+
+ # Didn't find a match
+ return "%s (%s)" %( splitPath[ ruleNum ][ 1 ], xbmc.getLocalizedString(13205) )
+
+ def displayRule( self, actionPath, ruleNum ):
+ try:
+ # Load the xml file
+ tree = xmltree.parse( unquote(actionPath) )
+ root = tree.getroot()
+ # Get the path
+ path = root.find( "path" ).text
+ # Split the path element
+ splitPath = self.ATTRIB.splitPath( path )
+ # Get the rules
+ rules = self.getRulesForPath( splitPath[ 0 ] )
+ if len( splitPath ) == int( ruleNum ):
+ # This rule doesn't exist - create it
+ self.ATTRIB.writeUpdatedPath( actionPath, ( ruleNum, rules[ 0 ][ 1 ], "" ) )
+ rule = rules[ 0 ]
+ translatedValue = ""
+ else:
+ # Find the matching rule
+ rule = self.getMatchingRule( splitPath[ ruleNum ], rules )
+ translatedValue = self.translateValue( rule, splitPath, ruleNum )
+
+ #Component
+ listitem = xbmcgui.ListItem( label="%s" % ( self.translateComponent( rule, splitPath[ruleNum] ) ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=editPathMatch&actionPath=%s&rule=%d" %( self.ltype, actionPath, ruleNum )
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
+
+ # Value
+ listitem = xbmcgui.ListItem( label="%s" % ( translatedValue ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=editPathValue&actionPath=%s&rule=%s" %( self.ltype, actionPath, ruleNum )
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
+ # Browse
+ if rule[ 3 ] is not None:
+ listitem = xbmcgui.ListItem( label=LANGUAGE(30107) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=browsePathValue&actionPath=%s&rule=%s" %( self.ltype, actionPath, ruleNum )
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
+
+ xbmcplugin.setContent(int(sys.argv[1]), 'files')
+ xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+ return
+ except:
+ log( "Failed" )
+ print_exc()
+
+ def getRulesForPath( self, path ):
+ # This function gets all valid rules for a given path
+ # Load the rules
+ tree = self._load_rules()
+ subSearch = None
+ content = []
+ elems = tree.getroot().find( "paths" ).findall( "type" )
+ for elem in elems:
+ if elem.attrib.get( "name" ) == path[ 0 ]:
+ for contentType in elem.findall( "content" ):
+ content.append( contentType.text )
+ subSearch = elem
+ break
+
+ if path[ 1 ] and subSearch is not None:
+ for elem in subSearch.findall( "type" ):
+ if elem.attrib.get( "name" ) == path[ 1 ]:
+ if elem.find( "content" ):
+ content = []
+ for contentType in elem.findall( "content" ):
+ content.append( contentType.text )
+ break
+
+ rules = []
+ for rule in tree.getroot().find( "pathRules" ).findall( "rule" ):
+ # Can it be browsed
+ if rule.find( "browse" ) is not None:
+ browse = rule.find( "browse" ).text
+ else:
+ browse = None
+ if len( content ) == 0:
+ rules.append( ( rule.attrib.get( "label" ), rule.attrib.get( "name" ), rule.find( "value" ).text, browse ) )
+ else:
+ for contentType in rule.findall( "content" ):
+ if contentType.text in content:
+ # If the root of the browse is changed dependant on what we're looking at, replace
+ # it now with the correct content
+ if browse is not None and "::root::" in browse:
+ browse = browse.replace( "::root::", content[ 0 ] )
+ rules.append( ( rule.attrib.get( "label" ), rule.attrib.get( "name" ), rule.find( "value" ).text, browse ) )
+
+ if len( rules ) == 0:
+ return [ ( None, "property", "string", None ) ]
+ return rules
+
+ def getMatchingRule( self, component, rules ):
+ # This function matches a component to its rule
+ for rule in rules:
+ if rule[ 1 ] == component[ 0 ]:
+ return rule
+
+ # No rule matched, return an empty one
+ return ( None, "property", "string", None )
+
+ def editMatch( self, actionPath, ruleNum ):
+ # Change the match element of a path component
+
+ # Load the xml file
+ tree = xmltree.parse( unquote(actionPath) )
+ root = tree.getroot()
+ # Get the path
+ path = root.find( "path" ).text
+ # Split the path element
+ splitPath = self.ATTRIB.splitPath( path )
+ # Get the rules and current value
+ rules = self.getRulesForPath( splitPath[ 0 ] )
+ currentValue = splitPath[ ruleNum ][ 1 ]
+
+ if rules[ 0 ][ 0 ] is None:
+ # There are no available choices
+ self.manuallyEditMatch( actionPath, ruleNum, splitPath[ ruleNum ][ 0 ], currentValue )
+ return
+
+ # Build list of rules to choose from
+ selectName = []
+ for rule in rules:
+ selectName.append( self.translateComponent( rule, None ) )
+
+ # Add a manual option
+ selectName.append( LANGUAGE( 30408 ) )
+
+ # Let the user select an operator
+ selectedOperator = xbmcgui.Dialog().select( LANGUAGE( 30305 ), selectName )
+ # If the user selected no operator...
+ if selectedOperator == -1:
+ return
+ elif selectedOperator == len( rules ):
+ # User choose custom property
+ self.manuallyEditMatch( actionPath, ruleNum, splitPath[ ruleNum ][ 0 ], currentValue )
+ return
+ else:
+ self.ATTRIB.writeUpdatedPath( actionPath, ( ruleNum, rules[ selectedOperator ][ 1 ], currentValue ) )
+
+ def manuallyEditMatch( self, actionPath, ruleNum, currentName, currentValue ):
+ type = xbmcgui.INPUT_ALPHANUM
+ returnVal = xbmcgui.Dialog().input( LANGUAGE( 30318 ), currentName, type=type )
+ if returnVal != "":
+ self.ATTRIB.writeUpdatedPath( actionPath, ( ruleNum, returnVal.decode( "utf-8" ), currentValue ) )
+
+ def editValue( self, actionPath, ruleNum ):
+ # Let the user edit the value of a path component
+
+ # Load the xml file
+ tree = xmltree.parse( unquote(actionPath) )
+ root = tree.getroot()
+ # Get the path
+ path = root.find( "path" ).text
+ # Split the path element
+ splitPath = self.ATTRIB.splitPath( path )
+ # Get the rules and current value
+ rules = self.getRulesForPath( splitPath[ 0 ] )
+ rule = self.getMatchingRule( splitPath[ ruleNum ], rules )
+
+ if rule[ 2 ] == "boolean":
+ # Let the user select a boolean
+ selectedBool = xbmcgui.Dialog().select( LANGUAGE( 30307 ), [ xbmc.getLocalizedString(20122), xbmc.getLocalizedString(20424) ] )
+ # If the user selected nothing...
+ if selectedBool == -1:
+ return
+ value = "true"
+ if selectedBool == 1:
+ value = "false"
+ self.ATTRIB.writeUpdatedPath( actionPath, ( ruleNum, splitPath[ ruleNum][ 0 ], value ) )
+ else:
+ type = xbmcgui.INPUT_ALPHANUM
+ if rule[ 2 ] == "integer":
+ type = xbmcgui.INPUT_NUMERIC
+ returnVal = xbmcgui.Dialog().input( LANGUAGE( 30307 ), splitPath[ ruleNum ][ 1 ], type=type )
+ if returnVal != "":
+ self.ATTRIB.writeUpdatedPath( actionPath, ( ruleNum, splitPath[ ruleNum ][ 0 ], returnVal ) )
+
+
+ def browse( self, actionPath, ruleNum ):
+ # This function launches the browser for the given property type
+
+ pDialog = xbmcgui.DialogProgress()
+ pDialog.create( ADDONNAME, LANGUAGE( 30317 ) )
+
+ # Load the xml file
+ tree = xmltree.parse( unquote(actionPath) )
+ root = tree.getroot()
+ # Get the path
+ path = root.find( "path" ).text
+ # Split the path element
+ splitPath = self.ATTRIB.splitPath( path )
+ # Get the rules and current value
+ rules = self.getRulesForPath( splitPath[ 0 ] )
+ rule = self.getMatchingRule( splitPath[ ruleNum ], rules )
+ title = self.translateComponent( rule, splitPath[ ruleNum ] )
+
+ # Get the path we'll be browsing
+ browsePath = self.getBrowsePath( splitPath, rule[ 3 ], ruleNum )
+
+ json_query = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "id": 0, "method": "Files.GetDirectory", "params": { "properties": ["title", "file", "thumbnail"], "directory": "%s", "media": "files" } }' % browsePath )
+ json_response = json.loads(json_query)
+ listings = []
+ values = []
+ # Add all directories returned by the json query
+ if json_response.get('result') and json_response['result'].get('files') and json_response['result']['files'] is not None:
+ total = len( json_response[ 'result' ][ 'files' ] )
+ for item in json_response['result']['files']:
+ if item[ "label" ] == "..":
+ continue
+ thumb = None
+ if item[ "thumbnail" ] is not "":
+ thumb = item[ "thumbnail" ]
+ listitem = xbmcgui.ListItem(label=item[ "label" ] )
+ listitem.setArt({'icon': thumb})
+ listitem.setProperty( "thumbnail", thumb )
+ listings.append( listitem )
+ if rule[ 2 ] == "integer" and "id" in item:
+ values.append( str( item[ "id" ] ) )
+ else:
+ values.append( item[ "label" ] )
+
+ pDialog.close()
+
+ if len( listings ) == 0:
+ # No browsable options found
+ xbmcgui.Dialog().ok( ADDONNAME, LANGUAGE( 30409 ) )
+ return
+
+ # Show dialog
+ w = ShowDialog( "DialogSelect.xml", CWD, listing=listings, windowtitle=title )
+ w.doModal()
+ selectedItem = w.result
+ del w
+ if selectedItem == "" or selectedItem == -1:
+ return None
+
+ self.ATTRIB.writeUpdatedPath( actionPath, ( ruleNum, splitPath[ ruleNum ][ 0 ], values[ selectedItem ] ) )
+
+ def getBrowsePath( self, splitPath, newBase, rule ):
+ # This function replaces the base path with the browse path, and removes the current
+ # component
+
+ returnText = ""
+
+ if "::root::" in newBase:
+ newBase = newBase.replace( "::root::", splitPath[ 0 ][ 0 ] )
+
+ # Enumarate through everything in the existing path
+ addedQ = False
+ for x, component in enumerate( splitPath ):
+ if x != rule:
+ # Transfer this component to the new path
+ if x == 0:
+ returnText = newBase
+ elif not addedQ:
+ returnText += "?%s=%s" %( component[ 0 ], quote(component[1]) )
+ addedQ = True
+ else:
+ returnText += "&%s=%s" %( component[ 0 ], quote(component[1]) )
+ return returnText
+
+ # in-place prettyprint formatter
+ def indent( self, elem, level=0 ):
+ i = "\n" + level*"\t"
+ if len(elem):
+ if not elem.text or not elem.text.strip():
+ elem.text = i + "\t"
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ for elem in elem:
+ self.indent(elem, level+1)
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ else:
+ if level and (not elem.tail or not elem.tail.strip()):
+ elem.tail = i
+
+# ============================
+# === PRETTY SELECT DIALOG ===
+# ============================
+class ShowDialog( xbmcgui.WindowXMLDialog ):
+ def __init__( self, *args, **kwargs ):
+ xbmcgui.WindowXMLDialog.__init__( self )
+ self.listing = kwargs.get( "listing" )
+ self.windowtitle = kwargs.get( "windowtitle" )
+ self.result = -1
+
+ def onInit(self):
+ try:
+ self.fav_list = self.getControl(6)
+ self.getControl(3).setVisible(False)
+ except:
+ print_exc()
+ self.fav_list = self.getControl(3)
+ self.getControl(5).setVisible(False)
+ self.getControl(1).setLabel(self.windowtitle)
+ for item in self.listing :
+ listitem = xbmcgui.ListItem(label=item.getLabel(), label2=item.getLabel2())
+ listitem.setArt({'icon': item.getProperty( "icon" ), 'thumb': item.getProperty( "thumbnail" )})
+ listitem.setProperty( "Addon.Summary", item.getLabel2() )
+ self.fav_list.addItem( listitem )
+ self.setFocus(self.fav_list)
+
+ def onAction(self, action):
+ if action.getId() in ( 9, 10, 92, 216, 247, 257, 275, 61467, 61448, ):
+ self.result = -1
+ self.close()
+
+ def onClick(self, controlID):
+ if controlID == 6 or controlID == 3:
+ num = self.fav_list.getSelectedPosition()
+ self.result = num
+ else:
+ self.result = -1
+ self.close()
+
+ def onFocus(self, controlID):
+ pass
diff --git a/plugin.library.node.editor/resources/lib/pluginBrowser.py b/plugin.library.node.editor/resources/lib/pluginBrowser.py
new file mode 100644
index 0000000000..1b7a8a1ec7
--- /dev/null
+++ b/plugin.library.node.editor/resources/lib/pluginBrowser.py
@@ -0,0 +1,54 @@
+# coding=utf-8
+import sys
+import xbmc, xbmcaddon, xbmcgui
+import json
+
+from resources.lib.common import *
+
+def getPluginPath( ltype, location = None ):
+ listings = []
+ listingsLabels = []
+
+ if location is not None:
+ # If location given, add 'create' item
+ listings.append( "::CREATE::" )
+ listingsLabels.append( LANGUAGE( 30411 ) )
+ else:
+ # If no location, build default
+ if location is None:
+ if ltype.startswith( "video" ):
+ location = "addons://sources/video"
+ else:
+ location = "addons://sources/audio"
+
+ # Show a waiting dialog, then get the listings for the directory
+ dialog = xbmcgui.DialogProgress()
+ dialog.create( ADDONNAME, LANGUAGE( 30410 ) )
+
+ json_query = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "id": 0, "method": "Files.GetDirectory", "params": { "properties": ["title", "file", "thumbnail", "episode", "showtitle", "season", "album", "artist", "imdbnumber", "firstaired", "mpaa", "trailer", "studio", "art"], "directory": "' + location + '", "media": "files" } }')
+ json_response = json.loads(json_query)
+
+ # Add all directories returned by the json query
+ if json_response.get('result') and json_response['result'].get('files') and json_response['result']['files']:
+ json_result = json_response['result']['files']
+
+ for item in json_result:
+ if item[ "file" ].startswith( "plugin://" ):
+ listings.append( item[ "file" ] )
+ listingsLabels.append( "%s >" %( item[ "label" ] ) )
+
+ # Close progress dialog
+ dialog.close()
+
+ selectedItem = xbmcgui.Dialog().select( LANGUAGE( 30309 ), listingsLabels )
+
+ if selectedItem == -1:
+ # User cancelled
+ return None
+
+ selectedAction = listings[ selectedItem ]
+ if selectedAction == "::CREATE::":
+ return location
+ else:
+ # User has chosen a sub-level to display, add details and re-call this function
+ return getPluginPath(ltype, selectedAction)
diff --git a/plugin.library.node.editor/resources/lib/rules.py b/plugin.library.node.editor/resources/lib/rules.py
new file mode 100644
index 0000000000..7bb708d05b
--- /dev/null
+++ b/plugin.library.node.editor/resources/lib/rules.py
@@ -0,0 +1,1128 @@
+# coding=utf-8
+import os, sys, shutil
+import xbmc, xbmcaddon, xbmcplugin, xbmcgui, xbmcvfs
+import xml.etree.ElementTree as xmltree
+import json
+from traceback import print_exc
+from urllib.parse import quote, unquote
+
+from resources.lib.common import *
+
+class RuleFunctions():
+ def __init__(self, ltype):
+ self.nodeRules = None
+ self.ltype = ltype
+
+ def _load_rules( self ):
+ if self.ltype.startswith('video'):
+ overridepath = os.path.join( DEFAULTPATH , "videorules.xml" )
+ else:
+ overridepath = os.path.join( DEFAULTPATH , "musicrules.xml" )
+ try:
+ tree = xmltree.parse( overridepath )
+ return tree
+ except:
+ return None
+
+ def translateRule( self, rule ):
+ # Load the rules
+ tree = self._load_rules()
+ hasValue = True
+ elems = tree.getroot().find( "matches" ).findall( "match" )
+ for elem in elems:
+ if elem.attrib.get( "name" ) == rule[ 0 ]:
+ match = xbmc.getLocalizedString( int( elem.find( "label" ).text ) )
+ group = elem.find( "operator" ).text
+ elems = tree.getroot().find( "operators" ).findall( "group" )
+ operator = None
+ defaultOperator = None
+ defaultOperatorValue = None
+ for elem in elems:
+ if elem.attrib.get( "name" ) == group:
+ for operators in elem.findall( "operator" ):
+ if operators.text == rule[ 1 ]:
+ operator = xbmc.getLocalizedString( int( operators.attrib.get( "label" ) ) )
+ if defaultOperator is None:
+ defaultOperator = xbmc.getLocalizedString( int( operators.attrib.get( "label" ) ) )
+ defaultOperatorValue = operators.text
+ if "option" in elem.attrib:
+ hasValue = False
+ # If we didn't match an operator, set it to the default
+ if operator is None:
+ operator = defaultOperator
+ rule[ 1 ] = defaultOperatorValue
+ if hasValue == False:
+ return [ [ match, rule[ 0 ] ], [ operator, group, rule[ 1 ] ], [ "|NONE|", "" ] ]
+ if len( rule ) == 2 or rule[ 2 ] == "" or rule[ 2 ] is None:
+ return [ [ match, rule[ 0 ] ], [ operator, group, rule[ 1 ] ], [ "", "" ] ]
+ return [ [ match, rule[ 0 ] ], [ operator, group, rule[ 1 ] ], [ rule[ 2 ], rule[ 2 ] ] ]
+
+ def displayRule( self, actionPath, path, ruleNum ):
+ if actionPath.endswith( "index.xml" ):
+ # If this is a parent node, call alternative function
+ self.displayNodeRule( actionPath, ruleNum )
+ return
+ try:
+ # Load the xml file
+ tree = xmltree.parse( unquote(actionPath) )
+ root = tree.getroot()
+ actionPath = actionPath
+ # Get the content type
+ content = root.find( "content" )
+ if content is not None:
+ content = content.text
+ else:
+ content = "NONE"
+ # Get all the rules
+ ruleCount = 0
+ rules = root.findall( "rule" )
+ if len( rules ) == int( ruleNum ):
+ # This rule doesn't exist - create it
+ self.newRule( tree, unquote( actionPath ) )
+ rules = root.findall( "rule" )
+ if rules is not None:
+ for rule in rules:
+ if str( ruleCount ) == ruleNum:
+ value = rule.find( "value" )
+ if value is None:
+ value = ""
+ else:
+ value = value.text
+ translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value ] )
+ # Rule to change match
+ listitem = xbmcgui.ListItem( label="%s" % ( translated[ 0 ][ 0 ] ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=editMatch&actionPath=" % self.ltype + actionPath + "&content=" + content + "&default=" + translated[ 0 ][ 1 ] + "&rule=" + str( ruleCount )
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
+ listitem = xbmcgui.ListItem( label="%s" % ( translated[ 1 ][ 0 ] ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=editOperator&actionPath=" % self.ltype + actionPath + "&group=" + translated[ 1 ][ 1 ] + "&default=" + translated[ 1 ][ 2 ] + "&rule=" + str( ruleCount )
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
+ if not ( translated[ 2 ][ 0 ] ) == "|NONE|":
+ listitem = xbmcgui.ListItem( label="%s" % ( translated[ 2 ][ 1 ] ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=editValue&actionPath=" % self.ltype + actionPath + "&rule=" + str( ruleCount )
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
+ # Check if this match type can be browsed
+ if self.canBrowse( translated[ 0 ][ 1 ], content ):
+ #listitem.addContextMenuItems( [(LANGUAGE(30107), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=browseValue&actionPath=" % ltype + actionPath + "&rule=" + str( ruleCount ) + "&match=" + translated[ 0 ][ 1 ] + "&content=" + content + ")" )], replaceItems = True )
+ listitem = xbmcgui.ListItem( label=LANGUAGE(30107) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=browseValue&actionPath=" % self.ltype + actionPath + "&rule=" + str( ruleCount ) + "&match=" + translated[ 0 ][ 1 ] + "&content=" + content
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
+ #self.browse( translated[ 0 ][ 1 ], content )
+ xbmcplugin.setContent(int(sys.argv[1]), 'files')
+ xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+ return
+ ruleCount = ruleCount + 1
+ except:
+ print_exc()
+
+ def editMatch( self, actionPath, ruleNum, content, default ):
+ # Load all operator groups
+ tree = self._load_rules().getroot()
+ elems = tree.find( "matches" ).findall( "match" )
+ selectName = []
+ selectValue = []
+ # Find the matches for the content we've been passed
+ for elem in elems:
+ if content != "NONE":
+ contentMatch = elem.find( content )
+ if contentMatch is not None:
+ selectName.append( xbmc.getLocalizedString( int( elem.find( "label" ).text ) ) )
+ selectValue.append( elem.attrib.get( "name" ) )
+ else:
+ pass
+ else:
+ selectName.append( xbmc.getLocalizedString( int( elem.find( "label" ).text ) ) )
+ selectValue.append( elem.attrib.get( "name" ) )
+ # Let the user select an operator
+ selectedOperator = xbmcgui.Dialog().select( LANGUAGE( 30305 ), selectName )
+ # If the user selected no operator...
+ if selectedOperator == -1:
+ return
+ self.writeUpdatedRule( actionPath, ruleNum, match = selectValue[ selectedOperator ] )
+
+ def editOperator( self, actionPath, ruleNum, group, default ):
+ # Load all operator groups
+ tree = self._load_rules().getroot()
+ elems = tree.find( "operators" ).findall( "group" )
+ selectName = []
+ selectValue = []
+ # Find the group we've been passed and load its operators
+ for elem in elems:
+ if elem.attrib.get( "name" ) == group:
+ for operators in elem.findall( "operator" ):
+ selectName.append( xbmc.getLocalizedString( int( operators.attrib.get( "label" ) ) ) )
+ selectValue.append( operators.text )
+ # Let the user select an operator
+ selectedOperator = xbmcgui.Dialog().select( LANGUAGE( 30306 ), selectName )
+ # If the user selected no operator...
+ if selectedOperator == -1:
+ return
+ self.writeUpdatedRule( actionPath, ruleNum, operator = selectValue[ selectedOperator ] )
+
+ def editValue( self, actionPath, ruleNum ):
+ # This function is the entry point for editing the value of a rule
+ # Because we can't always pass the current value through the uri, we first need
+ # to retrieve it, and the operator data type
+ actionPath = unquote(actionPath)
+ try:
+ if actionPath.endswith( "index.xml" ):
+ ( filePath, fileName ) = os.path.split(unquote(actionPath))
+ # Load the rules file
+ if self.ltype.startswith('video'):
+ tree = xmltree.parse( os.path.join( DATAPATH, "videorules.xml" ) )
+ else:
+ tree = xmltree.parse( os.path.join( DATAPATH, "musicrules.xml" ) )
+ root = tree.getroot()
+ nodes = root.findall( "node" )
+ for node in nodes:
+ if node.attrib.get( "name" ) == filePath:
+ rules = node.findall( "rule" )
+ ruleCount = 0
+ for rule in rules:
+ if ruleCount == int( ruleNum ):
+ # This is the rule we'll be updating
+ # Get the current value
+ curValue = rule.find( "value" )
+ if curValue is None:
+ curValue = ""
+ else:
+ curValue = curValue.text
+ match = rule.attrib.get( "field" )
+ operator = rule.attrib.get( "operator" )
+ ruleCount += 1
+ else:
+ # Load the xml file
+ tree = xmltree.parse( unquote(actionPath) )
+ root = tree.getroot()
+ # Get all the rules
+ ruleCount = 0
+ rules = root.findall( "rule" )
+ if rules is not None:
+ for rule in rules:
+ if str( ruleCount ) == ruleNum:
+ # This is the rule we'll be updating
+ # Get the current value
+ curValue = rule.find( "value" )
+ if curValue is None:
+ curValue = ""
+ else:
+ curValue = curValue.text
+ match = rule.attrib.get( "field" )
+ operator = rule.attrib.get( "operator" )
+ ruleCount = ruleCount + 1
+ # Now, use the match value to get the group of operators this
+ # comes from (this will tell us the data type in all types
+ # but "date")
+ tree = self._load_rules().getroot()
+ elems = tree.find( "matches" ).findall( "match" )
+ for elem in elems:
+ if elem.attrib.get( "name" ) == match:
+ group = elem.find( "operator" ).text
+ if group == "date":
+ # We probably should go through the tree again, but we'll just check
+ # for string ending in "inthelast", and switch the type to numeric
+ if operator.endswith( "inthelast" ):
+ group = "numeric"
+ # Set the type of text entry dialog to be used
+ if group == "string":
+ type = xbmcgui.INPUT_ALPHANUM
+ if group == "numeric":
+ type = xbmcgui.INPUT_NUMERIC
+ if group == "time":
+ type = xbmcgui.INPUT_TIME
+ if group == "date":
+ type = xbmcgui.INPUT_DATE
+ if group == "isornot":
+ type = xbmcgui.INPUT_ALPHANUM
+ if group == "seconds":
+ type = xbmcgui.INPUT_TIME
+ # album duration is in HH:MM:SS but we can't call that input dialog from python so
+ # find and strip any seconds that may be in an existing node. Use the HH:MM dialog
+ # and add the seconds field back on before we write the node
+ secPos = -1
+ if curValue is not None:
+ secPos = curValue.rfind(":00")
+ if secPos == -1:
+ secPos = len( curValue)
+ if ((curValue is not None) and (secPos >= 4)):
+ curValue = curValue[0:secPos]
+ if len( curValue ) < 5:
+ curValue = " " + curValue
+ returnVal = xbmcgui.Dialog().input( LANGUAGE( 30307 ), curValue, type=type )
+ if ( group == "seconds" and returnVal !="" ):
+ returnVal += ":00"# Add the seconds if we previously removed them
+ if returnVal != "":
+ self.writeUpdatedRule( unquote(actionPath), ruleNum, value=returnVal )
+ except:
+ print_exc()
+
+ def writeUpdatedRule( self, actionPath, ruleNum, match = None, operator = None, value = None ):
+ # This function writes an updated match, operator or value to a rule
+ if actionPath.endswith( "index.xml" ):
+ # This is a parent node rule, so call the relevant function
+ #( filePath, fileName ) = os.path.split( actionPath )
+ #self.editNodeRule( filePath, originalRule, translated )
+ self.editNodeRule( unquote(actionPath), ruleNum, match, operator, value )
+ return
+ try:
+ # Load the xml file
+ tree = xmltree.parse( unquote(actionPath) )
+ root = tree.getroot()
+ # Get all the rules
+ ruleCount = 0
+ rules = root.findall( "rule" )
+ if rules is not None:
+ for rule in rules:
+ if str( ruleCount ) == ruleNum:
+ # This is the rule we're updating
+ valueElem = rule.find( "value" )
+ if match is None:
+ match = rule.attrib.get( "field" )
+ if operator is None:
+ operator = rule.attrib.get( "operator" )
+ if value is None:
+ if valueElem is None:
+ value = ""
+ else:
+ value = valueElem.text
+ if value is None:
+ value = ""
+ translated = self.translateRule( [ match, operator, value ] )
+ # Update the rule
+ rule.set( "field", translated[ 0 ][ 1 ] )
+ rule.set( "operator", translated[ 1 ][ 2 ] )
+ if len( translated ) == 3:
+ if rule.find( "value" ) == None:
+ # Create a new rule node
+ xmltree.SubElement( rule, "value" ).text = translated[ 2 ][ 0 ]
+ else:
+ rule.find( "value" ).text = translated[ 2 ][ 0 ]
+ ruleCount = ruleCount + 1
+ # Save the file
+ self.indent( root )
+ tree.write( unquote(actionPath), encoding="UTF-8" )
+ except:
+ print_exc()
+
+ def newRule( self, tree, actionPath ):
+ # This function adds a new rule, with default match and operator, no value
+ try:
+ # Load the xml file
+ # tree = xmltree.parse( actionPath )
+ root = tree.getroot()
+ # Get the content type
+ content = root.find( "content" )
+ # Find the default match for this content type
+ ruleTree = self._load_rules().getroot()
+ elems = ruleTree.find( "matches" ).findall( "match" )
+ match = "title"
+ for elem in elems:
+ if content is not None:
+ contentCheck = elem.find( content.text )
+ if contentCheck is not None:
+ # We've found the first match for this type
+ match = elem.attrib.get( "name" )
+ operator = elem.find( "operator" ).text
+ break
+ else:
+ # We've found the first match for this type
+ match = elem.attrib.get( "name" )
+ operator = elem.find( "operator" ).text
+ break
+ # Find the default operator for this match
+ elems = ruleTree.find( "operators" ).findall( "group" )
+ for elem in elems:
+ if elem.attrib.get( "name" ) == operator:
+ operator = elem.find( "operator" ).text
+ break
+ # Write the new rule
+ newRule = xmltree.SubElement( root, "rule" )
+ newRule.set( "field", match )
+ newRule.set( "operator", operator )
+ xmltree.SubElement( newRule, "value" )
+ # Save the file
+ self.indent( root )
+ tree.write( unquote(actionPath), encoding="UTF-8" )
+ if actionPath.endswith( "index.xml" ):
+ ( filePath, fileName ) = os.path.split( actionPath )
+ newRule = self.translateRule( [ match, operator, "" ] )
+ self.addNodeRule( filePath, newRule )
+ except:
+ print_exc()
+
+ def deleteRule( self, actionPath, ruleNum ):
+ # This function deletes a rule
+ result = xbmcgui.Dialog().yesno(ADDONNAME, LANGUAGE( 30405 ) )
+ if not result:
+ return
+ if actionPath.endswith( "index.xml" ):
+ # This is a parent node rule, so call the relevant function
+ #( filePath, fileName ) = os.path.split( actionPath )
+ #self.deleteNodeRule( filePath, originalRule )
+ self.deleteNodeRule( unquote(actionPath), ruleNum )
+ try:
+ # Load the xml file
+ tree = xmltree.parse( unquote(actionPath) )
+ root = tree.getroot()
+ # Get all the rules
+ ruleCount = 0
+ rules = root.findall( "rule" )
+ if rules is not None:
+ for rule in rules:
+ if str( ruleCount ) == ruleNum:
+ # This is the rule we want to delete
+ if actionPath.endswith( "index.xml" ):
+ # Translate the rule, so we can delete it from the views
+ valueElem = rule.find( "value" )
+ origMatch = rule.attrib.get( "field" )
+ origOperator = rule.attrib.get( "operator" )
+ if valueElem is None:
+ origValue = ""
+ else:
+ origValue = valueElem.text
+ originalRule = self.translateRule( [ origMatch, origOperator, origValue ] )
+ # Delete the rule
+ root.remove( rule )
+ break
+ ruleCount = ruleCount + 1
+ # Save the file
+ self.indent( root )
+ tree.write( unquote(actionPath), encoding="UTF-8" )
+ except:
+ print_exc()
+
+ # Functions for managing rules in all views
+ def displayNodeRule( self, actionPath, ruleNum ):
+ # This function will load and display a parent node rule
+ # (and create one, if the ruleNum specified doesn't exist)
+ # Split the actionPath, to make things easier
+ ( filePath, fileName ) = os.path.split( unquote(actionPath) )
+ try:
+ # Load the rules file
+ if self.ltype.startswith('video'):
+ tree = xmltree.parse( os.path.join( DATAPATH, "videorules.xml" ) )
+ else:
+ tree = xmltree.parse( os.path.join( DATAPATH, "musicrules.xml" ) )
+ root = tree.getroot()
+ # Find the relevant node
+ nodes = root.findall( "node" )
+ if nodes is None:
+ self.newNodeRule( actionPath, ruleNum )
+ return
+ ruleNode = None
+ for node in nodes:
+ if node.attrib.get( "name" ) == filePath:
+ ruleNode = node
+ break
+ if ruleNode == None:
+ self.newNodeRule( actionPath, ruleNum )
+ return
+ # Find the relevant rule
+ rules = node.findall( "rule" )
+ if rules is None or len( rules ) == int( ruleNum ):
+ self.newNodeRule( actionPath, ruleNum )
+ return
+ ruleCount = 0
+ for rule in rules:
+ if ruleCount == int( ruleNum ):
+ value = rule.find( "value" )
+ if value is None:
+ value = ""
+ else:
+ value = value.text
+ translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value ] )
+ actionPath = quote( actionPath )
+ # Rule to change match
+ listitem = xbmcgui.ListItem( label="%s" % ( translated[ 0 ][ 0 ] ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=editMatch&actionPath=" % self.ltype + actionPath + "&default=" + translated[ 0 ][ 1 ] + "&rule=" + str( ruleCount ) + "&content=NONE"
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
+
+ listitem = xbmcgui.ListItem( label="%s" % ( translated[ 1 ][ 0 ] ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=editOperator&actionPath=" % self.ltype + actionPath + "&group=" + translated[ 1 ][ 1 ] + "&default=" + translated[ 1 ][ 2 ] + "&rule=" + str( ruleCount )
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
+ if not ( translated[ 2 ][ 0 ] ) == "|NONE|":
+ listitem = xbmcgui.ListItem( label="%s" % ( translated[ 2 ][ 1 ] ) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=editValue&actionPath=" % self.ltype + actionPath + "&rule=" + str( ruleCount )
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
+ # Check if this match type can be browsed
+ if self.canBrowse( translated[ 0 ][ 1 ] ):
+ #listitem.addContextMenuItems( [(LANGUAGE(30107), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=browseValue&actionPath=" % ltype + actionPath + "&rule=" + str( ruleCount ) + "&match=" + translated[ 0 ][ 1 ] + "&content=" + content + ")" )], replaceItems = True )
+ listitem = xbmcgui.ListItem( label=LANGUAGE(30107) )
+ action = "plugin://plugin.library.node.editor?ltype=%s&type=browseValue&actionPath=" % self.ltype + actionPath + "&rule=" + str( ruleCount ) + "&match=" + translated[ 0 ][ 1 ] + "&content=NONE"
+ xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
+ #self.browse( translated[ 0 ][ 1 ], content )
+ xbmcplugin.setContent(int(sys.argv[1]), 'files')
+ xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+ return
+ ruleCount += 1
+ except:
+ print_exc()
+ self.newNodeRule( actionPath, ruleNum )
+ return
+
+ def newNodeRule( self, actionPath, ruleNum ):
+ # This function creates a new node rule, then re-calls the displayNodeRule function
+ # Split the actionPath, to make things easier
+ ( filePath, fileName ) = os.path.split( unquote(actionPath) )
+ # Open the rules file if it exists, else create it
+ if self.ltype.startswith('video'):
+ rulesfile = 'videorules.xml'
+ else:
+ rulesfile = 'musicrules.xml'
+ if os.path.exists( os.path.join( DATAPATH, rulesfile ) ):
+ tree = xmltree.parse( os.path.join( DATAPATH, rulesfile ) )
+ root = tree.getroot()
+ else:
+ tree = xmltree.ElementTree( xmltree.Element( "rules" ) )
+ root = tree.getroot()
+ # See if we already have a element for the node we're parsing
+ nodes = root.findall( "node" )
+ ruleNode = None
+ if nodes is not None:
+ for node in nodes:
+ if node.attrib.get( "name" ) == filePath:
+ ruleNode = node
+ break
+ if ruleNode is None:
+ # We couldn't find an existing element for the node we're parsing - so create one
+ ruleNode = xmltree.SubElement( root, "node" )
+ ruleNode.set( "name", filePath )
+ # Create a new rule
+ ruleTree = self._load_rules().getroot()
+ elems = ruleTree.find( "matches" ).findall( "match" )
+ match = "title"
+ for elem in elems:
+ # We've found the first match for this type
+ match = elem.attrib.get( "name" )
+ operator = elem.find( "operator" ).text
+ break
+ # Find the default operator for this match
+ elems = ruleTree.find( "operators" ).findall( "group" )
+ for elem in elems:
+ if elem.attrib.get( "name" ) == operator:
+ operator = elem.find( "operator" ).text
+ break
+ # Write the new rule
+ newRule = xmltree.SubElement( ruleNode, "rule" )
+ newRule.set( "field", match )
+ newRule.set( "operator", operator )
+ xmltree.SubElement( newRule, "value" )
+ # Save the file
+ self.indent( root )
+ tree.write( os.path.join( DATAPATH, rulesfile ), encoding="UTF-8" )
+ # Now add the rule to all views within the node
+ dirs, files = xbmcvfs.listdir( filePath )
+ for file in files:
+ if file == "index.xml":
+ continue
+ elif file.endswith( ".xml" ):
+ filename = os.path.join( filePath, file )
+ try:
+ # Load the xml file
+ tree = xmltree.parse( filename )
+ root = tree.getroot()
+ rule = xmltree.SubElement( root, "rule" )
+ rule.set( "field", match )
+ rule.set( "operator", operator )
+ xmltree.SubElement( rule, "value" )
+ # Save the file
+ self.indent( root )
+ tree.write( filename, encoding="UTF-8" )
+ except:
+ print_exc()
+ # Re-call the displayNodeRule function
+ self.displayNodeRule( actionPath, ruleNum )
+
+ #def editNodeRule( self, actionPath, originalRule, newRule ):
+ def editNodeRule( self, actionPath, ruleNum, match, operator, value ):
+ ( filePath, fileName ) = os.path.split( unquote(actionPath) )
+ # Update the rule in the rules file
+ if self.ltype.startswith('video'):
+ rulesfile = 'videorules.xml'
+ else:
+ rulesfile = 'musicrules.xml'
+ try:
+ tree = xmltree.parse( os.path.join( DATAPATH, rulesfile ) )
+ root = tree.getroot()
+ nodes = root.findall( "node" )
+ for node in nodes:
+ if node.attrib.get( "name" ) == filePath:
+ ruleCount = 0
+ rules = node.findall( "rule" )
+ for rule in rules:
+ if ruleCount == int( ruleNum ):
+ # This is the rule we want to update
+ valueElem = rule.find( "value" )
+ if match is None:
+ match = rule.attrib.get( "field" )
+ if operator is None:
+ operator = rule.attrib.get( "operator" )
+ if value is None:
+ if valueElem is None:
+ value = ""
+ else:
+ value = valueElem.text
+ if value is None:
+ value = ""
+ newRule = self.translateRule( [ match, operator, value ] )
+ # Get the original rule
+ origMatch = rule.attrib.get( "field" )
+ origOperator = rule.attrib.get( "operator" )
+ if valueElem is None:
+ origValue = ""
+ else:
+ origValue = valueElem.text
+ originalRule = self.translateRule( [ origMatch, origOperator, origValue ] )
+ # Update the rule
+ rule.set( "field", newRule[ 0 ][ 1 ] )
+ rule.set( "operator", newRule[ 1 ][ 2 ] )
+ if len( newRule ) == 3:
+ if rule.find( "value" ) == None:
+ # Create a new rule node
+ xmltree.SubElement( rule, "value" ).text = newRule[ 2 ][ 0 ]
+ else:
+ rule.find( "value" ).text = newRule[ 2 ][ 0 ]
+ ruleCount += 1
+ # Save the file
+ self.indent( root )
+ tree.write( os.path.join( DATAPATH, rulesfile ), encoding="UTF-8" )
+ except:
+ print_exc()
+ return
+ # Now update the views with the new rule
+ dirs, files = xbmcvfs.listdir( filePath )
+ for file in files:
+ if file == "index.xml":
+ continue
+ elif file.endswith( ".xml" ):
+ filename = os.path.join( filePath, file )
+ # List the rules
+ try:
+ # Load the xml file
+ tree = xmltree.parse( filename )
+ root = tree.getroot()
+ # Look for any rules
+ rules = root.findall( "rule" )
+ if rules is not None:
+ for rule in rules:
+ value = rule.find( "value" )
+ if value is not None and value.text is not None:
+ translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value.text ] )
+ else:
+ translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), "" ] )
+ if originalRule[ 0 ][ 1 ] == translated[ 0 ][ 1 ] and originalRule[ 1 ][ 2 ] == translated[ 1 ][ 2 ] and originalRule[ 2 ][ 0 ] == translated[ 2 ][ 0 ]:
+ # This is the right rule, update it
+ rule.set( "field", newRule[ 0 ][ 1 ] )
+ rule.set( "operator", newRule[ 1 ][ 2 ] )
+ if value is not None:
+ value.text = newRule[ 2 ][ 0 ]
+ else:
+ xmltree.SubElement( rule, "value" ).text = newRule[ 2 ][ 0 ]
+ break
+ # Save the file
+ self.indent( root )
+ tree.write( filename, encoding="UTF-8" )
+ except:
+ print_exc()
+
+ #def deleteNodeRule( self, actionPath, originalRule ):
+ def deleteNodeRule( self, actionPath, ruleNum ):
+ ( filePath, fileName ) = os.path.split( actionPath )
+ # Delete the rule from the rules file
+ if self.ltype.startswith('video'):
+ rulesfile = 'videorules.xml'
+ else:
+ rulesfile = 'musicrules.xml'
+ try:
+ tree = xmltree.parse( os.path.join( DATAPATH, rulesfile ) )
+ root = tree.getroot()
+ nodes = root.findall( "node" )
+ for node in nodes:
+ if node.attrib.get( "name" ) == filePath:
+ ruleCount = 0
+ rules = node.findall( "rule" )
+ for rule in rules:
+ if ruleCount == int( ruleNum ):
+ # This is the rule we want to delete
+ # Translate the rule, so we can delete it from the views
+ valueElem = rule.find( "value" )
+ origMatch = rule.attrib.get( "field" )
+ origOperator = rule.attrib.get( "operator" )
+ if valueElem is None:
+ origValue = ""
+ else:
+ origValue = valueElem.text
+ originalRule = self.translateRule( [ origMatch, origOperator, origValue ] )
+ node.remove( rule )
+ ruleCount += 1
+ # Save the file
+ self.indent( root )
+ tree.write( os.path.join( DATAPATH, rulesfile ), encoding="UTF-8" )
+ except:
+ print_exc()
+ return
+ # Now delete the rule from all the views
+ dirs, files = xbmcvfs.listdir( filePath )
+ for file in files:
+ if file == "index.xml":
+ continue
+ elif file.endswith( ".xml" ):
+ filename = os.path.join( filePath, file )
+ # List the rules
+ try:
+ # Load the xml file
+ tree = xmltree.parse( filename )
+ root = tree.getroot()
+ # Look for any rules
+ rules = root.findall( "rule" )
+ if rules is not None:
+ for rule in rules:
+ value = rule.find( "value" )
+ if value is not None and value.text is not None:
+ translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value.text ] )
+ else:
+ translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), "" ] )
+
+ if originalRule[ 0 ][ 1 ] == translated[ 0 ][ 1 ] and originalRule[ 1 ][ 2 ] == translated[ 1 ][ 2 ] and originalRule[ 2 ][ 0 ] == translated[ 2 ][ 0 ]:
+ # This is the right rule, delete it
+ root.remove( rule )
+ break
+ # Save the file
+ self.indent( root )
+ tree.write( filename, encoding="UTF-8" )
+ except:
+ print_exc()
+
+ def deleteAllNodeRules( self, actionPath ):
+ if self.ltype.startswith('video'):
+ rulesfile = 'videorules.xml'
+ else:
+ rulesfile = 'musicrules.xml'
+ try:
+ # Remove all rules for this parent node from the rules file
+ tree = xmltree.parse( os.path.join( DATAPATH, rulesfile ) )
+ root = tree.getroot()
+ nodes = root.findall( "node" )
+ for node in nodes:
+ if node.attrib.get( "name" ) == actionPath:
+ root.remove( node )
+ # Write the updated file
+ self.indent( root )
+ tree.write( os.path.join( DATAPATH, rulesfile ), encoding="UTF-8" )
+ except:
+ print_exc()
+
+ def isNodeRule( self, viewRule, actionPath ):
+ if actionPath.endswith( "index.xml" ):
+ return False
+ if self.nodeRules is None:
+ self.nodeRules = []
+ # Load all the node rules for current directory
+ ( filePath, fileName ) = os.path.split( actionPath )
+ self.loadNodeRules( filePath )
+ # If there are no node rules, return False
+ if len( self.nodeRules ) == 0:
+ return False
+
+ # Compare the passed in rule with those in self.nodeRules
+ count = 0
+ for nodeRule in self.nodeRules:
+ if nodeRule[0] == viewRule[0][1] and nodeRule[1] == viewRule[1][2] and nodeRule[2] == viewRule[2][0]:
+ # Rule matches
+ self.nodeRules.pop( count )
+ return True
+ count += 1
+ return False
+
+ def addAllNodeRules( self, actionPath, root ):
+ if self.nodeRules is None:
+ self.loadNodeRules( actionPath )
+ if len( self.nodeRules ) == 0:
+ return
+ for nodeRule in self.nodeRules:
+ rule = xmltree.SubElement( root, "rule" )
+ rule.set( "field", nodeRule[ 0 ] )
+ rule.set( "operator", nodeRule[ 1 ] )
+ xmltree.SubElement( rule, "value" ).text = nodeRule[ 2 ]
+
+ def getNodeRules( self, actionPath ):
+ ( filePath, fileName ) = os.path.split( actionPath )
+ if self.nodeRules is None:
+ self.loadNodeRules( filePath )
+ if len( self.nodeRules ) == 0:
+ return None
+ else:
+ return self.nodeRules
+
+ def loadNodeRules( self, actionPath ):
+ self.nodeRules = []
+ # Load all the node rules for current directory
+ #actionPath = os.path.join( actionPath, "index.xml" )
+ if self.ltype.startswith('video'):
+ filename = os.path.join( DATAPATH, "videorules.xml" )
+ else:
+ filename = os.path.join( DATAPATH, "musicrules.xml" )
+ if os.path.exists( filename ):
+ try:
+ # Load the xml file
+ tree = xmltree.parse( filename )
+ root = tree.getroot()
+ # Find the node element for this path
+ nodes = root.findall( "node" )
+ if nodes is None:
+ return
+ ruleNode = None
+ for node in nodes:
+ if node.attrib.get( "name" ) == actionPath:
+ ruleNode = node
+ break
+ if ruleNode is None:
+ # There don't appear to be any rules
+ return
+ # Look for any rules
+ rules = ruleNode.findall( "rule" )
+ if rules is not None:
+ for rule in rules:
+ value = rule.find( "value" )
+ if value is not None and value.text is not None:
+ translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value.text ] )
+ else:
+ translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), "" ] )
+ # Save the rule
+ self.nodeRules.append( [ translated[0][1], translated[1][2], translated[2][0] ] )
+ except:
+ print_exc()
+
+ def moveNodeRuleToAppdata( self, path, actionPath ):
+ #BETA2 ONLY CODE
+ # This function will move any parent node rules out of the index.xml, and into the rules file in the plugins appdata folder
+ # Open the rules file if it exists, else create it
+ if self.ltype.startswith('video'):
+ rulesfile = 'videorules.xml'
+ else:
+ rulesfile = 'musicrules.xml'
+ if os.path.exists( os.path.join( DATAPATH, rulesfile ) ):
+ ruleTree = xmltree.parse( os.path.join( DATAPATH, rulesfile ) )
+ ruleRoot = ruleTree.getroot()
+ else:
+ ruleTree = xmltree.ElementTree( xmltree.Element( "rules" ) )
+ ruleRoot = ruleTree.getroot()
+ # See if we already have a element for the node we're parsing
+ nodes = ruleRoot.findall( "node" )
+ ruleNode = None
+ if nodes is not None:
+ for node in nodes:
+ if node.attrib.get( "name" ) == path:
+ ruleNode = node
+ break
+ if ruleNode is None:
+ # We couldn't find an existing element for the node we're parsing - so create one
+ ruleNode = xmltree.SubElement( ruleRoot, "node" )
+ ruleNode.set( "name", path )
+ try:
+ tree = xmltree.parse( unquote(actionPath) )
+ root = tree.getroot()
+ rules = root.findall( "rule" )
+ if rules is not None:
+ for rule in rules:
+ # Create a new rule in the ruleTree
+ newRule = xmltree.SubElement( ruleNode, "rule" )
+ newRule.set( "field", rule.attrib.get( "field" ) )
+ newRule.set( "operator", rule.attrib.get( "operator" ) )
+ value = rule.find( "value" )
+ if value is not None:
+ xmltree.SubElement( newRule, "value" ).text = value.text
+ # Delete the rule from the tree
+ root.remove( rule )
+ # Write both files
+ self.indent( root )
+ tree.write( unquote(actionPath), encoding="UTF-8" )
+ self.indent( ruleRoot )
+ ruleTree.write( os.path.join( DATAPATH, rulesfile ), encoding="UTF-8" )
+ except:
+ print_exc()
+ #/BETA2 ONLY CODE
+
+ # Functions for browsing for value
+ def canBrowse( self, match, content = None ):
+ # Check whether the match type allows browsing
+ if content == "NONE":
+ content = None
+ # Load the rules
+ tree = self._load_rules()
+ elems = tree.getroot().find( "matches" ).findall( "match" )
+ for elem in elems:
+ if elem.attrib.get( "name" ) == match:
+ canBrowse = elem.find( "browse" )
+ if canBrowse is None:
+ # This match type is marked as non-browsable
+ return False
+ if content is None:
+ # If we haven't been passed a content type, allow to browse all
+ return True
+ canBrowse = elem.find( content )
+ if canBrowse is None:
+ # We can't browse for this content type
+ return False
+ # We can browse this content type
+ return True
+ return False
+
+ def browse( self, actionPath, ruleNum, match, content = None ):
+ # This function launches the browser for the given match and content type
+ if content is None or content == "" or content == "NONE":
+ if match != "path" and match != "playlist":
+ # No content parameter passed, so check what contents are valid for
+ # this type
+ tree = self._load_rules()
+ elems = tree.getroot().find( "matches" ).findall( "match" )
+ matches = {}
+ for elem in elems:
+ if elem.attrib.get( "name" ) == match:
+ if self.ltype.startswith('video'):
+ matches["movies"] = elem.find( "movies" )
+ matches["tvshows"] = elem.find( "tvshows" )
+ matches["episodes"] = elem.find( "episodes" )
+ matches["musicvideos"] = elem.find( "musicvideos" )
+ else:
+ matches["artists"] = elem.find( "artists" )
+ matches["albums"] = elem.find( "albums" )
+ matches["songs"] = elem.find( "songs" )
+ break
+ matchesList = []
+ matchesValue = []
+ # Generate a list of the available content types
+ elems = tree.getroot().find( "content" ).findall( "type" )
+ for elem in elems:
+ if matches[ elem.text ] is not None:
+ matchesList.append( xbmc.getLocalizedString( int( elem.attrib.get( "label" ) ) ) )
+ matchesValue.append( elem.text )
+ if len( matchesList ) == 0:
+ return
+ if len( matchesList ) == 1:
+ # Only one returned, no point offering a choice of content type
+ content = matchesValue[ 0 ]
+ else:
+ # Display a select dialog so user can choose their content
+ selectedContent = xbmcgui.Dialog().select( LANGUAGE( 30308 ), matchesList )
+ # If the user selected nothing...
+ if selectedContent == -1:
+ return
+ content = matchesValue[ selectedContent ]
+ if match == "title":
+ self.createBrowseNode( content, None )
+ returnVal = self.browser( self.niceMatchName( match ) )
+ elif match == "tvshow":
+ if content == "episodes":
+ content = "tvshows"
+ self.createBrowseNode( content, None )
+ returnVal = self.browser( self.niceMatchName( match ) )
+ elif match == "genre":
+ self.createBrowseNode( content, "genres" )
+ returnVal = self.browser( self.niceMatchName( match ) )
+ elif match == "album":
+ self.createBrowseNode( content, "none" )
+ returnVal = self.browser( self.niceMatchName( match ) )
+ elif match == "country":
+ self.createBrowseNode( content, "countries" )
+ returnVal = self.browser( self.niceMatchName( match ) )
+ elif match == "year":
+ if content == "episodes":
+ content = "tvshows"
+ self.createBrowseNode( content, "years" )
+ returnVal = self.browser( self.niceMatchName( match ) )
+ elif match == "artist":
+ self.createBrowseNode( content, "artists" )
+ returnVal = self.browser( self.niceMatchName( match ) )
+ elif match == "director":
+ self.createBrowseNode( content, "directors" )
+ returnVal = self.browser( self.niceMatchName( match ) )
+ elif match == "actor":
+ if content == "episodes":
+ content = "tvshows"
+ self.createBrowseNode( content, "actors" )
+ returnVal = self.browser( self.niceMatchName( match ) )
+ elif match == "studio":
+ self.createBrowseNode( content, "studios" )
+ returnVal = self.browser( self.niceMatchName( match ) )
+ elif match == "path":
+ returnVal = xbmcgui.Dialog().browse(0, self.niceMatchName( match ), self.ltype )
+ elif match == "set":
+ self.createBrowseNode( content, "sets" )
+ returnVal = self.browser( self.niceMatchName( match ) )
+ elif match == "tag":
+ self.createBrowseNode( content, "tags" )
+ returnVal = self.browser( self.niceMatchName( match ) )
+ elif match == "playlist":
+ returnVal = self.browserPlaylist( self.niceMatchName( match ) )
+ elif match == "virtualfolder":
+ returnVal = self.browserPlaylist( self.niceMatchName( match ) )
+ elif match == "albumartist":
+ self.createBrowseNode( content, "artists" )
+ returnVal = self.browser( self.niceMatchName( match ) )
+ try:
+ # Delete any fake node
+ xbmcvfs.delete( os.path.join( xbmcvfs.translatePath( "special://profile" ), "library", self.ltype, "plugin.library.node.editor", "temp.xml" ) )
+ except:
+ print_exc()
+ self.writeUpdatedRule( actionPath, ruleNum, value = returnVal )
+
+ def niceMatchName( self, match ):
+ # This function retrieves the translated label for a given match
+ tree = self._load_rules()
+ elems = tree.getroot().find( "matches" ).findall( "match" )
+ matches = {}
+ for elem in elems:
+ if elem.attrib.get( "name" ) == match:
+ return xbmc.getLocalizedString( int( elem.find( "label" ).text ) )
+
+ def createBrowseNode( self, content, grouping = None ):
+ # This function creates a fake node which we'll use for browsing
+ targetDir = os.path.join( xbmcvfs.translatePath( "special://profile" ), "library", self.ltype, "plugin.library.node.editor" )
+ if not os.path.exists( targetDir ):
+ xbmcvfs.mkdirs( targetDir )
+ # Create a new etree
+ tree = xmltree.ElementTree(xmltree.Element( "node" ) )
+ root = tree.getroot()
+ root.set( "type", "filter" )
+ xmltree.SubElement( root, "label" ).text = "Fake node used for browsing"
+ xmltree.SubElement( root, "content" ).text = content
+ if grouping is not None:
+ xmltree.SubElement( root, "group" ).text = grouping
+ else:
+ order = xmltree.SubElement( root, "order" )
+ order.text = "sorttitle"
+ order.set( "direction", "ascending" )
+ self.indent( root )
+ tree.write( os.path.join( targetDir, "temp.xml" ), encoding="UTF-8" )
+
+ def browser( self, title ):
+ # Browser instance used by majority of browses
+ json_query = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "id": 0, "method": "Files.GetDirectory", "params": { "properties": ["title", "file", "genre", "studio", "director", "thumbnail"], "directory": "library://%s/plugin.library.node.editor/temp.xml", "media": "files" } }' % self.ltype)
+ json_response = json.loads(json_query)
+ listings = []
+ values = []
+ # Add all directories returned by the json query
+ if json_response.get('result') and json_response['result'].get('files') and json_response['result']['files'] is not None:
+ for item in json_response['result']['files']:
+ if item[ "label" ] == "..":
+ continue
+ thumb = None
+ if item[ "thumbnail" ] is not "":
+ thumb = item[ "thumbnail" ]
+
+ if "videodb://" not in item["file"]:
+ if title.lower() == "genre":
+ for genre in item["genre"]:
+ label = genre
+ elif title.lower() == "studios":
+ for studio in item["studio"]:
+ label = studio
+ elif title.lower() == "director":
+ for director in item["director"]:
+ label = director
+ else:
+ label = item["label"]
+ else:
+ label = item["label"]
+
+ if label not in values:
+ listitem = xbmcgui.ListItem(label=label)
+ if thumb:
+ listitem.setArt({'icon': thumb, 'thumb': thumb})
+ listitem.setProperty( "thumbnail", thumb )
+ listings.append( listitem )
+ values.append( label )
+ # Show dialog
+ w = ShowDialog( "DialogSelect.xml", CWD, listing=listings, windowtitle=title )
+ w.doModal()
+ selectedItem = w.result
+ del w
+ if selectedItem == "" or selectedItem == -1:
+ return None
+ return values[ selectedItem ]
+
+ def browserPlaylist( self, title ):
+ # Browser instance used by playlists
+ json_query = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "id": 0, "method": "Files.GetDirectory", "params": { "properties": ["title", "file", "thumbnail"], "directory": "special://%splaylists/", "media": "files" } }' % self.ltype)
+ json_response = json.loads(json_query)
+ listings = []
+ values = []
+ # Add all directories returned by the json query
+ if json_response.get('result') and json_response['result'].get('files') and json_response['result']['files'] is not None:
+ for item in json_response['result']['files']:
+ if item[ "label" ] == "..":
+ continue
+ thumb = None
+ if item[ "thumbnail" ] is not "":
+ thumb = item[ "thumbnail" ]
+ listitem = xbmcgui.ListItem(label=item[ "label" ])
+ listitem.setArt({"icon": thumb, "thumbnail": thumb})
+ listitem.setProperty( "thumbnail", thumb )
+ listings.append( listitem )
+ values.append( item[ "label" ] )
+ # Show dialog
+ w = ShowDialog( "DialogSelect.xml", CWD, listing=listings, windowtitle=title )
+ w.doModal()
+ selectedItem = w.result
+ del w
+ if selectedItem == "" or selectedItem == -1:
+ return None
+ return values[ selectedItem ]
+
+ # in-place prettyprint formatter
+ def indent( self, elem, level=0 ):
+ i = "\n" + level*"\t"
+ if len(elem):
+ if not elem.text or not elem.text.strip():
+ elem.text = i + "\t"
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ for elem in elem:
+ self.indent(elem, level+1)
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ else:
+ if level and (not elem.tail or not elem.tail.strip()):
+ elem.tail = i
+
+# ============================
+# === PRETTY SELECT DIALOG ===
+# ============================
+class ShowDialog( xbmcgui.WindowXMLDialog ):
+ def __init__( self, *args, **kwargs ):
+ xbmcgui.WindowXMLDialog.__init__( self )
+ self.listing = kwargs.get( "listing" )
+ self.windowtitle = kwargs.get( "windowtitle" )
+ self.result = -1
+
+ def onInit(self):
+ try:
+ self.fav_list = self.getControl(6)
+ self.getControl(3).setVisible(False)
+ except:
+ print_exc()
+ self.fav_list = self.getControl(3)
+ self.getControl(5).setVisible(False)
+ self.getControl(1).setLabel(self.windowtitle)
+ for item in self.listing :
+ listitem = xbmcgui.ListItem(label=item.getLabel(), label2=item.getLabel2())
+ listitem.setArt({"icon": item.getProperty( "icon" ), "thumbnail": item.getProperty( "thumbnail" )})
+ listitem.setProperty( "Addon.Summary", item.getLabel2() )
+ self.fav_list.addItem( listitem )
+ self.setFocus(self.fav_list)
+
+ def onAction(self, action):
+ if action.getId() in ( 9, 10, 92, 216, 247, 257, 275, 61467, 61448, ):
+ self.result = -1
+ self.close()
+
+ def onClick(self, controlID):
+ if controlID == 6 or controlID == 3:
+ num = self.fav_list.getSelectedPosition()
+ self.result = num
+ else:
+ self.result = -1
+ self.close()
+
+ def onFocus(self, controlID):
+ pass
diff --git a/plugin.library.node.editor/resources/lib/viewattrib.py b/plugin.library.node.editor/resources/lib/viewattrib.py
new file mode 100644
index 0000000000..1c93688963
--- /dev/null
+++ b/plugin.library.node.editor/resources/lib/viewattrib.py
@@ -0,0 +1,353 @@
+# coding=utf-8
+import os, sys
+import xbmc, xbmcaddon, xbmcplugin, xbmcgui, xbmcvfs
+import xml.etree.ElementTree as xmltree
+import json
+from traceback import print_exc
+from urllib.parse import quote, unquote
+
+from resources.lib.common import *
+from resources.lib import pluginBrowser
+
+class ViewAttribFunctions():
+ def __init__(self, ltype):
+ self.ltype = ltype
+
+ def _load_rules( self ):
+ if self.ltype.startswith('video'):
+ overridepath = os.path.join( DEFAULTPATH , "videorules.xml" )
+ else:
+ overridepath = os.path.join( DEFAULTPATH , "musicrules.xml" )
+ try:
+ tree = xmltree.parse( overridepath )
+ return tree
+ except:
+ return None
+
+ def translateContent( self, content ):
+ # Load the rules
+ tree = self._load_rules()
+ hasValue = True
+ elems = tree.getroot().find( "content" ).findall( "type" )
+ for elem in elems:
+ if elem.text == content:
+ return xbmc.getLocalizedString( int( elem.attrib.get( "label" ) ) )
+ return None
+
+ def editContent( self, actionPath, default ):
+ # Load all the rules
+ tree = self._load_rules().getroot()
+ elems = tree.find( "content" ).findall( "type" )
+ selectName = []
+ selectValue = []
+ # Find all the content types
+ for elem in elems:
+ selectName.append( xbmc.getLocalizedString( int( elem.attrib.get( "label" ) ) ) )
+ selectValue.append( elem.text )
+ # Let the user select a content type
+ selectedContent = xbmcgui.Dialog().select( LANGUAGE( 30309 ), selectName )
+ # If the user selected no operator...
+ if selectedContent == -1:
+ return
+ self.writeUpdatedRule( actionPath, "content", selectValue[ selectedContent ], addFilter = True )
+
+ def translateGroup( self, grouping ):
+ # Load the rules
+ tree = self._load_rules()
+ hasValue = True
+ elems = tree.getroot().find( "groupings" ).findall( "grouping" )
+ for elem in elems:
+ if elem.attrib.get( "name" ) == grouping:
+ return xbmc.getLocalizedString( int( elem.find( "label" ).text ) )
+ return None
+
+ def editGroup( self, actionPath, content, default ):
+ # Load all the rules
+ tree = self._load_rules().getroot()
+ elems = tree.find( "groupings" ).findall( "grouping" )
+ selectName = []
+ selectValue = []
+ # Find all the content types
+ for elem in elems:
+ checkContent = elem.find( content )
+ if checkContent is not None:
+ selectName.append( xbmc.getLocalizedString( int( elem.find( "label" ).text ) ) )
+ selectValue.append( elem.attrib.get( "name" ) )
+ # Let the user select a content type
+ selectedGrouping = xbmcgui.Dialog().select( LANGUAGE( 30310 ), selectName )
+ # If the user selected no operator...
+ if selectedGrouping == -1:
+ return
+ self.writeUpdatedRule( actionPath, "group", selectValue[ selectedGrouping ] )
+
+ def addLimit( self, actionPath ):
+ # Load all the rules
+ try:
+ tree = xmltree.parse( unquote(actionPath) )
+ root = tree.getroot()
+ # Add a new content tag
+ newContent = xmltree.SubElement( root, "limit" )
+ newContent.text = "25"
+ # Save the file
+ self.indent( root )
+ tree.write( unquote(actionPath), encoding="UTF-8" )
+ except:
+ print_exc()
+
+ def editLimit( self, actionPath, curValue ):
+ returnVal = xbmcgui.Dialog().input( LANGUAGE( 30311 ), curValue, type=xbmcgui.INPUT_NUMERIC )
+ if returnVal != "":
+ self.writeUpdatedRule( actionPath, "limit", returnVal )
+
+ def addPath( self, actionPath ):
+ # Load all the rules
+ tree = self._load_rules().getroot()
+ elems = tree.find( "paths" ).findall( "type" )
+ selectName = []
+ selectValue = []
+ # Find all the path types
+ for elem in elems:
+ selectName.append( xbmc.getLocalizedString( int( elem.attrib.get( "label" ) ) ) )
+ selectValue.append( elem.attrib.get( "name" ) )
+ # Find any sub-path types
+ for subElem in elem.findall( "type" ):
+ selectName.append( " - %s" %( xbmc.getLocalizedString( int( subElem.attrib.get( "label" ) ) ) ) )
+ selectValue.append( "%s/%s" %( elem.attrib.get( "name" ), subElem.attrib.get( "name" ) ) )
+
+ # Add option to select a plugin
+ selectName.append( LANGUAGE( 30514 ) )
+ selectValue.append( "::PLUGIN::" )
+
+ # Let the user select a path
+ selectedContent = xbmcgui.Dialog().select( LANGUAGE( 30309 ), selectName )
+ # If the user selected no operator...
+ if selectedContent == -1:
+ return
+ if selectValue[ selectedContent ] == "::PLUGIN::":
+ # The user has asked to browse for a plugin
+ path = pluginBrowser.getPluginPath(self.ltype)
+ if path is not None:
+ # User has selected a plugin
+ self.writeUpdatedPath( actionPath, (0, path), addFolder = True)
+ else:
+ # The user has chosen a specific path
+ self.writeUpdatedPath( actionPath, (0, selectValue[ selectedContent ] ), addFolder = True )
+
+ def editPath( self, actionPath, curValue ):
+ returnVal = xbmcgui.Dialog().input( LANGUAGE( 30312 ), curValue, type=xbmcgui.INPUT_ALPHANUM )
+ if returnVal != "":
+ self.writeUpdatedRule( actionPath, "path", returnVal )
+
+ def editIcon( self, actionPath, curValue ):
+ returnVal = xbmcgui.Dialog().input( LANGUAGE( 30313 ), curValue, type=xbmcgui.INPUT_ALPHANUM )
+ if returnVal != "":
+ self.writeUpdatedRule( actionPath, "icon", returnVal )
+
+ def browseIcon( self, actionPath ):
+ returnVal = xbmcgui.Dialog().browse( 2, LANGUAGE( 30313 ), "files", useThumbs = True )
+ if returnVal:
+ self.writeUpdatedRule( actionPath, "icon", returnVal )
+
+ def writeUpdatedRule( self, actionPath, attrib, value, addFilter = False ):
+ # This function writes an updated match, operator or value to a rule
+ try:
+ # Load the xml file
+ tree = xmltree.parse( actionPath )
+ root = tree.getroot()
+ # Add type="filter" if requested
+ if addFilter:
+ root.set( "type", "filter" )
+ # Find the attribute and update it
+ elem = root.find( attrib )
+ if elem is None:
+ # There's no existing attribute with this name, so create one
+ elem = xmltree.SubElement( root, attrib )
+ elem.text = value
+ # Save the file
+ self.indent( root )
+ tree.write( actionPath, encoding="UTF-8" )
+ except:
+ print_exc()
+
+ def writeUpdatedPath( self, actionPath, newComponent, addFolder = False ):
+ # This functions writes an updated path
+ try:
+ # Load the xml file
+ tree = xmltree.parse( unquote(actionPath) )
+ root = tree.getroot()
+ # Add type="folder" if requested
+ if addFolder:
+ root.set( "type", "folder" )
+ # Find the current path element
+ elem = root.find( "path" )
+ if elem is None:
+ # There's no existing path element, so create one
+ elem = xmltree.SubElement( root, "path" )
+ # Get the split version of the path
+ splitPath = self.splitPath( elem.text )
+ elem.text = ""
+ if len( splitPath ) == 0:
+ # If the splitPath is empty, add our new component straight away
+ elem.text = "%s/" %( newComponent[ 1 ] )
+ elif newComponent[ 0 ] == 0 and newComponent[ 1 ].startswith( "plugin://"):
+ # We've been passed a plugin, so only want to write that plugin
+ elem.text = newComponent[ 1 ]
+ else:
+ # Enumarate through everything in the existing path
+ for x, component in enumerate( splitPath ):
+ if x != newComponent[ 0 ]:
+ # Transfer this component to the new path
+ if x == 0:
+ elem.text = self.joinPath( component )
+ elif x == 1:
+ elem.text += "?%s=%s" %( component[ 0 ], quote(component[1]) )
+ else:
+ elem.text += "&%s=%s" %( component[ 0 ], quote(component[1]) )
+ else:
+ # Add our new component
+ if x == 0:
+ elem.text = "%s/" %( newComponent[ 1 ] )
+ elif x == 1:
+ elem.text += "?%s=%s" %( newComponent[ 1 ], quote(newComponent[2]) )
+ else:
+ elem.text += "&%s=%s" %( newComponent[ 1 ], quote(newComponent[2]) )
+ # Check that we added it
+ if x < newComponent[ 0 ]:
+ if newComponent[ 0 ] == 1:
+ elem.text += "?%s=%s" %( newComponent[ 1 ], quote(newComponent[2]) )
+ else:
+ elem.text += "&%s=%s" %( newComponent[ 1 ], quote(newComponent[2]) )
+ # Save the file
+ self.indent( root )
+ tree.write( unquote(actionPath), encoding="UTF-8" )
+ except:
+ print_exc()
+
+ def deletePathRule( self, actionPath, rule ):
+ # This function deletes a rule from a path component
+ result = xbmcgui.Dialog().yesno(ADDONNAME, LANGUAGE( 30407 ) )
+ if not result:
+ return
+
+ try:
+ # Load the xml file
+ tree = xmltree.parse( actionPath )
+ root = tree.getroot()
+ # Find the current path element
+ elem = root.find( "path" )
+ # Get the split version of the path
+ splitPath = self.splitPath( elem.text )
+ elem.text = ""
+
+ # Enumarate through everything in the existing path
+ addedQ = False
+ for x, component in enumerate( splitPath ):
+ if x != rule:
+ if x == 0:
+ elem.text = self.joinPath( component )
+ elif not addedQ:
+ elem.text += "?%s=%s" %( component[ 0 ], quote(component[1]) )
+ addedQ = True
+ else:
+ elem.text += "&%s=%s" %( component[ 0 ], quote(component[1]) )
+ # Save the file
+ self.indent( root )
+ tree.write( actionPath, encoding="UTF-8" )
+ except:
+ print_exc()
+
+ def splitPath( self, completePath ):
+ # This function returns an array of the different components of a path
+ # [library]://[primary path]/[secondary path]/?attribute1=value1&attribute2=value2...
+ # [( , )] [( , )] [( , )]...
+ splitPath = []
+
+ # If completePath is empty, return an empty list
+ if completePath is None:
+ return []
+
+ # If it's a plugin, then we don't want to split it as its unlikely the user will want to edit individual components
+ if completePath.startswith( "plugin://" ):
+ return [ ( completePath, None ) ]
+
+ # Split, get the library://primarypath/[secondarypath]
+ split = completePath.rsplit( "/", 1 )
+ if split[ 0 ].count( "/" ) == 3:
+ # There's a secondary path
+ paths = split[ 0 ].rsplit( "/", 1 )
+ splitPath.append( ( paths[0], paths[1] ) )
+ else:
+ splitPath.append( ( split[ 0 ], None ) )
+
+
+ # Now split the components
+ if len( split ) != 1 and split[ 1 ].startswith( "?" ):
+ for component in split[ 1 ][ 1: ].split( "&" ):
+ componentSplit = component.split( "=" )
+ splitPath.append( ( componentSplit[ 0 ], unquote( componentSplit[1]) ) )
+
+ return splitPath
+
+ def joinPath( self, components ):
+ # This function rejoins the library://path/subpath components of a path
+ returnPath = "%s/" %( components[ 0 ] )
+ if components[ 1 ] is not None:
+ returnPath += "%s/" %( components[ 1 ] )
+ return returnPath
+
+
+ def translatePath( self, path ):
+ # Load the rules
+ tree = self._load_rules()
+ subSearch = None
+ translated = [ path[ 0 ], path[ 1 ] ]
+ elems = tree.getroot().find( "paths" ).findall( "type" )
+ for elem in elems:
+ if elem.attrib.get( "name" ) == path[ 0 ]:
+ translated[ 0 ] = xbmc.getLocalizedString( int( elem.attrib.get( "label" ) ) )
+ subSearch = elem
+ break
+
+ if path[ 1 ] and subSearch is not None:
+ for elem in subSearch.findall( "type" ):
+ if elem.attrib.get( "name" ) == path[ 1 ]:
+ translated[ 1 ] = xbmc.getLocalizedString( int( elem.attrib.get( "label" ) ) )
+ break
+
+ returnString = translated[ 0 ]
+ if translated[ 1 ]:
+ returnString += " - %s" %( translated[ 1 ] )
+
+ return returnString
+
+ def translateMatch( self, value ):
+ if value == "any":
+ return xbmc.getLocalizedString(21426).capitalize()
+ else:
+ return xbmc.getLocalizedString(21425).capitalize()
+
+ def editMatch( self, actionPath ):
+ selectName = [ xbmc.getLocalizedString(21425).capitalize(), xbmc.getLocalizedString(21426).capitalize() ]
+ selectValue = [ "all", "any" ]
+ # Let the user select wether any or all rules need match
+ selectedMatch = xbmcgui.Dialog().select( LANGUAGE( 30310 ), selectName )
+ # If the user made no selection...
+ if selectedMatch == -1:
+ return
+ self.writeUpdatedRule( actionPath, "match", selectValue[ selectedMatch ] )
+
+ # in-place prettyprint formatter
+ def indent( self, elem, level=0 ):
+ i = "\n" + level*"\t"
+ if len(elem):
+ if not elem.text or not elem.text.strip():
+ elem.text = i + "\t"
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ for elem in elem:
+ self.indent(elem, level+1)
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ else:
+ if level and (not elem.tail or not elem.tail.strip()):
+ elem.tail = i
diff --git a/plugin.library.node.editor/resources/musicrules.xml b/plugin.library.node.editor/resources/musicrules.xml
new file mode 100644
index 0000000000..322cfcddc9
--- /dev/null
+++ b/plugin.library.node.editor/resources/musicrules.xml
@@ -0,0 +1,364 @@
+
+
+
+
+ True
+ True
+ True
+ string
+ True
+
+
+
+ True
+ True
+ True
+ string
+ True
+
+
+
+ True
+ True
+ True
+ string
+
+
+
+ True
+ True
+ string
+
+
+
+ True
+ string
+
+
+
+ True
+ string
+
+
+
+ True
+ string
+
+
+
+ True
+ string
+
+
+
+ True
+ string
+
+
+
+ True
+ string
+
+
+
+ True
+ True
+ True
+ isornot
+ True
+
+
+
+ True
+ True
+ True
+ isornot
+ True
+
+
+
+ True
+ True
+ string
+ True
+
+
+
+ True
+ True
+ string
+ True
+
+
+
+ True
+ True
+ numeric
+ True
+
+
+
+ True
+ string
+
+
+
+ True
+ string
+
+
+
+ True
+ string
+
+
+
+ True
+ string
+
+
+
+ True
+ True
+ numeric
+
+
+
+ True
+ True
+ numeric
+
+
+
+ True
+ string
+ True
+
+
+
+ True
+ time
+
+
+
+ True
+ numeric
+
+
+
+ True
+ string
+
+
+
+ True
+ True
+ True
+ string
+ True
+
+
+
+ True
+ True
+ date
+
+
+
+ True
+ string
+
+
+
+ True
+ numeric
+
+
+
+ True
+ numeric
+
+
+
+ True
+ numeric
+
+
+
+ True
+ numeric
+
+
+
+ True
+ numeric
+
+
+
+ True
+ string
+
+
+
+ True
+ seconds
+
+
+
+
+ contains
+ doesnotcontain
+ startswith
+ endswith
+ is
+ isnot
+
+
+ is
+ isnot
+ greaterthan
+ lessthan
+
+
+ is
+ isnot
+ greaterthan
+ lessthan
+
+
+ after
+ before
+ inthelast
+ notinthelast
+
+
+ is
+ isnot
+
+
+
+
+
+ True
+
+
+
+ True
+
+
+
+ True
+
+
+
+ ascending
+ descending
+
+
+ artists
+ albums
+ songs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ None
+
+
+ None
+
+
+
+
+
+ integer
+ artists
+ albums
+ musicdb://roles/
+
+
+ string
+ artists
+ albums
+ musicdb://roles/
+
+
+ integer
+ artists
+ albums
+ songs
+ singles
+ musicdb://artists/
+
+
+ integer
+ artists
+ albums
+ songs
+ singles
+ musicdb://genres/
+
+
+ string
+ artists
+ albums
+ songs
+ singles
+ musicdb://genres/
+
+
+ integer
+ artists
+ songs
+ singles
+ musicdb://albums/
+
+
+ string
+ artists
+ songs
+ singles
+ musicdb://albums/
+
+
+ integer
+ artists
+ musicdb://songs/
+
+
+ boolean
+ artists
+
+
+ integer
+ albums
+ songs
+ singles
+ musicdb://years/
+
+
+ boolean
+ albums
+ songs
+ singles
+
+
+ boolean
+ albums
+
+
+
diff --git a/plugin.library.node.editor/resources/videorules.xml b/plugin.library.node.editor/resources/videorules.xml
new file mode 100644
index 0000000000..21926f7d7f
--- /dev/null
+++ b/plugin.library.node.editor/resources/videorules.xml
@@ -0,0 +1,529 @@
+
+
+
+
+ True
+ True
+ True
+ True
+ string
+ True
+
+
+
+ True
+ string
+ True
+
+
+
+ True
+ True
+ True
+ True
+ string
+
+
+
+ True
+ string
+
+
+ True
+
+ True
+ True
+ string
+
+
+
+ True
+ string
+
+
+
+ True
+ string
+
+
+
+ True
+ True
+ True
+ numeric
+
+
+
+ True
+ True
+ True
+ time
+
+
+
+ True
+ True
+ string
+
+
+
+ True
+ date
+
+
+
+ True
+ True
+ True
+ True
+ numeric
+
+
+
+ True
+ True
+ True
+ True
+ date
+
+
+
+ True
+ True
+ True
+ boolean
+
+
+
+ True
+ True
+ True
+ True
+ string
+ True
+
+
+
+ True
+ string
+ True
+
+
+
+ True
+ string
+ True
+
+
+
+ True
+ True
+ True
+ True
+ numeric
+ True
+
+
+
+ True
+ string
+ True
+
+
+
+ True
+ True
+ True
+ True
+ string
+ True
+
+
+
+ True
+ True
+ True
+ string
+ True
+
+
+
+ True
+ numeric
+
+
+
+ True
+ numeric
+
+
+
+ True
+ numeric
+
+
+
+ True
+ numeric
+
+
+
+ True
+ True
+ True
+ string
+
+
+
+ True
+ numeric
+
+
+
+ True
+ True
+ True
+ True
+ string
+ True
+
+
+
+ True
+ boolean
+
+
+
+ True
+ True
+ True
+ string
+
+
+
+ True
+ True
+ True
+ True
+ string
+ True
+
+
+
+ True
+ string
+ True
+
+
+
+ True
+ True
+ True
+ string
+ True
+
+
+
+ True
+ True
+ True
+ True
+ date
+
+
+
+ True
+ True
+ True
+ numeric
+
+
+
+ True
+ True
+ True
+ string
+
+
+
+ True
+ True
+ True
+ isornot
+
+
+
+ True
+ True
+ True
+ isornot
+
+
+
+ True
+ True
+ True
+ isornot
+
+
+
+ True
+ True
+ True
+ isornot
+
+
+
+ True
+ True
+ True
+ numeric
+
+
+
+ True
+ True
+ True
+ True
+ isornot
+ True
+
+
+
+ True
+ True
+ True
+ True
+ isornot
+ True
+
+
+
+
+ contains
+ doesnotcontain
+ startswith
+ endswith
+ is
+ isnot
+
+
+ is
+ isnot
+ greaterthan
+ lessthan
+
+
+ is
+ isnot
+ greaterthan
+ lessthan
+
+
+ after
+ before
+ inthelast
+ notinthelast
+
+
+ true
+ false
+
+
+ is
+ isnot
+
+
+
+
+
+ True
+
+
+
+ True
+ True
+ True
+
+
+
+ True
+ True
+ True
+
+
+
+ True
+ True
+ True
+
+
+
+ True
+
+
+
+ True
+ True
+ True
+ True
+
+
+
+ True
+ True
+ True
+
+
+
+ True
+
+
+
+ True
+
+
+
+ True
+ True
+ True
+
+
+
+ ascending
+ descending
+
+
+ movies
+ tvshows
+ episodes
+ musicvideos
+
+
+
+
+ movies
+
+
+
+
+
+
+
+
+
+
+
+ tvshows
+
+
+
+
+
+
+
+
+ musicvideos
+
+
+
+
+
+
+
+
+
+
+ movies
+
+
+ tvshows
+
+
+ musicvideos
+
+
+ tvshows
+
+
+ None
+
+
+ None
+
+
+ None
+
+
+
+
+ integer
+ movies
+ tvshows
+ musicvideos
+ videodb://::root::/genres/
+
+
+ integer
+ movies
+ videodb://movies/countries/
+
+
+ integer
+ movies
+ tvshows
+ musicvideos
+ videodb://::root::/studios/
+
+
+ integer
+ movies
+ tvshows
+ musicvideos
+ videodb://::root::/directors/
+
+
+ integer
+ movies
+ tvshows
+ musicvideos
+ videodb://::root::/years/
+
+
+ integer
+ movies
+ tvshows
+ videodb://::root::/actors/
+
+
+ integer
+ movies
+ videodb://::root::/sets/
+
+
+ integer
+ movies
+ tvshows
+ videodb://::root::/tags/
+
+
+ integer
+ tvshows
+ videodb://tvshows/
+
+
+ integer
+ tvshows
+
+
+ integer
+ musicvideos
+ videodb://musicvideos/artists/
+
+
+ integer
+ musicvideos
+ videodb://musicvideos/albums/
+
+
+
diff --git a/plugin.onedrive/LICENSE.txt b/plugin.onedrive/LICENSE.txt
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/plugin.onedrive/LICENSE.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/plugin.onedrive/README.md b/plugin.onedrive/README.md
new file mode 100644
index 0000000000..755213b315
--- /dev/null
+++ b/plugin.onedrive/README.md
@@ -0,0 +1,15 @@
+# OneDrive KODI Add-on
+OneDrive for KODI
+
+Play all your media from Microsoft OneDrive including Videos, Music and Pictures.
+* Unlimited personal or business accounts
+* Playback your music and videos. Listing of videos with thumbnails.
+* Use OneDrive as a source.
+* Subtitles can be assigned automatically if a .str file exists next to the video.
+* Export your videos to your library (.strm files). You can export your music too, but kodi won't support it yet. It's a Kodi issue for now.
+* Show your photos individually or run a slideshow of them. Listing of pictures with thumbnails.
+* Auto-Refreshed slideshow.
+* Use of OAuth 2 login. You don't have to write your user/password within the add-on. Use the login process in your browser.
+* Extremely fast. Using the Microsoft Graph API
+
+This program is not affiliated with or sponsored by Microsoft.
diff --git a/plugin.onedrive/addon.xml b/plugin.onedrive/addon.xml
new file mode 100644
index 0000000000..4ea5016d2a
--- /dev/null
+++ b/plugin.onedrive/addon.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+ image audio video
+
+
+
+ all
+ Microsoft OneDrive for KODI
+
+Play all your media from OneDrive including Videos, Music and Pictures.
+ - Unlimited number of personal or business accounts.
+ - Search over your drive.
+ - Auto-Refreshed slideshow.
+ - Export your videos to your library (.strm files)
+ - Use OneDrive as a source
+ - This program is not affiliated with or sponsored by Microsoft.
+
+ כונן OneDrive של מיקרוסופט עבור קודי
+
+הפעל את כל המדיה שלך מ- OneDrive כולל וידאו, מוסיקה ותמונות.
+ - מספר בלתי מוגבל של חשבונות אישיים או עסקיים.
+ - חיפוש ומציאת הכונן שלך.
+ - ריענון מצגת אוטומטית.
+ - ייצוא קטעי הווידאו לספרייה שלך (קבצי .strm)
+ - שימוש ב-OneDrive כמקור
+ - תוכנית זו אינה מזוהה עם או בחסות מיקרוסופט.
+
+ GPL-3.0-or-later
+ https://github.com/cguZZman/plugin.onedrive
+ https://github.com/cguZZman/plugin.onedrive/issues
+ https://addons.kodi.tv/show/plugin.onedrive
+
+ icon.png
+ fanart.jpg
+
+
+v2.3.0 released Jan 21, 2023:
+- Kodi 20 fix
+
+
+This cloud drive addon uses a third-party authentication mechanism commonly known as OAuth 2.0.
+If you want to know more about OAuth 2.0 you can visit the following pages:
+- https://oauth.net/2/
+- https://developers.google.com/identity/protocols/OAuth2
+- https://docs.microsoft.com/en-us/onedrive/developer/rest-api/getting-started/msa-oauth
+
+Kodi and myself take no responsibility or liability.
+
+The authentication server URL is specified in Settings / Advanced / Sign-in Server. The Sign-in Server implements the OAuth 2.0 protocol.
+The complete source code of the Sign-in Server can be download here: https://github.com/cguZZman/drive-login
+You can clone the project and host it in your own server.
+
+
+
diff --git a/plugin.onedrive/entrypoint.py b/plugin.onedrive/entrypoint.py
new file mode 100644
index 0000000000..b426082f4b
--- /dev/null
+++ b/plugin.onedrive/entrypoint.py
@@ -0,0 +1,21 @@
+#-------------------------------------------------------------------------------
+# Copyright (C) 2017 Carlos Guzman (cguZZman) carlosguzmang@protonmail.com
+#
+# This file is part of OneDrive for Kodi
+#
+# OneDrive for Kodi is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Cloud Drive Common Module for Kodi is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#-------------------------------------------------------------------------------
+
+from resources.lib.addon import OneDriveAddon
+OneDriveAddon().route()
diff --git a/plugin.onedrive/fanart.jpg b/plugin.onedrive/fanart.jpg
new file mode 100644
index 0000000000..59c89d3957
Binary files /dev/null and b/plugin.onedrive/fanart.jpg differ
diff --git a/plugin.onedrive/icon.png b/plugin.onedrive/icon.png
new file mode 100644
index 0000000000..7b8a1d940b
Binary files /dev/null and b/plugin.onedrive/icon.png differ
diff --git a/plugin.onedrive/resources/__init__.py b/plugin.onedrive/resources/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugin.onedrive/resources/language/resource.language.en_gb/strings.po b/plugin.onedrive/resources/language/resource.language.en_gb/strings.po
new file mode 100644
index 0000000000..7e72200536
--- /dev/null
+++ b/plugin.onedrive/resources/language/resource.language.en_gb/strings.po
@@ -0,0 +1,121 @@
+# Kodi Media Center language file
+# Addon Name: OneDrive
+# Addon id: plugin.onedrive
+# Addon Provider: Carlos Guzman (cguZZman)
+msgid ""
+msgstr ""
+"Project-Id-Version: Kodi Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#32000"
+msgid "Player Service"
+msgstr ""
+
+msgctxt "#32001"
+msgid "Export Service"
+msgstr ""
+
+msgctxt "#32002"
+msgid "Source Service"
+msgstr ""
+
+msgctxt "#32003"
+msgid "Before export to library, remove previous files and folders"
+msgstr ""
+
+msgctxt "#32004"
+msgid "Automatically set subtitles from cloud drive"
+msgstr ""
+
+msgctxt "#32005"
+msgid "Auto-Refreshed slideshow"
+msgstr ""
+
+msgctxt "#32006"
+msgid "Refresh interval in minutes"
+msgstr ""
+
+msgctxt "#32007"
+msgid "Special: Photos"
+msgstr ""
+
+msgctxt "#32008"
+msgid "Special: Camera Roll"
+msgstr ""
+
+msgctxt "#32009"
+msgid "Special: Music"
+msgstr ""
+
+msgctxt "#32010"
+msgid "Recursive auto-refreshed slideshow"
+msgstr ""
+
+msgctxt "#32011"
+msgid "Resume playing when resume point exists in library"
+msgstr ""
+
+msgctxt "#32012"
+msgid "Open Cloud Drive Common Settings..."
+msgstr ""
+
+msgctxt "#32017"
+msgid "Ask to resume if resume point exists in library"
+msgstr ""
+
+msgctxt "#32018"
+msgid "Save resume points and watched status in library"
+msgstr ""
+
+msgctxt "#32019"
+msgid "Do not include filename extension in a .strm"
+msgstr ""
+
+msgctxt "#32020"
+msgid "Hide exporting progress dialog"
+msgstr ""
+
+msgctxt "#32030"
+msgid "Collaboration"
+msgstr ""
+
+msgctxt "#32031"
+msgid "Report errors automatically to help resolve them quickly"
+msgstr ""
+
+msgctxt "#32032"
+msgid "Advanced"
+msgstr ""
+
+msgctxt "#32033"
+msgid "Sign-in Server"
+msgstr ""
+
+msgctxt "#32034"
+msgid "Cache expiration time (in minutes)"
+msgstr ""
+
+msgctxt "#32035"
+msgid "Clear cache now"
+msgstr ""
+
+msgctxt "#32067"
+msgid "Services"
+msgstr ""
+
+msgctxt "#32068"
+msgid "Allow using OneDrive as a source"
+msgstr ""
+
+msgctxt "#32069"
+msgid " Source server port - http://localhost:/source"
+msgstr ""
\ No newline at end of file
diff --git a/plugin.onedrive/resources/language/resource.language.he_il/strings.po b/plugin.onedrive/resources/language/resource.language.he_il/strings.po
new file mode 100644
index 0000000000..dbe8f8051a
--- /dev/null
+++ b/plugin.onedrive/resources/language/resource.language.he_il/strings.po
@@ -0,0 +1,70 @@
+# Kodi Media Center language file
+# Addon Name: OneDrive
+# Addon id: plugin.onedrive
+# Addon Provider: Carlos Guzman (cguZZman)
+msgid ""
+msgstr ""
+"Project-Id-Version: Kodi Addons\n"
+"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2017-10-19 11:41+0300\n"
+"Last-Translator: A. Dambledore\n"
+"Language-Team: Eng2Heb\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: he_IL\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#32000"
+msgid "Account"
+msgstr "חשבון"
+
+msgctxt "#32001"
+msgid "Video Library Export Folder"
+msgstr "תיקיית ייצוא ספריית וידאו"
+
+msgctxt "#32002"
+msgid "Music Library Export Folder"
+msgstr "תיקיית ייצוא ספריית מוזיקה"
+
+msgctxt "#32003"
+msgid "Before export to library, remove previous files and folders"
+msgstr "לפני ייצוא לספריה, הסר את הקבצים והתיקיות הקודמים"
+
+msgctxt "#32004"
+msgid "When playing videos, set the subtitle file located next to the video (.srt)"
+msgstr "בעת ניגון וידאו, הגדר את קובץ הכתוביות הממוקם ליד הווידאו (.srt)"
+
+msgctxt "#32005"
+msgid "Auto-Refreshed slideshow"
+msgstr "מצגת עם רענון אוטומטי"
+
+msgctxt "#32006"
+msgid "Refresh interval in minutes"
+msgstr "מרווח זמן לרענון בדקות"
+
+msgctxt "#32007"
+msgid "Special: Photos"
+msgstr "מיוחד: תמונות"
+
+msgctxt "#32008"
+msgid "Special: Camera Roll"
+msgstr "מיוחד: סיבוב המצלמה"
+
+msgctxt "#32009"
+msgid "Special: Music"
+msgstr "מיוחד: מוסיקה"
+
+msgctxt "#32010"
+msgid "Recursive auto-refreshed slideshow"
+msgstr "רענן מצגת באופן רקורסיבי ואוטומטי"
+
+msgctxt "#32011"
+msgid "Common Settings"
+msgstr "הגדרות נפוצות"
+
+msgctxt "#32012"
+msgid "Open Cloud Drive Common Settings..."
+msgstr "פתח הגדרות נפוצות של כונן ענן ..."
+
diff --git a/plugin.onedrive/resources/lib/__init__.py b/plugin.onedrive/resources/lib/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugin.onedrive/resources/lib/addon.py b/plugin.onedrive/resources/lib/addon.py
new file mode 100644
index 0000000000..a36dc7684d
--- /dev/null
+++ b/plugin.onedrive/resources/lib/addon.py
@@ -0,0 +1,68 @@
+#-------------------------------------------------------------------------------
+# Copyright (C) 2017 Carlos Guzman (cguZZman) carlosguzmang@protonmail.com
+#
+# This file is part of OneDrive for Kodi
+#
+# OneDrive for Kodi is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Cloud Drive Common Module for Kodi is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#-------------------------------------------------------------------------------
+
+import urllib
+
+from clouddrive.common.ui.addon import CloudDriveAddon
+from clouddrive.common.utils import Utils
+from resources.lib.provider.onedrive import OneDrive
+
+class OneDriveAddon(CloudDriveAddon):
+ _provider = OneDrive()
+ _action = None
+
+ def __init__(self):
+ super(OneDriveAddon, self).__init__()
+
+ def get_provider(self):
+ return self._provider
+
+ def get_custom_drive_folders(self, driveid):
+ drive = self._account_manager.get_by_driveid('drive', driveid)
+ drive_folders = []
+ if drive['type'] == 'personal':
+ if self._content_type == 'image':
+ path = 'special/photos'
+ params = {'action': '_slideshow', 'content_type': self._content_type, 'driveid': driveid, 'path': path}
+ context_options = [(self._common_addon.getLocalizedString(32032), 'RunPlugin('+self._addon_url + '?' + urllib.parse.urlencode(params)+')')]
+ drive_folders.append({'name' : self._addon.getLocalizedString(32007), 'path' : path, 'context_options': context_options})
+
+ path = 'special/cameraroll'
+ params['path'] = path
+ context_options = [(self._common_addon.getLocalizedString(32032), 'RunPlugin('+self._addon_url + '?' + urllib.parse.urlencode(params)+')')]
+ drive_folders.append({'name' : self._addon.getLocalizedString(32008), 'path' : path, 'context_options': context_options})
+ elif self._content_type == 'audio':
+ drive_folders.append({'name' : self._addon.getLocalizedString(32009), 'path' : 'special/music'})
+ drive_folders.append({'name' : self._common_addon.getLocalizedString(32053), 'path' : 'recent'})
+ if drive['type'] != 'documentLibrary':
+ drive_folders.append({'name' : self._common_addon.getLocalizedString(32058), 'path' : 'sharedWithMe'})
+ return drive_folders
+
+ def _rename_action(self):
+ if self._action == 'open_drive_folder':
+ self._addon_params['path'] = Utils.get_safe_value(self._addon_params, 'folder')
+ self._action = Utils.get_safe_value({
+ 'open_folder': '_list_folder',
+ 'open_drive': '_list_drive',
+ 'open_drive_folder': '_list_folder'
+ }, self._action, self._action)
+
+if __name__ == '__main__':
+ OneDriveAddon().route()
+
diff --git a/plugin.onedrive/resources/lib/provider/__init__.py b/plugin.onedrive/resources/lib/provider/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugin.onedrive/resources/lib/provider/onedrive.py b/plugin.onedrive/resources/lib/provider/onedrive.py
new file mode 100644
index 0000000000..62d39447bb
--- /dev/null
+++ b/plugin.onedrive/resources/lib/provider/onedrive.py
@@ -0,0 +1,229 @@
+#-------------------------------------------------------------------------------
+# Copyright (C) 2017 Carlos Guzman (cguZZman) carlosguzmang@protonmail.com
+#
+# This file is part of OneDrive for Kodi
+#
+# OneDrive for Kodi is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Cloud Drive Common Module for Kodi is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#-------------------------------------------------------------------------------
+
+from clouddrive.common.remote.provider import Provider
+from clouddrive.common.utils import Utils
+from clouddrive.common.exception import RequestException, ExceptionUtils
+
+import urllib
+from urllib.error import HTTPError
+
+class OneDrive(Provider):
+ _extra_parameters = {'expand': 'thumbnails'}
+
+ def __init__(self, source_mode = False):
+ super(OneDrive, self).__init__('onedrive', source_mode)
+
+ def _get_api_url(self):
+ return 'https://graph.microsoft.com/v1.0'
+
+ def _get_request_headers(self):
+ return None
+
+ def get_account(self, request_params=None, access_tokens=None):
+ me = self.get('/me/', request_params=request_params, access_tokens=access_tokens)
+ if not me:
+ raise Exception('NoAccountInfo')
+ return { 'id' : me['id'], 'name' : me['displayName']}
+
+ def get_drives(self, request_params=None, access_tokens=None):
+ drives = []
+ drives_id_list =[]
+ try:
+ response = self.get('/drives', request_params=request_params, access_tokens=access_tokens)
+ for drive in response['value']:
+ drives_id_list.append(drive['id'])
+ drives.append({
+ 'id' : drive['id'],
+ 'name' : Utils.get_safe_value(drive, 'name', ''),
+ 'type' : drive['driveType']
+ })
+ except RequestException as ex:
+ httpex = ExceptionUtils.extract_exception(ex, HTTPError)
+ if not httpex or httpex.code != 403:
+ raise ex
+
+ response = self.get('/me/drives', request_params=request_params, access_tokens=access_tokens)
+ for drive in response['value']:
+ if not drive['id'] in drives_id_list:
+ drives_id_list.append(drive['id'])
+ drives.append({
+ 'id' : drive['id'],
+ 'name' : Utils.get_safe_value(drive, 'name', ''),
+ 'type' : drive['driveType']
+ })
+ return drives
+
+ def get_drive_type_name(self, drive_type):
+ if drive_type == 'personal':
+ return 'OneDrive Personal'
+ elif drive_type == 'business':
+ return 'OneDrive for Business'
+ elif drive_type == 'documentLibrary':
+ return ' SharePoint Document Library'
+ return drive_type
+
+ def get_folder_items(self, item_driveid=None, item_id=None, path=None, on_items_page_completed=None, include_download_info=False, on_before_add_item=None):
+ item_driveid = Utils.default(item_driveid, self._driveid)
+ if item_id:
+ files = self.get('/drives/'+item_driveid+'/items/' + item_id + '/children', parameters = self._extra_parameters)
+ elif path == 'sharedWithMe' or path == 'recent':
+ files = self.get('/drives/'+self._driveid+'/' + path)
+ else:
+ if path == '/':
+ path = 'root'
+ else:
+ parts = path.split('/')
+ if len(parts) > 1 and not parts[0]:
+ path = 'root:'+path+':'
+ files = self.get('/drives/'+self._driveid+'/' + path + '/children', parameters = self._extra_parameters)
+ if self.cancel_operation():
+ return
+ return self.process_files(files, on_items_page_completed, include_download_info, on_before_add_item=on_before_add_item)
+
+ def process_files(self, files, on_items_page_completed=None, include_download_info=False, extra_info=None, on_before_add_item=None):
+ items = []
+ for f in files['value']:
+ f = Utils.get_safe_value(f, 'remoteItem', f)
+ item = self._extract_item(f, include_download_info)
+ if on_before_add_item:
+ on_before_add_item(item)
+ items.append(item)
+ if on_items_page_completed:
+ on_items_page_completed(items)
+ if type(extra_info) is dict:
+ if '@odata.deltaLink' in files:
+ extra_info['change_token'] = files['@odata.deltaLink']
+
+ if '@odata.nextLink' in files:
+ next_files = self.get(files['@odata.nextLink'])
+ if self.cancel_operation():
+ return
+ items.extend(self.process_files(next_files, on_items_page_completed, include_download_info, extra_info, on_before_add_item))
+ return items
+
+ def _extract_item(self, f, include_download_info=False):
+ name = Utils.get_safe_value(f, 'name', '')
+ parent_reference = Utils.get_safe_value(f, 'parentReference', {})
+ item = {
+ 'id': f['id'],
+ 'name': name,
+ 'name_extension' : Utils.get_extension(name),
+ 'drive_id' : Utils.get_safe_value(parent_reference, 'driveId'),
+ 'parent' : Utils.get_safe_value(parent_reference, 'id'),
+ 'mimetype' : Utils.get_safe_value(Utils.get_safe_value(f, 'file', {}), 'mimeType'),
+ 'last_modified_date' : Utils.get_safe_value(f,'lastModifiedDateTime'),
+ 'size': Utils.get_safe_value(f, 'size', 0),
+ 'description': Utils.get_safe_value(f, 'description', ''),
+ 'deleted': 'deleted' in f
+ }
+ if 'folder' in f:
+ item['folder'] = {
+ 'child_count' : Utils.get_safe_value(f['folder'],'childCount',0)
+ }
+ if 'video' in f:
+ video = f['video']
+ item['video'] = {
+ 'width' : Utils.get_safe_value(video,'width', 0),
+ 'height' : Utils.get_safe_value(video, 'height', 0),
+ 'duration' : Utils.get_safe_value(video, 'duration', 0) /1000
+ }
+ if 'audio' in f:
+ audio = f['audio']
+ item['audio'] = {
+ 'tracknumber' : Utils.get_safe_value(audio, 'track'),
+ 'discnumber' : Utils.get_safe_value(audio, 'disc'),
+ 'duration' : int(Utils.get_safe_value(audio, 'duration') or '0') / 1000,
+ 'year' : Utils.get_safe_value(audio, 'year'),
+ 'genre' : Utils.get_safe_value(audio, 'genre'),
+ 'album': Utils.get_safe_value(audio, 'album'),
+ 'artist': Utils.get_safe_value(audio, 'artist'),
+ 'title': Utils.get_safe_value(audio, 'title')
+ }
+ if 'image' in f or 'photo' in f:
+ item['image'] = {
+ 'size' : Utils.get_safe_value(f, 'size', 0)
+ }
+ if 'thumbnails' in f and type(f['thumbnails']) == list and len(f['thumbnails']) > 0:
+ thumbnails = f['thumbnails'][0]
+ item['thumbnail'] = Utils.get_safe_value(Utils.get_safe_value(thumbnails, 'large', {}), 'url', '')
+ if include_download_info:
+ item['download_info'] = {
+ 'url' : Utils.get_safe_value(f,'@microsoft.graph.downloadUrl')
+ }
+ return item
+
+ def search(self, query, item_driveid=None, item_id=None, on_items_page_completed=None):
+ item_driveid = Utils.default(item_driveid, self._driveid)
+ url = '/drives/'
+ if item_id:
+ url += item_driveid+'/items/' + item_id
+ else:
+ url += self._driveid
+ url += '/search(q=\''+urllib.parse.quote(Utils.str(query))+'\')'
+ self._extra_parameters['filter'] = 'file ne null'
+ files = self.get(url, parameters = self._extra_parameters)
+ if self.cancel_operation():
+ return
+ return self.process_files(files, on_items_page_completed)
+
+ def get_subtitles(self, parent, name, item_driveid=None, include_download_info=False):
+ item_driveid = Utils.default(item_driveid, self._driveid)
+ subtitles = []
+ search_url = '/drives/'+item_driveid+'/items/' + parent + '/search(q=\''+urllib.parse.quote(Utils.str(Utils.remove_extension(name)).replace("'","''"))+'\')'
+ files = self.get(search_url)
+ for f in files['value']:
+ subtitle = self._extract_item(f, include_download_info)
+ if subtitle['name_extension'].lower() in ('srt','idx','sub','sbv','ass','ssa','smi'):
+ subtitles.append(subtitle)
+ return subtitles
+
+ def get_item(self, item_driveid=None, item_id=None, path=None, find_subtitles=False, include_download_info=False):
+ item_driveid = Utils.default(item_driveid, self._driveid)
+ if item_id:
+ f = self.get('/drives/'+item_driveid+'/items/' + item_id, parameters = self._extra_parameters)
+ elif path == 'sharedWithMe' or path == 'recent':
+ return
+ else:
+ if path == '/':
+ path = 'root'
+ else:
+ parts = path.split('/')
+ if len(parts) > 1 and not parts[0]:
+ path = 'root:'+path+':'
+ f = self.get('/drives/'+self._driveid+'/' + path, parameters = self._extra_parameters)
+
+ item = self._extract_item(f, include_download_info)
+ if find_subtitles:
+ subtitles = self.get_subtitles(item['parent'], item['name'], item_driveid, include_download_info)
+ if subtitles:
+ item['subtitles'] = subtitles
+ return item
+
+ def changes(self):
+ f = self.get(Utils.default(self.get_change_token(), '/drives/'+self._driveid+'/root/delta?token=latest'), request_params = {'on_exception': self.on_exception})
+ extra_info = {}
+ changes = self.process_files(f, include_download_info=True, extra_info=extra_info)
+ self.persist_change_token(Utils.get_safe_value(extra_info, 'change_token'))
+ return changes
+
+ def on_exception(self, request, e):
+ ex = ExceptionUtils.extract_exception(e, HTTPError)
+ if ex and ex.code == 404:
+ self.persist_change_token(None)
\ No newline at end of file
diff --git a/plugin.onedrive/resources/settings.xml b/plugin.onedrive/resources/settings.xml
new file mode 100644
index 0000000000..9b9694d1cb
--- /dev/null
+++ b/plugin.onedrive/resources/settings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugin.onedrive/service.py b/plugin.onedrive/service.py
new file mode 100644
index 0000000000..a53b1fea62
--- /dev/null
+++ b/plugin.onedrive/service.py
@@ -0,0 +1,30 @@
+#-------------------------------------------------------------------------------
+# Copyright (C) 2017 Carlos Guzman (cguZZman) carlosguzmang@protonmail.com
+#
+# This file is part of OneDrive for Kodi
+#
+# OneDrive for Kodi is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Cloud Drive Common Module for Kodi is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#-------------------------------------------------------------------------------
+
+from clouddrive.common.service.download import DownloadService
+from clouddrive.common.service.source import SourceService
+from clouddrive.common.service.utils import ServiceUtil
+from resources.lib.provider.onedrive import OneDrive
+from clouddrive.common.service.export import ExportService
+from clouddrive.common.service.player import PlayerService
+
+
+if __name__ == '__main__':
+ ServiceUtil.run([DownloadService(OneDrive), SourceService(OneDrive),
+ ExportService(OneDrive), PlayerService(OneDrive)])
\ No newline at end of file
diff --git a/plugin.picture.googlephotos/LICENSE.txt b/plugin.picture.googlephotos/LICENSE.txt
new file mode 100644
index 0000000000..f288702d2f
--- /dev/null
+++ b/plugin.picture.googlephotos/LICENSE.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/plugin.picture.googlephotos/README.md b/plugin.picture.googlephotos/README.md
new file mode 100644
index 0000000000..b1630c940c
--- /dev/null
+++ b/plugin.picture.googlephotos/README.md
@@ -0,0 +1,23 @@
+# Google Photos Kodi Addon
+View Google Photos on Kodi!
+* Unlimited accounts
+* Shared Album Support
+* Custom Filter Support
+* Video Playback (Exists but broken due to Photos API limitations)
+
+This program is not affiliated with or sponsored by Google.
+
+## Installation Instructions
+### Method 1
+Use official KODI Repository
+Add-ons -> Install from repository -> Kodi Add-on Repository -> Picture add-ons -> Google Photos -> Install
+
+### Method 2
+From zip
+1. Download zip from releases.
+2. Add-ons -> Install from zip file -> Locate the file -> Click OK.
+
+
+Note: Instructions to generate client credentials can be found at https://photos-kodi-addon.onrender.com/credentialsguide
+
+
diff --git a/plugin.picture.googlephotos/addon.xml b/plugin.picture.googlephotos/addon.xml
new file mode 100644
index 0000000000..97624927ea
--- /dev/null
+++ b/plugin.picture.googlephotos/addon.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+ image
+
+
+ View Google Photos on Kodi!
+ See all your photos and videos on Kodi.
+- Unlimited number of accounts.
+- Custom Filter support
+- With Pagination for small load times
+Video Seeking does not work due to limitations in the API. If video playback is failing, try the solution given in https://forum.kodi.tv/showthread.php?tid=361046
+
+Google Photos for Kodi uses a third-party authentication mechanism commonly known as OAuth 2.0.
+If you want to know more about OAuth 2.0 you can visit the following pages:
+- https://oauth.net/2/
+- https://developers.google.com/identity/protocols/OAuth2
+
+Kodi and I take no responsibility or liability.
+
+The authentication server URL is specified in Settings / Sign-in Server. The Sign-in Server implements the OAuth 2.0 protocol.
+The complete source code of the Sign-in Server can be download here: https://github.com/pransin/Device-Authorization-Grant-Proxy-Server
+You can clone the project and host it in your own server.
+
+ GPL-3.0-or-later
+
+ icon.png
+ fanart.jpg
+
+
+ v2.0.0 (2022-09-14)
+ CONTAINS BREAKING CHANGES. WILL REMOVE SAVED ACCOUNTS.
+ - Changed sign-in server to photos-kodi-login.onrender.com
+ - Client credentials must be entered in the settings to use the addon
+ Note: I will update the addon if I find a way to simplify the login procedure
+
+
+
\ No newline at end of file
diff --git a/plugin.picture.googlephotos/fanart.jpg b/plugin.picture.googlephotos/fanart.jpg
new file mode 100644
index 0000000000..954a333874
Binary files /dev/null and b/plugin.picture.googlephotos/fanart.jpg differ
diff --git a/plugin.picture.googlephotos/icon.png b/plugin.picture.googlephotos/icon.png
new file mode 100644
index 0000000000..a58dce6527
Binary files /dev/null and b/plugin.picture.googlephotos/icon.png differ
diff --git a/plugin.picture.googlephotos/main.py b/plugin.picture.googlephotos/main.py
new file mode 100644
index 0000000000..0001b65e80
--- /dev/null
+++ b/plugin.picture.googlephotos/main.py
@@ -0,0 +1,417 @@
+import json
+import sys
+import requests
+from pathlib import Path
+from resources.lib import auth
+import resources.lib.config as config
+from urllib import parse
+import xbmc
+import xbmcaddon
+import xbmcvfs
+import xbmcgui
+import xbmcplugin
+import threading
+import traceback
+import time
+
+from resources.lib.auth import read_credentials, get_device_code
+import resources.lib.dialogs as dialogs
+# from resources.lib.ui.custom_filter_dialog import FilterDialog
+import resources.lib.utils as utils
+
+base_url = sys.argv[0]
+addon_handle = int(sys.argv[1])
+qs = sys.argv[2][1:]
+args = parse.parse_qs(qs)
+
+mode = args.get('mode')
+token = None # Access token
+
+# Addon dir path
+__addon__ = xbmcaddon.Addon()
+# addon_path = __addon__.getAddonInfo('path')
+profile_path = xbmcvfs.translatePath(
+ __addon__.getAddonInfo('profile'))
+token_folder = Path(profile_path + config.token_folder)
+token_path = None
+if args.get('token_filename'):
+ token_path = Path(token_folder / args.get('token_filename')[0])
+xbmcplugin.setContent(addon_handle, 'images')
+
+
+def new_account():
+ xbmc.log('Executing new_account function', xbmc.LOGDEBUG)
+
+ # Open dialog
+ baseUrl = __addon__.getSettingString('baseUrl')
+ login_dialog = dialogs.QRDialogProgress.create()
+ login_dialog.show()
+ # Gives enough time for window to initialize before sending the device code request
+ xbmc.sleep(10)
+ # Get User Code from auth server
+ code_json = get_device_code()
+ expires_at = time.time() + 0.99 * float(code_json["expires_in"])
+ login_dialog.update(
+ int(code_json["expires_in"]), code=code_json["userCode"])
+ last_req_time = time.time()
+
+ # Update progress dialog indicating time left for complete
+ while not login_dialog.iscanceled():
+ time_left = round(expires_at - time.time())
+ login_dialog.update(time_left=time_left)
+ xbmc.sleep(1000)
+
+ status_code = -1 # Indicates no request sent
+ # Check if last request is sufficiently old
+ if (time_left > 0) and (time.time() - last_req_time >= code_json["interval"]):
+ status_code = auth.fetch_and_save_token(
+ code_json['deviceCode'], token_folder)
+ last_req_time = time.time()
+ if status_code == 200:
+ xbmcgui.Dialog().notification(__addon__.getLocalizedString(30424),
+ __addon__.getLocalizedString(30402), xbmcgui.NOTIFICATION_INFO, 3000)
+ break
+
+ if status_code == 403: # 403 indicates rate limiting
+ xbmc.sleep(1000)
+
+ # Refresh Code if time is over
+ if (time_left < 0) or (status_code and (status_code == 400)):
+ code_json = get_device_code()
+ expires_at = time.time() + 0.99 * float(code_json["expires_in"])
+ login_dialog.update(
+ int(code_json["expires_in"]), code_json["userCode"])
+ xbmcgui.Dialog().notification(__addon__.getLocalizedString(30424),
+ __addon__.getLocalizedString(30403), xbmcgui.NOTIFICATION_INFO, 3000)
+ login_dialog.close()
+ xbmc.executebuiltin('Container.Refresh')
+
+
+def remove_account():
+ xbmcvfs.delete(str(token_path))
+ xbmc.executebuiltin('Container.Refresh')
+
+
+def remove_all_accounts():
+ token_folder.mkdir(parents=True, exist_ok=True)
+ for file in token_folder.iterdir():
+ xbmcvfs.delete(str(file))
+
+
+def list_options():
+ items = []
+
+ # Third string in the tuples are API endpoint's route
+ modes = [(__addon__.getLocalizedString(30404), 'list_media', 'all'),
+ (__addon__.getLocalizedString(30405), 'list_albums', 'albums'),
+ (__addon__.getLocalizedString(30406), 'list_albums', 'sharedAlbums'),
+ (__addon__.getLocalizedString(30407), 'custom_filter')]
+
+ for mode in modes:
+ if len(mode) == 2:
+ url = utils.build_url(
+ base_url, {'mode': mode[1], 'token_filename': token_path.name})
+ else:
+ url = utils.build_url(
+ base_url, {'mode': mode[1], 'type': mode[2], 'token_filename': token_path.name})
+ li = xbmcgui.ListItem(mode[0])
+ items.append((url, li, True)) # (url, listitem[, isFolder])
+
+ xbmcplugin.addDirectoryItems(addon_handle, items)
+ xbmcplugin.endOfDirectory(addon_handle)
+
+
+def refresh():
+ '''
+ Refreshes the current window
+ IMP: Pass previous query string as argument in the url with key prev_q
+ '''
+ id = xbmcgui.getCurrentWindowId()
+ pos = int(xbmc.getInfoLabel(f'Container.CurrentItem'))
+ cont_path = utils.build_url(
+ base_url, {'call_type': 1}, args.get('prev_q')[0])
+ xbmc.executebuiltin(f'Container.Update({cont_path})')
+
+ # get active window
+ win = xbmcgui.Window(id)
+ cid = win.getFocusId()
+ # Check if window is fully loaded
+ while not xbmc.getCondVisibility(f'Control.IsVisible({cid})'):
+ xbmc.sleep(150)
+ # Focus on the last focused position
+ xbmc.executebuiltin(f'SetFocus({cid},{pos},absolute)')
+
+
+def get_items(pageToken=None) -> dict:
+
+ # Prepare request
+ params = {'pageSize': 100}
+ if pageToken:
+ params['pageToken'] = pageToken
+ headers = {'Authorization': 'Bearer ' + token}
+
+ # Check type of required listing
+ list_type = args.get('type')[0]
+ if list_type == 'all':
+ error = __addon__.getLocalizedString(30408)
+ res = requests.get(config.service_endpoint + '/mediaItems',
+ headers=headers, params=params)
+ elif list_type == 'album':
+ error = __addon__.getLocalizedString(30409)
+ params['albumId'] = args.get('id')[0]
+ res = requests.post(config.service_endpoint + '/mediaItems:search',
+ headers=headers, data=params)
+ elif list_type == 'filter':
+ # params
+ params['filters'] = utils.buildFilter(__addon__)
+ if not bool(params['filters']):
+ return None
+ error = __addon__.getLocalizedString(30410)
+ res = requests.post(config.service_endpoint + '/mediaItems:search',
+ headers=headers, json=params)
+ if res.status_code != 200:
+ dialog = xbmcgui.Dialog()
+ dialog.notification(
+ __addon__.getLocalizedString(30411), f'{error}. {__addon__.getLocalizedString(30412)}:{res.status_code}', xbmcgui.NOTIFICATION_ERROR, 3000)
+ return None
+ return res.json()
+
+
+def get_items_bg(result, path):
+ if 'nextPageToken' in result[-1]:
+ pageToken = result[-1]["nextPageToken"]
+ media = get_items(pageToken)
+ if not media:
+ return None
+ utils.storeData(path, media)
+
+
+def list_media():
+ # For all photo directories
+
+ path = profile_path + config.media_filename
+ if not args.get('call_type'):
+ xbmcvfs.delete(path)
+
+ result = utils.loadData(path)
+ if not result:
+ media = get_items()
+ if media:
+ utils.storeData(path, media)
+ result = [media]
+ else:
+ xbmcgui.Dialog().notification(__addon__.getLocalizedString(30425),
+ __addon__.getLocalizedString(30413), xbmcgui.NOTIFICATION_INFO, 3000)
+ return
+ # List for media
+ items = []
+ for item in result:
+ items += create_media_list(item)
+
+ # Add list to directory
+ xbmcplugin.addDirectoryItems(
+ addon_handle, items, totalItems=len(items))
+
+ if 'nextPageToken' in result[-1]:
+ url = utils.build_url(base_url, {'mode': 'refresh', 'prev_q': qs})
+ li = xbmcgui.ListItem(__addon__.getLocalizedString(30414))
+ li.setProperty('IsPlayable', 'false')
+ xbmcplugin.addDirectoryItem(addon_handle, url, li)
+ if args.get('call_type'):
+ updateListing = True
+ else:
+ updateListing = False
+ xbmcplugin.endOfDirectory(
+ addon_handle, updateListing=updateListing, cacheToDisc=False)
+
+ if 'nextPageToken' in result[-1]:
+ loader = threading.Thread(target=get_items_bg, args=(
+ result, path,))
+ loader.start()
+ loader.join()
+
+
+def create_media_list(media: dict):
+ # Creates a list of (url, li) from media dictionary
+ items = []
+ for item in media.get("mediaItems", {}):
+ item_type = item["mimeType"].split('/')[0]
+ li = xbmcgui.ListItem(item["filename"])
+ url = None
+ h = xbmcgui.getScreenHeight()
+ w = xbmcgui.getScreenWidth()
+ if item_type == 'image':
+ # thumb_url = item["baseUrl"] + f'=w{w}-h{h}'
+ thumb_url = item["baseUrl"] + f'=w{960}-h{540}'
+ img_url = item["baseUrl"] + f'=w{w}-h{h}'
+ li.setArt(
+ {'icon': 'DefaultIconInfo.png'})
+ url = img_url
+ elif item_type == 'video':
+ vid_url = item["baseUrl"] + '=dv'
+ thumb_url = item["baseUrl"] + f'=w{w}-h{h}'
+ url = utils.build_url(
+ base_url, {'mode': 'play_video', 'path': vid_url,
+ 'status': item["mediaMetadata"]["video"]["status"], 'token_filename': token_path.name})
+ li.setArt({'thumb': thumb_url, 'icon': thumb_url})
+ li.setProperty('IsPlayable', 'true')
+ else:
+ continue
+ li.setProperty('MimeType', item["mimeType"])
+ items.append((url, li))
+ return items
+
+
+def play_video():
+ # https://kodi.wiki/view/HOW-TO:Video_addon
+ if args.get('status')[0] != 'READY':
+ xbmcgui.Dialog().notification(__addon__.getLocalizedString(30411),
+ __addon__.getLocalizedString(30415), xbmcgui.NOTIFICATION_ERROR, 3000)
+ else:
+ # Create a playable item with a path to play.
+ play_item = xbmcgui.ListItem(path=args.get('path')[0])
+ # Pass the item to the Kodi player.
+ xbmcplugin.setResolvedUrl(addon_handle, True, listitem=play_item)
+
+
+def custom_filter():
+ __addon__.openSettings()
+ url = utils.build_url(
+ base_url, {'mode': 'list_media', 'type': 'filter'}, qs)
+ li = xbmcgui.ListItem(__addon__.getLocalizedString(30416))
+ li.setProperty('isPlayable', 'false')
+ xbmcplugin.addDirectoryItem(addon_handle, url, li, isFolder=True)
+ xbmcplugin.endOfDirectory(addon_handle)
+
+
+def list_albums():
+ # For shared albums and regular albums
+ # https://developers.google.com/photos/library/guides/list
+
+ request_type = args.get('type')[0]
+ # Request for listing albums or sharedAlbums
+ params = {}
+ if args.get('pageToken'):
+ params['pageToken'] = args.get('pageToken')[0]
+ headers = {'Authorization': 'Bearer ' + token}
+ res = requests.get(config.service_endpoint + f'/{request_type}',
+ headers=headers, params=params)
+
+ # Parse response
+ a_num = 1 # For albums without name TODO: a_num should be in nextPage URL
+ if res.status_code != 200:
+ dialog = xbmcgui.Dialog()
+ dialog.notification(
+ 'Error', f'{__addon__.getLocalizedString(30417)}{res.status_code}', xbmcgui.NOTIFICATION_ERROR, 3000)
+ else:
+ album_data = res.json() # { albums, nextPageToken}
+ items = []
+ if album_data:
+ for album in album_data[request_type]:
+ url = utils.build_url(
+ base_url, {'mode': 'list_media', 'type': 'album', 'id': album["id"], 'token_filename': token_path.name})
+ if 'title' in album:
+ li = xbmcgui.ListItem(album["title"])
+ else:
+ li = xbmcgui.ListItem(
+ f'{__addon__.getLocalizedString(30418)} {a_num}')
+ a_num += 1
+ thumb_url = album["coverPhotoBaseUrl"] + \
+ f'=w{xbmcgui.getScreenWidth()}-h{xbmcgui.getScreenHeight()}'
+ li.setArt(
+ {'thumb': thumb_url})
+ items.append((url, li, True))
+
+ xbmcplugin.addDirectoryItems(
+ addon_handle, items, totalItems=len(items))
+
+ # Pagination
+ if 'nextPageToken' in album_data:
+ url = utils.build_url(
+ base_url, {'mode': 'list_albums', 'pageToken': album_data['nextPageToken'], 'token_filename': token_path.name, 'type': request_type})
+ li = xbmcgui.ListItem(__addon__.getLocalizedString(30419))
+ xbmcplugin.addDirectoryItem(addon_handle, url, li, True)
+
+ xbmcplugin.endOfDirectory(addon_handle)
+
+# Modify this function to force changes after update
+
+
+def make_changes():
+ version_file = profile_path + 'info'
+ xbmcvfs.mkdirs(profile_path)
+ with open(version_file, 'w+') as file:
+ try:
+ past_info = json.load(file)
+ except:
+ past_info = {}
+
+ if bool(past_info) or past_info.get('version') != __addon__.getAddonInfo('version'):
+ past_info['version'] = __addon__.getAddonInfo('version')
+ __addon__.setSettingString(
+ "baseUrl", "https://photos-kodi-addon.onrender.com")
+ with open(version_file, 'w') as file:
+ json.dump(past_info, file, default=str)
+
+
+def display_page_content():
+ if mode is None:
+ # Display logged in accounts
+ token_folder.mkdir(parents=True, exist_ok=True)
+ for file in token_folder.iterdir():
+ try:
+ creds = read_credentials(file)
+ except:
+ xbmc.log(str(traceback.format_exc()), xbmc.LOGDEBUG)
+ err_dialog = xbmcgui.Dialog()
+ err_dialog.notification(__addon__.getLocalizedString(30411),
+ __addon__.getLocalizedString(30422),
+ xbmcgui.NOTIFICATION_ERROR, 3000)
+ exit()
+ email = creds["email"]
+ url = utils.build_url(
+ base_url, {'mode': 'list_options', 'token_filename': file.name})
+ li = xbmcgui.ListItem(email)
+ removePath = utils.build_url(
+ base_url, {'mode': 'remove_account', 'email': email, 'token_filename': file.name})
+ contextItems = [(__addon__.getLocalizedString(
+ 30420), f'RunPlugin({removePath})')]
+ li.addContextMenuItems(contextItems)
+ xbmcplugin.addDirectoryItem(handle=addon_handle, url=url,
+ listitem=li, isFolder=True)
+ # Add Account Button
+ url = utils.build_url(base_url, {'mode': 'new_account'})
+ li = xbmcgui.ListItem(__addon__.getLocalizedString(30421))
+ xbmcplugin.addDirectoryItem(handle=addon_handle, url=url,
+ listitem=li)
+ xbmcplugin.endOfDirectory(addon_handle)
+ else:
+ # Read account credentials if present
+ if token_path and mode[0] != 'remove_account':
+ try:
+ creds = read_credentials(token_path)
+ except:
+ err_dialog = xbmcgui.Dialog()
+ err_dialog.notification(__addon__.getLocalizedString(30411),
+ __addon__.getLocalizedString(30422),
+ xbmcgui.NOTIFICATION_ERROR, 3000)
+ exit()
+ global token
+ token = creds["token"]
+ eval(mode[0] + '()') # Fire up the actual function
+
+
+make_changes()
+# check for credentials
+if not __addon__.getSettingString('client_id') or not __addon__.getSettingString('client_secret'):
+ open_settings_dialog = xbmcgui.Dialog().ok(__addon__.getLocalizedString(30428),
+ __addon__.getLocalizedString(30429))
+ remove_all_accounts()
+ __addon__.openSettings()
+else:
+ display_page_content()
+ # Updates:
+ # Slideshow
+
+ # Not on list
+ # Video seeking - Not possible due to API limitations
diff --git a/plugin.picture.googlephotos/resources/language/resource.language.en_gb/strings.po b/plugin.picture.googlephotos/resources/language/resource.language.en_gb/strings.po
new file mode 100644
index 0000000000..53ce1850ab
--- /dev/null
+++ b/plugin.picture.googlephotos/resources/language/resource.language.en_gb/strings.po
@@ -0,0 +1,343 @@
+# Kodi Media Center language file
+# Addon Name: Google Photos
+# Addon id: plugin.picture.googlephotos
+# Addon Provider: Pranjal Singhal
+msgid ""
+msgstr ""
+
+#settings screen settings.xml
+#setting category
+msgctxt "#30100"
+msgid "Sign-in Server"
+msgstr ""
+
+msgctxt "#30101"
+msgid "Base URL"
+msgstr ""
+
+msgctxt "#30102"
+msgid "Device Code Subroute"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Access Token Subroute"
+msgstr ""
+
+msgctxt "#30104"
+msgid "Refresh Token Subroute"
+msgstr ""
+
+msgctxt "#30105"
+msgid "Client ID "
+msgstr ""
+
+msgctxt "#30106"
+msgid "Client Secret"
+msgstr ""
+
+msgctxt "#30107"
+msgid "Client Credentials"
+msgstr ""
+
+#cache file name
+msgctxt "#30107"
+msgid "media_db"
+msgstr ""
+
+msgctxt "#30108"
+msgid "accounts/"
+msgstr ""
+
+msgctxt "#30109"
+msgid "Sign-in Details"
+msgstr ""
+
+# filters
+msgctxt "#30200"
+msgid "Filter"
+msgstr ""
+
+msgctxt "#30201"
+msgid "Date Filter"
+msgstr ""
+
+msgctxt "#30202"
+msgid "Start Date"
+msgstr ""
+
+msgctxt "#30203"
+msgid "End Date"
+msgstr ""
+
+msgctxt "#30203"
+msgid "End Date"
+msgstr ""
+
+msgctxt "#30204"
+msgid "Content Filter"
+msgstr ""
+
+msgctxt "#30205"
+msgid "Content type"
+msgstr ""
+
+msgctxt "#30206"
+msgid "Media Filter"
+msgstr ""
+
+msgctxt "#30207"
+msgid "Media type"
+msgstr ""
+
+msgctxt "#30208"
+msgid "All Media"
+msgstr ""
+
+msgctxt "#30209"
+msgid "Video"
+msgstr ""
+
+msgctxt "#30210"
+msgid "Photo"
+msgstr ""
+
+msgctxt "#30211"
+msgid "Feature Filter"
+msgstr ""
+
+msgctxt "#30212"
+msgid "Show only favourites"
+msgstr ""
+
+msgctxt "#30213"
+msgid "Use date filter"
+msgstr ""
+
+msgctxt "#30400"
+msgid "Enter the following code at "
+msgstr ""
+
+msgctxt "#30401"
+msgid "Authenticate"
+msgstr ""
+
+msgctxt "#30402"
+msgid "Account added successfully"
+msgstr ""
+
+msgctxt "#30403"
+msgid "Code refreshed"
+msgstr ""
+
+msgctxt "#30404"
+msgid "All Media"
+msgstr ""
+
+msgctxt "#30405"
+msgid "All Albums"
+msgstr ""
+
+msgctxt "#30406"
+msgid "Shared Albums"
+msgstr ""
+
+msgctxt "#30407"
+msgid "Custom Filter"
+msgstr ""
+
+msgctxt "#30408"
+msgid "Unable to retrieve media items from Google"
+msgstr ""
+
+msgctxt "#30409"
+msgid "Unable to load album items"
+msgstr ""
+
+msgctxt "#30410"
+msgid "Error in filtering photos"
+msgstr ""
+
+msgctxt "#30411"
+msgid "Error"
+msgstr ""
+
+msgctxt "#30412"
+msgid "Error Code"
+msgstr ""
+
+msgctxt "#30413"
+msgid "No Media Item to load"
+msgstr ""
+
+msgctxt "#30414"
+msgid "Show more..."
+msgstr ""
+
+msgctxt "#30415"
+msgid "Video is not ready for viewing yet"
+msgstr ""
+
+msgctxt "#30416"
+msgid "Display Filtered Results"
+msgstr ""
+
+msgctxt "#30417"
+msgid "Unable to retrieve album list. Error Code:"
+msgstr ""
+
+msgctxt "#30418"
+msgid "Nameless album"
+msgstr ""
+
+msgctxt "#30419"
+msgid "Next Page"
+msgstr ""
+
+msgctxt "#30420"
+msgid "Remove Account"
+msgstr ""
+
+msgctxt "#30421"
+msgid "Add New Account"
+msgstr ""
+
+msgctxt "#30422"
+msgid "No response from proxy server. Try again."
+msgstr ""
+
+msgctxt "#30423"
+msgid "Loading..."
+msgstr ""
+
+msgctxt "#30424"
+msgid "Success"
+msgstr ""
+
+msgctxt "#30425"
+msgid "No items"
+msgstr ""
+
+msgctxt "#30426"
+msgid "Expires in"
+msgstr ""
+
+msgctxt "#30427"
+msgid "seconds"
+msgstr ""
+
+msgctxt "#30428"
+msgid "Credentials Required"
+msgstr ""
+
+msgctxt "#30429"
+msgid "Please input client ID and secret in settings. Follow the instructions at www.photos-kodi-addon.onrender.com/credentialsguide to generate credentials."
+msgstr ""
+# No need most probably
+# # Content filter
+# msgctxt "#30301"
+# msgid "None"
+# msgstr ""
+
+# msgctxt "#30302"
+# msgid "Landscapes"
+# msgstr ""
+
+# msgctxt "#30303"
+# msgid "Receipts"
+# msgstr ""
+
+# msgctxt "#30304"
+# msgid "Cityscapes"
+# msgstr ""
+
+# msgctxt "#30305"
+# msgid "Landmarks"
+# msgstr ""
+
+# msgctxt "#30306"
+# msgid "Selfies"
+# msgstr ""
+
+# msgctxt "#30307"
+# msgid "People"
+# msgstr ""
+
+# msgctxt "#30308"
+# msgid "Pets"
+# msgstr ""
+
+# msgctxt "#30309"
+# msgid "Weddings"
+# msgstr ""
+
+# msgctxt "#30310"
+# msgid "Birthdays"
+# msgstr ""
+
+# msgctxt "#30311"
+# msgid "Documents"
+# msgstr ""
+
+# msgctxt "#30312"
+# msgid "Travel"
+# msgstr ""
+
+# msgctxt "#30313"
+# msgid "Animals"
+# msgstr ""
+
+# msgctxt "#30314"
+# msgid "Food"
+# msgstr ""
+
+# msgctxt "#30315"
+# msgid "Sport"
+# msgstr ""
+
+# msgctxt "#30316"
+# msgid "Night"
+# msgstr ""
+
+# msgctxt "#30317"
+# msgid "Performances"
+# msgstr ""
+
+# msgctxt "#30318"
+# msgid "Whiteboards"
+# msgstr ""
+
+# msgctxt "#30319"
+# msgid "Screenshots"
+# msgstr ""
+
+# msgctxt "#30320"
+# msgid "Utility"
+# msgstr ""
+
+# msgctxt "#30321"
+# msgid "Arts"
+# msgstr ""
+
+# msgctxt "#30322"
+# msgid "Crafts"
+# msgstr ""
+
+# msgctxt "#30323"
+# msgid "Fashion"
+# msgstr ""
+
+# msgctxt "#303024"
+# msgid "Houses"
+# msgstr ""
+
+# msgctxt "#303025"
+# msgid "Gardens"
+# msgstr ""
+
+# msgctxt "#303026"
+# msgid "Flowers"
+# msgstr ""
+
+# msgctxt "#303027"
+# msgid "Holidays"
+# msgstr ""
\ No newline at end of file
diff --git a/plugin.picture.googlephotos/resources/lib/__init__.py b/plugin.picture.googlephotos/resources/lib/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugin.picture.googlephotos/resources/lib/auth.py b/plugin.picture.googlephotos/resources/lib/auth.py
new file mode 100644
index 0000000000..0cee93dd01
--- /dev/null
+++ b/plugin.picture.googlephotos/resources/lib/auth.py
@@ -0,0 +1,139 @@
+import datetime
+import time
+from pathlib import Path
+from . import config
+from resources.lib.utils import join_path
+import requests
+import json
+import xbmcaddon
+import xbmc
+
+
+SCOPES = ['https://www.googleapis.com/auth/photoslibrary.readonly',
+ 'email',
+ 'openid']
+
+
+# The file token.json stores the user's access and refresh tokens, and is
+# created automatically when the authorization flow completes for the first
+# time.
+
+__addon__ = xbmcaddon.Addon()
+clientCreds = {
+ 'clientId': __addon__.getSettingString('client_id'),
+ 'clientSecret': __addon__.getSettingString('client_secret')
+}
+
+
+def get_device_code():
+ # On success, returns json containing user code, device code etc. and None on failure
+ # Exception Possible
+ path = join_path(__addon__.getSettingString(
+ 'baseUrl'), __addon__.getSettingString('deviceCodeUrl'))
+
+ # Exchange client credentials for device code and user code
+ res = requests.post(path, data=clientCreds)
+ if res.status_code != 200:
+ xbmc.log(f'GP: {str(res.status_code)}', xbmc.LOGDEBUG)
+ return None
+ return res.json()
+
+
+def fetch_and_save_token(device_code, tf_path):
+ '''
+ Takes in token folder path
+ Fetch token from the auth server and save in token.json if successful
+ Returns: 200 if successful
+ 202 if user has not completed login on other device
+ 403 if server asks to slow down
+ In any other case server response is returned.
+ P.S. This function does not continously poll the auth server. It needs to be repeatedly called by the caller code.
+ '''
+ pl_path = Path(tf_path) # Pathlib path
+ token_url = join_path(__addon__.getSettingString(
+ 'baseUrl'), __addon__.getSettingString('tokenUrl'))
+ res = requests.post(token_url, data={
+ 'deviceCode': device_code, 'grant_type': 'urn:ietf:params:oauth:grant-type:device_code'})
+ if res.status_code == 202 or res.status_code == 403:
+ return res.status_code
+ if res.status_code != 200:
+ xbmc.log(res.text, xbmc.LOGDEBUG)
+ return res.status_code
+
+ token_data = res.json()
+ # Fetch email and a unique identifier(called sub) from Google
+ headers = {'Authorization': 'Bearer ' + token_data["access_token"]}
+ openid_res = requests.get(config.email_url, headers=headers)
+ if openid_res.status_code == 200:
+ res_json = openid_res.json()
+ email = res_json["email"]
+ sub_json = res_json["sub"] + ".json"
+ else:
+ raise Exception(
+ f"Unknown response from server: {openid_res.status_code}")
+ # Construct token
+ token = {
+ "token": token_data["access_token"],
+ "refresh_token": token_data["refresh_token"],
+ "email": email,
+ "scopes": SCOPES,
+ "expiry": datetime.datetime.utcnow()
+ + datetime.timedelta(seconds=token_data["expires_in"])
+ }
+ # Write to sub.json
+ pl_path.mkdir(parents=True, exist_ok=True)
+ with open(pl_path / sub_json, mode='w') as token_json:
+ json.dump(token, token_json, default=str)
+ return 200
+
+
+def refresh_access_token(creds, path):
+ # Returns new access token and expiry
+ refresh_url = join_path(__addon__.getSettingString(
+ 'baseUrl'), __addon__.getSettingString('refreshUrl'))
+
+ data = {
+ 'refresh_token': creds["refresh_token"],
+ 'grant_type': 'refresh_token'
+ }
+ res = requests.post(refresh_url, data={**data, **clientCreds})
+ if res.status_code != 200:
+ return res.status_code
+ token_data = res.json()
+ creds["token"] = token_data["access_token"]
+ creds["expiry"] = (datetime.datetime.utcnow()
+ + datetime.timedelta(seconds=token_data["expires_in"]))
+ with open(path, mode='w') as token_json:
+ json.dump(creds, token_json, default=str)
+ return 200
+
+
+def read_credentials(path):
+ '''
+ Reads credentials from the provided file path
+ Refreshes and saves the credentials if expired
+ Returns: credentials json if successful
+ None if the file does not exist
+ '''
+ pl_path = Path(path) # Pathlib path
+ if not pl_path.exists():
+ return None
+ creds = None
+ # Read creds
+ with open(path, mode='r') as token_json:
+ creds = json.load(token_json)
+
+ # Refresh creds if expired
+ expiry = creds["expiry"]
+ format = "%Y-%m-%d %H:%M:%S.%f"
+ try:
+ exp = datetime.datetime.strptime(expiry, format)
+ except TypeError:
+ exp = datetime.datetime(*(time.strptime(expiry, format)[0:6]))
+ if exp < datetime.datetime.utcnow():
+ status = refresh_access_token(
+ creds, path) # Refreshes creds
+ if status != 200:
+ xbmc.log(str(status), xbmc.LOGDEBUG)
+
+ return creds
diff --git a/plugin.picture.googlephotos/resources/lib/config.py b/plugin.picture.googlephotos/resources/lib/config.py
new file mode 100644
index 0000000000..43c2c07a57
--- /dev/null
+++ b/plugin.picture.googlephotos/resources/lib/config.py
@@ -0,0 +1,8 @@
+# Endpoint to get user email (https://accounts.google.com/.well-known/openid-configuration)
+email_url = 'https://openidconnect.googleapis.com/v1/userinfo'
+
+# Google Photos API
+service_endpoint = 'https://photoslibrary.googleapis.com/v1'
+
+token_folder = 'accounts/'
+media_filename = 'media_db'
diff --git a/plugin.picture.googlephotos/resources/lib/dialogs.py b/plugin.picture.googlephotos/resources/lib/dialogs.py
new file mode 100644
index 0000000000..9c4b804452
--- /dev/null
+++ b/plugin.picture.googlephotos/resources/lib/dialogs.py
@@ -0,0 +1,74 @@
+from time import time
+import xbmcgui
+import xbmcvfs
+import xbmcaddon
+import os
+
+
+# https://github.com/cguZZman/script.module.clouddrive.common/blob/master/clouddrive/common/ui/dialog.py
+class QRDialogProgress(xbmcgui.WindowXMLDialog):
+ _heading_control = 1000
+ _qr_control = 1001
+ _text_control = 1002
+ _cancel_btn_control = 1003
+ addon = xbmcaddon.Addon()
+
+ def __init__(self, *args, **kwargs):
+ self.heading = QRDialogProgress.addon.getLocalizedString(30401)
+ self.qr_code = ""
+ self.line1 = QRDialogProgress.addon.getLocalizedString(
+ 30400) + QRDialogProgress.addon.getSettingString('baseUrl') + ' :'
+ self.line2 = QRDialogProgress.addon.getLocalizedString(30423)
+ self.line3 = QRDialogProgress.addon.getLocalizedString(30426)
+ self.line4 = QRDialogProgress.addon.getLocalizedString(30427)
+ self.time_left = 0
+ self._image_path = None
+ self.canceled = False
+
+ def __del__(self):
+ xbmcvfs.delete(self._image_path)
+ pass
+
+ @staticmethod
+ def create():
+ return QRDialogProgress("pin-dialog.xml", QRDialogProgress.addon.getAddonInfo('path'), "default", "1080i")
+
+ def iscanceled(self):
+ return self.canceled
+
+ def onInit(self):
+ self.getControl(self._heading_control).setLabel(self.heading)
+ self.update(self.time_left, self.line2)
+
+ def update(self, time_left=None, code=None):
+ if time_left:
+ self.time_left = time_left
+
+ if code:
+ self.line2 = code
+ self.qr_code = os.path.join(QRDialogProgress.addon.getSettingString(
+ 'baseUrl'), f'authenticate?code={code}')
+
+ text = f'{self.line1}[CR][COLOR red][B]{self.line2}[/B][/COLOR][CR][CR]{self.line3} {self.time_left} {self.line4}.'
+ self.getControl(self._text_control).setText(text)
+ self.updateQr()
+ self.setFocus(self.getControl(self._cancel_btn_control))
+
+ def onClick(self, control_id):
+ if control_id == self._cancel_btn_control:
+ self.canceled = True
+ self.close()
+
+ def onAction(self, action):
+ if action.getId() == xbmcgui.ACTION_PREVIOUS_MENU or action.getId() == xbmcgui.ACTION_NAV_BACK:
+ self.canceled = True
+ super(QRDialogProgress, self).onAction(action)
+
+ def updateQr(self):
+ import pyqrcode
+ self._image_path = os.path.join(xbmcvfs.translatePath(
+ self.addon.getAddonInfo('profile')), "qr.png")
+ qrcode = pyqrcode.create(self.qr_code)
+ qrcode.png(self._image_path, scale=10)
+ del qrcode
+ self.getControl(self._qr_control).setImage(self._image_path)
diff --git a/plugin.picture.googlephotos/resources/lib/utils.py b/plugin.picture.googlephotos/resources/lib/utils.py
new file mode 100644
index 0000000000..e5f8872eb8
--- /dev/null
+++ b/plugin.picture.googlephotos/resources/lib/utils.py
@@ -0,0 +1,107 @@
+from urllib import parse
+import pickle
+import os
+
+
+def build_url(base_url, query: dict, qs=None) -> str:
+ '''
+ Options: query to be encoded
+ qs to be modified with items from the query dict
+ Assumes single value for a key
+ Returns a url
+ '''
+ if qs:
+ qdict = dict(parse.parse_qsl(qs))
+ for key, val in query.items():
+ qdict[key] = val
+ return base_url + '?' + parse.urlencode(qdict)
+ return base_url + '?' + parse.urlencode(query)
+
+def join_path(*args):
+ '''
+ Joins path parts and strips extra slashes
+ '''
+ return '/'.join(s.strip('/') for s in args)
+def sleep_time(time=300):
+ '''
+ Input: Time to complete authentication
+ Return: time to sleep(in msec)
+ '''
+ return (time * 10)
+
+# Portability not guaranteed
+# check xbmcvfs
+
+
+def loadData(path):
+ # For caching links
+ if not os.path.exists(path):
+ return None
+ data = []
+ with open(path, 'rb') as fr:
+ try:
+ while True:
+ data.append(pickle.load(fr))
+ except EOFError:
+ pass
+ return data
+
+
+def storeData(path, db: dict):
+ # Appends data to file
+ dbfile = open(path, 'ab')
+ # source, destination
+ pickle.dump(db, dbfile)
+ dbfile.close()
+
+
+def buildFilter(__addon__):
+ is_date_filter_applied = __addon__.getSettingBool('date_filter')
+ start_date = __addon__.getSettingString('start_date')
+ end_date = __addon__.getSettingString('end_date')
+ media_type = __addon__.getSettingString('media_filter')
+ content_type = __addon__.getSettingString('content_filter')
+ favourites = __addon__.getSettingBool('favourites')
+ filters = {}
+ if (is_date_filter_applied and (start_date < end_date)):
+ sd = start_date.split('-')
+ ed = end_date.split('-')
+ filters['dateFilter'] = {
+ "ranges": [
+ {
+ "startDate": {
+ "year": sd[0],
+ "month": sd[1],
+ "day": sd[2]
+ },
+ "endDate":{
+ "year": ed[0],
+ "month": ed[1],
+ "day": ed[2]
+ }
+ }
+ ]
+ }
+
+ if media_type != 'All Media':
+ filters['mediaTypeFilter'] = {
+ 'mediaTypes': [
+ media_type.upper().replace(' ', '_')
+ ]
+ }
+
+ if content_type != 'None':
+ filters['contentFilter'] = {
+ 'includedContentCategories': [
+ content_type.upper()
+ ]
+ }
+
+ if favourites:
+ filters['featureFilter'] = {
+ "includedFeatures": [
+ 'FAVOURITES'
+ ]
+ }
+
+ return filters
diff --git a/plugin.picture.googlephotos/resources/settings.xml b/plugin.picture.googlephotos/resources/settings.xml
new file mode 100644
index 0000000000..ab6223bec4
--- /dev/null
+++ b/plugin.picture.googlephotos/resources/settings.xml
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+ 0
+
+ true
+
+
+
+
+ 0
+
+ true
+
+
+
+
+
+
+
+ 0
+ https://photos-kodi-addon.onrender.com
+
+ false
+
+
+
+
+
+ 3
+ /device/code
+
+ false
+
+
+
+
+
+ 3
+ /token
+
+ false
+
+
+
+
+
+ 3
+ /refresh
+
+ false
+
+
+
+
+
+
+
+
+
+ 0
+ false
+
+
+
+ 0
+ 1700-01-01
+
+ true
+
+
+ 30202
+
+
+
+ 0
+ 2200-01-01
+
+ true
+
+
+ 30203
+
+
+
+
+
+
+ 0
+ All Media
+
+
+
+
+ false
+
+
+ 30205
+
+
+
+ 0
+ false
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugin.picture.googlephotos/resources/skins/default/1080i/pin-dialog.xml b/plugin.picture.googlephotos/resources/skins/default/1080i/pin-dialog.xml
new file mode 100644
index 0000000000..09cc50c78b
--- /dev/null
+++ b/plugin.picture.googlephotos/resources/skins/default/1080i/pin-dialog.xml
@@ -0,0 +1,85 @@
+
+
+
+ 385
+ 315
+
+
+
+
+
+
+
+
+
+
+
+ -1920
+ -1080
+ 5760
+ 3240
+ WindowOpen
+ WindowClose
+ black.png
+
+
+
+ 0
+ 0
+ 1150
+ 450
+ dialog-bg.png
+
+
+
+ 0
+ 0
+ 1150
+ 70
+ white.png
+
+
+
+ 20
+ 0
+ 0
+ 1150
+ 70
+ font30_title
+ left
+ center
+ FF000000
+
+
+
+ 20
+ 90
+ 340
+ 340
+ keep
+
+
+
+ 380
+ 90
+ 770
+ 340
+ font12_title
+ left
+ top
+ FF000000
+
+
+
+ 850
+ 350
+ 300
+ 100
+ font12_title
+ FFFFFFFF
+
+ center
+ center
+
+
+
\ No newline at end of file
diff --git a/plugin.picture.googlephotos/resources/skins/default/media/black.png b/plugin.picture.googlephotos/resources/skins/default/media/black.png
new file mode 100644
index 0000000000..2ff1770d53
Binary files /dev/null and b/plugin.picture.googlephotos/resources/skins/default/media/black.png differ
diff --git a/plugin.picture.googlephotos/resources/skins/default/media/dialog-bg.png b/plugin.picture.googlephotos/resources/skins/default/media/dialog-bg.png
new file mode 100644
index 0000000000..e8c13ebd70
Binary files /dev/null and b/plugin.picture.googlephotos/resources/skins/default/media/dialog-bg.png differ
diff --git a/plugin.picture.googlephotos/resources/skins/default/media/white.png b/plugin.picture.googlephotos/resources/skins/default/media/white.png
new file mode 100644
index 0000000000..528c66f6e8
Binary files /dev/null and b/plugin.picture.googlephotos/resources/skins/default/media/white.png differ
diff --git a/plugin.program.AML/AUTHORS.md b/plugin.program.AML/AUTHORS.md
new file mode 100644
index 0000000000..77d49a5062
--- /dev/null
+++ b/plugin.program.AML/AUTHORS.md
@@ -0,0 +1,11 @@
+## Advanced MAME Launcher
+
+Advanced MAME Launcher was coded from scratch by Wintermute0110 .
+The first public released version of Advanced MAME Launcher was 0.9.0 on Jan 2017.
+
+[Kodi forum thread](http://forum.kodi.tv/showthread.php?tid=304186)
+
+### AML contributors
+
+ * PDF rendering code taken from **PDF Reader** addon by i96751414.
+ [PDF Reader source code](https://github.com/i96751414/plugin.image.pdfreader)
diff --git a/plugin.program.AML/LICENSE.txt b/plugin.program.AML/LICENSE.txt
new file mode 100644
index 0000000000..d159169d10
--- /dev/null
+++ b/plugin.program.AML/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/plugin.program.AML/NOTES.md b/plugin.program.AML/NOTES.md
new file mode 100644
index 0000000000..21df0f1429
--- /dev/null
+++ b/plugin.program.AML/NOTES.md
@@ -0,0 +1,352 @@
+[AML python2..master comparison](https://github.com/Wintermute0110/plugin.program.AML.dev/compare/python2..master)
+
+[AML master..python2 comparison](https://github.com/Wintermute0110/plugin.program.AML.dev/compare/master..python2)
+
+[Kodi Documentation (codedocs)](https://codedocs.xyz/xbmc/xbmc/)
+
+[RA buildbot cores](https://buildbot.libretro.com/nightly/)
+
+[RA MAME 2003 core controls](https://docs.libretro.com/library/mame_2003/)
+
+MAME 2003 Plus RA core write the file `mame2003-plus.xml` in `SAVES_DIR/mame2003-plus/` directory.
+
+## AML and Python 2 (Kodi Krypton and Leia) / Python 3 (Kodi Matrix)
+
+ * AML releases `0.9.x` and `0.10.x` will be **Python 2** for Kodi Krypton and Kodi Leia. **Python 2** code will be in branch `python2`.
+
+ * AML releases `1.x.y` will be **Pyhton 3** for Kodi Matrix and up. **Pyhton 3** code will be in branch `master`.
+
+ * From now on (August 2020), focus will be on release `1.x.y`. Make some features from **Pyhton 3** will be backported to **Python 2**.
+
+## Porting Python 2 to Pyhton 3 ##
+
+**TODO**
+
+Remove tasks once finished.
+
+ * Create a function in `disk_IO.py` to write text files, arguments filename and slist. Use this function to write al reports and text files. Use `io.open`.
+
+ * Call function in `utils_kodi` to display text window.
+
+**Language specific issues**
+
+ * The `urlparse` module is renamed to `urllib.parse` in Python 3.
+
+ * The `StringIO` modules is gone. Use `io.StringIO` and `io.BytesIO`.
+
+ * Python 2 unicode object is str object in Python 3. Python 2 str object is bytes in Python 3.
+
+ * Python 2 dict.iteritems() must be converted to dict.items()
+
+ [StackOverflow: When should iteritems() be used instead of items()?](https://stackoverflow.com/questions/13998492/when-should-iteritems-be-used-instead-of-items)
+
+ * Python 2 iterator method .next() is built-in function next(iterator) or .__next__() method in Python 3.
+
+ * `xml.etree.cElementTree` is deprecated. It will be used automatically by `xml.etree.ElementTree` whenever available.
+
+ * Use `io.open()` and not built-in `open()` in Python 2. `io.open()` in Python 2 supports the `encoding` argument and it's compatible with Python 3 `io.open()`. Moreover, in Python 3 `open()` is an alias of `io.open()`.
+
+**Kodi specific issues**
+
+ * AML Python 2 is Krypton compatible. This means in Python 3 all the API updates can be applied.
+
+ * Kodi functions now take/return Unicode strings (str type in Python 3)
+
+ [Kodi six](https://github.com/romanvm/kodi.six)
+
+ * Leia change: Addon setting functions getSettingBool(), getSettingInt(), etc.
+
+ * Matrix change: Addon settings *should* be converted. The old settings are deprecated, quoting from the wiki: "Deprecated - Addons submitted to the Kodi 19 Matrix (and up) can use the new setting format. See Add-on_settings_conversion."
+
+ [Kodi wiki: Add-on settings](https://kodi.wiki/view/Add-on_settings) [Kodi wiki: Addon settings conversion](https://kodi.wiki/view/Add-on_settings_conversion) [Kodi Matrix alpha 1, addon settings do not show](https://forum.kodi.tv/showthread.php?tid=356245)
+
+ * Matrix change: `XBMC.RunPlugin({}?command={})` must be changed to `RunPlugin({}?command={})`.
+
+ * Matrix change: `xbmcgui.Dialog().yesno()` add support for custom button.
+
+ * Matrix change: `xbmcgui.Dialog().yesno()`, `.cancel()`, `.ok()`, `xbmcgui.DialogProgress().create()`, `.update()`, Removed Line2, Line3.
+
+ All progress dialogs (search for `pDialog.create()` in the code) must use the new `KodiProgressDialog()` class.
+
+**Travis errors**
+
+Travis suggest using `list(dic.items())` instead of `dic.items()` when iterating the keys and values of a dictionary in Python 3. However, this can be harmful for performance!
+
+```
+- for m_name, r_name in catalog_dic.items():
++ for m_name, r_name in list(catalog_dic.items()):
+ sl.append('')
+```
+
+[Stack overflow: Difference between iterate dictionary.items() vs list(dictionary.items())](https://stackoverflow.com/questions/63706787/difference-between-iterate-dictionary-items-vs-listdictionary-items)
+
+**References**
+
+[The Conservative Python 3 Porting Guide](https://portingguide.readthedocs.io/en/latest/index.html)
+
+[Kodi forum: Changes to the python API for Kodi Matrix](https://forum.kodi.tv/showthread.php?tid=344263)
+
+[Kodi forum: Changes to the python API for Kodi Leia](https://forum.kodi.tv/showthread.php?tid=303073)
+
+[Processing Text Files in Python 3](http://python-notes.curiousefficiency.org/en/latest/python3/text_file_processing.html)
+
+## Installing multiple Kodi versions in Windows for development ##
+
+ 1. Download and then run the executable installer file for the version of Kodi you want to install. **You must change the installation location from the default location.**
+
+ 2. Find the `Kodi.exe` application in the folder you just installed. Right-click the application, and choose ‘Create shortcut’.
+
+ 3. Right-click the shortcut you created, and choose ‘Properties’. In the ‘Target’ text field, add the argument `-p` after the file location.
+
+ 4. If you create another shortcut and fail to add the `-p` switch, or start this portable version of Kodi in a different way, then the default userdata folder will be created, and might overwrite your standard installation of Kodi, if you have one.
+
+ 5. The Kodi folder which you nominated above will be used to host the data folder (where Kodi stores scripts, plugins, skins and userdata) in a subfolder named `portable_data`. `portable_data` is mapped to `special://home/`.
+
+**References**
+
+[Kodi Wiki: HOW-TO:Install_Kodi_for_Windows](https://kodi.wiki/view/HOW-TO:Install_Kodi_for_Windows#Portable_Mode)
+
+## Installing multiple Kodi versions in Linux for development ##
+
+WRITE ME.
+
+**References**
+
+[Kodi forum: Development with several Kodi versions and userdata directories](https://forum.kodi.tv/showthread.php?tid=356152)
+
+## Publishing AML into the Kodi repository (Tortoise Git) ##
+
+**Setup**
+
+First make sure the remote repository is OK. In `Tortoise Git`, `Settings`, in the `Remote` option there should be a remote named `upstream` with URL `https://github.com/xbmc/repo-plugins.git`.
+
+**Updating repository**
+
+Suppose we want to update the branch `krypton`. Use `Git show log` to make sure the repository is on the `krypton` branch.
+
+To update the working copy with the contents of upstream use `Pull` with remote `upstream` and remote branch `krypton`.
+
+**Update addon**
+
+Create a branch with `Create branch...`. The branch name must be `plugin.program.AML`. Make the description the same as the branch name. Use the `Switch/Checkout...` command to switch to the new branch.
+
+Make sure the repository is on the branch `plugin.program.AML`. Make the changes to update the addon and then do a single commit named `[plugin.program.AML] x.y.z`.
+
+Push the branch `plugin.program.AML` to the remote `origin`. Finally, open the pull request in Github.
+
+**Updating the pull request**
+
+Updating your pull request can be done by applying your changes and squashing them in the already present commit.
+
+**References**
+
+[Kodi xbmc-repoplugins: CONTRIBUTING](https://github.com/xbmc/repo-plugins/blob/master/CONTRIBUTING.md)
+
+## Kodi repository Travis rules ##
+
+ * Screenshots maximum file size is 750 KB.
+
+## Resolution table ##
+
+| Name | Resolution | Notes |
+|----------------|---------------|------------------------------------------------------------|
+| SDTV 480i NTSC | ` 704 x 480` | AR 4:3 NTSC, 720 x 480 full frame with horizontal blanking |
+| SDTV 576i PAL | ` 704 x 576` | AR 4:3 NTSC, 720 x 576 full frame with horizontal blanking |
+| Standard HD | `1280 x 720` | AR 16:9 |
+| Full HD | `1920 x 1080` | AR 16:9, informally referred as 2K |
+| 4K Ultra HD | `3840 x 2160` | AR 16:9 |
+| 8K Ultra HD | `7680 x 4320` | AR 16:9 |
+
+ * In **SDTV** the pixel aspect ratio (PAR) is not square, and the PAR changes depending on the display aspect ratio (DAR). In other words, the SDTV resolution for 4:3 DAR or 16:9 DAR is the same, what changes is the pixel aspect ratio and hence the physical size of the display.
+
+## MAME implicit/explicit ROM merging ##
+
+ClrMAME Pro merges clone ROMs implicitly if a ROM with same CRC exists in the parent set. There is some info in PD forum about this.
+
+## Known media types in MAME ###
+
+Machines may have more than one media type. In this case, a number is appended at the end. For
+example, a machine with 2 cartriged slots has `cartridge1` and `cartridge2`.
+
+| Name | Short name | Machine example |
+|------------|------------|------------------|
+| cartridge | cart | 32x, sms, smspal |
+| cassete | cass | |
+| floppydisk | flop | |
+| quickload | quick | |
+| snapshot | dump | |
+| harddisk | hard | |
+| cdrom | cdrm | |
+| printer | prin | |
+
+## Cartridges ###
+
+Most consoles have only one cartridge slot, for example `32x`.
+
+```
+
+...
+
+
+
+
+
+```
+
+Device name and its brief version can be used at command line to launch a specific program/game.
+
+```
+mame 32x -cartridge foo1.32x
+mame 32x -cart foo1.32x
+```
+
+A machine may have more than one cartridge slot, for example `abc110`.
+
+```
+
+...
+
+
+
+
+
+
+
+
+
+
+...
+```
+
+Launching command example.
+
+```
+mame abc110 -cart1 foo1.bin -cart2 foo2.bin
+```
+
+## Launching Software Lists ##
+
+Example of machines with SL: `32x`.
+
+```
+
+...
+
+
+
+
+
+
+
+
+
+```
+
+**References**
+
+[MESS wiki: HOWTO](http://mess.redump.net/mess/howto)
+
+[MESS wiki: Software List Format](http://mess.redump.net/mess/swlist_format)
+
+## Special SL items in Software Lists ##
+
+### Implicit ROM merging ###
+
+Software List XMLs do not have the ROM `merge` attribute. However, ClrMAME Pro merges SL
+clone ROMs implicitly if a ROM with same CRC exists in the parent set.
+
+SL `sms`, item `teddyboy` and `teddyboyc`.
+
+### SL ROMS with `loadflag` attribute ###
+
+MAME 0.196, SL `neogeo`, item `aof` "Art of Fighting / Ryuuko no Ken (NGM-044 ~ NGH-044)".
+
+```
+
+ Art of Fighting / Ryuuko no Ken (NGM-044 ~ NGH-044)
+ 1992
+ SNK
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## AML memory consumption in Windows ##
+
+Windows 7 Ultimate version 6.1 64 bit, Kodi Krypton 17.6. MAME 0.197, Peak Working Set (Memory)
+Kodi is restarted before each test.
+Options: no ROM/Asset cache, with SLs.
+
+Build all databases, no INIs/DATs -> 863 MB
+Build all databases, with INIs/DATs, with ROM/Asset cache, OPTION_COMPACT_JSON -> 866 MB
+Build all databases, with INIs/DATs, with ROM/Asset cache -> 914 MB
+Build all databases, with INIs/DATs -> 916 MB
+Build MAME database, with INIs/DATs -> 916 MB
+Build MAME Audit database, with INIs/DATs -> 938 MB
+Build MAME database, with INIs/DATs, OPTION_COMPACT_JSON -> 870 MB
+Build MAME Audit database, with INIs/DATs, OPTION_COMPACT_JSON -> 905 MB
+
+I coded an interative JSON writer. See [Stackoverflow: memoryerror-using-json-dumps](https://stackoverflow.com/questions/24239613/memoryerror-using-json-dumps). Options: no ROM/Asset cache, with SLs, with INIs/DATs.
+
+Build all databases, older fast writer, OPTION_COMPACT_JSON -> Peak memory 867 MB
+```
+fs_write_JSON_file() "C:\Kodi\userdata\addon_data\plugin.program.AML\MAME_DB_main.json"
+fs_write_JSON_file() Writing time 8.258000 s
+fs_write_JSON_file() "C:\Kodi\userdata\addon_data\plugin.program.AML\MAME_DB_render.json"
+fs_write_JSON_file() Writing time 2.981000 s
+fs_write_JSON_file() "C:\Kodi\userdata\addon_data\plugin.program.AML\MAME_DB_roms.json"
+fs_write_JSON_file() Writing time 13.724000 s
+fs_write_JSON_file() "C:\Kodi\userdata\addon_data\plugin.program.AML\ROM_Audit_DB.json"
+fs_write_JSON_file() Writing time 12.347000 s
+```
+
+Build all databases, newer slow writer, OPTION_COMPACT_JSON -> Peak memory 621 MB
+```
+fs_write_JSON_file_lowmem() "C:\Kodi\userdata\addon_data\plugin.program.AML\MAME_DB_main.json"
+fs_write_JSON_file_lowmem() Writing time 13.526000 s
+fs_write_JSON_file_lowmem() "C:\Kodi\userdata\addon_data\plugin.program.AML\MAME_DB_render.json"
+fs_write_JSON_file_lowmem() Writing time 4.979000 s
+fs_write_JSON_file_lowmem() "C:\Kodi\userdata\addon_data\plugin.program.AML\MAME_DB_roms.json"
+fs_write_JSON_file_lowmem() Writing time 22.240000 s
+fs_write_JSON_file_lowmem() "C:\Kodi\userdata\addon_data\plugin.program.AML\ROM_Audit_DB.json"
+fs_write_JSON_file_lowmem() Writing time 20.663000 s
+```
+
+The iterative JSON encoder consumes much less memory and is about twice as slow.
diff --git a/plugin.program.AML/README.md b/plugin.program.AML/README.md
new file mode 100644
index 0000000000..ebc291c0dd
--- /dev/null
+++ b/plugin.program.AML/README.md
@@ -0,0 +1,88 @@
+# Advanced MAME Launcher #
+
+*Advanced MAME Launcher* is an advanced MAME front end for Kodi media center. AML has support for both
+MAME archade machines and Software Lists. AML supports `Merged`, `Split` and `Non-merged` ROM sets and
+has the ability to fully audit your ROM and CHD collection.
+
+## Getting Started guide and Documentation ##
+
+A *Getting Started* guide with installation instructions and more information about AML can be
+found in the [Advanced MAME Launcher thread] in the Kodi forum. Feel free to ask there any
+AML-related question you may have.
+
+You may also find some documentation is in the [Advanced MAME Launcher wiki] in Github. Note that currently
+this guide is far from complete and I will try to improve it soon.
+
+[Advanced MAME Launcher thread]: https://forum.kodi.tv/showthread.php?tid=304186
+
+[Advanced MAME Launcher wiki]: https://github.com/Wintermute0110/plugin.program.AML.dev/wiki
+
+## Screenshot gallery ##
+
+All the screenshots have been taken using the skin [Estuary AEL MOD](https://forum.kodi.tv/showthread.php?tid=287826&pid=2398922#pid2398922). Kodi skins may not show all AML metadata and artwork.
+
+**Addon main window**
+
+
+
+**Browsing MAME machines**
+
+
+
+**Browsing Software Lists**
+
+
+
+**Fanart and 3D Box generation**
+
+
+
+
+
+
+
+
+
+**Audit and ROM browser**
+
+
+
+
+
+
+
+## Installing the latest released version ##
+
+Advanced MAME Launcher is now available in the
+[Kodi Official Addon repository](https://kodi.tv/addon/plugins-program-add-ons/advanced-mame-launcher-0).
+To install the latests release AML version follow the instructions in the
+[Kodi wiki](https://kodi.wiki/view/Add-on_manager). Advanced MAME Launcher is inside the
+category **Program add-ons**.
+
+## Installing the latest development version ##
+
+The development version of AML is a separate addon from the stable version. Both can be
+coinstalled on the same Kodi machine and won't interfere with each other. In other words,
+changing settings in the development version will not affect your stable AML installation.
+
+The name of the AML stable version is **Advanced MAME Launcher** and the name of the
+development version is **Advanced MAME Launcher (dev version)**.
+
+**IMPORTANT** If you are using Kodi Matrix use the `master` branch. If you are using Kodi Krypton or Kodi Leia use the `python2` branch. To change the branch use the drop-down button on top of the page. The default branch is `master`.
+
+It is important that you follow this instructions or the Advanced MAME Launcher development
+version won't work well.
+
+ 1) In this page click on the green button `Clone or Download` --> `Download ZIP`
+
+ 2) Uncompress this ZIP file. This will create a folder named `plugin.program.AML.dev-master` or `plugin.program.AML.dev-python2`
+
+ 3) Rename that folder to `plugin.program.AML.dev`.
+
+ 4) Compress that folder again into a ZIP file named `plugin.program.AML.dev.zip`.
+
+ 5) In Kodi, use that ZIP file (and not the original one) to install the addon.
+
+ 6) If you get a warning message dialog `For security, installation of add-ons from
+ unknown sources is disabled.` then click on `Settings` button and then activate
+ the option `Unknown sources`.
diff --git a/plugin.program.AML/SKINNING.md b/plugin.program.AML/SKINNING.md
new file mode 100644
index 0000000000..0012c48966
--- /dev/null
+++ b/plugin.program.AML/SKINNING.md
@@ -0,0 +1,64 @@
+# Advanced MAME Launcher metadata and artwork model #
+
+AML metadata/assets model is as much compatible with [Advanced Emulator Launcher] as possible.
+
+[Advanced Emulator Launcher]: http://github.com/Wintermute0110/plugin.program.advanced.emulator.launcher/
+
+## MAME machine metadata labels ##
+
+| Metadata name | setInfo label | setProperty label | Infolabel |
+|---------------|---------------|-------------------|--------------------------------------|
+| Title | title | | `$INFO[ListItem.Label]` |
+| Year | year | | `$INFO[ListItem.Year]` |
+| Genre | genre | | `$INFO[ListItem.Genre]` |
+| Manufacturer | studio | | `$INFO[ListItem.Studio]` |
+| Plot | plot | | `$INFO[ListItem.Plot]` |
+| NPlayers | | nplayers | `$INFO[ListItem.Property(nplayers)]` |
+| Platform | | platform | `$INFO[ListItem.Property(platform)]` |
+| History.DAT | | history | `$INFO[ListItem.Property(history)]` |
+
+## MAME machine asset labels ##
+
+| Asset name | setArt label | setInfo label | Infolabel |
+|------------|--------------|---------------|----------------------------------|
+| Title | title | | `$INFO[ListItem.Art(title)]` |
+| Snap | snap | | `$INFO[ListItem.Art(snap)]` |
+| Cabinet | boxfront | | `$INFO[ListItem.Art(boxfront)]` |
+| CPanel | boxback | | `$INFO[ListItem.Art(boxback)]` |
+| PCB | cartridge | | `$INFO[ListItem.Art(cartridge)]` |
+| Flyer | flyer | | `$INFO[ListItem.Art(flyer)]` |
+| 3D Box | 3dbox | | `$INFO[ListItem.Art(3dbox)]` |
+| Icon | icon | | `$INFO[ListItem.Icon]` |
+| Fanart | fanart | | `$INFO[ListItem.Fanart]` |
+| Marquee | banner | | `$INFO[ListItem.Art(banner)]` |
+| Clearlogo | clearlogo | | `$INFO[ListItem.Art(clearlogo)]` |
+| Flyer | poster | | `$INFO[ListItem.Art(poster)]` |
+| Trailer | | trailer | `$INFO[ListItem.trailer]` |
+
+## MAME machine asset availability ##
+
+| Artwork site | Title | Snap | Preview | Boss | End | GameOver | HowTo | Logo | Scores | Select |
+|-------------------|-------|-------|---------|------|-----|----------|-------|------|--------|--------|
+| [Pleasuredome] | YES | YES | YES | YES | YES | YES | YES | YES | YES | YES |
+| [ProgrrettoSNAPS] | YES | YES | YES | YES | YES | YES | YES | YES | YES | YES |
+
+
+| Artwork site | Versus | Cabinet | CPanel | Flyer | Icon | Marquee | PCB | Manual | Trailer |
+|-------------------|--------|---------|--------|--------|------|---------|-----|--------|---------|
+| [Pleasuredome] | YES | YES | YES | YES | YES | YES | YES | YES | YES |
+| [ProgrrettoSNAPS] | YES | YES | YES | YES | YES | YES | YES | YES | YES |
+
+
+## Software Lists asset availability ##
+
+| Artwork site | Title | Snap | Fanart | Banner | Boxfront | Boxback | Manual | Trailer |
+|-------------------|--------|------|--------|--------|----------|----------|--------|---------|
+| [Pleasuredome] | YES | YES | NO | NO | YES | NO | YES | YES |
+| [ProgrrettoSNAPS] | YES | YES | NO | NO | YES | NO | YES | YES |
+
+ * Many consoles/computers have the same artwork as arcade. For MAME, both arcade and
+ consoles/computers are just "machines". For example, CPanel of MegaDrive is the
+ SEGA 3 button joystick.
+
+[Pleasuredome]: http://www.pleasuredome.org.uk/
+[ProgrrettoSNAPS]: http://www.progettosnaps.net
diff --git a/plugin.program.AML/addon.py b/plugin.program.AML/addon.py
new file mode 100644
index 0000000000..2784c77f7b
--- /dev/null
+++ b/plugin.program.AML/addon.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2016-2020 Wintermute0110
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+
+# Advanced MAME Launcher main script file.
+
+# --- Modules/packages in this plugin ---
+import resources.main
+
+# --- Python standard library ---
+import sys
+
+# -------------------------------------------------------------------------------------------------
+# main()
+# -------------------------------------------------------------------------------------------------
+# In Kodi Leia the submodules are cached and only the entry point is interpreted on every addon
+# call. sys.argv must be propagated into submodules. For addon development, caching of submodules
+# must be disabled in addon.xml.
+#
+# Put the main bulk of the code in files inside /resources/, which is a package directory.
+# This way, the Python interpreter will precompile them into bytecode (files PYC/PYO) so
+# loading time is faster compared to PY files.
+# See http://www.network-theory.co.uk/docs/pytut/CompiledPythonfiles.html
+#
+resources.main.run_plugin(sys.argv)
diff --git a/plugin.program.AML/addon.xml b/plugin.program.AML/addon.xml
new file mode 100644
index 0000000000..df941b4018
--- /dev/null
+++ b/plugin.program.AML/addon.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+ executable game
+
+
+
+
+ Kodi meets MAME!
+ MAME front-end for Kodi that supports both MAME machines and Software Lists. For help look at the Advanced MAME Launcher thread in the Kodi forum.
+ android freebsd ios linux osx windows
+ GPL-2.0-only
+ wintermute0110@gmail.com
+ https://forum.kodi.tv/showthread.php?tid=304186
+ https://github.com/Wintermute0110/plugin.program.AML.dev/wiki
+ https://github.com/Wintermute0110/plugin.program.AML.dev/
+
+ media/icon.png
+ media/fanart.jpg
+ media/shot_01_main_window.jpg
+ media/shot_02_MAME_pclone_list.jpg
+ media/shot_03_SL_pclone_list.jpg
+ media/shot_04_MAME_fanart.jpg
+ media/shot_05_SL_fanart.jpg
+ media/shot_06_MAME_3dbox.jpg
+ media/shot_07_SL_3dbox.jpg
+ media/shot_08_MAME_History_viewer.jpg
+ media/shot_09_MAME_ROMs_db.jpg
+ media/shot_10_MAME_Audit_db.jpg
+ media/shot_11_MAME_Audit_machine.jpg
+
+ See changelog.txt file in https://github.com/Wintermute0110/plugin.program.AML.dev/ for information about the latest release.
+
+
diff --git a/plugin.program.AML/changelog.txt b/plugin.program.AML/changelog.txt
new file mode 100644
index 0000000000..d19108a70d
--- /dev/null
+++ b/plugin.program.AML/changelog.txt
@@ -0,0 +1,684 @@
+[B]AML ideas and planned features (more futuristic ideas)[/B]
+
+WIP [CORE] JSON database files (specially MAME_DB_main.json, MAME_DB_render.json and
+ MAME_assets.json) could be compressed with DEFLATE to reduce the size on disk. They
+ will decompressed on the fly. I have to test if this is faster than no compression at all.
+ Use low values of compression in the deflate algorithm. Maximum compression is very
+ slow but less compression is fast and may reduce the JSON file size a lot.
+
+ According to Zachmorris, MessagePack is the fastest serialization method in Python 3.
+
+ The module xmltodict is also very interesting and the performance very good in Python 3.
+
+ See https://forum.kodi.tv/showthread.php?tid=329315&pid=2975588#pid2975588
+ See https://github.com/vsergeev/u-msgpack-python
+ See https://github.com/martinblech/xmltodict
+
+WIP [AUDIT] Samples distributed with MAME are uncompressed and not stored into ZIP files.
+ The MAME audit engine must take this into account.
+ See comments in mame_scan_MAME_ROMs() @mame.py
+
+WIP [CORE] AML/AEL must report the aspect ratio of artwork to the skin.
+ How to implement this? Using PIL?
+ I think best way is that core developers expose the image aspect ratio to the
+ skin using infolabels.
+
+
+[B]AML ideas and planned features (read to implement)[/B]
+
+WIP [DOCS] Improve the AML wiki in Github and move documentation from Kodi forum to
+ Github wiki.
+
+WIP [CORE] Implement the "Read-only launchers". ROLs are XML files that include the
+ launcher information as well as the ROMs information. ROLs are generated with AML,
+ can be exported as XML and then used with AEL. AEL will handle ROLs as read-only
+ (cannot be edited). If a ROL should be changed, then the XML is edited or regenerated
+ and then imported again into AML.
+
+WIP [MANUALS] Increase the number of supported PDF filters.
+ This may require A LOT of Python coding.
+
+WIP [MANUALS] Support for CBZ/CBR manuals.
+ In theory Kodi VFS is able to read RAR files.
+ Wait until this is implemented in AEL and then port it from AEL into AML?
+
+WIP [CORE] Recursive context menus.
+ Pay attention to the select() bug in Kodi Krypton.
+ This requires Kodi version detection and having different code for Krypton/Leia.
+ Start with the context menu in which recursiveness ir more useful.
+
+WIP [GRAPHICS] Optimize the generation of 3D boxes.
+ Cache the background images used in all boxes.
+
+WIP [GRAPHICS] Respect the aspect ratio in 3D boxes. For example, if the poster in the
+ front size of the 3D box is square the 3D box must be square, do not strech the
+ poster texture. ScreenScraper respects the aspect ratio of the 3D boxes, have
+ a look at examples there (for example, see Mega Drive 3D box and PSX 3D box).
+
+WIP Retroarch core MAME 2003 Plus allows to save the MAME XML file needed by AML.
+ In this core, the XML is extracted using the MAME internal menu, which must be
+ opened with the Retroarch core options menu. The XML file is saved in the SAVES
+ directory with name mame2003-plus/mame2003-plus.xml
+ Exploit this to give support to MAME 2003 and then Android. According to
+ Chrisism, Retroarch is currently the best way to use MAME in Android.
+ TTBOMK, currently it is the only core to support this feature. Investigate if there
+ are more Retroarch cores with this feature.
+
+ See https://github.com/libretro/mame2003-libretro/pull/348
+ See https://github.com/libretro/mame2003-plus-libretro/pull/8
+ See https://github.com/libretro/mame2010-libretro/pull/123
+ See https://github.com/libretro/mame2010-libretro/tree/master/metadata
+
+
+[B]Advanced MAME Launcher | version 1.0.2 | 18 June 2021[/B]
+
+NOTE Version was bumped to 1.0.2 to keep feature-sync with 0.10.2
+
+FEATURE [CORE] Synched utils.py and misc.py with AEL.
+
+FEATURE [CORE] Support new history.dat XML format.
+ See https://forum.kodi.tv/showthread.php?tid=304186&pid=3029005#pid3029005
+
+
+[B]Advanced MAME Launcher | version 1.0.1 | non-released[/B]
+
+NOTE This version was never uploaded to the Kodi repo.
+
+FIX [CORE] Fix crash when building Controls (Expanded) catalog in MAME 2003 Plus mode.
+ Also fixed Controls (Expanded) and Controls (Compact) catalogues for empty controls.
+
+FIX [CORE] Fix SKIN_SHOW_* launchers.
+
+FIX [CORE] Fix bug in mame_update_MAME_MostPlay_objects().
+
+
+[B]Advanced MAME Launcher | version 1.0.0 | 27 November 2020[/B]
+
+FEATURE [CORE] Port the addon to Kodi Matrix and Python 3.
+
+FEATURE [CORE] xbmc.translatePath is deprecated in Matrix, use xbmcvfs.translatePath instead.
+
+FIX [MANUALS] The pdfwr library is not working well with Python 3. PDF image extraction
+ have been disabled until this issue is fixed.
+
+
+[B]Advanced MAME Launcher | version 0.10.2 | xx June 2021[/B]
+
+NOTE Code synched with 1.0.2.
+
+FEATURE [CORE] Synched utils.py and misc.py with AEL.
+
+FEATURE [CORE] Support new history.dat XML format.
+ See https://forum.kodi.tv/showthread.php?tid=304186&pid=3029005#pid3029005
+
+
+[B]Advanced MAME Launcher | version 0.10.1 | 04 February 2021[/B]
+
+FIX [CORE] Fix crash when building Controls (Expanded) catalog in MAME 2003 Plus mode.
+ Also fixed Controls (Expanded) and Controls (Compact) catalogues for empty controls.
+
+FIX [CORE] Fix SKIN_SHOW_* launchers.
+
+FIX [CORE] Fix bug in mame_update_MAME_MostPlay_objects().
+
+
+[B]Advanced MAME Launcher | version 0.10.0 | 27 November 2020[/B]
+
+FEATURE [CORE] Big code refactoring to prepare Kodi Python API changes.
+
+FEATURE [CORE] Include Software Lists that have no associated MAME machines in "Machines
+ by Software List" filter.
+
+FEATURE [CORE] Create branch python2 and place series 0.10.x into this branch.
+
+FEATURE [CORE] Support Retroarch MAME 2003 Plus.
+
+FEATURE [FILTERS] Implement filtering options NoMissingROMs, NoMissingCHDs and NoMissingSamples.
+ See https://forum.kodi.tv/showthread.php?tid=304186&pid=2964109#pid2964109
+
+FEATURE [CORE] Different colours for different filters in the root window.
+
+FEATURE [CORE] In the Setup Plugin CM, Build Fanarts and 3D boxes: add an option to
+ build all the Fanarts and 3D boxes at once.
+
+FEATURE [CORE] Reorganised the Setup Plugin context menu a bit.
+
+FEATURE [CORE] Export MAME info with billyc999s XML format.
+
+FEATURE [CORE] New Utility "Show machines with biggest ROMs"
+
+FEATURE [CORE] New Utility "Show machines with smallest ROMs"
+
+FEATURE [CORE] Disable Kodi screensaver when launching MAME and reenable after MAME finishes.
+ See https://forum.kodi.tv/showthread.php?tid=304186&pid=2934194#pid2934194
+
+FIX [CORE] Fixed crash when creating ROM audit database when MAME CHD set was SPLIT.
+ See https://forum.kodi.tv/showthread.php?tid=304186&pid=2975566#pid2975566
+
+FIX [CORE] Fix parsing of mameinfo.dat 0.226.
+
+
+[B]Advanced MAME Launcher | version 0.9.12 | 10 February 2020[/B]
+
+FEATURE Create a new infolabel $INFO[ListItem.Property(history)] which shows the contents
+ of history.dat for a machine. Add an option to include the contents of History.DAT
+ in the asset database.
+ Once this is tested remove the code to include History.DAT in the plot.
+ See https://forum.kodi.tv/showthread.php?tid=304186&pid=2916270#pid2916270
+
+FEATURE Option to hide MAME machine flags.
+ Contributed by Rychem28.
+
+FEATURE Option to hide SL item flags.
+ Contributed by Rychem28.
+
+FEATURE Option to display only SL items with ROMs/CHDs available.
+ Contributed by Rychem28.
+
+FIX Fixed parsing of History.dat 2.17.
+ See https://forum.kodi.tv/showthread.php?tid=304186&pid=2913592#pid2913592
+
+
+[B]Advanced MAME Launcher | version 0.9.11 | 26 November 2019[/B]
+
+FEATURE [CORE] For Software Lists, instead of showing the number of ROMs show the number
+ of Items in Flat display mode or Parents in Parent/Clone display mode.
+
+FEATURE [CORE] Support for MASH's Alltime.ini.
+
+FEATURE [CORE] Improvements in the JSON index of DAT files. Use dictionaries and not list
+ to reduce JSON file size and speed up data access.
+
+FEATURE [CORE] Improved main statistics.
+
+FEATURE [CORE] Improve some Software List long names until they are fixed in MAME repository.
+
+FIX Fixed conversion to int of addon version str in fs_AML_version_str_to_int()
+
+FIX New history.dat makes AML to crash because one entry could be for more than one
+ machine. In addition, the same description can be used over several SLs.
+
+
+[B]Advanced MAME Launcher | version 0.9.10 | 10 May 2019[/B]
+
+FEATURE Option to disable Software Lists at all in Vanilla MAME mode.
+
+FEATURE Management of MAME Favourite objects (delete missing, delete single).
+
+FEATURE Management of SL Favourite objects (delete missing, delete single).
+
+FEATURE Initial support for Retroarch MAME 2003 Plus core.
+
+FEATURE [CORE] Ability to export DAT files in Logiqx format for MAME ROMs and MAME CHDs.
+
+FEATURE [CORE] Differentiate between Non-merged and Fully non-merged MAME ROM sets.
+ Non-merged sets do not include BIOS and device ROMs.
+ Fully non-merged sets include BIOS and devices ROMs (every ROM required to run each
+ machine). Pleasuredome ROM sets are Fully non-merged.
+ This needs more testing.
+
+FEATURE [FILTERING] Test the filters for errors. For example, test that all the drivers
+ defined in exist. Another example, test that the genres defined in
+ and the controls defined in exist.
+
+FEATURE [FILTERING] Warn the user if errors found in the XML filter definition, maybe in
+ a report the user can read later.
+
+FEATURE [FILTERING] Make a filter report the user can read. Now, only the number of machines
+ after filtering is reported.
+
+FEATURE [GRAPHICS] Generate MAME machines and SL items 3D Boxes.
+
+FEATURE [GRAPHICS] Refactoring of the Fanart and 3D Box code generation. Creation of graphics.py.
+
+FEATURE [GRAPHICS] Only generate 3D Boxes if both Fyler and Clearlogo (MAME) or Boxfront (SL)
+ are present. Avoid having empty 3D boxes.
+
+FEATURE [CORE] Improved filter "Machines by Display Type" and removed filter
+ "Machines by Display Rotation."
+
+FEATURE [CORE] New catalog filter "Machines by Display VSync freq." Inspired by
+ MASH's MAMEINFO Vsync.ini.
+
+FEATURE [CORE] New catalog filter "Machines by Display Resolution" Inspired by
+ MASH's MAMEINFO Screen.ini.
+
+FEATURE [CORE] New catalog filter "Machines by CPU" Inspired by
+ MASH's MAMEINFO CPU.ini.
+
+FEATURE [CORE] Rewrite the INI loading engine.
+ See comments in header of function mame_load_INI_datfile().
+
+FEATURE [CORE] New INI file Artwork.ini (produced by MASH's MAMEINFO).
+ Catalog filter "Machines by Artwork".
+ Note that Artwork.ini places the same machine in different categories. Maybe other
+ INIs do the same and currently AML does not support this. This requires
+ some changes in AML engine.
+
+FEATURE New catalog "Version added"
+
+FEATURE [CORE] New INI file Category.ini (produced by MASH's MAMEINFO).
+ Catalog filter "Machines by Category".
+
+FEATURE [CORE] New "All in one (Extract, Build and Scan)" options:
+ 1) Delete current to "All in one (Extract, Build, Scan)"
+ 2) Add "All in one (Extract, Build, Scan, Filters)"
+ 3) Add "All in one (Extract, Build, Scan, Filters, Audit)"
+
+FEATURE [CORE] By request of Rufoo, support MAME 3dboxes.
+
+FIX Fixed a couple of crashes when executing context menus, thanks to Dax9.
+ See https://forum.kodi.tv/showthread.php?tid=304186&pid=2838180#pid2838180
+
+FIX Fix the render_skin_*() functions. These are called by skins to get a list of
+ filters.
+
+
+[B]Advanced MAME Launcher | version 0.9.9 | 22 March 2019[/B]
+
+FEATURE [CORE] Improve statistics of working, non-working games, etc. for the Main filters.
+
+FEATURE [CORE] [LEIA] Check out the ListItem constructor offscreen parameter in Leia.
+ The offscreen parameter increases the speed a bit.
+ This requires Kodi version detection and having different code for Krypton/Leia.
+ See https://forum.kodi.tv/showthread.php?tid=329315&pid=2711937#pid2711937
+ and https://forum.kodi.tv/showthread.php?tid=307394&pid=2531524
+
+FEATURE [CORE] [LEIA] Use the new API function ListItem.setProperties({p1:v1, p2:v2, ...})
+ This requires Kodi version detection and having different code for Krypton/Leia.
+ See https://forum.kodi.tv/showthread.php?tid=332283
+
+FEATURE [FANARTS] Set an order to print fanart assets. This will allow to have images printed
+ on top of each other.
+
+FEATURE [CORE] Improve the Samples scanner and report.
+
+FEATURE [CORE] MAME and SL plots build timestamp.
+
+FEATURE [CORE] MAME and SL Fanart build timestamp.
+
+FEATURE [CORE] MAME render and asset cache build timestamp.
+
+FEATURE [CORE] Move the utilities from the addon settings into the root menu.
+
+FEATURE [CORE] Move the report viewer from the context menu to the root menu. The context
+ menu is a little bit overloaded and this will alleviate the situation.
+
+FEATURE [CORE] Use xbmcplugin.addDirectoryItems() instead of xbmcplugin.addDirectoryItem().
+ According to the docs "Large lists benefit over using the standard addDirectoryItem().
+ You may call this more than once to add items in chunks."
+
+FEATURE [CORE] Configuring every DAT and INI file one by one is tedious. Instead, define
+ a directory where the DATs must be placed and pick the files from there automatically.
+
+FEATURE [CORE] Add plots for entries in the root window.
+
+FEATURE [CORE] Renamed the "Machines by Score" filter to "Machines by Rating", to avoid
+ confusing with scores in games.
+
+FEATURE [CORE] Implement context menu "All in one (Extract, Build and Scan)"
+
+FEATURE [CORE] Refactoring and code cleaning of the addon setup functions (DB build, scanner).
+
+FEATURE [CORE] Remove redundant fields from MAME DB 'coins' and 'control_type'. Use new
+ 'input' data structure to replace them.
+
+FEATURE [CORE] At least two directories for samples are needed. MAME includes a default samples
+ directory with some samples used by some machines. Both directories must be configured
+ in mame.ini in order to get all the samples working.
+
+ NOTE This feature was cancelled. Only one Samples directory. If the user wants to have
+ a complete Good audit, then the samples shipped with MAME must be compressed and
+ the ZIP file placed in the unique samples directory. In any case, only three machines
+ are affected.
+
+FEATURE [FILTERING] Implement , and tags.
+ This will require more work than expected. Currently, only parent machines are filtered
+ and clone machines are added after the filtering process. However, to implement
+ Include, Exclude and Change tags, all machines must be included in the filter list.
+ This will require modification of the filter render engine (must be rendered always in
+ flat mode and not in parent/clone mode).
+
+FEATURE [MANUALS] Progress bar when extracting PDF pages.
+
+FEATURE [MANUALS] When displaying manuals use cached extracted images if they exist.
+ When the manual is extracted, create a small JSON file with the timestamp of
+ the extraction so it can be compared with the timestamp of the PDF file to
+ test if images must be extracted again.
+
+FIX In MAME 0.206, some clone merged ROMs are not present in the parent machine, only
+ in the BIOS. For example, in machine adonisce (clone of adonis). Before 0.206, such
+ ROMs were also present on the parent machine. This change in behaviour crashed AML.
+
+FIX Fixed the AML addon and MAME numerical versioning scheme.
+
+FIX Fix crash in "Build MAME databases".
+ See https://forum.kodi.tv/showthread.php?tid=304186&pid=2822949#pid2822949
+
+FIX Some Software List ROMs are compressed using non-ASCII characters and this make
+ the audit engine to crash. I have to investigate how to fix this issue.
+ Maybe use the chardet library https://github.com/Wintermute0110/chardet/tree/master/chardet
+ I think this should reported creating an issue in MAME project in Github.
+ Problematic SL ROM example: SL ibm5170, item wordfndr
+ https://github.com/mamedev/mame/blob/master/hash/ibm5170.xml#L7521
+ The current implementation just ignores non-ASCII files and the audit fails for those
+ SL items.
+
+
+[B]Advanced MAME Launcher | version 0.9.8 | 23 June 2018[/B]
+
+FEATURE [DOCS] Documentation in README.md improved.
+
+FEATURE [LEIA] Kodi Leia will cache the Python interpreter which means submodules will only
+ be executed once and cached. sys.argv must be propagated from the entry point code
+ into the submodules.
+ See https://github.com/xbmc/xbmc/pull/13814
+ and https://forum.kodi.tv/showthread.php?tid=303073&pid=2729071#pid2729071
+
+FIX Changed source code files to remove BOM. This is necessary to pass Travis tests of
+ Kodi official repo.
+
+FIX ActivateWindow(busydialog) and Dialog.Close(busydialog) have been deprecated.
+ Use DialogProgress() for all operations.
+ See https://github.com/xbmc/xbmc/pull/13958
+ and https://github.com/xbmc/xbmc/pull/13954
+ and https://github.com/xbmc/xbmc/pull/10699
+
+FIX Do not use the xbmc.Player() in launcher addons. Instead, use functions like
+ xbmc.getCondVisibility("Player.HasMedia"), xbmc.executebuiltin("PlayerControl(stop)"), etc.
+ Change proposed by enen92.
+ See https://github.com/xbmc/repo-plugins/pull/1886#discussion_r196591764
+
+
+[B]Advanced MAME Launcher | version 0.9.7 | 09 June 2018[/B]
+
+FEATURE Implemented settings "display_rom_available" and "display_chd_available".
+
+FEATURE [LAUNCH] Implement "Action on Kodi playing media", "After/before launch delay (ms)", and
+ "Suspend/resume Kodi audio engine".
+ See https://github.com/Wintermute0110/plugin.program.AML/issues/3
+
+FEATURE [MAME FILTERING] Improve the Custom Filters (add more filtering options as defined
+ in the reference filter file `AML-MAME-filters-reference.xml`).
+
+FEATURE [CORE] Render the `In Favourites` flag for MAME machines.
+
+FEATURE [CORE] Optimize the rendering of ROMs in 3 steps: a) Loading, b) Processing and c) Rendering.
+ Processing computes all data to render ROMs and Rendering calls Kodi functions. This will
+ allow to measure how long does it take to call the Kodi functions for ListItem generation.
+
+FEATURE [CORE] Reduce the memory consumption during the database generation.
+ By default use the options OPTION_COMPACT_JSON = True and OPTION_LOWMEM_WRITE_JSON = True
+ See https://stackoverflow.com/questions/24239613/memoryerror-using-json-dumps
+
+FIX Fix crash when executing "Check/Update all objects" if Favourites are empty.
+
+
+[B]Advanced MAME Launcher | version 0.9.6 | 25 May 2018[/B]
+
+FEATURE Improve the user experience when the addon is just installed. Check if databases
+ have been built, check for errors, etc.
+
+FEATURE Add a isMature field to MAME DB. Take the mature information from mature.ini included
+ in the Catver.ini ZIP file.
+
+FEATURE Option in settings to completely hide Mature machines and filter categories.
+
+FEATURE Asset hashed database. This will speed up launching MAME machines. Note that the asset
+ DB must be opened for the Most Played and Recently Played DBs.
+
+FEATURE Prettify the "Display rotation" filter (use Horizontal/Vertical instead of numbers).
+
+FEATURE Include number of buttons in controls and some other control information.
+
+FEATURE Add the Samples of each machine to the ROM database.
+
+FEATURE Audit the ROM samples inside ZIP files.
+
+FEATURE Implement "Most played MAME machines"
+
+FEATURE Implement "Recently played MAME machines"
+
+FEATURE Option in settings to update all MAME and SL Favourite ROMs. Useful for plugin upgrades.
+
+FEATURE Implement "Most played SL ROMs"
+
+FEATURE Implement "Recently played SL ROMs"
+
+
+[B]Advanced MAME Launcher | version 0.9.5 | 11 May 2018[/B]
+
+FEATURE Option to disable the ROM and asset caches.
+
+FEATURE CRC32 hash collision detector for MAME and SL ROMs.
+
+FEATURE MAME ROM and asset cache disable by default. They may be enabled by user that want to
+ increase the loading speed. This will be very useful for develpment because
+ cache rebuilding takes a long time.
+
+FEATURE Check if AML configuration is OK or not, and warn the user about warnings/errors.
+
+FEATURE Improved PDF manual rendering. Use the library pdfrw for image extraction.
+
+FEATURE Clean ROM cache before rebuilding cache.
+
+FEATURE Clean asset cache before rebuilding cache.
+
+FEATURE Clean filters directory before rebuilding custom filters.
+
+FEATURE MAME audit statistics.
+
+FEATURE SL audit statistics.
+
+FEATURE Support for SL Merged ROM/CHD sets (currently only Split).
+
+FEATURE Added audit timestamps (MAME machines and Software Lists).
+
+FEATURE Move driver aristmk5.cpp (Aristocrat gambling machines) from Standard to Unusual.
+ Also, adp.cpp, mpu4vid.cpp, cubo.cpp, sfbonus.cpp, peplus.cpp.
+
+FIX Software List ROM size was stored as string and not as int. This made the SL Audit to
+ completely fail.
+
+FIX Fixed audit of MAME machine ROMs (wrong function name).
+
+FIX Lots of fixes to MAME ROM audit engine.
+
+FIX Lots of fixes to Software Lists audit engine.
+
+
+[B]Advanced MAME Launcher | version 0.9.4 | 29 March 2018[/B]
+
+FEATURE File cache for SL ROMs/CHDs and SL assets.
+
+FEATURE Port the file scanner cache from AEL to AML. This will increase the scanning speed a lot!
+ Also, this will allow supporting more image types (currently only PNG), manual
+ types (currently only PDF) and trailer types (currently MP4 only).
+
+FEATURE Create an AEL virtual launcher in XML from any AML filter.
+
+FEATURE Use proper Software List name in "Machines by Software List" filter.
+
+FEATURE Use proper short name in "Machines by MAME short name" filter.
+
+FEATURE Clean Render and main machine JSON files. Currently, there are repeated fields on both
+ databases like nplayers.
+
+FEATURE Move flags and plot from the render database to the assets database. Flags are modified
+ by the scanner only and plot generated after the scanner. If flags and plot are in
+ the asset DB, the ROM cache and hashed DB must be regenerated after the database building
+ only and not always like now.
+
+FEATURE Render PDF manuals consisting of image scans (99% of game manuals are scans of images).
+ Thank you very much to i96751414 for allowing use of his PDF reader addon code.
+ Have a look at the PDF reader addon https://forum.kodi.tv/showthread.php?tid=187421
+ and https://github.com/i96751414/plugin.image.pdfreader
+ This initial implementation somewhat works for some PDFs but code can be improved a lot.
+
+FEATURE Create a hased database for all catalog filter combination. This will require the
+ creation of about 5000 json files but will make AEL as fast as possible.
+
+FEATURE Hashed database for assets, in a similar fashion to the catalog ROM hashed database.
+
+FEATURE Make a ROM cache and a assets cache for the MAME filters. That will increase the
+ loading speed of the MAME filters a lot.
+
+FEATURE Support MAME artwork by Mr. Do's. Note that Clones use Parent's artwork automatically.
+
+FEATURE Use Parent/Clone substituted artwork in MAME. For example, most trailers are only available
+ for the Parent machine and can be used by Clone machines.
+
+FEATURE Use Parent/Clone substituted artwork in Software Lists.
+
+FEATURE Build Fanarts from other pieces of artwork for Software List items.
+
+FEATURE Build Fanarts from other pieces of artwork for MAME machines.
+
+FEATURE Test MAME and SL Fanart building.
+
+FEATURE Custom MAME filters, using XML files. Merge some of the functionality of NARS into AML.
+ First, give to support to filter by driver. Later, more filters can be added.
+
+FEATURE "Browse by MAME short name" and "Browse by MAME long name" alphabetical catalogs.
+
+FEATURE Renamed plugin from plugin.program.advanced.MAME.launcher to plugin.program.AML.
+ Shorter name, shorter databases, higher speed.
+
+FEATURE Some skin helper commands to display widgets.
+
+FEATURE Support bestgames.ini and series.ini.
+
+FEATURE Generate machine plot from MAME XML information.
+
+FEATURE New Main filters Normal and Unusual.
+
+FEATURE Show ROMs of a MAME machine that should be in a ZIP file. Supports Merged, Split and
+ Non-merged sets, CHDs, BIOS and Devices with ROMs.
+
+FEATURE Audit MAME ROMs for all machines.
+
+FEATURE Show SL ROMs of a SL entry. Supports Merged, Split and Non-merged sets and SL CHDs.
+
+FEATURE Audit SL ROMs.
+
+FEATURE Display MAMEINFO.DAT information.
+
+FEATURE Display HISTORY.DAT in information.
+
+FEATURE Display gameinit.dat in information.
+
+FEATURE Display command.dat in information.
+
+FEATURE At launching, do not check ROMs for machines which doesn't have ROMs.
+ Requires loading machines database, which will slow down launching process a lot!
+ A hashed database of machines is necessary to speed up plugin.
+ Better solution for now: do not do any check. Let MAME fail if there are ROM/CHD errors.
+
+FEATURE Allow user to choose default assets as AEL does in addon seetings.
+
+FEATURE Trailer support in MAME machines and Software Lists.
+
+FEATURE Manage MAME Favourites context menu.
+
+FEATURE Manage SL Favourites context menu.
+
+FEATURE Create a hased database for main ROM database and Audit ROM database.
+
+
+[B]Advanced MAME Launcher | version 0.9.3 | 30 May 2017[/B]
+
+FEATURE Ability to choose default Icon and Fanart for MAME and SL ROMs in addon settings.
+
+FEATURE "Parent only" view mode.
+
+FEATURE Plugin speed has been increased a lot owing to a brand new database design.
+
+FEATURE Unified catalog system and new machine rendering method.
+ Requires wiping of ADDON_DATA_DIR to avoid problems.
+
+FEATURE Properties can be configured for every individual list in AML.
+
+FEATURE New Status Device flag. Marks wheter a device is mandatory or not.
+
+FEATURE Show database statistics.
+
+FEATURE Favourite MAME machines.
+
+FEATURE Favourite Software Lists ROMs.
+
+FEATURE Scan SL assets/artwork.
+
+FEATURE Manage MAME Favourites.
+
+
+[B]Advanced MAME Launcher | version 0.9.2 | 12 February 2017[/B]
+
+FEATURE Ability to sort cataloged filters by number of machines.
+
+FEATURE New Main Filter "Machines with no ROMs".
+
+FEATURE Launch parents with no clones from the parents list in Catalogued filters.
+
+FEATURE Use a fancy name for well-known MAME drivers.
+
+FEATURE On filter `Machines by Software List`, substitute short SL name by the proper SL name.
+
+FEATURE Display MAME stdout/stderr.
+
+FEATURE Scan Software Lists.
+
+FEATURE Launch machines with software lists.
+
+FIX Use SORT_METHOD_LABEL_IGNORE_FOLDERS insead of SORT_METHOD_LABEL. This avoids folders
+ to be rendered first when sorting listitems alpahbetically.
+
+
+[B]Advanced MAME Launcher | version 0.9.1 | 04 February 2017[/B]
+
+FEATURE AML only works on Krypton now. Updated addon.xml with new fields.
+
+FEATURE Add support for nplayers.ini.
+
+FEATURE Count machines in "Extract MAME.xml" step and not in "Build MAME database" step.
+
+FEATURE Print the number of clones each machine has. In general, print the number of items
+ on a submenu.
+
+FEATURE Add catalog by Devices. This will help launching software list machines.
+
+FEATURE In a parent list, if there is not clones, then add the ability to launch games from the
+ parent list. Only coded for indexed machines and not for cataloged machines.
+ See http://forums.bannister.org/ubbthreads.php?ubb=showflat&Number=108507#Post108507
+
+FEATURE Switch in settings to diplay Working machines only.
+ See http://forum.kodi.tv/showthread.php?tid=304186&pid=2506150#pid2506150
+
+FEATURE Improved categories in "Machines by Control Type catalog".
+
+FIX "I get an error whenever trying to open any "Ball & Paddle" category. I'm pretty sure this
+ is due to the ampersand, because all the other categories I've tried work. This issue doesn't
+ affect ROMs with an ampersand in their name, like Cloak & Dagger."
+ See http://forum.kodi.tv/showthread.php?tid=304186&pid=2506150#pid2506150
+
+ Problem was that the '&' in the Kodi URL was not escaped.
+
+
+[B]Advanced MAME Launcher | version 0.9.0 | 15 January 2017[/B]
+
+ Initial release
+
+FEATURE Extract MAME.xml from MAME executable. Tested only on Linux.
+
+FEATURE Generate main MAME machine database, indices and catalogs from MAME.xml.
+
+FEATURE Scan ROMs and tell the user about Have/Missing ROMs.
+
+FEATURE Launch MAME non-Software List (arcade) machines.
+
+FEATURE Scan CHDs and samples.
+
+FEATURE Scan assets and build assets database.
+
+FEATURE Display MAME machine metadata/artwork.
+
+FEATURE Build Software List catalog.
diff --git a/plugin.program.AML/filters/AML-MAME-filters-reference.xml b/plugin.program.AML/filters/AML-MAME-filters-reference.xml
new file mode 100644
index 0000000000..d724ebd2fc
--- /dev/null
+++ b/plugin.program.AML/filters/AML-MAME-filters-reference.xml
@@ -0,0 +1,98 @@
+
+
+
+
+ Example filter name
+
+
+ NoClones, NoCoin, NoCoinLess, NoROMs, NoCHDs, NoSamples
+ NoMature, NoBIOS, NoMechanical, NoImperfect, NoNonworking
+ NoVertical, NoHorizontal
+
+
+ NoMissingROMs, NoMissingCHDs, NoMissingSamples
+
+
+ has cps1.cpp or has cps2.cpp
+
+
+ has Konami or has Namco
+
+
+ has Driving and has "Board Game"
+
+
+ lacks Mahjong and lacks Gambling and lacks Hanafuda
+
+
+ has Cartridge and has Harddisk
+
+
+ year >= 1990 and year < 2000
+
+
+ 005, 100lions, 10yard
+
+
+ 005, 100lions, 10yard
+
+
+ dino with dinoj
+
+
diff --git a/plugin.program.AML/filters/AML-MAME-filters.xml b/plugin.program.AML/filters/AML-MAME-filters.xml
new file mode 100644
index 0000000000..7d2ab7fd00
--- /dev/null
+++ b/plugin.program.AML/filters/AML-MAME-filters.xml
@@ -0,0 +1,321 @@
+
+
+
+
+
+has cps1.cpp or has cps2.cpp or has cps3.cpp
+has neogeo.cpp
+
+
+has "Ball & Paddle" or has Climbing or has Driving or has Fighter or has Maze or has MultiGame or has Multiplay or has Platform or has Puzzle or has Shooter or has Sports or has Whac-A-Mole
+
+
+has Dial or has Joy or has Lightgun or has "Only Buttons" or has Paddle or has Pedal or has Positional or has Stick or has Trackball
+
+
+
+ Atari arcade games
+ Arcade games by [COLOR orange]Atari[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Atari
+
+
+
+ Capcom arcade games
+ Arcade games by [COLOR orange]Capcom[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Capcom
+
+
+
+ Capcom CPS board
+ Machines in [COLOR orange]Capcom CPS[/COLOR] arcade boards.
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ CAPCOM_CPS_DRIVERS
+
+
+
+ Cave arcade games
+ Machines in [COLOR orange]Cave[/COLOR] arcade boards.
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Cave
+
+
+
+ Data East arcade games
+ Arcade games by [COLOR orange]Data East[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has "Data East"
+
+
+
+ Gaelco arcade games
+ Arcade games by [COLOR orange]Gaelco[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Gaelco
+
+
+
+ IGS arcade games
+ Arcade games by [COLOR orange]IGS[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Igs or has IGS
+
+
+
+ Irem arcade games
+ Arcade games by [COLOR orange]Irem[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Irem
+
+
+
+ Jaleco arcade games
+ Arcade games by [COLOR orange]Jaleco[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Jaleco
+
+
+
+ Kaneko arcade games
+ Arcade games by [COLOR orange]Kaneko[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Kaneko
+
+
+
+ Konami arcade games
+ Arcade games by [COLOR orange]Konami[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Konami
+
+
+ simpsons2p, tmht2p
+
+
+
+
+
+
+
+
+
+ Mitchell arcade games
+ Arcade games by [COLOR orange]Mitchell[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Mitchell
+
+
+
+ Midway arcade games
+ Arcade games by [COLOR orange]Midway[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Midway
+
+
+
+ Namco arcade games
+ Arcade games by [COLOR orange]Namco[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Namco
+
+
+ pacman
+
+
+
+ Nintendo arcade games
+ Arcade games by [COLOR orange]Nintendo[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Nintendo
+
+
+
+ Sammy arcade games
+ Arcade games by [COLOR orange]Sammy[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Sammy
+
+
+
+ SEGA arcade games
+ Arcade games by [COLOR orange]SEGA[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Sega or has SEGA
+
+
+
+ Seta arcade games
+ Arcade games by [COLOR orange]Seta[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Seta or has SETA
+
+
+
+ SNK arcade games
+ Arcade games by [COLOR orange]SNK[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has SNK or has Snk
+
+ ng_mv1, ng_mv2f, ng_mv4f, neogeo
+
+
+
+ SNK NeoGeo MVS board
+ Machines in [COLOR orange]SNK Neo Geo MVS[/COLOR] arcade board.
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ NEOGEO_MVS_DRIVER
+
+ ng_mv1, ng_mv2f, ng_mv4f, neogeo
+
+
+
+ TAD Corporation arcade games
+ Arcade games by [COLOR orange]TAD Corporation[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Tad or has TAD
+
+
+
+ Taito arcade games
+ Arcade games by [COLOR orange]Taito[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Taito
+
+
+
+ Tecmo arcade games
+ Arcade games by [COLOR orange]Tecmo[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Tecmo
+
+
+
+ Technos arcade games
+ Arcade games by [COLOR orange]Technos[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Technos
+
+
+
+ Toaplan arcade games
+ Arcade games by [COLOR orange]Toaplan[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Toaplan
+
+
+
+ Visco arcade games
+ Arcade games by [COLOR orange]Visco[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Visco
+
+
+
+ Williams arcade games
+ Arcade games by [COLOR orange]Williams[/COLOR].
+ NoClones, NoCoinLess, NoBIOS, NoMechanical, NoNonworking
+ has Williams
+
+
+
+
+ Family games from the 70s
+ Arcade machines from the 1970s with standard genres and controls, excluding mature, BIOSes, mechanical and non-working machines
+ NoClones, NoCoinLess, NoMature, NoBIOS, NoMechanical, NoNonworking
+ STANDARD_GENRES
+ STANDARD_CONTROLS
+ year > 1000 and year < 1980
+
+
+
+ Family games from the 80s
+ Arcade machines from 1980 to 1984 with standard genres and controls, excluding mature, BIOSes, mechanical and non-working machines.
+ NoClones, NoCoinLess, NoMature, NoBIOS, NoMechanical, NoNonworking
+ STANDARD_GENRES
+ STANDARD_CONTROLS
+ year >= 1980 and year < 1985
+
+ pacman
+
+
+
+ Family games from the 85s
+ Arcade machines from 1985 to 1989 with standard genres and controls, excluding mature, BIOSes, mechanical and non-working machines.
+ NoClones, NoCoinLess, NoMature, NoBIOS, NoMechanical, NoNonworking
+ STANDARD_GENRES
+ STANDARD_CONTROLS
+ year >= 1985 and year < 1990
+
+
+
+ Family games from the 90s
+ Arcade machines from 1990 to 1994 with standard genres and controls, excluding mature, BIOSes, mechanical and non-working machines.
+ NoClones, NoCoinLess, NoMature, NoBIOS, NoMechanical, NoNonworking
+ STANDARD_GENRES
+ STANDARD_CONTROLS
+ year >= 1990 and year < 1995
+
+
+
+ Family games from the 95s
+ Arcade machines from 1995 to 1999 with standard genres and controls, excluding mature, BIOSes, mechanical and non-working machines.
+ NoClones, NoCoinLess, NoMature, NoBIOS, NoMechanical, NoNonworking
+ STANDARD_GENRES
+ STANDARD_CONTROLS
+ year >= 1995 and year < 2000
+
+
+
+ Family games from the 00s
+ Arcade machines from the 2000s with standard genres and controls, excluding mature, BIOSes, mechanical and non-working machines.
+ NoClones, NoCoinLess, NoMature, NoBIOS, NoMechanical, NoNonworking
+ STANDARD_GENRES
+ STANDARD_CONTROLS
+ year >= 2000 and year < 2010
+
+
+
+ Family games from the 10s
+ Arcade machines from the 2010s an up with standard genres and controls, excluding mature, BIOSes, mechanical and non-working machines.
+ NoClones, NoCoinLess, NoMature, NoBIOS, NoMechanical, NoNonworking
+ STANDARD_GENRES
+ STANDARD_CONTROLS
+ year >= 2010
+
+
+
+ Family games with Horizonal Screen
+ Arcade machines with a Horizontal screen with standard genres and controls, excluding mature, BIOSes, mechanical and non-working machines.
+ NoClones, NoCoinLess, NoMature, NoBIOS, NoMechanical, NoNonworking
+ NoVertical
+ STANDARD_GENRES
+ STANDARD_CONTROLS
+
+
+
+ Family games with Vertical Screen
+ Arcade machines with a Vertical screen with standard genres and controls, excluding mature, BIOSes, mechanical and non-working machines.
+ NoClones, NoCoinLess, NoMature, NoBIOS, NoMechanical, NoNonworking
+ NoHorizontal
+ STANDARD_GENRES
+ STANDARD_CONTROLS
+
+
+
+ Complete machines
+ Complete arcade machines, that is, machines with no missing ROMs, CHDs, or Samples, with standard genres and controls, excluding mature, BIOSes, mechanical and non-working machines.
+ NoClones, NoCoinLess, NoMature, NoBIOS, NoMechanical, NoNonworking
+ NoMissingROMs, NoMissingCHDs, NoMissingSamples
+ STANDARD_GENRES
+ STANDARD_CONTROLS
+
+
diff --git a/plugin.program.AML/fonts/Inconsolata.otf b/plugin.program.AML/fonts/Inconsolata.otf
new file mode 100644
index 0000000000..348889828d
Binary files /dev/null and b/plugin.program.AML/fonts/Inconsolata.otf differ
diff --git a/plugin.program.AML/media/MAME_assets/dino_PCB.png b/plugin.program.AML/media/MAME_assets/dino_PCB.png
new file mode 100644
index 0000000000..030fa7664f
Binary files /dev/null and b/plugin.program.AML/media/MAME_assets/dino_PCB.png differ
diff --git a/plugin.program.AML/media/MAME_assets/dino_artpreview.png b/plugin.program.AML/media/MAME_assets/dino_artpreview.png
new file mode 100644
index 0000000000..cb682baa63
Binary files /dev/null and b/plugin.program.AML/media/MAME_assets/dino_artpreview.png differ
diff --git a/plugin.program.AML/media/MAME_assets/dino_cabinet.png b/plugin.program.AML/media/MAME_assets/dino_cabinet.png
new file mode 100644
index 0000000000..fc8c85dd79
Binary files /dev/null and b/plugin.program.AML/media/MAME_assets/dino_cabinet.png differ
diff --git a/plugin.program.AML/media/MAME_assets/dino_clearlogo.png b/plugin.program.AML/media/MAME_assets/dino_clearlogo.png
new file mode 100644
index 0000000000..33f0323e52
Binary files /dev/null and b/plugin.program.AML/media/MAME_assets/dino_clearlogo.png differ
diff --git a/plugin.program.AML/media/MAME_assets/dino_cpanel.png b/plugin.program.AML/media/MAME_assets/dino_cpanel.png
new file mode 100644
index 0000000000..d2a9edac19
Binary files /dev/null and b/plugin.program.AML/media/MAME_assets/dino_cpanel.png differ
diff --git a/plugin.program.AML/media/MAME_assets/dino_flyer.png b/plugin.program.AML/media/MAME_assets/dino_flyer.png
new file mode 100644
index 0000000000..de392cbf39
Binary files /dev/null and b/plugin.program.AML/media/MAME_assets/dino_flyer.png differ
diff --git a/plugin.program.AML/media/MAME_assets/dino_marquee.png b/plugin.program.AML/media/MAME_assets/dino_marquee.png
new file mode 100644
index 0000000000..18d88ac4bc
Binary files /dev/null and b/plugin.program.AML/media/MAME_assets/dino_marquee.png differ
diff --git a/plugin.program.AML/media/MAME_assets/dino_snap.png b/plugin.program.AML/media/MAME_assets/dino_snap.png
new file mode 100644
index 0000000000..d7925d6e56
Binary files /dev/null and b/plugin.program.AML/media/MAME_assets/dino_snap.png differ
diff --git a/plugin.program.AML/media/MAME_assets/dino_title.png b/plugin.program.AML/media/MAME_assets/dino_title.png
new file mode 100644
index 0000000000..87f969cc1f
Binary files /dev/null and b/plugin.program.AML/media/MAME_assets/dino_title.png differ
diff --git a/plugin.program.AML/media/MAME_assets/mslug_clearlogo.png b/plugin.program.AML/media/MAME_assets/mslug_clearlogo.png
new file mode 100644
index 0000000000..c7daa06ae7
Binary files /dev/null and b/plugin.program.AML/media/MAME_assets/mslug_clearlogo.png differ
diff --git a/plugin.program.AML/media/MAME_assets/mslug_flyer.png b/plugin.program.AML/media/MAME_assets/mslug_flyer.png
new file mode 100644
index 0000000000..71079cdb0b
Binary files /dev/null and b/plugin.program.AML/media/MAME_assets/mslug_flyer.png differ
diff --git a/plugin.program.AML/media/MAME_clearlogo.png b/plugin.program.AML/media/MAME_clearlogo.png
new file mode 100644
index 0000000000..f305131424
Binary files /dev/null and b/plugin.program.AML/media/MAME_clearlogo.png differ
diff --git a/plugin.program.AML/media/SL_assets/doom_boxfront.png b/plugin.program.AML/media/SL_assets/doom_boxfront.png
new file mode 100644
index 0000000000..9e79ed9aa5
Binary files /dev/null and b/plugin.program.AML/media/SL_assets/doom_boxfront.png differ
diff --git a/plugin.program.AML/media/SL_assets/doom_clearlogo.png b/plugin.program.AML/media/SL_assets/doom_clearlogo.png
new file mode 100644
index 0000000000..a535c7192d
Binary files /dev/null and b/plugin.program.AML/media/SL_assets/doom_clearlogo.png differ
diff --git a/plugin.program.AML/media/SL_assets/doom_snap.png b/plugin.program.AML/media/SL_assets/doom_snap.png
new file mode 100644
index 0000000000..733da4e258
Binary files /dev/null and b/plugin.program.AML/media/SL_assets/doom_snap.png differ
diff --git a/plugin.program.AML/media/SL_assets/doom_title.png b/plugin.program.AML/media/SL_assets/doom_title.png
new file mode 100644
index 0000000000..c9d2a18e9a
Binary files /dev/null and b/plugin.program.AML/media/SL_assets/doom_title.png differ
diff --git a/plugin.program.AML/media/SL_assets/sonic3_boxfront.png b/plugin.program.AML/media/SL_assets/sonic3_boxfront.png
new file mode 100644
index 0000000000..3ada3bc1f6
Binary files /dev/null and b/plugin.program.AML/media/SL_assets/sonic3_boxfront.png differ
diff --git a/plugin.program.AML/media/SL_assets/sonic3_clearlogo.png b/plugin.program.AML/media/SL_assets/sonic3_clearlogo.png
new file mode 100644
index 0000000000..12c3aef5bd
Binary files /dev/null and b/plugin.program.AML/media/SL_assets/sonic3_clearlogo.png differ
diff --git a/plugin.program.AML/media/fanart.jpg b/plugin.program.AML/media/fanart.jpg
new file mode 100644
index 0000000000..b7e2ba3927
Binary files /dev/null and b/plugin.program.AML/media/fanart.jpg differ
diff --git a/plugin.program.AML/media/icon.png b/plugin.program.AML/media/icon.png
new file mode 100644
index 0000000000..9ba2defd59
Binary files /dev/null and b/plugin.program.AML/media/icon.png differ
diff --git a/plugin.program.AML/media/shot_01_main_window.jpg b/plugin.program.AML/media/shot_01_main_window.jpg
new file mode 100644
index 0000000000..ee8b3586f0
Binary files /dev/null and b/plugin.program.AML/media/shot_01_main_window.jpg differ
diff --git a/plugin.program.AML/media/shot_02_MAME_pclone_list.jpg b/plugin.program.AML/media/shot_02_MAME_pclone_list.jpg
new file mode 100644
index 0000000000..afb799908b
Binary files /dev/null and b/plugin.program.AML/media/shot_02_MAME_pclone_list.jpg differ
diff --git a/plugin.program.AML/media/shot_03_SL_pclone_list.jpg b/plugin.program.AML/media/shot_03_SL_pclone_list.jpg
new file mode 100644
index 0000000000..fd183ffd1b
Binary files /dev/null and b/plugin.program.AML/media/shot_03_SL_pclone_list.jpg differ
diff --git a/plugin.program.AML/media/shot_04_MAME_fanart.jpg b/plugin.program.AML/media/shot_04_MAME_fanart.jpg
new file mode 100644
index 0000000000..f420780a94
Binary files /dev/null and b/plugin.program.AML/media/shot_04_MAME_fanart.jpg differ
diff --git a/plugin.program.AML/media/shot_05_SL_fanart.jpg b/plugin.program.AML/media/shot_05_SL_fanart.jpg
new file mode 100644
index 0000000000..4900e557f6
Binary files /dev/null and b/plugin.program.AML/media/shot_05_SL_fanart.jpg differ
diff --git a/plugin.program.AML/media/shot_06_MAME_3dbox.jpg b/plugin.program.AML/media/shot_06_MAME_3dbox.jpg
new file mode 100644
index 0000000000..da9ea9ed2e
Binary files /dev/null and b/plugin.program.AML/media/shot_06_MAME_3dbox.jpg differ
diff --git a/plugin.program.AML/media/shot_07_SL_3dbox.jpg b/plugin.program.AML/media/shot_07_SL_3dbox.jpg
new file mode 100644
index 0000000000..5126a0343d
Binary files /dev/null and b/plugin.program.AML/media/shot_07_SL_3dbox.jpg differ
diff --git a/plugin.program.AML/media/shot_08_MAME_History_viewer.jpg b/plugin.program.AML/media/shot_08_MAME_History_viewer.jpg
new file mode 100644
index 0000000000..c469d9a041
Binary files /dev/null and b/plugin.program.AML/media/shot_08_MAME_History_viewer.jpg differ
diff --git a/plugin.program.AML/media/shot_09_MAME_ROMs_db.jpg b/plugin.program.AML/media/shot_09_MAME_ROMs_db.jpg
new file mode 100644
index 0000000000..f69e25905d
Binary files /dev/null and b/plugin.program.AML/media/shot_09_MAME_ROMs_db.jpg differ
diff --git a/plugin.program.AML/media/shot_10_MAME_Audit_db.jpg b/plugin.program.AML/media/shot_10_MAME_Audit_db.jpg
new file mode 100644
index 0000000000..01831f9706
Binary files /dev/null and b/plugin.program.AML/media/shot_10_MAME_Audit_db.jpg differ
diff --git a/plugin.program.AML/media/shot_11_MAME_Audit_machine.jpg b/plugin.program.AML/media/shot_11_MAME_Audit_machine.jpg
new file mode 100644
index 0000000000..935f3d9dae
Binary files /dev/null and b/plugin.program.AML/media/shot_11_MAME_Audit_machine.jpg differ
diff --git a/plugin.program.AML/pdfrw/LICENSE.txt b/plugin.program.AML/pdfrw/LICENSE.txt
new file mode 100644
index 0000000000..8250f252bc
--- /dev/null
+++ b/plugin.program.AML/pdfrw/LICENSE.txt
@@ -0,0 +1,75 @@
+pdfrw (github.com/pmaupin/pdfrw)
+
+The majority of pdfrw was written by Patrick Maupin and is licensed
+under the MIT license (reproduced below). Other contributors include
+Attila Tajti and Nerijus Mika. It appears that some of the decompression
+code was based on the decompressor from PyPDF2, which was written by
+Mathieu Fenniak and licensed under the BSD license (also reproduced below).
+
+Please add any missing authors here:
+
+Copyright (c) 2006-2017 Patrick Maupin. All rights reserved.
+Copyright (c) 2006 Mathieu Fenniak. All rights reserved.
+Copyright (c) 2010 Attila Tajti. All rights reserved.
+Copyright (c) 2012 Nerijus Mika. All rights reserved.
+Copyright (c) 2015 Bastien Gandouet. All rights reserved.
+Copyright (c) 2015 Tzerjen Wei. All rights reserved.
+Copyright (c) 2015 Jorj X. McKie. All rights reserved.
+Copyright (c) 2015 Nicholas Devenish. All rights reserved.
+Copyright (c) 2015-2016 Jonatan Dellagostin. All rights reserved.
+Copyright (c) 2016-2017 Thomas Kluyver. All rights reserved.
+Copyright (c) 2016 James Laird-Wah. All rights reserved.
+Copyright (c) 2016 Marcus Brinkmann. All rights reserved.
+Copyright (c) 2016 Edward Betts. All rights reserved.
+Copyright (c) 2016 Patrick Mazulo. All rights reserved.
+Copyright (c) 2017 Haochen Wu. All rights reserved.
+Copyright (c) 2017 Jon Lund Steffensen. All rights reserved.
+Copyright (c) 2017 Henddher Pedroza. All rights reserved.
+
+
+MIT License:
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+
+BSD License:
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+* The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/plugin.program.AML/pdfrw/README.rst b/plugin.program.AML/pdfrw/README.rst
new file mode 100644
index 0000000000..f41fb76128
--- /dev/null
+++ b/plugin.program.AML/pdfrw/README.rst
@@ -0,0 +1,803 @@
+==================
+pdfrw 0.4
+==================
+
+:Author: Patrick Maupin
+
+.. contents::
+ :backlinks: none
+
+.. sectnum::
+
+Introduction
+============
+
+**pdfrw** is a Python library and utility that reads and writes PDF files:
+
+* Version 0.4 is tested and works on Python 2.6, 2.7, 3.3, 3.4, 3.5, and 3.6
+* Operations include subsetting, merging, rotating, modifying metadata, etc.
+* The fastest pure Python PDF parser available
+* Has been used for years by a printer in pre-press production
+* Can be used with rst2pdf to faithfully reproduce vector images
+* Can be used either standalone, or in conjunction with `reportlab`__
+ to reuse existing PDFs in new ones
+* Permissively licensed
+
+__ http://www.reportlab.org/
+
+
+pdfrw will faithfully reproduce vector formats without
+rasterization, so the rst2pdf package has used pdfrw
+for PDF and SVG images by default since March 2010.
+
+pdfrw can also be used in conjunction with reportlab, in order
+to re-use portions of existing PDFs in new PDFs created with
+reportlab.
+
+
+Examples
+=========
+
+The library comes with several examples that show operation both with
+and without reportlab.
+
+
+All examples
+------------------
+
+The examples directory has a few scripts which use the library.
+Note that if these examples do not work with your PDF, you should
+try to use pdftk to uncompress and/or unencrypt them first.
+
+* `4up.py`__ will shrink pages down and place 4 of them on
+ each output page.
+* `alter.py`__ shows an example of modifying metadata, without
+ altering the structure of the PDF.
+* `booklet.py`__ shows an example of creating a 2-up output
+ suitable for printing and folding (e.g on tabloid size paper).
+* `cat.py`__ shows an example of concatenating multiple PDFs together.
+* `extract.py`__ will extract images and Form XObjects (embedded pages)
+ from existing PDFs to make them easier to use and refer to from
+ new PDFs (e.g. with reportlab or rst2pdf).
+* `poster.py`__ increases the size of a PDF so it can be printed
+ as a poster.
+* `print_two.py`__ Allows creation of 8.5 X 5.5" booklets by slicing
+ 8.5 X 11" paper apart after printing.
+* `rotate.py`__ Rotates all or selected pages in a PDF.
+* `subset.py`__ Creates a new PDF with only a subset of pages from the
+ original.
+* `unspread.py`__ Takes a 2-up PDF, and splits out pages.
+* `watermark.py`__ Adds a watermark PDF image over or under all the pages
+ of a PDF.
+* `rl1/4up.py`__ Another 4up example, using reportlab canvas for output.
+* `rl1/booklet.py`__ Another booklet example, using reportlab canvas for
+ output.
+* `rl1/subset.py`__ Another subsetting example, using reportlab canvas for
+ output.
+* `rl1/platypus_pdf_template.py`__ Another watermarking example, using
+ reportlab canvas and generated output for the document. Contributed
+ by user asannes.
+* `rl2`__ Experimental code for parsing graphics. Needs work.
+* `subset_booklets.py`__ shows an example of creating a full printable pdf
+ version in a more professional and pratical way ( take a look at
+ http://www.wikihow.com/Bind-a-Book )
+
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/4up.py
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/alter.py
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/booklet.py
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/cat.py
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/extract.py
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/poster.py
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/print_two.py
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/rotate.py
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/subset.py
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/unspread.py
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/watermark.py
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/rl1/4up.py
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/rl1/booklet.py
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/rl1/subset.py
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/rl1/platypus_pdf_template.py
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/rl2/
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/subset_booklets.py
+
+Notes on selected examples
+------------------------------------
+
+Reorganizing pages and placing them two-up
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A printer with a fancy printer and/or a full-up copy of Acrobat can
+easily turn your small PDF into a little booklet (for example, print 4
+letter-sized pages on a single 11" x 17").
+
+But that assumes several things, including that the personnel know how
+to operate the hardware and software. `booklet.py`__ lets you turn your PDF
+into a preformatted booklet, to give them fewer chances to mess it up.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/booklet.py
+
+Adding or modifying metadata
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The `cat.py`__ example will accept multiple input files on the command
+line, concatenate them and output them to output.pdf, after adding some
+nonsensical metadata to the output PDF file.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/cat.py
+
+The `alter.py`__ example alters a single metadata item in a PDF,
+and writes the result to a new PDF.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/alter.py
+
+
+One difference is that, since **cat** is creating a new PDF structure,
+and **alter** is attempting to modify an existing PDF structure, the
+PDF produced by alter (and also by watermark.py) *should* be
+more faithful to the original (except for the desired changes).
+
+For example, the alter.py navigation should be left intact, whereas with
+cat.py it will be stripped.
+
+
+Rotating and doubling
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you ever want to print something that is like a small booklet, but
+needs to be spiral bound, you either have to do some fancy rearranging,
+or just waste half your paper.
+
+The `print_two.py`__ example program will, for example, make two side-by-side
+copies each page of of your PDF on a each output sheet.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/print_two.py
+
+But, every other page is flipped, so that you can print double-sided and
+the pages will line up properly and be pre-collated.
+
+Graphics stream parsing proof of concept
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The `copy.py`__ script shows a simple example of reading in a PDF, and
+using the decodegraphics.py module to try to write the same information
+out to a new PDF through a reportlab canvas. (If you know about reportlab,
+you know that if you can faithfully render a PDF to a reportlab canvas, you
+can do pretty much anything else with that PDF you want.) This kind of
+low level manipulation should be done only if you really need to.
+decodegraphics is really more than a proof of concept than anything
+else. For most cases, just use the Form XObject capability, as shown in
+the examples/rl1/booklet.py demo.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/examples/rl2/copy.py
+
+pdfrw philosophy
+==================
+
+Core library
+-------------
+
+The philosophy of the library portion of pdfrw is to provide intuitive
+functions to read, manipulate, and write PDF files. There should be
+minimal leakage between abstraction layers, although getting useful
+work done makes "pure" functionality separation difficult.
+
+A key concept supported by the library is the use of Form XObjects,
+which allow easy embedding of pieces of one PDF into another.
+
+Addition of core support to the library is typically done carefully
+and thoughtfully, so as not to clutter it up with too many special
+cases.
+
+There are a lot of incorrectly formatted PDFs floating around; support
+for these is added in some cases. The decision is often based on what
+acroread and okular do with the PDFs; if they can display them properly,
+then eventually pdfrw should, too, if it is not too difficult or costly.
+
+Contributions are welcome; one user has contributed some decompression
+filters and the ability to process PDF 1.5 stream objects. Additional
+functionality that would obviously be useful includes additional
+decompression filters, the ability to process password-protected PDFs,
+and the ability to output linearized PDFs.
+
+Examples
+--------
+
+The philosophy of the examples is to provide small, easily-understood
+examples that showcase pdfrw functionality.
+
+
+PDF files and Python
+======================
+
+Introduction
+------------
+
+In general, PDF files conceptually map quite well to Python. The major
+objects to think about are:
+
+- **strings**. Most things are strings. These also often decompose
+ naturally into
+- **lists of tokens**. Tokens can be combined to create higher-level
+ objects like
+- **arrays** and
+- **dictionaries** and
+- **Contents streams** (which can be more streams of tokens)
+
+Difficulties
+------------
+
+The apparent primary difficulty in mapping PDF files to Python is the
+PDF file concept of "indirect objects." Indirect objects provide
+the efficiency of allowing a single piece of data to be referred to
+from more than one containing object, but probably more importantly,
+indirect objects provide a way to get around the chicken and egg
+problem of circular object references when mapping arbitrary data
+structures to files. To flatten out a circular reference, an indirect
+object is *referred to* instead of being *directly included* in another
+object. PDF files have a global mechanism for locating indirect objects,
+and they all have two reference numbers (a reference number and a
+"generation" number, in case you wanted to append to the PDF file
+rather than just rewriting the whole thing).
+
+pdfrw automatically handles indirect references on reading in a PDF
+file. When pdfrw encounters an indirect PDF file object, the
+corresponding Python object it creates will have an 'indirect' attribute
+with a value of True. When writing a PDF file, if you have created
+arbitrary data, you just need to make sure that circular references are
+broken up by putting an attribute named 'indirect' which evaluates to
+True on at least one object in every cycle.
+
+Another PDF file concept that doesn't quite map to regular Python is a
+"stream". Streams are dictionaries which each have an associated
+unformatted data block. pdfrw handles streams by placing a special
+attribute on a subclassed dictionary.
+
+Usage Model
+-----------
+
+The usage model for pdfrw treats most objects as strings (it takes their
+string representation when writing them to a file). The two main
+exceptions are the PdfArray object and the PdfDict object.
+
+PdfArray is a subclass of list with two special features. First,
+an 'indirect' attribute allows a PdfArray to be written out as
+an indirect PDF object. Second, pdfrw reads files lazily, so
+PdfArray knows about, and resolves references to other indirect
+objects on an as-needed basis.
+
+PdfDict is a subclass of dict that also has an indirect attribute
+and lazy reference resolution as well. (And the subclassed
+IndirectPdfDict has indirect automatically set True).
+
+But PdfDict also has an optional associated stream. The stream object
+defaults to None, but if you assign a stream to the dict, it will
+automatically set the PDF /Length attribute for the dictionary.
+
+Finally, since PdfDict instances are indexed by PdfName objects (which
+always start with a /) and since most (all?) standard Adobe PdfName
+objects use names formatted like "/CamelCase", it makes sense to allow
+access to dictionary elements via object attribute accesses as well as
+object index accesses. So usage of PdfDict objects is normally via
+attribute access, although non-standard names (though still with a
+leading slash) can be accessed via dictionary index lookup.
+
+Reading PDFs
+~~~~~~~~~~~~~~~
+
+The PdfReader object is a subclass of PdfDict, which allows easy access
+to an entire document::
+
+ >>> from pdfrw import PdfReader
+ >>> x = PdfReader('source.pdf')
+ >>> x.keys()
+ ['/Info', '/Size', '/Root']
+ >>> x.Info
+ {'/Producer': '(cairo 1.8.6 (http://cairographics.org))',
+ '/Creator': '(cairo 1.8.6 (http://cairographics.org))'}
+ >>> x.Root.keys()
+ ['/Type', '/Pages']
+
+Info, Size, and Root are retrieved from the trailer of the PDF file.
+
+In addition to the tree structure, pdfrw creates a special attribute
+named *pages*, that is a list of all the pages in the document. pdfrw
+creates the *pages* attribute as a simplification for the user, because
+the PDF format allows arbitrarily complicated nested dictionaries to
+describe the page order. Each entry in the *pages* list is the PdfDict
+object for one of the pages in the file, in order.
+
+::
+
+ >>> len(x.pages)
+ 1
+ >>> x.pages[0]
+ {'/Parent': {'/Kids': [{...}], '/Type': '/Pages', '/Count': '1'},
+ '/Contents': {'/Length': '11260', '/Filter': None},
+ '/Resources': ... (Lots more stuff snipped)
+ >>> x.pages[0].Contents
+ {'/Length': '11260', '/Filter': None}
+ >>> x.pages[0].Contents.stream
+ 'q\n1 1 1 rg /a0 gs\n0 0 0 RG 0.657436
+ w\n0 J\n0 j\n[] 0.0 d\n4 M q' ... (Lots more stuff snipped)
+
+Writing PDFs
+~~~~~~~~~~~~~~~
+
+As you can see, it is quite easy to dig down into a PDF document. But
+what about when it's time to write it out?
+
+::
+
+ >>> from pdfrw import PdfWriter
+ >>> y = PdfWriter()
+ >>> y.addpage(x.pages[0])
+ >>> y.write('result.pdf')
+
+That's all it takes to create a new PDF. You may still need to read the
+`Adobe PDF reference manual`__ to figure out what needs to go *into*
+the PDF, but at least you don't have to sweat actually building it
+and getting the file offsets right.
+
+__ http://www.adobe.com/devnet/acrobat/pdfs/pdf_reference_1-7.pdf
+
+Manipulating PDFs in memory
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For the most part, pdfrw tries to be agnostic about the contents of
+PDF files, and support them as containers, but to do useful work,
+something a little higher-level is required, so pdfrw works to
+understand a bit about the contents of the containers. For example:
+
+- PDF pages. pdfrw knows enough to find the pages in PDF files you read
+ in, and to write a set of pages back out to a new PDF file.
+- Form XObjects. pdfrw can take any page or rectangle on a page, and
+ convert it to a Form XObject, suitable for use inside another PDF
+ file. It knows enough about these to perform scaling, rotation,
+ and positioning.
+- reportlab objects. pdfrw can recursively create a set of reportlab
+ objects from its internal object format. This allows, for example,
+ Form XObjects to be used inside reportlab, so that you can reuse
+ content from an existing PDF file when building a new PDF with
+ reportlab.
+
+There are several examples that demonstrate these features in
+the example code directory.
+
+Missing features
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Even as a pure PDF container library, pdfrw comes up a bit short. It
+does not currently support:
+
+- Most compression/decompression filters
+- encryption
+
+`pdftk`__ is a wonderful command-line
+tool that can convert your PDFs to remove encryption and compression.
+However, in most cases, you can do a lot of useful work with PDFs
+without actually removing compression, because only certain elements
+inside PDFs are actually compressed.
+
+__ https://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/
+
+Library internals
+==================
+
+Introduction
+------------
+
+**pdfrw** currently consists of 19 modules organized into a main
+package and one sub-package.
+
+The `__init.py__`__ module does the usual thing of importing a few
+major attributes from some of the submodules, and the `errors.py`__
+module supports logging and exception generation.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/__init__.py
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/errors.py
+
+
+PDF object model support
+--------------------------
+
+The `objects`__ sub-package contains one module for each of the
+internal representations of the kinds of basic objects that exist
+in a PDF file, with the `objects/__init__.py`__ module in that
+package simply gathering them up and making them available to the
+main pdfrw package.
+
+One feature that all the PDF object classes have in common is the
+inclusion of an 'indirect' attribute. If 'indirect' exists and evaluates
+to True, then when the object is written out, it is written out as an
+indirect object. That is to say, it is addressable in the PDF file, and
+could be referenced by any number (including zero) of container objects.
+This indirect object capability saves space in PDF files by allowing
+objects such as fonts to be referenced from multiple pages, and also
+allows PDF files to contain internal circular references. This latter
+capability is used, for example, when each page object has a "parent"
+object in its dictionary.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/objects/
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/objects/__init__.py
+
+Ordinary objects
+~~~~~~~~~~~~~~~~
+
+The `objects/pdfobject.py`__ module contains the PdfObject class, which is
+a subclass of str, and is the catch-all object for any PDF file elements
+that are not explicitly represented by other objects, as described below.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/objects/pdfobject.py
+
+Name objects
+~~~~~~~~~~~~
+
+The `objects/pdfname.py`__ module contains the PdfName singleton object,
+which will convert a string into a PDF name by prepending a slash. It can
+be used either by calling it or getting an attribute, e.g.::
+
+ PdfName.Rotate == PdfName('Rotate') == PdfObject('/Rotate')
+
+In the example above, there is a slight difference between the objects
+returned from PdfName, and the object returned from PdfObject. The
+PdfName objects are actually objects of class "BasePdfName". This
+is important, because only these may be used as keys in PdfDict objects.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/objects/pdfname.py
+
+String objects
+~~~~~~~~~~~~~~
+
+The `objects/pdfstring.py`__
+module contains the PdfString class, which is a subclass of str that is
+used to represent encoded strings in a PDF file. The class has encode
+and decode methods for the strings.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/objects/pdfstring.py
+
+
+Array objects
+~~~~~~~~~~~~~
+
+The `objects/pdfarray.py`__
+module contains the PdfArray class, which is a subclass of list that is
+used to represent arrays in a PDF file. A regular list could be used
+instead, but use of the PdfArray class allows for an indirect attribute
+to be set, and also allows for proxying of unresolved indirect objects
+(that haven't been read in yet) in a manner that is transparent to pdfrw
+clients.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/objects/pdfarray.py
+
+Dict objects
+~~~~~~~~~~~~
+
+The `objects/pdfdict.py`__
+module contains the PdfDict class, which is a subclass of dict that is
+used to represent dictionaries in a PDF file. A regular dict could be
+used instead, but the PdfDict class matches the requirements of PDF
+files more closely:
+
+* Transparent (from the library client's viewpoint) proxying
+ of unresolved indirect objects
+* Return of None for non-existent keys (like dict.get)
+* Mapping of attribute accesses to the dict itself
+ (pdfdict.Foo == pdfdict[NameObject('Foo')])
+* Automatic management of following stream and /Length attributes
+ for content dictionaries
+* Indirect attribute
+* Other attributes may be set for private internal use of the
+ library and/or its clients.
+* Support for searching parent dictionaries for PDF "inheritable"
+ attributes.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/objects/pdfdict.py
+
+If a PdfDict has an associated data stream in the PDF file, the stream
+is accessed via the 'stream' (all lower-case) attribute. Setting the
+stream attribute on the PdfDict will automatically set the /Length attribute
+as well. If that is not what is desired (for example if the the stream
+is compressed), then _stream (same name with an underscore) may be used
+to associate the stream with the PdfDict without setting the length.
+
+To set private attributes (that will not be written out to a new PDF
+file) on a dictionary, use the 'private' attribute::
+
+ mydict.private.foo = 1
+
+Once the attribute is set, it may be accessed directly as an attribute
+of the dictionary::
+
+ foo = mydict.foo
+
+Some attributes of PDF pages are "inheritable." That is, they may
+belong to a parent dictionary (or a parent of a parent dictionary, etc.)
+The "inheritable" attribute allows for easy discovery of these::
+
+ mediabox = mypage.inheritable.MediaBox
+
+
+Proxy objects
+~~~~~~~~~~~~~
+
+The `objects/pdfindirect.py`__
+module contains the PdfIndirect class, which is a non-transparent proxy
+object for PDF objects that have not yet been read in and resolved from
+a file. Although these are non-transparent inside the library, client code
+should never see one of these -- they exist inside the PdfArray and PdfDict
+container types, but are resolved before being returned to a client of
+those types.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/objects/pdfindirect.py
+
+
+File reading, tokenization and parsing
+--------------------------------------
+
+`pdfreader.py`__
+contains the PdfReader class, which can read a PDF file (or be passed a
+file object or already read string) and parse it. It uses the PdfTokens
+class in `tokens.py`__ for low-level tokenization.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/pdfreader.py
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/tokens.py
+
+
+The PdfReader class does not, in general, parse into containers (e.g.
+inside the content streams). There is a proof of concept for doing that
+inside the examples/rl2 subdirectory, but that is slow and not well-developed,
+and not useful for most applications.
+
+An instance of the PdfReader class is an instance of a PdfDict -- the
+trailer dictionary of the PDF file, to be exact. It will have a private
+attribute set on it that is named 'pages' that is a list containing all
+the pages in the file.
+
+When instantiating a PdfReader object, there are options available
+for decompressing all the objects in the file. pdfrw does not currently
+have very many options for decompression, so this is not all that useful,
+except in the specific case of compressed object streams.
+
+Also, there are no options for decryption yet. If you have PDF files
+that are encrypted or heavily compressed, you may find that using another
+program like pdftk on them can make them readable by pdfrw.
+
+In general, the objects are read from the file lazily, but this is not
+currently true with compressed object streams -- all of these are decompressed
+and read in when the PdfReader is instantiated.
+
+
+File output
+-----------
+
+`pdfwriter.py`__
+contains the PdfWriter class, which can create and output a PDF file.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/pdfwriter.py
+
+There are a few options available when creating and using this class.
+
+In the simplest case, an instance of PdfWriter is instantiated, and
+then pages are added to it from one or more source files (or created
+programmatically), and then the write method is called to dump the
+results out to a file.
+
+If you have a source PDF and do not want to disturb the structure
+of it too badly, then you may pass its trailer directly to PdfWriter
+rather than letting PdfWriter construct one for you. There is an
+example of this (alter.py) in the examples directory.
+
+
+Advanced features
+-----------------
+
+`buildxobj.py`__
+contains functions to build Form XObjects out of pages or rectangles on
+pages. These may be reused in new PDFs essentially as if they were images.
+
+buildxobj is careful to cache any page used so that it only appears in
+the output once.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/buildxobj.py
+
+
+`toreportlab.py`__
+provides the makerl function, which will translate pdfrw objects into a
+format which can be used with `reportlab `__.
+It is normally used in conjunction with buildxobj, to be able to reuse
+parts of existing PDFs when using reportlab.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/toreportlab.py
+
+
+`pagemerge.py`__ builds on the foundation laid by buildxobj. It
+contains classes to create a new page (or overlay an existing page)
+using one or more rectangles from other pages. There are examples
+showing its use for watermarking, scaling, 4-up output, splitting
+each page in 2, etc.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/pagemerge.py
+
+`findobjs.py`__ contains code that can find specific kinds of objects
+inside a PDF file. The extract.py example uses this module to create
+a new PDF that places each image and Form XObject from a source PDF onto
+its own page, e.g. for easy reuse with some of the other examples or
+with reportlab.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/findobjs.py
+
+
+Miscellaneous
+----------------
+
+`compress.py`__ and `uncompress.py`__
+contains compression and decompression functions. Very few filters are
+currently supported, so an external tool like pdftk might be good if you
+require the ability to decompress (or, for that matter, decrypt) PDF
+files.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/compress.py
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/uncompress.py
+
+
+`py23_diffs.py`__ contains code to help manage the differences between
+Python 2 and Python 3.
+
+__ https://github.com/pmaupin/pdfrw/tree/master/pdfrw/py23_diffs.py
+
+Testing
+===============
+
+The tests associated with pdfrw require a large number of PDFs,
+which are not distributed with the library.
+
+To run the tests:
+
+* Download or clone the full package from github.com/pmaupin/pdfrw
+* cd into the tests directory, and then clone the package
+ github.com/pmaupin/static_pdfs into a subdirectory (also named
+ static_pdfs).
+* Now the tests may be run from tests directory using unittest, or
+ py.test, or nose.
+* travisci is used at github, and runs the tests with py.test
+
+.. code-block:: bash
+ $ pip install pytest
+ $ pip install reportlab
+ $ pwd
+ <...>/pdfrw/tests
+ $ git clone https://github.com/pmaupin/static_pdfs
+ $ ln -s ../pdfrw
+ $ pytest
+
+To run a single test-case:
+
+.. code-block:: bash
+ $ pytest test_roundtrip.py -k "test_compress_9f98322c243fe67726d56ccfa8e0885b.pdf"
+
+Other libraries
+=====================
+
+Pure Python
+-----------
+
+- `reportlab `__
+
+ reportlab is must-have software if you want to programmatically
+ generate arbitrary PDFs.
+
+- `pyPdf `__
+
+ pyPdf is, in some ways, very full-featured. It can do decompression
+ and decryption and seems to know a lot about items inside at least
+ some kinds of PDF files. In comparison, pdfrw knows less about
+ specific PDF file features (such as metadata), but focuses on trying
+ to have a more Pythonic API for mapping the PDF file container
+ syntax to Python, and (IMO) has a simpler and better PDF file
+ parser. The Form XObject capability of pdfrw means that, in many
+ cases, it does not actually need to decompress objects -- they
+ can be left compressed.
+
+- `pdftools `__
+
+ pdftools feels large and I fell asleep trying to figure out how it
+ all fit together, but many others have done useful things with it.
+
+- `pagecatcher `__
+
+ My understanding is that pagecatcher would have done exactly what I
+ wanted when I built pdfrw. But I was on a zero budget, so I've never
+ had the pleasure of experiencing pagecatcher. I do, however, use and
+ like `reportlab `__ (open source, from
+ the people who make pagecatcher) so I'm sure pagecatcher is great,
+ better documented and much more full-featured than pdfrw.
+
+- `pdfminer `__
+
+ This looks like a useful, actively-developed program. It is quite
+ large, but then, it is trying to actively comprehend a full PDF
+ document. From the website:
+
+ "PDFMiner is a suite of programs that help extracting and analyzing
+ text data of PDF documents. Unlike other PDF-related tools, it
+ allows to obtain the exact location of texts in a page, as well as
+ other extra information such as font information or ruled lines. It
+ includes a PDF converter that can transform PDF files into other
+ text formats (such as HTML). It has an extensible PDF parser that
+ can be used for other purposes instead of text analysis."
+
+non-pure-Python libraries
+-------------------------
+
+- `pyPoppler `__ can read PDF
+ files.
+- `pycairo `__ can write PDF
+ files.
+- `PyMuPDF `_ high performance rendering
+ of PDF, (Open)XPS, CBZ and EPUB
+
+Other tools
+-----------
+
+- `pdftk `__ is a wonderful command
+ line tool for basic PDF manipulation. It complements pdfrw extremely
+ well, supporting many operations such as decryption and decompression
+ that pdfrw cannot do.
+- `MuPDF `_ is a free top performance PDF, (Open)XPS, CBZ and EPUB rendering library
+ that also comes with some command line tools. One of those, ``mutool``, has big overlaps with pdftk's -
+ except it is up to 10 times faster.
+
+Release information
+=======================
+
+Revisions:
+
+0.4 -- Released 18 September, 2017
+
+ - Python 3.6 added to test matrix
+ - Proper unicode support for text strings in PDFs added
+ - buildxobj fixes allow better support creating form XObjects
+ out of compressed pages in some cases
+ - Compression fixes for Python 3+
+ - New subset_booklets.py example
+ - Bug with non-compressed indices into compressed object streams fixed
+ - Bug with distinguishing compressed object stream first objects fixed
+ - Better error reporting added for some invalid PDFs (e.g. when reading
+ past the end of file)
+ - Better scrubbing of old bookmark information when writing PDFs, to
+ remove dangling references
+ - Refactoring of pdfwriter, including updating API, to allow future
+ enhancements for things like incremental writing
+ - Minor tokenizer speedup
+ - Some flate decompressor bugs fixed
+ - Compression and decompression tests added
+ - Tests for new unicode handling added
+ - PdfReader.readpages() recursion error (issue #92) fixed.
+ - Initial crypt filter support added
+
+
+0.3 -- Released 19 October, 2016.
+
+ - Python 3.5 added to test matrix
+ - Better support under Python 3.x for in-memory PDF file-like objects
+ - Some pagemerge and Unicode patches added
+ - Changes to logging allow better coexistence with other packages
+ - Fix for "from pdfrw import \*"
+ - New fancy_watermark.py example shows off capabilities of pagemerge.py
+ - metadata.py example renamed to cat.py
+
+
+0.2 -- Released 21 June, 2015. Supports Python 2.6, 2.7, 3.3, and 3.4.
+
+ - Several bugs have been fixed
+ - New regression test functionally tests core with dozens of
+ PDFs, and also tests examples.
+ - Core has been ported and tested on Python3 by round-tripping
+ several difficult files and observing binary matching results
+ across the different Python versions.
+ - Still only minimal support for compression and no support
+ for encryption or newer PDF features. (pdftk is useful
+ to put PDFs in a form that pdfrw can use.)
+
+0.1 -- Released to PyPI in 2012. Supports Python 2.5 - 2.7
+
diff --git a/plugin.program.AML/pdfrw/pdfrw/__init__.py b/plugin.program.AML/pdfrw/pdfrw/__init__.py
new file mode 100644
index 0000000000..cf7644a286
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/__init__.py
@@ -0,0 +1,23 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2006-2015 Patrick Maupin, Austin, Texas
+# MIT license -- See LICENSE.txt for details
+
+from .pdfwriter import PdfWriter
+from .pdfreader import PdfReader
+from .objects import (PdfObject, PdfName, PdfArray,
+ PdfDict, IndirectPdfDict, PdfString)
+from .tokens import PdfTokens
+from .errors import PdfParseError
+from .pagemerge import PageMerge
+
+__version__ = '0.4'
+
+# Add a tiny bit of compatibility to pyPdf
+
+PdfFileReader = PdfReader
+PdfFileWriter = PdfWriter
+
+__all__ = """PdfWriter PdfReader PdfObject PdfName PdfArray
+ PdfTokens PdfParseError PdfDict IndirectPdfDict
+ PdfString PageMerge""".split()
+
diff --git a/plugin.program.AML/pdfrw/pdfrw/buildxobj.py b/plugin.program.AML/pdfrw/pdfrw/buildxobj.py
new file mode 100644
index 0000000000..f132795323
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/buildxobj.py
@@ -0,0 +1,363 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2006-2015 Patrick Maupin, Austin, Texas
+# MIT license -- See LICENSE.txt for details
+
+'''
+
+This module contains code to build PDF "Form XObjects".
+
+A Form XObject allows a fragment from one PDF file to be cleanly
+included in another PDF file.
+
+Reference for syntax: "Parameters for opening PDF files" from SDK 8.1
+
+ http://www.adobe.com/devnet/acrobat/pdfs/pdf_open_parameters.pdf
+
+ supported 'page=xxx', 'viewrect=,,,'
+
+ Also supported by this, but not by Adobe:
+ 'rotate=xxx' where xxx in [0, 90, 180, 270]
+
+ Units are in points
+
+
+Reference for content: Adobe PDF reference, sixth edition, version 1.7
+
+ http://www.adobe.com/devnet/acrobat/pdfs/pdf_reference_1-7.pdf
+
+ Form xobjects discussed chapter 4.9, page 355
+'''
+
+from .objects import PdfDict, PdfArray, PdfName
+from .pdfreader import PdfReader
+from .errors import log, PdfNotImplementedError
+from .py23_diffs import iteritems
+from .uncompress import uncompress
+from .compress import compress
+
+
+class ViewInfo(object):
+ ''' Instantiate ViewInfo with a uri, and it will parse out
+ the filename, page, and viewrect into object attributes.
+
+ Note 1:
+ Viewrects follow the adobe definition. (See reference
+ above). They are arrays of 4 numbers:
+
+ - Distance from left of document in points
+ - Distance from top (NOT bottom) of document in points
+ - Width of rectangle in points
+ - Height of rectangle in points
+
+ Note 2:
+ For simplicity, Viewrects can also be specified
+ in fractions of the document. If every number in
+ the viewrect is between 0 and 1 inclusive, then
+ viewrect elements 0 and 2 are multiplied by the
+ mediabox width before use, and viewrect elements
+ 1 and 3 are multiplied by the mediabox height before
+ use.
+
+ Note 3:
+ By default, an XObject based on the view will be
+ cacheable. It should not be cacheable if the XObject
+ will be subsequently modified.
+ '''
+ doc = None
+ docname = None
+ page = None
+ viewrect = None
+ rotate = None
+ cacheable = True
+
+ def __init__(self, pageinfo='', **kw):
+ pageinfo = pageinfo.split('#', 1)
+ if len(pageinfo) == 2:
+ pageinfo[1:] = pageinfo[1].replace('&', '#').split('#')
+ for key in 'page viewrect'.split():
+ if pageinfo[0].startswith(key + '='):
+ break
+ else:
+ self.docname = pageinfo.pop(0)
+ for item in pageinfo:
+ key, value = item.split('=')
+ key = key.strip()
+ value = value.replace(',', ' ').split()
+ if key in ('page', 'rotate'):
+ assert len(value) == 1
+ setattr(self, key, int(value[0]))
+ elif key == 'viewrect':
+ assert len(value) == 4
+ setattr(self, key, [float(x) for x in value])
+ else:
+ log.error('Unknown option: %s', key)
+ for key, value in iteritems(kw):
+ assert hasattr(self, key), key
+ setattr(self, key, value)
+
+
+def get_rotation(rotate):
+ ''' Return clockwise rotation code:
+ 0 = unrotated
+ 1 = 90 degrees
+ 2 = 180 degrees
+ 3 = 270 degrees
+ '''
+ try:
+ rotate = int(rotate)
+ except (ValueError, TypeError):
+ return 0
+ if rotate % 90 != 0:
+ return 0
+ return rotate // 90
+
+
+def rotate_point(point, rotation):
+ ''' Rotate an (x,y) coordinate clockwise by a
+ rotation code specifying a multiple of 90 degrees.
+ '''
+ if rotation & 1:
+ point = point[1], -point[0]
+ if rotation & 2:
+ point = -point[0], -point[1]
+ return point
+
+
+def rotate_rect(rect, rotation):
+ ''' Rotate both points within the rectangle, then normalize
+ the rectangle by returning the new lower left, then new
+ upper right.
+ '''
+ rect = rotate_point(rect[:2], rotation) + rotate_point(rect[2:], rotation)
+ return (min(rect[0], rect[2]), min(rect[1], rect[3]),
+ max(rect[0], rect[2]), max(rect[1], rect[3]))
+
+
+def getrects(inheritable, pageinfo, rotation):
+ ''' Given the inheritable attributes of a page and
+ the desired pageinfo rectangle, return the page's
+ media box and the calculated boundary (clip) box.
+ '''
+ mbox = tuple([float(x) for x in inheritable.MediaBox])
+ cbox = tuple([float(x) for x in (inheritable.CropBox or mbox)])
+ vrect = pageinfo.viewrect
+ if vrect is not None:
+ # Rotate the media box to match what the user sees,
+ # figure out the clipping box, then rotate back
+ mleft, mbot, mright, mtop = rotate_rect(cbox, rotation)
+ x, y, w, h = vrect
+
+ # Support operations in fractions of a page
+ if 0 <= min(vrect) < max(vrect) <= 1:
+ mw = mright - mleft
+ mh = mtop - mbot
+ x *= mw
+ w *= mw
+ y *= mh
+ h *= mh
+
+ cleft = mleft + x
+ ctop = mtop - y
+ cright = cleft + w
+ cbot = ctop - h
+ cbox = (max(mleft, cleft), max(mbot, cbot),
+ min(mright, cright), min(mtop, ctop))
+ cbox = rotate_rect(cbox, -rotation)
+ return mbox, cbox
+
+
+def _build_cache(contents, allow_compressed):
+ ''' Build a new dictionary holding the stream,
+ and save it along with private cache info.
+ Assumes validity has been pre-checked if
+ we have a non-None xobj_copy.
+
+ Also, the spec says nothing about nested arrays,
+ so we assume those don't exist until we see one
+ in the wild.
+ '''
+ try:
+ xobj_copy = contents.xobj_copy
+ except AttributeError:
+ # Should have a PdfArray here...
+ array = contents
+ private = contents
+ else:
+ # Should have a PdfDict here -- might or might not have cache copy
+ if xobj_copy is not None:
+ return xobj_copy
+ array = [contents]
+ private = contents.private
+
+ # If we don't allow compressed objects, OR if we have multiple compressed
+ # objects, we try to decompress them, and fail if we cannot do that.
+
+ if not allow_compressed or len(array) > 1:
+ keys = set(x[0] for cdict in array for x in iteritems(cdict))
+ was_compressed = len(keys) > 1
+ if was_compressed:
+ # Make copies of the objects before we uncompress them.
+ array = [PdfDict(x) for x in array]
+ if not uncompress(array):
+ raise PdfNotImplementedError(
+ 'Xobjects with these compression parameters not supported: %s' %
+ keys)
+
+ xobj_copy = PdfDict(array[0])
+ xobj_copy.private.xobj_cachedict = {}
+ private.xobj_copy = xobj_copy
+
+ if len(array) > 1:
+ newstream = '\n'.join(x.stream for x in array)
+ newlength = sum(int(x.Length) for x in array) + len(array) - 1
+ assert newlength == len(newstream)
+ xobj_copy.stream = newstream
+ if was_compressed and allow_compressed:
+ compress(xobj_copy)
+
+ return xobj_copy
+
+
+def _cache_xobj(contents, resources, mbox, bbox, rotation, cacheable=True):
+ ''' Return a cached Form XObject, or create a new one and cache it.
+ Adds private members x, y, w, h
+ '''
+ cachedict = contents.xobj_cachedict
+ cachekey = mbox, bbox, rotation
+ result = cachedict.get(cachekey) if cacheable else None
+ if result is None:
+ # If we are not getting a full page, or if we are going to
+ # modify the results, first retrieve an underlying Form XObject
+ # that represents the entire page, so that we are not copying
+ # the full page data into the new file multiple times
+ func = (_get_fullpage, _get_subpage)[mbox != bbox or not cacheable]
+ result = PdfDict(
+ func(contents, resources, mbox),
+ Type=PdfName.XObject,
+ Subtype=PdfName.Form,
+ FormType=1,
+ BBox=PdfArray(bbox),
+ )
+ rect = bbox
+ if rotation:
+ matrix = (rotate_point((1, 0), rotation) +
+ rotate_point((0, 1), rotation))
+ result.Matrix = PdfArray(matrix + (0, 0))
+ rect = rotate_rect(rect, rotation)
+
+ private = result.private
+ private.x = rect[0]
+ private.y = rect[1]
+ private.w = rect[2] - rect[0]
+ private.h = rect[3] - rect[1]
+ if cacheable:
+ cachedict[cachekey] = result
+ return result
+
+
+def _get_fullpage(contents, resources, mbox):
+ ''' fullpage is easy. Just copy the contents,
+ set up the resources, and let _cache_xobj handle the
+ rest.
+ '''
+ return PdfDict(contents, Resources=resources)
+
+
+def _get_subpage(contents, resources, mbox):
+ ''' subpages *could* be as easy as full pages, but we
+ choose to complicate life by creating a Form XObject
+ for the page, and then one that references it for
+ the subpage, on the off-chance that we want multiple
+ items from the page.
+ '''
+ return PdfDict(
+ stream='/FullPage Do\n',
+ Resources=PdfDict(
+ XObject=PdfDict(
+ FullPage=_cache_xobj(contents, resources, mbox, mbox, 0)
+ )
+ )
+ )
+
+
+def pagexobj(page, viewinfo=ViewInfo(), allow_compressed=True):
+ ''' pagexobj creates and returns a Form XObject for
+ a given view within a page (Defaults to entire page.)
+
+ pagexobj is passed a page and a viewrect.
+ '''
+ inheritable = page.inheritable
+ resources = inheritable.Resources
+ rotation = get_rotation(inheritable.Rotate)
+ mbox, bbox = getrects(inheritable, viewinfo, rotation)
+ rotation += get_rotation(viewinfo.rotate)
+ contents = _build_cache(page.Contents, allow_compressed)
+ return _cache_xobj(contents, resources, mbox, bbox, rotation,
+ viewinfo.cacheable)
+
+
+def docxobj(pageinfo, doc=None, allow_compressed=True):
+ ''' docinfo reads a page out of a document and uses
+ pagexobj to create the Form XObject based on
+ the page.
+
+ This is a convenience function for things like
+ rst2pdf that want to be able to pass in textual
+ filename/location descriptors and don't want to
+ know about using PdfReader.
+
+ Can work standalone, or in conjunction with
+ the CacheXObj class (below).
+
+ '''
+ if not isinstance(pageinfo, ViewInfo):
+ pageinfo = ViewInfo(pageinfo)
+
+ # If we're explicitly passed a document,
+ # make sure we don't have one implicitly as well.
+ # If no implicit or explicit doc, then read one in
+ # from the filename.
+ if doc is not None:
+ assert pageinfo.doc is None
+ pageinfo.doc = doc
+ elif pageinfo.doc is not None:
+ doc = pageinfo.doc
+ else:
+ doc = pageinfo.doc = PdfReader(pageinfo.docname,
+ decompress=not allow_compressed)
+ assert isinstance(doc, PdfReader)
+
+ sourcepage = doc.pages[(pageinfo.page or 1) - 1]
+ return pagexobj(sourcepage, pageinfo, allow_compressed)
+
+
+class CacheXObj(object):
+ ''' Use to keep from reparsing files over and over,
+ and to keep from making the output too much
+ bigger than it ought to be by replicating
+ unnecessary object copies.
+
+ This is a convenience function for things like
+ rst2pdf that want to be able to pass in textual
+ filename/location descriptors and don't want to
+ know about using PdfReader.
+ '''
+ def __init__(self, decompress=False):
+ ''' Set decompress true if you need
+ the Form XObjects to be decompressed.
+ Will decompress what it can and scream
+ about the rest.
+ '''
+ self.cached_pdfs = {}
+ self.decompress = decompress
+
+ def load(self, sourcename):
+ ''' Load a Form XObject from a uri
+ '''
+ info = ViewInfo(sourcename)
+ fname = info.docname
+ pcache = self.cached_pdfs
+ doc = pcache.get(fname)
+ if doc is None:
+ doc = pcache[fname] = PdfReader(fname, decompress=self.decompress)
+ return docxobj(info, doc, allow_compressed=not self.decompress)
diff --git a/plugin.program.AML/pdfrw/pdfrw/compress.py b/plugin.program.AML/pdfrw/pdfrw/compress.py
new file mode 100644
index 0000000000..b7b4e756be
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/compress.py
@@ -0,0 +1,27 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2006-2015 Patrick Maupin, Austin, Texas
+# MIT license -- See LICENSE.txt for details
+
+'''
+Currently, this sad little file only knows how to compress
+using the flate (zlib) algorithm. Maybe more later, but it's
+not a priority for me...
+'''
+
+from .objects import PdfName
+from .uncompress import streamobjects
+from .py23_diffs import zlib, convert_load, convert_store
+
+
+def compress(mylist):
+ flate = PdfName.FlateDecode
+ for obj in streamobjects(mylist):
+ ftype = obj.Filter
+ if ftype is not None:
+ continue
+ oldstr = obj.stream
+ newstr = convert_load(zlib.compress(convert_store(oldstr)))
+ if len(newstr) < len(oldstr) + 30:
+ obj.stream = newstr
+ obj.Filter = flate
+ obj.DecodeParms = None
diff --git a/plugin.program.AML/pdfrw/pdfrw/crypt.py b/plugin.program.AML/pdfrw/pdfrw/crypt.py
new file mode 100644
index 0000000000..dc00676cbf
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/crypt.py
@@ -0,0 +1,150 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2017 Jon Lund Steffensen
+# MIT license -- See LICENSE.txt for details
+
+from __future__ import division
+
+import hashlib
+import struct
+
+try:
+ from Crypto.Cipher import ARC4, AES
+ HAS_CRYPTO = True
+except ImportError:
+ HAS_CRYPTO = False
+
+from .objects import PdfDict, PdfName
+
+_PASSWORD_PAD = (
+ '(\xbfN^Nu\x8aAd\x00NV\xff\xfa\x01\x08'
+ '..\x00\xb6\xd0h>\x80/\x0c\xa9\xfedSiz')
+
+
+def streamobjects(mylist, isinstance=isinstance, PdfDict=PdfDict):
+ for obj in mylist:
+ if isinstance(obj, PdfDict) and obj.stream is not None:
+ yield obj
+
+
+def create_key(password, doc):
+ """Create an encryption key (Algorithm 2 in PDF spec)."""
+ key_size = int(doc.Encrypt.Length or 40) // 8
+ padded_pass = (password + _PASSWORD_PAD)[:32]
+ hasher = hashlib.md5()
+ hasher.update(padded_pass)
+ hasher.update(doc.Encrypt.O.to_bytes())
+ hasher.update(struct.pack('= 3:
+ for _ in range(50):
+ temp_hash = hashlib.md5(temp_hash[:key_size]).digest()
+
+ return temp_hash[:key_size]
+
+
+def create_user_hash(key, doc):
+ """Create the user password hash (Algorithm 4/5)."""
+ revision = int(doc.Encrypt.R or 0)
+ if revision < 3:
+ cipher = ARC4.new(key)
+ return cipher.encrypt(_PASSWORD_PAD)
+ else:
+ hasher = hashlib.md5()
+ hasher.update(_PASSWORD_PAD)
+ hasher.update(doc.ID[0].to_bytes())
+ temp_hash = hasher.digest()
+
+ for i in range(20):
+ temp_key = ''.join(chr(i ^ ord(x)) for x in key)
+ cipher = ARC4.new(temp_key)
+ temp_hash = cipher.encrypt(temp_hash)
+
+ return temp_hash
+
+
+def check_user_password(key, doc):
+ """Check that the user password is correct (Algorithm 6)."""
+ expect_user_hash = create_user_hash(key, doc)
+ revision = int(doc.Encrypt.R or 0)
+ if revision < 3:
+ return doc.Encrypt.U.to_bytes() == expect_user_hash
+ else:
+ return doc.Encrypt.U.to_bytes()[:16] == expect_user_hash
+
+
+class AESCryptFilter(object):
+ """Crypt filter corresponding to /AESV2."""
+ def __init__(self, key):
+ self._key = key
+
+ def decrypt_data(self, num, gen, data):
+ """Decrypt data (string/stream) using key (Algorithm 1)."""
+ key_extension = struct.pack('= 1 and ftype[0] == PdfName.Crypt:
+ ftype = ftype[1:]
+ parms = obj.DecodeParms or obj.DP
+ filter = filters[parms.Name]
+
+ num, gen = obj.indirect
+ obj.stream = filter.decrypt_data(num, gen, obj.stream)
+ obj.private.decrypted = True
+ obj.Filter = ftype or None
diff --git a/plugin.program.AML/pdfrw/pdfrw/errors.py b/plugin.program.AML/pdfrw/pdfrw/errors.py
new file mode 100644
index 0000000000..ef6ab7d58b
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/errors.py
@@ -0,0 +1,41 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2006-2015 Patrick Maupin, Austin, Texas
+# MIT license -- See LICENSE.txt for details
+
+'''
+PDF Exceptions and error handling
+'''
+
+import logging
+
+
+fmt = logging.Formatter('[%(levelname)s] %(filename)s:%(lineno)d %(message)s')
+
+handler = logging.StreamHandler()
+handler.setFormatter(fmt)
+
+log = logging.getLogger('pdfrw')
+log.setLevel(logging.WARNING)
+log.addHandler(handler)
+
+
+class PdfError(Exception):
+ "Abstract base class of exceptions thrown by this module"
+
+ def __init__(self, msg):
+ self.msg = msg
+
+ def __str__(self):
+ return self.msg
+
+
+class PdfParseError(PdfError):
+ "Error thrown by parser/tokenizer"
+
+
+class PdfOutputError(PdfError):
+ "Error thrown by PDF writer"
+
+
+class PdfNotImplementedError(PdfError):
+ "Error thrown on missing features"
diff --git a/plugin.program.AML/pdfrw/pdfrw/findobjs.py b/plugin.program.AML/pdfrw/pdfrw/findobjs.py
new file mode 100644
index 0000000000..67d33a08f0
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/findobjs.py
@@ -0,0 +1,137 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2015 Patrick Maupin, Austin, Texas
+# MIT license -- See LICENSE.txt for details
+
+''' This module contains a function to find all the XObjects
+ in a document, and another function that will wrap them
+ in page objects.
+'''
+
+from .objects import PdfDict, PdfArray, PdfName
+
+
+def find_objects(source, valid_types=(PdfName.XObject, None),
+ valid_subtypes=(PdfName.Form, PdfName.Image),
+ no_follow=(PdfName.Parent,),
+ isinstance=isinstance, id=id, sorted=sorted,
+ reversed=reversed, PdfDict=PdfDict):
+ '''
+ Find all the objects of a particular kind in a document
+ or array. Defaults to looking for Form and Image XObjects.
+
+ This could be done recursively, but some PDFs
+ are quite deeply nested, so we do it without
+ recursion.
+
+ Note that we don't know exactly where things appear on pages,
+ but we aim for a sort order that is (a) mostly in document order,
+ and (b) reproducible. For arrays, objects are processed in
+ array order, and for dicts, they are processed in key order.
+ '''
+ container = (PdfDict, PdfArray)
+
+ # Allow passing a list of pages, or a dict
+ if isinstance(source, PdfDict):
+ source = [source]
+ else:
+ source = list(source)
+
+ visited = set()
+ source.reverse()
+ while source:
+ obj = source.pop()
+ if not isinstance(obj, container):
+ continue
+ myid = id(obj)
+ if myid in visited:
+ continue
+ visited.add(myid)
+ if isinstance(obj, PdfDict):
+ if obj.Type in valid_types and obj.Subtype in valid_subtypes:
+ yield obj
+ obj = [y for (x, y) in sorted(obj.iteritems())
+ if x not in no_follow]
+ else:
+ # TODO: This forces resolution of any indirect objects in
+ # the array. It may not be necessary. Don't know if
+ # reversed() does any voodoo underneath the hood.
+ # It's cheap enough for now, but might be removeable.
+ obj and obj[0]
+ source.extend(reversed(obj))
+
+
+def wrap_object(obj, width, margin):
+ ''' Wrap an xobj in its own page object.
+ '''
+ fmt = 'q %s 0 0 %s %s %s cm /MyImage Do Q'
+ contents = PdfDict(indirect=True)
+ subtype = obj.Subtype
+ if subtype == PdfName.Form:
+ contents._stream = obj.stream
+ contents.Length = obj.Length
+ contents.Filter = obj.Filter
+ contents.DecodeParms = obj.DecodeParms
+ resources = obj.Resources
+ mbox = obj.BBox
+ elif subtype == PdfName.Image: # Image
+ xoffset = margin[0]
+ yoffset = margin[1]
+ cw = width - margin[0] - margin[2]
+ iw, ih = float(obj.Width), float(obj.Height)
+ ch = 1.0 * cw / iw * ih
+ height = ch + margin[1] + margin[3]
+ p = tuple(('%.9f' % x).rstrip('0').rstrip('.') for x in (cw, ch, xoffset, yoffset))
+ contents.stream = fmt % p
+ resources = PdfDict(XObject=PdfDict(MyImage=obj))
+ mbox = PdfArray((0, 0, width, height))
+ else:
+ raise TypeError("Expected Form or Image XObject")
+
+ return PdfDict(
+ indirect=True,
+ Type=PdfName.Page,
+ MediaBox=mbox,
+ Resources=resources,
+ Contents=contents,
+ )
+
+
+def trivial_xobjs(maxignore=300):
+ ''' Ignore XObjects that trivially contain other XObjects.
+ '''
+ ignore = set('q Q cm Do'.split())
+ Image = PdfName.Image
+
+ def check(obj):
+ if obj.Subtype == Image:
+ return False
+ s = obj.stream
+ if len(s) < maxignore:
+ s = (x for x in s.split() if not x.startswith('/') and
+ x not in ignore)
+ s = (x.replace('.', '').replace('-', '') for x in s)
+ if not [x for x in s if not x.isdigit()]:
+ return True
+ return check
+
+
+def page_per_xobj(xobj_iter, width=8.5 * 72, margin=0.0 * 72,
+ image_only=False, ignore=trivial_xobjs(),
+ wrap_object=wrap_object):
+ ''' page_per_xobj wraps every XObj found
+ in its own page object.
+ width and margin are used to set image sizes.
+ '''
+ try:
+ iter(margin)
+ except:
+ margin = [margin]
+ while len(margin) < 4:
+ margin *= 2
+
+ if isinstance(xobj_iter, (list, dict)):
+ xobj_iter = find_objects(xobj_iter)
+ for obj in xobj_iter:
+ if not ignore(obj):
+ if not image_only or obj.Subtype == PdfName.IMage:
+ yield wrap_object(obj, width, margin)
diff --git a/plugin.program.AML/pdfrw/pdfrw/objects/__init__.py b/plugin.program.AML/pdfrw/pdfrw/objects/__init__.py
new file mode 100644
index 0000000000..879e0ef6cf
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/objects/__init__.py
@@ -0,0 +1,19 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2006-2015 Patrick Maupin, Austin, Texas
+# MIT license -- See LICENSE.txt for details
+
+'''
+Objects that can occur in PDF files. The most important
+objects are arrays and dicts. Either of these can be
+indirect or not, and dicts could have an associated
+stream.
+'''
+from .pdfname import PdfName
+from .pdfdict import PdfDict, IndirectPdfDict
+from .pdfarray import PdfArray
+from .pdfobject import PdfObject
+from .pdfstring import PdfString
+from .pdfindirect import PdfIndirect
+
+__all__ = """PdfName PdfDict IndirectPdfDict PdfArray
+ PdfObject PdfString PdfIndirect""".split()
diff --git a/plugin.program.AML/pdfrw/pdfrw/objects/pdfarray.py b/plugin.program.AML/pdfrw/pdfrw/objects/pdfarray.py
new file mode 100644
index 0000000000..e15f4ad69d
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/objects/pdfarray.py
@@ -0,0 +1,71 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2006-2015 Patrick Maupin, Austin, Texas
+# MIT license -- See LICENSE.txt for details
+
+from .pdfindirect import PdfIndirect
+from .pdfobject import PdfObject
+
+
+def _resolved():
+ pass
+
+
+class PdfArray(list):
+ ''' A PdfArray maps the PDF file array object into a Python list.
+ It has an indirect attribute which defaults to False.
+ '''
+ indirect = False
+
+ def __init__(self, source=[]):
+ self._resolve = self._resolver
+ self.extend(source)
+
+ def _resolver(self, isinstance=isinstance, enumerate=enumerate,
+ listiter=list.__iter__, PdfIndirect=PdfIndirect,
+ resolved=_resolved, PdfNull=PdfObject('null')):
+ for index, value in enumerate(list.__iter__(self)):
+ if isinstance(value, PdfIndirect):
+ value = value.real_value()
+ if value is None:
+ value = PdfNull
+ self[index] = value
+ self._resolve = resolved
+
+ def __getitem__(self, index, listget=list.__getitem__):
+ self._resolve()
+ return listget(self, index)
+
+ try:
+ def __getslice__(self, i, j, listget=list.__getslice__):
+ self._resolve()
+ return listget(self, i, j)
+ except AttributeError:
+ pass
+
+ def __iter__(self, listiter=list.__iter__):
+ self._resolve()
+ return listiter(self)
+
+ def count(self, item):
+ self._resolve()
+ return list.count(self, item)
+
+ def index(self, item):
+ self._resolve()
+ return list.index(self, item)
+
+ def remove(self, item):
+ self._resolve()
+ return list.remove(self, item)
+
+ def sort(self, *args, **kw):
+ self._resolve()
+ return list.sort(self, *args, **kw)
+
+ def pop(self, *args):
+ self._resolve()
+ return list.pop(self, *args)
+
+ def __reversed__(self):
+ self._resolve()
+ return list.__reversed__(self)
diff --git a/plugin.program.AML/pdfrw/pdfrw/objects/pdfdict.py b/plugin.program.AML/pdfrw/pdfrw/objects/pdfdict.py
new file mode 100644
index 0000000000..888fc83ac7
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/objects/pdfdict.py
@@ -0,0 +1,241 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2006-2015 Patrick Maupin, Austin, Texas
+# MIT license -- See LICENSE.txt for details
+
+from .pdfname import PdfName, BasePdfName
+from .pdfindirect import PdfIndirect
+from .pdfobject import PdfObject
+from ..py23_diffs import iteritems
+from ..errors import PdfParseError
+
+
+class _DictSearch(object):
+ ''' Used to search for inheritable attributes.
+ '''
+
+ def __init__(self, basedict):
+ self.basedict = basedict
+
+ def __getattr__(self, name, PdfName=PdfName):
+ return self[PdfName(name)]
+
+ def __getitem__(self, name, set=set, getattr=getattr, id=id):
+ visited = set()
+ mydict = self.basedict
+ while 1:
+ value = mydict[name]
+ if value is not None:
+ return value
+ myid = id(mydict)
+ assert myid not in visited
+ visited.add(myid)
+ mydict = mydict.Parent
+ if mydict is None:
+ return
+
+
+class _Private(object):
+ ''' Used to store private attributes (not output to PDF files)
+ on PdfDict classes
+ '''
+
+ def __init__(self, pdfdict):
+ vars(self)['pdfdict'] = pdfdict
+
+ def __setattr__(self, name, value):
+ vars(self.pdfdict)[name] = value
+
+
+class PdfDict(dict):
+ ''' PdfDict objects are subclassed dictionaries
+ with the following features:
+
+ - Every key in the dictionary starts with "/"
+
+ - A dictionary item can be deleted by assigning it to None
+
+ - Keys that (after the initial "/") conform to Python
+ naming conventions can also be accessed (set and retrieved)
+ as attributes of the dictionary. E.g. mydict.Page is the
+ same thing as mydict['/Page']
+
+ - Private attributes (not in the PDF space) can be set
+ on the dictionary object attribute dictionary by using
+ the private attribute:
+
+ mydict.private.foo = 3
+ mydict.foo = 5
+ x = mydict.foo # x will now contain 3
+ y = mydict['/foo'] # y will now contain 5
+
+ Most standard adobe dictionary keys start with an upper case letter,
+ so to avoid conflicts, it is best to start private attributes with
+ lower case letters.
+
+ - PdfDicts have the following read-only properties:
+
+ - private -- as discussed above, provides write access to
+ dictionary's attributes
+ - inheritable -- this creates and returns a "view" attribute
+ that will search through the object hierarchy for
+ any desired attribute, such as /Rotate or /MediaBox
+
+ - PdfDicts also have the following special attributes:
+ - indirect is not stored in the PDF dictionary, but in the object's
+ attribute dictionary
+ - stream is also stored in the object's attribute dictionary
+ and will also update the stream length.
+ - _stream will store in the object's attribute dictionary without
+ updating the stream length.
+
+ It is possible, for example, to have a PDF name such as "/indirect"
+ or "/stream", but you cannot access such a name as an attribute:
+
+ mydict.indirect -- accesses object's attribute dictionary
+ mydict["/indirect"] -- accesses actual PDF dictionary
+ '''
+ indirect = False
+ stream = None
+
+ _special = dict(indirect=('indirect', False),
+ stream=('stream', True),
+ _stream=('stream', False),
+ )
+
+ def __setitem__(self, name, value, setter=dict.__setitem__,
+ BasePdfName=BasePdfName, isinstance=isinstance):
+ if not isinstance(name, BasePdfName):
+ raise PdfParseError('Dict key %s is not a PdfName' % repr(name))
+ if value is not None:
+ setter(self, name, value)
+ elif name in self:
+ del self[name]
+
+ def __init__(self, *args, **kw):
+ if args:
+ if len(args) == 1:
+ args = args[0]
+ self.update(args)
+ if isinstance(args, PdfDict):
+ self.indirect = args.indirect
+ self._stream = args.stream
+ for key, value in iteritems(kw):
+ setattr(self, key, value)
+
+ def __getattr__(self, name, PdfName=PdfName):
+ ''' If the attribute doesn't exist on the dictionary object,
+ try to slap a '/' in front of it and get it out
+ of the actual dictionary itself.
+ '''
+ return self.get(PdfName(name))
+
+ def get(self, key, dictget=dict.get, isinstance=isinstance,
+ PdfIndirect=PdfIndirect):
+ ''' Get a value out of the dictionary,
+ after resolving any indirect objects.
+ '''
+ value = dictget(self, key)
+ if isinstance(value, PdfIndirect):
+ # We used to use self[key] here, but that does an
+ # unwanted check on the type of the key (github issue #98).
+ # Python will keep the old key object in the dictionary,
+ # so that check is not necessary.
+ value = value.real_value()
+ if value is not None:
+ dict.__setitem__(self, key, value)
+ else:
+ del self[key]
+ return value
+
+ def __getitem__(self, key):
+ return self.get(key)
+
+ def __setattr__(self, name, value, special=_special.get,
+ PdfName=PdfName, vars=vars):
+ ''' Set an attribute on the dictionary. Handle the keywords
+ indirect, stream, and _stream specially (for content objects)
+ '''
+ info = special(name)
+ if info is None:
+ self[PdfName(name)] = value
+ else:
+ name, setlen = info
+ vars(self)[name] = value
+ if setlen:
+ notnone = value is not None
+ self.Length = notnone and PdfObject(len(value)) or None
+
+ def iteritems(self, dictiter=iteritems,
+ isinstance=isinstance, PdfIndirect=PdfIndirect,
+ BasePdfName=BasePdfName):
+ ''' Iterate over the dictionary, resolving any unresolved objects
+ '''
+ for key, value in list(dictiter(self)):
+ if isinstance(value, PdfIndirect):
+ self[key] = value = value.real_value()
+ if value is not None:
+ if not isinstance(key, BasePdfName):
+ raise PdfParseError('Dict key %s is not a PdfName' %
+ repr(key))
+ yield key, value
+
+ def items(self):
+ return list(self.iteritems())
+
+ def itervalues(self):
+ for key, value in self.iteritems():
+ yield value
+
+ def values(self):
+ return list((value for key, value in self.iteritems()))
+
+ def keys(self):
+ return list((key for key, value in self.iteritems()))
+
+ def __iter__(self):
+ for key, value in self.iteritems():
+ yield key
+
+ def iterkeys(self):
+ return iter(self)
+
+ def copy(self):
+ return type(self)(self)
+
+ def pop(self, key):
+ value = self.get(key)
+ del self[key]
+ return value
+
+ def popitem(self):
+ key, value = dict.pop(self)
+ if isinstance(value, PdfIndirect):
+ value = value.real_value()
+ return value
+
+ def inheritable(self):
+ ''' Search through ancestors as needed for inheritable
+ dictionary items.
+ NOTE: You might think it would be a good idea
+ to cache this class, but then you'd have to worry
+ about it pointing to the wrong dictionary if you
+ made a copy of the object...
+ '''
+ return _DictSearch(self)
+ inheritable = property(inheritable)
+
+ def private(self):
+ ''' Allows setting private metadata for use in
+ processing (not sent to PDF file).
+ See note on inheritable
+ '''
+ return _Private(self)
+ private = property(private)
+
+
+class IndirectPdfDict(PdfDict):
+ ''' IndirectPdfDict is a convenience class. You could
+ create a direct PdfDict and then set indirect = True on it,
+ or you could just create an IndirectPdfDict.
+ '''
+ indirect = True
diff --git a/plugin.program.AML/pdfrw/pdfrw/objects/pdfindirect.py b/plugin.program.AML/pdfrw/pdfrw/objects/pdfindirect.py
new file mode 100644
index 0000000000..4df8ac327d
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/objects/pdfindirect.py
@@ -0,0 +1,22 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2006-2015 Patrick Maupin, Austin, Texas
+# MIT license -- See LICENSE.txt for details
+
+
+class _NotLoaded(object):
+ pass
+
+
+class PdfIndirect(tuple):
+ ''' A placeholder for an object that hasn't been read in yet.
+ The object itself is the (object number, generation number) tuple.
+ The attributes include information about where the object is
+ referenced from and the file object to retrieve the real object from.
+ '''
+ value = _NotLoaded
+
+ def real_value(self, NotLoaded=_NotLoaded):
+ value = self.value
+ if value is NotLoaded:
+ value = self.value = self._loader(self)
+ return value
diff --git a/plugin.program.AML/pdfrw/pdfrw/objects/pdfname.py b/plugin.program.AML/pdfrw/pdfrw/objects/pdfname.py
new file mode 100644
index 0000000000..28a146448c
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/objects/pdfname.py
@@ -0,0 +1,81 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2006-2015 Patrick Maupin, Austin, Texas
+# MIT license -- See LICENSE.txt for details
+
+import re
+
+from ..errors import log
+
+warn = log.warning
+
+
+class BasePdfName(str):
+ ''' A PdfName is an identifier that starts with
+ a slash.
+
+ If a PdfName has illegal space or delimiter characters,
+ then it will be decorated with an "encoded" attribute that
+ has those characters properly escaped as #
+
+ The "encoded" attribute is what is sent out to a PDF file,
+ the non-encoded main object is what is compared for equality
+ in a PDF dictionary.
+ '''
+
+ indirect = False
+ encoded = None
+
+ whitespace = '\x00 \t\f\r\n'
+ delimiters = '()<>{}[]/%'
+ forbidden = list(whitespace) + list('\\' + x for x in delimiters)
+ remap = dict((x, '#%02X' % ord(x)) for x in (whitespace + delimiters))
+ split_to_encode = re.compile('(%s)' % '|'.join(forbidden)).split
+ split_to_decode = re.compile(r'\#([0-9A-Fa-f]{2})').split
+
+ def __new__(cls, name, pre_encoded=True, remap=remap,
+ join=''.join, new=str.__new__, chr=chr, int=int,
+ split_to_encode=split_to_encode,
+ split_to_decode=split_to_decode,
+ ):
+ ''' We can build a PdfName from scratch, or from
+ a pre-encoded name (e.g. coming in from a file).
+ '''
+ # Optimization for normal case
+ if name[1:].isalnum():
+ return new(cls, name)
+ encoded = name
+ if pre_encoded:
+ if '#' in name:
+ substrs = split_to_decode(name)
+ substrs[1::2] = (chr(int(x, 16)) for x in substrs[1::2])
+ name = join(substrs)
+ else:
+ encoded = split_to_encode(encoded)
+ encoded[3::2] = (remap[x] for x in encoded[3::2])
+ encoded = join(encoded)
+ self = new(cls, name)
+ if encoded != name:
+ self.encoded = encoded
+ return self
+
+
+# We could have used a metaclass, but this matches what
+# we were doing historically.
+
+class PdfName(object):
+ ''' Two simple ways to get a PDF name from a string:
+
+ x = PdfName.FooBar
+ x = pdfName('FooBar')
+
+ Either technique will return "/FooBar"
+
+ '''
+
+ def __getattr__(self, name, BasePdfName=BasePdfName):
+ return BasePdfName('/' + name, False)
+
+ def __call__(self, name, BasePdfName=BasePdfName):
+ return BasePdfName('/' + name, False)
+
+PdfName = PdfName()
diff --git a/plugin.program.AML/pdfrw/pdfrw/objects/pdfobject.py b/plugin.program.AML/pdfrw/pdfrw/objects/pdfobject.py
new file mode 100644
index 0000000000..7317395688
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/objects/pdfobject.py
@@ -0,0 +1,11 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2006-2015 Patrick Maupin, Austin, Texas
+# MIT license -- See LICENSE.txt for details
+
+
+class PdfObject(str):
+ ''' A PdfObject is a textual representation of any PDF file object
+ other than an array, dict or string. It has an indirect attribute
+ which defaults to False.
+ '''
+ indirect = False
diff --git a/plugin.program.AML/pdfrw/pdfrw/objects/pdfstring.py b/plugin.program.AML/pdfrw/pdfrw/objects/pdfstring.py
new file mode 100644
index 0000000000..906f30e9f2
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/objects/pdfstring.py
@@ -0,0 +1,553 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2006-2017 Patrick Maupin, Austin, Texas
+# 2016 James Laird-Wah, Sydney, Australia
+# MIT license -- See LICENSE.txt for details
+
+"""
+
+================================
+PdfString encoding and decoding
+================================
+
+Introduction
+=============
+
+
+This module handles encoding and decoding of PDF strings. PDF strings
+are described in the PDF 1.7 reference manual, mostly in chapter 3
+(sections 3.2 and 3.8) and chapter 5.
+
+PDF strings are used in the document structure itself, and also inside
+the stream of page contents dictionaries.
+
+A PDF string can represent pure binary data (e.g. for a font or an
+image), or text, or glyph indices. For Western fonts, the glyph indices
+usually correspond to ASCII, but that is not guaranteed. (When it does
+happen, it makes examination of raw PDF data a lot easier.)
+
+The specification defines PDF string encoding at two different levels.
+At the bottom, it defines ways to encode arbitrary bytes so that a PDF
+tokenizer can understand they are a string of some sort, and can figure
+out where the string begins and ends. (That is all the tokenizer itself
+cares about.) Above that level, if the string represents text, the
+specification defines ways to encode Unicode text into raw bytes, before
+the byte encoding is performed.
+
+There are two ways to do the byte encoding, and two ways to do the text
+(Unicode) encoding.
+
+Encoding bytes into PDF strings
+================================
+
+Adobe calls the two ways to encode bytes into strings "Literal strings"
+and "Hexadecimal strings."
+
+Literal strings
+------------------
+
+A literal string is delimited by ASCII parentheses ("(" and ")"), and a
+hexadecimal string is delimited by ASCII less-than and greater-than
+signs ("<" and ">").
+
+A literal string may encode bytes almost unmolested. The caveat is
+that if a byte has the same value as a parenthesis, it must be escaped
+so that the tokenizer knows the string is not finished. This is accomplished
+by using the ASCII backslash ("\") as an escape character. Of course,
+now any backslash appearing in the data must likewise be escaped.
+
+Hexadecimal strings
+---------------------
+
+A hexadecimal string requires twice as much space as the source data
+it represents (plus two bytes for the delimiter), simply storing each
+byte as two hexadecimal digits, most significant digit first. The spec
+allows for lower or upper case hex digits, but most PDF encoders seem
+to use upper case.
+
+Special cases -- Legacy systems and readability
+-----------------------------------------------
+
+It is possible to create a PDF document that uses 7 bit ASCII encoding,
+and it is desirable in many cases to create PDFs that are reasonably
+readable when opened in a text editor. For these reasons, the syntax
+for both literal strings and hexadecimal strings is slightly more
+complicated that the initial description above. In general, the additional
+syntax allows the following features:
+
+ - Making the delineation between characters, or between sections of
+ a string, apparent, and easy to see in an editor.
+ - Keeping output lines from getting too wide for some editors
+ - Keeping output lines from being so narrow that you can only see the
+ small fraction of a string at a time in an editor.
+ - Suppressing unprintable characters
+ - Restricting the output string to 7 bit ASCII
+
+Hexadecimal readability
+~~~~~~~~~~~~~~~~~~~~~~~
+
+For hexadecimal strings, only the first two bullets are relevant. The syntax
+to accomplish this is simple, allowing any ASCII whitespace to be inserted
+anywhere in the encoded hex string.
+
+Literal readability
+~~~~~~~~~~~~~~~~~~~
+
+For literal strings, all of the bullets except the first are relevant.
+The syntax has two methods to help with these goals. The first method
+is to overload the escape operator to be able to do different functions,
+and the second method can reduce the number of escapes required for
+parentheses in the normal case.
+
+The escape function works differently, depending on what byte follows
+the backslash. In all cases, the escaping backslash is discarded,
+and then the next character is examined:
+
+ - For parentheses and backslashes (and, in fact, for all characters
+ not described otherwise in this list), the character after the
+ backslash is preserved in the output.
+ - A letter from the set of "nrtbf" following a backslash is interpreted as
+ a line feed, carriage return, tab, backspace, or form-feed, respectively.
+ - One to three octal digits following the backslash indicate the
+ numeric value of the encoded byte.
+ - A carriage return, carriage return/line feed, or line feed following
+ the backslash indicates a line break that was put in for readability,
+ and that is not part of the actual data, so this is discarded.
+
+The second method that can be used to improve readability (and reduce space)
+in literal strings is to not escape parentheses. This only works, and is
+only allowed, when the parentheses are properly balanced. For example,
+"((Hello))" is a valid encoding for a literal string, but "((Hello)" is not;
+the latter case should be encoded "(\(Hello)"
+
+Encoding text into strings
+==========================
+
+Section 3.8.1 of the PDF specification describes text strings.
+
+The individual characters of a text string can all be considered to
+be Unicode; Adobe specifies two different ways to encode these characters
+into a string of bytes before further encoding the byte string as a
+literal string or a hexadecimal string.
+
+The first way to encode these strings is called PDFDocEncoding. This
+is mostly a one-for-one mapping of bytes into single bytes, similar to
+Latin-1. The representable character set is limited to the number of
+characters that can fit in a byte, and this encoding cannot be used
+with Unicode strings that start with the two characters making up the
+UTF-16-BE BOM.
+
+The second way to encode these strings is with UTF-16-BE. Text strings
+encoded with this method must start with the BOM, and although the spec
+does not appear to mandate that the resultant bytes be encoded into a
+hexadecimal string, that seems to be the canonical way to do it.
+
+When encoding a string into UTF-16-BE, this module always adds the BOM,
+and when decoding a string from UTF-16-BE, this module always strips
+the BOM. If a source string contains a BOM, that will remain in the
+final string after a round-trip through the encoder and decoder, as
+the goal of the encoding/decoding process is transparency.
+
+
+PDF string handling in pdfrw
+=============================
+
+Responsibility for handling PDF strings in the pdfrw library is shared
+between this module, the tokenizer, and the pdfwriter.
+
+tokenizer string handling
+--------------------------
+
+As far as the tokenizer and its clients such as the pdfreader are concerned,
+the PdfString class must simply be something that it can instantiate by
+passing a string, that doesn't compare equal (or throw an exception when
+compared) to other possible token strings. The tokenizer must understand
+enough about the syntax of the string to successfully find its beginning
+and end in a stream of tokens, but doesn't otherwise know or care about
+the data represented by the string.
+
+pdfwriter string handling
+--------------------------
+
+The pdfwriter knows and cares about two attributes of PdfString instances:
+
+ - First, PdfString objects have an 'indirect' attribute, which pdfwriter
+ uses as an indication that the object knows how to represent itself
+ correctly when output to a new PDF. (In the case of a PdfString object,
+ no work is really required, because it is already a string.)
+ - Second, the PdfString.encode() method is used as a convenience to
+ automatically convert any user-supplied strings (that didn't come
+ from PDFs) when a PDF is written out to a file.
+
+pdfstring handling
+-------------------
+
+The code in this module is designed to support those uses by the
+tokenizer and the pdfwriter, and to additionally support encoding
+and decoding of PdfString objects as a convenience for the user.
+
+Most users of the pdfrw library never encode or decode a PdfString,
+so it is imperative that (a) merely importing this module does not
+take a significant amount of CPU time; and (b) it is cheap for the
+tokenizer to produce a PdfString, and cheap for the pdfwriter to
+consume a PdfString -- if the tokenizer finds a string that conforms
+to the PDF specification, it will be wrapped in a PdfString object,
+and if the pdfwriter finds an object with an indirect attribute, it
+simply calls str() to ask it to format itself.
+
+Encoding and decoding are not actually performed very often at all,
+compared to how often tokenization and then subsequent concatenation
+by the pdfwriter are performed. In fact, versions of pdfrw prior to
+0.4 did not even support Unicode for this function. Encoding and
+decoding can also easily be performed by the user, outside of the
+library, and this might still be recommended, at least for encoding,
+if the visual appeal of encodings generated by this module is found
+lacking.
+
+
+Decoding strings
+~~~~~~~~~~~~~~~~~~~
+
+Decoding strings can be tricky, but is a bounded process. Each
+properly-encoded encoded string represents exactly one output string,
+with the caveat that is up to the caller of the function to know whether
+he expects a Unicode string, or just bytes.
+
+The caller can call PdfString.to_bytes() to get a byte string (which may
+or may not represent encoded Unicode), or may call PdfString.to_unicode()
+to get a Unicode string. Byte strings will be regular strings in Python 2,
+and b'' bytes in Python 3; Unicode strings will be regular strings in
+Python 3, and u'' unicode strings in Python 2.
+
+To maintain application compatibility with earlier versions of pdfrw,
+PdfString.decode() is an alias for PdfString.to_unicode().
+
+Encoding strings
+~~~~~~~~~~~~~~~~~~
+
+PdfString has three factory functions that will encode strings into
+PdfString objects:
+
+ - PdfString.from_bytes() accepts a byte string (regular string in Python 2
+ or b'' bytes string in Python 3) and returns a PdfString object.
+ - PdfString.from_unicode() accepts a Unicode string (u'' Unicode string in
+ Python 2 or regular string in Python 3) and returns a PdfString object.
+ - PdfString.encode() examines the type of object passed, and either
+ calls from_bytes() or from_unicode() to do the real work.
+
+Unlike decoding(), encoding is not (mathematically) a function.
+There are (literally) an infinite number of ways to encode any given
+source string. (Of course, most of them would be stupid, unless
+the intent is some sort of denial-of-service attack.)
+
+So encoding strings is either simpler than decoding, or can be made to
+be an open-ended science fair project (to create the best looking
+encoded strings).
+
+There are parameters to the encoding functions that allow control over
+the final encoded string, but the intention is to make the default values
+produce a reasonable encoding.
+
+As mentioned previously, if encoding does not do what a particular
+user needs, that user is free to write his own encoder, and then
+simply instantiate a PdfString object by passing a string to the
+default constructor, the same way that the tokenizer does it.
+
+However, if desirable, encoding may gradually become more capable
+over time, adding the ability to generate more aesthetically pleasing
+encoded strings.
+
+PDFDocString encoding and decoding
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To handle this encoding in a fairly standard way, this module registers
+an encoder and decoder for PDFDocEncoding with the codecs module.
+
+"""
+
+import re
+import codecs
+import binascii
+import itertools
+from ..py23_diffs import convert_load, convert_store
+
+def find_pdfdocencoding(encoding):
+ """ This function conforms to the codec module registration
+ protocol. It defers calculating data structures until
+ a pdfdocencoding encode or decode is required.
+
+ PDFDocEncoding is described in the PDF 1.7 reference manual.
+ """
+
+ if encoding != 'pdfdocencoding':
+ return
+
+ # Create the decoding map based on the table in section D.2 of the
+ # PDF 1.7 manual
+
+ # Start off with the characters with 1:1 correspondence
+ decoding_map = set(range(0x20, 0x7F)) | set(range(0xA1, 0x100))
+ decoding_map.update((0x09, 0x0A, 0x0D))
+ decoding_map.remove(0xAD)
+ decoding_map = dict((x, x) for x in decoding_map)
+
+ # Add in the special Unicode characters
+ decoding_map.update(zip(range(0x18, 0x20), (
+ 0x02D8, 0x02C7, 0x02C6, 0x02D9, 0x02DD, 0x02DB, 0x02DA, 0x02DC)))
+ decoding_map.update(zip(range(0x80, 0x9F), (
+ 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, 0x2013, 0x0192, 0x2044,
+ 0x2039, 0x203A, 0x2212, 0x2030, 0x201E, 0x201C, 0x201D, 0x2018,
+ 0x2019, 0x201A, 0x2122, 0xFB01, 0xFB02, 0x0141, 0x0152, 0x0160,
+ 0x0178, 0x017D, 0x0131, 0x0142, 0x0153, 0x0161, 0x017E)))
+ decoding_map[0xA0] = 0x20AC
+
+ # Make the encoding map from the decoding map
+ encoding_map = codecs.make_encoding_map(decoding_map)
+
+ # Not every PDF producer follows the spec, so conform to Postel's law
+ # and interpret encoded strings if at all possible. In particular, they
+ # might have nulls and form-feeds, judging by random code snippets
+ # floating around the internet.
+ decoding_map.update(((x, x) for x in range(0x18)))
+
+ def encode(input, errors='strict'):
+ return codecs.charmap_encode(input, errors, encoding_map)
+
+ def decode(input, errors='strict'):
+ return codecs.charmap_decode(input, errors, decoding_map)
+
+ return codecs.CodecInfo(encode, decode, name='pdfdocencoding')
+
+codecs.register(find_pdfdocencoding)
+
+class PdfString(str):
+ """ A PdfString is an encoded string. It has a decode
+ method to get the actual string data out, and there
+ is an encode class method to create such a string.
+ Like any PDF object, it could be indirect, but it
+ defaults to being a direct object.
+ """
+ indirect = False
+
+
+ # The byte order mark, and unicode that could be
+ # wrongly encoded into the byte order mark by the
+ # pdfdocencoding codec.
+
+ bytes_bom = codecs.BOM_UTF16_BE
+ bad_pdfdoc_prefix = bytes_bom.decode('latin-1')
+
+ # Used by decode_literal; filled in on first use
+
+ unescape_dict = None
+ unescape_func = None
+
+ @classmethod
+ def init_unescapes(cls):
+ """ Sets up the unescape attributes for decode_literal
+ """
+ unescape_pattern = r'\\([0-7]{1,3}|\r\n|.)'
+ unescape_func = re.compile(unescape_pattern, re.DOTALL).split
+ cls.unescape_func = unescape_func
+
+ unescape_dict = dict(((chr(x), chr(x)) for x in range(0x100)))
+ unescape_dict.update(zip('nrtbf', '\n\r\t\b\f'))
+ unescape_dict['\r'] = ''
+ unescape_dict['\n'] = ''
+ unescape_dict['\r\n'] = ''
+ for i in range(0o10):
+ unescape_dict['%01o' % i] = chr(i)
+ for i in range(0o100):
+ unescape_dict['%02o' % i] = chr(i)
+ for i in range(0o400):
+ unescape_dict['%03o' % i] = chr(i)
+ cls.unescape_dict = unescape_dict
+ return unescape_func
+
+ def decode_literal(self):
+ """ Decode a PDF literal string, which is enclosed in parentheses ()
+
+ Many pdfrw users never decode strings, so defer creating
+ data structures to do so until the first string is decoded.
+
+ Possible string escapes from the spec:
+ (PDF 1.7 Reference, section 3.2.3, page 53)
+
+ 1. \[nrtbf\()]: simple escapes
+ 2. \\d{1,3}: octal. Must be zero-padded to 3 digits
+ if followed by digit
+ 3. \: line continuation. We don't know the EOL
+ marker used in the PDF, so accept \r, \n, and \r\n.
+ 4. Any other character following \ escape -- the backslash
+ is swallowed.
+ """
+ result = (self.unescape_func or self.init_unescapes())(self[1:-1])
+ if len(result) == 1:
+ return convert_store(result[0])
+ unescape_dict = self.unescape_dict
+ result[1::2] = [unescape_dict[x] for x in result[1::2]]
+ return convert_store(''.join(result))
+
+
+ def decode_hex(self):
+ """ Decode a PDF hexadecimal-encoded string, which is enclosed
+ in angle brackets <>.
+ """
+ hexstr = convert_store(''.join(self[1:-1].split()))
+ if len(hexstr) % 1: # odd number of chars indicates a truncated 0
+ hexstr += '0'
+ return binascii.unhexlify(hexstr)
+
+
+ def to_bytes(self):
+ """ Decode a PDF string to bytes. This is a convenience function
+ for user code, in that (as of pdfrw 0.3) it is never
+ actually used inside pdfrw.
+ """
+ if self.startswith('(') and self.endswith(')'):
+ return self.decode_literal()
+
+ elif self.startswith('<') and self.endswith('>'):
+ return self.decode_hex()
+
+ else:
+ raise ValueError('Invalid PDF string "%s"' % repr(self))
+
+ def to_unicode(self):
+ """ Decode a PDF string to a unicode string. This is a
+ convenience function for user code, in that (as of
+ pdfrw 0.3) it is never actually used inside pdfrw.
+
+ There are two Unicode storage methods used -- either
+ UTF16_BE, or something called PDFDocEncoding, which
+ is defined in the PDF spec. The determination of
+ which decoding method to use is done by examining the
+ first two bytes for the byte order marker.
+ """
+ raw = self.to_bytes()
+
+ if raw[:2] == self.bytes_bom:
+ return raw[2:].decode('utf-16-be')
+ else:
+ return raw.decode('pdfdocencoding')
+
+ # Legacy-compatible interface
+ decode = to_unicode
+
+ # Internal value used by encoding
+
+ escape_splitter = None # Calculated on first use
+
+ @classmethod
+ def init_escapes(cls):
+ """ Initialize the escape_splitter for the encode method
+ """
+ cls.escape_splitter = re.compile(br'(\(|\\|\))').split
+ return cls.escape_splitter
+
+ @classmethod
+ def from_bytes(cls, raw, bytes_encoding='auto'):
+ """ The from_bytes() constructor is called to encode a source raw
+ byte string into a PdfString that is suitable for inclusion
+ in a PDF.
+
+ NOTE: There is no magic in the encoding process. A user
+ can certainly do his own encoding, and simply initialize a
+ PdfString() instance with his encoded string. That may be
+ useful, for example, to add line breaks to make it easier
+ to load PDFs into editors, or to not bother to escape balanced
+ parentheses, or to escape additional characters to make a PDF
+ more readable in a file editor. Those are features not
+ currently supported by this method.
+
+ from_bytes() can use a heuristic to figure out the best
+ encoding for the string, or the user can control the process
+ by changing the bytes_encoding parameter to 'literal' or 'hex'
+ to force a particular conversion method.
+ """
+
+ # If hexadecimal is not being forced, then figure out how long
+ # the escaped literal string will be, and fall back to hex if
+ # it is too long.
+
+ force_hex = bytes_encoding == 'hex'
+ if not force_hex:
+ if bytes_encoding not in ('literal', 'auto'):
+ raise ValueError('Invalid bytes_encoding value: %s'
+ % bytes_encoding)
+ splitlist = (cls.escape_splitter or cls.init_escapes())(raw)
+ if bytes_encoding == 'auto' and len(splitlist) // 2 >= len(raw):
+ force_hex = True
+
+ if force_hex:
+ # The spec does not mandate uppercase,
+ # but it seems to be the convention.
+ fmt = '<%s>'
+ result = binascii.hexlify(raw).upper()
+ else:
+ fmt = '(%s)'
+ splitlist[1::2] = [(b'\\' + x) for x in splitlist[1::2]]
+ result = b''.join(splitlist)
+
+ return cls(fmt % convert_load(result))
+
+ @classmethod
+ def from_unicode(cls, source, text_encoding='auto',
+ bytes_encoding='auto'):
+ """ The from_unicode() constructor is called to encode a source
+ string into a PdfString that is suitable for inclusion in a PDF.
+
+ NOTE: There is no magic in the encoding process. A user
+ can certainly do his own encoding, and simply initialize a
+ PdfString() instance with his encoded string. That may be
+ useful, for example, to add line breaks to make it easier
+ to load PDFs into editors, or to not bother to escape balanced
+ parentheses, or to escape additional characters to make a PDF
+ more readable in a file editor. Those are features not
+ supported by this method.
+
+ from_unicode() can use a heuristic to figure out the best
+ encoding for the string, or the user can control the process
+ by changing the text_encoding parameter to 'pdfdocencoding'
+ or 'utf16', and/or by changing the bytes_encoding parameter
+ to 'literal' or 'hex' to force particular conversion methods.
+
+ The function will raise an exception if it cannot perform
+ the conversion as requested by the user.
+ """
+
+ # Give preference to pdfdocencoding, since it only
+ # requires one raw byte per character, rather than two.
+ if text_encoding != 'utf16':
+ force_pdfdoc = text_encoding == 'pdfdocencoding'
+ if text_encoding != 'auto' and not force_pdfdoc:
+ raise ValueError('Invalid text_encoding value: %s'
+ % text_encoding)
+
+ if source.startswith(cls.bad_pdfdoc_prefix):
+ if force_pdfdoc:
+ raise UnicodeError('Prefix of string %r cannot be encoded '
+ 'in pdfdocencoding' % source[:20])
+ else:
+ try:
+ raw = source.encode('pdfdocencoding')
+ except UnicodeError:
+ if force_pdfdoc:
+ raise
+ else:
+ return cls.from_bytes(raw, bytes_encoding)
+
+ # If the user is not forcing literal strings,
+ # it makes much more sense to use hexadecimal with 2-byte chars
+ raw = cls.bytes_bom + source.encode('utf-16-be')
+ encoding = 'hex' if bytes_encoding == 'auto' else bytes_encoding
+ return cls.from_bytes(raw, encoding)
+
+ @classmethod
+ def encode(cls, source, uni_type = type(u''), isinstance=isinstance):
+ """ The encode() constructor is a legacy function that is
+ also a convenience for the PdfWriter.
+ """
+ if isinstance(source, uni_type):
+ return cls.from_unicode(source)
+ else:
+ return cls.from_bytes(source)
diff --git a/plugin.program.AML/pdfrw/pdfrw/pagemerge.py b/plugin.program.AML/pdfrw/pdfrw/pagemerge.py
new file mode 100644
index 0000000000..455511077a
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/pagemerge.py
@@ -0,0 +1,250 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2015 Patrick Maupin, Austin, Texas
+# MIT license -- See LICENSE.txt for details
+
+'''
+This module contains code to edit pages. Sort of a canvas, I
+suppose, but I wouldn't want to call it that and get people all
+excited or anything.
+
+No, this is just for doing basic things like merging/splitting
+apart pages, watermarking, etc. All it does is allow converting
+pages (or parts of pages) into Form XObject rectangles, and then
+plopping those down on new or pre-existing pages.
+'''
+
+from .objects import PdfDict, PdfArray, PdfName
+from .buildxobj import pagexobj, ViewInfo
+
+NullInfo = ViewInfo()
+
+
+class RectXObj(PdfDict):
+ ''' This class facilitates doing positioning (moving and scaling)
+ of Form XObjects within their containing page, by modifying
+ the Form XObject's transformation matrix.
+
+ By default, this class keeps the aspect ratio locked. For
+ example, if your object is foo, you can write 'foo.w = 200',
+ and it will scale in both the x and y directions.
+
+ To unlock the aspect ration, you have to do a tiny bit of math
+ and call the scale function.
+ '''
+ def __init__(self, page, viewinfo=NullInfo, **kw):
+ ''' The page is a page returned by PdfReader. It will be
+ turned into a cached Form XObject (so that multiple
+ rectangles can be extracted from it if desired), and then
+ another Form XObject will be built using it and the viewinfo
+ (which should be a ViewInfo class). The viewinfo includes
+ source coordinates (from the top/left) and rotation information.
+
+ Once the object has been built, its destination coordinates
+ may be examined and manipulated by using x, y, w, h, and
+ scale. The destination coordinates are in the normal
+ PDF programmatic system (starting at bottom left).
+ '''
+ if kw:
+ if viewinfo is not NullInfo:
+ raise ValueError("Cannot modify preexisting ViewInfo")
+ viewinfo = ViewInfo(**kw)
+ viewinfo.cacheable = False
+ base = pagexobj(page, viewinfo)
+ self.update(base)
+ self.indirect = True
+ self.stream = base.stream
+ private = self.private
+ private._rect = [base.x, base.y, base.w, base.h]
+ matrix = self.Matrix
+ if matrix is None:
+ matrix = self.Matrix = PdfArray((1, 0, 0, 1, 0, 0))
+ private._matrix = matrix # Lookup optimization
+ # Default to lower-left corner
+ self.x = 0
+ self.y = 0
+
+ @property
+ def x(self):
+ ''' X location (from left) of object in points
+ '''
+ return self._rect[0]
+
+ @property
+ def y(self):
+ ''' Y location (from bottom) of object in points
+ '''
+ return self._rect[1]
+
+ @property
+ def w(self):
+ ''' Width of object in points
+ '''
+ return self._rect[2]
+
+ @property
+ def h(self):
+ ''' Height of object in points
+ '''
+ return self._rect[3]
+
+ def __setattr__(self, name, value, next=PdfDict.__setattr__,
+ mine=set('x y w h'.split())):
+ ''' The underlying __setitem__ won't let us use a property
+ setter, so we have to fake one.
+ '''
+ if name not in mine:
+ return next(self, name, value)
+ if name in 'xy':
+ r_index, m_index = (0, 4) if name == 'x' else (1, 5)
+ self._rect[r_index], old = value, self._rect[r_index]
+ self._matrix[m_index] += value - old
+ else:
+ index = 2 + (value == 'h')
+ self.scale(value / self._rect[index])
+
+ def scale(self, x_scale, y_scale=None):
+ ''' Current scaling deals properly with things that
+ have been rotated in 90 degree increments
+ (via the ViewMerge object given when instantiating).
+ '''
+ if y_scale is None:
+ y_scale = x_scale
+ x, y, w, h = rect = self._rect
+ ao, bo, co, do, eo, fo = matrix = self._matrix
+ an = ao * x_scale
+ bn = bo * y_scale
+ cn = co * x_scale
+ dn = do * y_scale
+ en = x + (eo - x) * 1.0 * (an + cn) / (ao + co)
+ fn = y + (fo - y) * 1.0 * (bn + dn) / (bo + do)
+ matrix[:] = an, bn, cn, dn, en, fn
+ rect[:] = x, y, w * x_scale, h * y_scale
+
+ @property
+ def box(self):
+ ''' Return the bounding box for the object
+ '''
+ x, y, w, h = self._rect
+ return PdfArray([x, y, x + w, y + h])
+
+
+class PageMerge(list):
+ ''' A PageMerge object can have 0 or 1 underlying pages
+ (that get edited with the results of the merge)
+ and 0-n RectXObjs that can be applied before or
+ after the underlying page.
+ '''
+ page = None
+ mbox = None
+ cbox = None
+ resources = None
+ rotate = None
+ contents = None
+
+ def __init__(self, page=None):
+ if page is not None:
+ self.setpage(page)
+
+ def setpage(self, page):
+ if page.Type != PdfName.Page:
+ raise TypeError("Expected page")
+ self.append(None) # Placeholder
+ self.page = page
+ inheritable = page.inheritable
+ self.mbox = inheritable.MediaBox
+ self.cbox = inheritable.CropBox
+ self.resources = inheritable.Resources
+ self.rotate = inheritable.Rotate
+ self.contents = page.Contents
+
+ def __add__(self, other):
+ if isinstance(other, dict):
+ other = [other]
+ for other in other:
+ self.add(other)
+ return self
+
+ def add(self, obj, prepend=False, **kw):
+ if kw:
+ obj = RectXObj(obj, **kw)
+ elif obj.Type == PdfName.Page:
+ obj = RectXObj(obj)
+ if prepend:
+ self.insert(0, obj)
+ else:
+ self.append(obj)
+ return self
+
+ def render(self):
+ def do_xobjs(xobj_list, restore_first=False):
+ content = ['Q'] if restore_first else []
+ for obj in xobj_list:
+ index = PdfName('pdfrw_%d' % (key_offset + len(xobjs)))
+ if xobjs.setdefault(index, obj) is not obj:
+ raise KeyError("XObj key %s already in use" % index)
+ content.append('%s Do' % index)
+ return PdfDict(indirect=True, stream='\n'.join(content))
+
+ mbox = self.mbox
+ cbox = self.cbox
+ page = self.page
+ old_contents = self.contents
+ resources = self.resources or PdfDict()
+
+ key_offset = 0
+ xobjs = resources.XObject
+ if xobjs is None:
+ xobjs = resources.XObject = PdfDict()
+ else:
+ allkeys = xobjs.keys()
+ if allkeys:
+ keys = (x for x in allkeys if x.startswith('/pdfrw_'))
+ keys = (x for x in keys if x[7:].isdigit())
+ keys = sorted(keys, key=lambda x: int(x[7:]))
+ key_offset = (int(keys[-1][7:]) + 1) if keys else 0
+ key_offset -= len(allkeys)
+
+ if old_contents is None:
+ new_contents = do_xobjs(self)
+ else:
+ isdict = isinstance(old_contents, PdfDict)
+ old_contents = [old_contents] if isdict else old_contents
+ new_contents = PdfArray()
+ index = self.index(None)
+ if index:
+ new_contents.append(do_xobjs(self[:index]))
+
+ index += 1
+ if index < len(self):
+ # There are elements to add after the original page contents,
+ # so push the graphics state to the stack. Restored below.
+ new_contents.append(PdfDict(indirect=True, stream='q'))
+
+ new_contents.extend(old_contents)
+
+ if index < len(self):
+ # Restore graphics state and add other elements.
+ new_contents.append(do_xobjs(self[index:], restore_first=True))
+
+ if mbox is None:
+ cbox = None
+ mbox = self.xobj_box
+ mbox[0] = min(0, mbox[0])
+ mbox[1] = min(0, mbox[1])
+
+ page = PdfDict(indirect=True) if page is None else page
+ page.Type = PdfName.Page
+ page.Resources = resources
+ page.MediaBox = mbox
+ page.CropBox = cbox
+ page.Rotate = self.rotate
+ page.Contents = new_contents
+ return page
+
+ @property
+ def xobj_box(self):
+ ''' Return the smallest box that encloses every object
+ in the list.
+ '''
+ a, b, c, d = zip(*(xobj.box for xobj in self))
+ return PdfArray((min(a), min(b), max(c), max(d)))
diff --git a/plugin.program.AML/pdfrw/pdfrw/pdfreader.py b/plugin.program.AML/pdfrw/pdfrw/pdfreader.py
new file mode 100644
index 0000000000..c2ae030192
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/pdfreader.py
@@ -0,0 +1,691 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2006-2015 Patrick Maupin, Austin, Texas
+# Copyright (C) 2012-2015 Nerijus Mika
+# MIT license -- See LICENSE.txt for details
+
+'''
+The PdfReader class reads an entire PDF file into memory and
+parses the top-level container objects. (It does not parse
+into streams.) The object subclasses PdfDict, and the
+document pages are stored in a list in the pages attribute
+of the object.
+'''
+import gc
+import binascii
+import collections
+import itertools
+
+from .errors import PdfParseError, log
+from .tokens import PdfTokens
+from .objects import PdfDict, PdfArray, PdfName, PdfObject, PdfIndirect
+from .uncompress import uncompress
+from . import crypt
+from .py23_diffs import convert_load, convert_store, iteritems
+
+
+class PdfReader(PdfDict):
+
+ def findindirect(self, objnum, gennum, PdfIndirect=PdfIndirect, int=int):
+ ''' Return a previously loaded indirect object, or create
+ a placeholder for it.
+ '''
+ key = int(objnum), int(gennum)
+ result = self.indirect_objects.get(key)
+ if result is None:
+ self.indirect_objects[key] = result = PdfIndirect(key)
+ self.deferred_objects.add(key)
+ result._loader = self.loadindirect
+ return result
+
+ def readarray(self, source, PdfArray=PdfArray):
+ ''' Found a [ token. Parse the tokens after that.
+ '''
+ specialget = self.special.get
+ result = []
+ pop = result.pop
+ append = result.append
+
+ for value in source:
+ if value in ']R':
+ if value == ']':
+ break
+ generation = pop()
+ value = self.findindirect(pop(), generation)
+ else:
+ func = specialget(value)
+ if func is not None:
+ value = func(source)
+ append(value)
+ return PdfArray(result)
+
+ def readdict(self, source, PdfDict=PdfDict):
+ ''' Found a << token. Parse the tokens after that.
+ '''
+ specialget = self.special.get
+ result = PdfDict()
+ next = source.next
+
+ tok = next()
+ while tok != '>>':
+ if not tok.startswith('/'):
+ source.error('Expected PDF /name object')
+ tok = next()
+ continue
+ key = tok
+ value = next()
+ func = specialget(value)
+ if func is not None:
+ value = func(source)
+ tok = next()
+ else:
+ tok = next()
+ if value.isdigit() and tok.isdigit():
+ tok2 = next()
+ if tok2 != 'R':
+ source.error('Expected "R" following two integers')
+ tok = tok2
+ continue
+ value = self.findindirect(value, tok)
+ tok = next()
+ result[key] = value
+ return result
+
+ def empty_obj(self, source, PdfObject=PdfObject):
+ ''' Some silly git put an empty object in the
+ file. Back up so the caller sees the endobj.
+ '''
+ source.floc = source.tokstart
+
+ def badtoken(self, source):
+ ''' Didn't see that coming.
+ '''
+ source.exception('Unexpected delimiter')
+
+ def findstream(self, obj, tok, source, len=len):
+ ''' Figure out if there is a content stream
+ following an object, and return the start
+ pointer to the content stream if so.
+
+ (We can't read it yet, because we might not
+ know how long it is, because Length might
+ be an indirect object.)
+ '''
+
+ fdata = source.fdata
+ startstream = source.tokstart + len(tok)
+ gotcr = fdata[startstream] == '\r'
+ startstream += gotcr
+ gotlf = fdata[startstream] == '\n'
+ startstream += gotlf
+ if not gotlf:
+ if not gotcr:
+ source.error(r'stream keyword not followed by \n')
+ else:
+ source.warning(r"stream keyword terminated "
+ r"by \r without \n")
+ return startstream
+
+ def readstream(self, obj, startstream, source, exact_required=False,
+ streamending='endstream endobj'.split(), int=int):
+ fdata = source.fdata
+ length = int(obj.Length)
+ source.floc = target_endstream = startstream + length
+ endit = source.multiple(2)
+ obj._stream = fdata[startstream:target_endstream]
+ if endit == streamending:
+ return
+
+ if exact_required:
+ source.exception('Expected endstream endobj')
+
+ # The length attribute does not match the distance between the
+ # stream and endstream keywords.
+
+ # TODO: Extract maxstream from dictionary of object offsets
+ # and use rfind instead of find.
+ maxstream = len(fdata) - 20
+ endstream = fdata.find('endstream', startstream, maxstream)
+ source.floc = startstream
+ room = endstream - startstream
+ if endstream < 0:
+ source.error('Could not find endstream')
+ return
+ if (length == room + 1 and
+ fdata[startstream - 2:startstream] == '\r\n'):
+ source.warning(r"stream keyword terminated by \r without \n")
+ obj._stream = fdata[startstream - 1:target_endstream - 1]
+ return
+ source.floc = endstream
+ if length > room:
+ source.error('stream /Length attribute (%d) appears to '
+ 'be too big (size %d) -- adjusting',
+ length, room)
+ obj.stream = fdata[startstream:endstream]
+ return
+ if fdata[target_endstream:endstream].rstrip():
+ source.error('stream /Length attribute (%d) appears to '
+ 'be too small (size %d) -- adjusting',
+ length, room)
+ obj.stream = fdata[startstream:endstream]
+ return
+ endobj = fdata.find('endobj', endstream, maxstream)
+ if endobj < 0:
+ source.error('Could not find endobj after endstream')
+ return
+ if fdata[endstream:endobj].rstrip() != 'endstream':
+ source.error('Unexpected data between endstream and endobj')
+ return
+ source.error('Illegal endstream/endobj combination')
+
+ def loadindirect(self, key, PdfDict=PdfDict,
+ isinstance=isinstance):
+ result = self.indirect_objects.get(key)
+ if not isinstance(result, PdfIndirect):
+ return result
+ source = self.source
+ offset = int(self.source.obj_offsets.get(key, '0'))
+ if not offset:
+ source.warning("Did not find PDF object %s", key)
+ return None
+
+ # Read the object header and validate it
+ objnum, gennum = key
+ source.floc = offset
+ objid = source.multiple(3)
+ ok = len(objid) == 3
+ ok = ok and objid[0].isdigit() and int(objid[0]) == objnum
+ ok = ok and objid[1].isdigit() and int(objid[1]) == gennum
+ ok = ok and objid[2] == 'obj'
+ if not ok:
+ source.floc = offset
+ source.next()
+ objheader = '%d %d obj' % (objnum, gennum)
+ fdata = source.fdata
+ offset2 = (fdata.find('\n' + objheader) + 1 or
+ fdata.find('\r' + objheader) + 1)
+ if (not offset2 or
+ fdata.find(fdata[offset2 - 1] + objheader, offset2) > 0):
+ source.warning("Expected indirect object '%s'", objheader)
+ return None
+ source.warning("Indirect object %s found at incorrect "
+ "offset %d (expected offset %d)",
+ objheader, offset2, offset)
+ source.floc = offset2 + len(objheader)
+
+ # Read the object, and call special code if it starts
+ # an array or dictionary
+ obj = source.next()
+ func = self.special.get(obj)
+ if func is not None:
+ obj = func(source)
+
+ self.indirect_objects[key] = obj
+ self.deferred_objects.remove(key)
+
+ # Mark the object as indirect, and
+ # just return it if it is a simple object.
+ obj.indirect = key
+ tok = source.next()
+ if tok == 'endobj':
+ return obj
+
+ # Should be a stream. Either that or it's broken.
+ isdict = isinstance(obj, PdfDict)
+ if isdict and tok == 'stream':
+ self.readstream(obj, self.findstream(obj, tok, source), source)
+ return obj
+
+ # Houston, we have a problem, but let's see if it
+ # is easily fixable. Leaving out a space before endobj
+ # is apparently an easy mistake to make on generation
+ # (Because it won't be noticed unless you are specifically
+ # generating an indirect object that doesn't end with any
+ # sort of delimiter.) It is so common that things like
+ # okular just handle it.
+
+ if isinstance(obj, PdfObject) and obj.endswith('endobj'):
+ source.error('No space or delimiter before endobj')
+ obj = PdfObject(obj[:-6])
+ else:
+ source.error("Expected 'endobj'%s token",
+ isdict and " or 'stream'" or '')
+ obj = PdfObject('')
+
+ obj.indirect = key
+ self.indirect_objects[key] = obj
+ return obj
+
+ def read_all(self):
+ deferred = self.deferred_objects
+ prev = set()
+ while 1:
+ new = deferred - prev
+ if not new:
+ break
+ prev |= deferred
+ for key in new:
+ self.loadindirect(key)
+
+ def decrypt_all(self):
+ self.read_all()
+
+ if self.crypt_filters is not None:
+ crypt.decrypt_objects(
+ self.indirect_objects.values(), self.stream_crypt_filter,
+ self.crypt_filters)
+
+ def uncompress(self):
+ self.read_all()
+
+ uncompress(self.indirect_objects.values())
+
+ def load_stream_objects(self, object_streams):
+ # read object streams
+ objs = []
+ for num in object_streams:
+ obj = self.findindirect(num, 0).real_value()
+ assert obj.Type == '/ObjStm'
+ objs.append(obj)
+
+ # read objects from stream
+ if objs:
+ # Decrypt
+ if self.crypt_filters is not None:
+ crypt.decrypt_objects(
+ objs, self.stream_crypt_filter, self.crypt_filters)
+
+ # Decompress
+ uncompress(objs)
+
+ for obj in objs:
+ objsource = PdfTokens(obj.stream, 0, False)
+ next = objsource.next
+ offsets = []
+ firstoffset = int(obj.First)
+ while objsource.floc < firstoffset:
+ offsets.append((int(next()), firstoffset + int(next())))
+ for num, offset in offsets:
+ # Read the object, and call special code if it starts
+ # an array or dictionary
+ objsource.floc = offset
+ sobj = next()
+ func = self.special.get(sobj)
+ if func is not None:
+ sobj = func(objsource)
+
+ key = (num, 0)
+ self.indirect_objects[key] = sobj
+ if key in self.deferred_objects:
+ self.deferred_objects.remove(key)
+
+ # Mark the object as indirect, and
+ # add it to the list of streams if it starts a stream
+ sobj.indirect = key
+
+ def findxref(self, fdata):
+ ''' Find the cross reference section at the end of a file
+ '''
+ startloc = fdata.rfind('startxref')
+ if startloc < 0:
+ raise PdfParseError('Did not find "startxref" at end of file')
+ source = PdfTokens(fdata, startloc, False, self.verbose)
+ tok = source.next()
+ assert tok == 'startxref' # (We just checked this...)
+ tableloc = source.next_default()
+ if not tableloc.isdigit():
+ source.exception('Expected table location')
+ if source.next_default().rstrip().lstrip('%') != 'EOF':
+ source.exception('Expected %%EOF')
+ return startloc, PdfTokens(fdata, int(tableloc), True, self.verbose)
+
+ def parse_xref_stream(self, source, int=int, range=range,
+ enumerate=enumerate, islice=itertools.islice,
+ defaultdict=collections.defaultdict,
+ hexlify=binascii.hexlify):
+ ''' Parse (one of) the cross-reference file section(s)
+ '''
+
+ def readint(s, lengths):
+ offset = 0
+ for length in itertools.cycle(lengths):
+ next = offset + length
+ yield int(hexlify(s[offset:next]), 16) if length else None
+ offset = next
+
+ setdefault = source.obj_offsets.setdefault
+ next = source.next
+ # check for xref stream object
+ objid = source.multiple(3)
+ ok = len(objid) == 3
+ ok = ok and objid[0].isdigit()
+ ok = ok and objid[1] == 'obj'
+ ok = ok and objid[2] == '<<'
+ if not ok:
+ source.exception('Expected xref stream start')
+ obj = self.readdict(source)
+ if obj.Type != PdfName.XRef:
+ source.exception('Expected dict type of /XRef')
+ tok = next()
+ self.readstream(obj, self.findstream(obj, tok, source), source, True)
+ old_strm = obj.stream
+ if not uncompress([obj], True):
+ source.exception('Could not decompress Xref stream')
+ stream = obj.stream
+ # Fix for issue #76 -- goofy compressed xref stream
+ # that is NOT ACTUALLY COMPRESSED
+ stream = stream if stream is not old_strm else convert_store(old_strm)
+ num_pairs = obj.Index or PdfArray(['0', obj.Size])
+ num_pairs = [int(x) for x in num_pairs]
+ num_pairs = zip(num_pairs[0::2], num_pairs[1::2])
+ entry_sizes = [int(x) for x in obj.W]
+ if len(entry_sizes) != 3:
+ source.exception('Invalid entry size')
+ object_streams = defaultdict(list)
+ get = readint(stream, entry_sizes)
+ for objnum, size in num_pairs:
+ for cnt in range(size):
+ xtype, p1, p2 = islice(get, 3)
+ if xtype in (1, None):
+ if p1:
+ setdefault((objnum, p2 or 0), p1)
+ elif xtype == 2:
+ object_streams[p1].append((objnum, p2))
+ objnum += 1
+
+ obj.private.object_streams = object_streams
+ return obj
+
+ def parse_xref_table(self, source, int=int, range=range):
+ ''' Parse (one of) the cross-reference file section(s)
+ '''
+ setdefault = source.obj_offsets.setdefault
+ next = source.next
+ # plain xref table
+ start = source.floc
+ try:
+ while 1:
+ tok = next()
+ if tok == 'trailer':
+ return
+ startobj = int(tok)
+ for objnum in range(startobj, startobj + int(next())):
+ offset = int(next())
+ generation = int(next())
+ inuse = next()
+ if inuse == 'n':
+ if offset != 0:
+ setdefault((objnum, generation), offset)
+ elif inuse != 'f':
+ raise ValueError
+ except:
+ pass
+ try:
+ # Table formatted incorrectly.
+ # See if we can figure it out anyway.
+ end = source.fdata.rindex('trailer', start)
+ table = source.fdata[start:end].splitlines()
+ for line in table:
+ tokens = line.split()
+ if len(tokens) == 2:
+ objnum = int(tokens[0])
+ elif len(tokens) == 3:
+ offset, generation, inuse = (int(tokens[0]),
+ int(tokens[1]), tokens[2])
+ if offset != 0 and inuse == 'n':
+ setdefault((objnum, generation), offset)
+ objnum += 1
+ elif tokens:
+ log.error('Invalid line in xref table: %s' %
+ repr(line))
+ raise ValueError
+ log.warning('Badly formatted xref table')
+ source.floc = end
+ next()
+ except:
+ source.floc = start
+ source.exception('Invalid table format')
+
+ def parsexref(self, source):
+ ''' Parse (one of) the cross-reference file section(s)
+ '''
+ next = source.next
+ try:
+ tok = next()
+ except StopIteration:
+ tok = ''
+ if tok.isdigit():
+ return self.parse_xref_stream(source), True
+ elif tok == 'xref':
+ self.parse_xref_table(source)
+ tok = next()
+ if tok != '<<':
+ source.exception('Expected "<<" starting catalog')
+ return self.readdict(source), False
+ else:
+ source.exception('Expected "xref" keyword or xref stream object')
+
+ def readpages(self, node):
+ pagename = PdfName.Page
+ pagesname = PdfName.Pages
+ catalogname = PdfName.Catalog
+ typename = PdfName.Type
+ kidname = PdfName.Kids
+
+ try:
+ result = []
+ stack = [node]
+ append = result.append
+ pop = stack.pop
+ while stack:
+ node = pop()
+ nodetype = node[typename]
+ if nodetype == pagename:
+ append(node)
+ elif nodetype == pagesname:
+ stack.extend(reversed(node[kidname]))
+ elif nodetype == catalogname:
+ stack.append(node[pagesname])
+ else:
+ log.error('Expected /Page or /Pages dictionary, got %s' %
+ repr(node))
+ return result
+ except (AttributeError, TypeError) as s:
+ log.error('Invalid page tree: %s' % s)
+ return []
+
+ def _parse_encrypt_info(self, source, password, trailer):
+ """Check password and initialize crypt filters."""
+ # Create and check password key
+ key = crypt.create_key(password, trailer)
+
+ if not crypt.check_user_password(key, trailer):
+ source.warning('User password does not validate')
+
+ # Create default crypt filters
+ private = self.private
+ crypt_filters = self.crypt_filters
+ version = int(trailer.Encrypt.V or 0)
+ if version in (1, 2):
+ crypt_filter = crypt.RC4CryptFilter(key)
+ private.stream_crypt_filter = crypt_filter
+ private.string_crypt_filter = crypt_filter
+ elif version == 4:
+ if PdfName.CF in trailer.Encrypt:
+ for name, params in iteritems(trailer.Encrypt.CF):
+ if name == PdfName.Identity:
+ continue
+
+ cfm = params.CFM
+ if cfm == PdfName.AESV2:
+ crypt_filters[name] = crypt.AESCryptFilter(key)
+ elif cfm == PdfName.V2:
+ crypt_filters[name] = crypt.RC4CryptFilter(key)
+ else:
+ source.warning(
+ 'Unsupported crypt filter: {}, {}'.format(
+ name, cfm))
+
+ # Read default stream filter
+ if PdfName.StmF in trailer.Encrypt:
+ name = trailer.Encrypt.StmF
+ if name in crypt_filters:
+ private.stream_crypt_filter = crypt_filters[name]
+ else:
+ source.warning(
+ 'Invalid crypt filter name in /StmF:'
+ ' {}'.format(name))
+
+ # Read default string filter
+ if PdfName.StrF in trailer.Encrypt:
+ name = trailer.Encrypt.StrF
+ if name in crypt_filters:
+ private.string_crypt_filter = crypt_filters[name]
+ else:
+ source.warning(
+ 'Invalid crypt filter name in /StrF:'
+ ' {}'.format(name))
+ else:
+ source.warning(
+ 'Unsupported Encrypt version: {}'.format(version))
+
+ def __init__(self, fname=None, fdata=None, decompress=False,
+ decrypt=False, password='', disable_gc=True, verbose=True):
+ self.private.verbose = verbose
+
+ # Runs a lot faster with GC off.
+ disable_gc = disable_gc and gc.isenabled()
+ if disable_gc:
+ gc.disable()
+
+ try:
+ if fname is not None:
+ assert fdata is None
+ # Allow reading preexisting streams like pyPdf
+ if hasattr(fname, 'read'):
+ fdata = fname.read()
+ else:
+ try:
+ f = open(fname, 'rb')
+ fdata = f.read()
+ f.close()
+ except IOError:
+ raise PdfParseError('Could not read PDF file %s' %
+ fname)
+
+ assert fdata is not None
+ fdata = convert_load(fdata)
+
+ if not fdata.startswith('%PDF-'):
+ startloc = fdata.find('%PDF-')
+ if startloc >= 0:
+ log.warning('PDF header not at beginning of file')
+ else:
+ lines = fdata.lstrip().splitlines()
+ if not lines:
+ raise PdfParseError('Empty PDF file!')
+ raise PdfParseError('Invalid PDF header: %s' %
+ repr(lines[0]))
+
+ self.private.version = fdata[5:8]
+
+ endloc = fdata.rfind('%EOF')
+ if endloc < 0:
+ raise PdfParseError('EOF mark not found: %s' %
+ repr(fdata[-20:]))
+ endloc += 6
+ junk = fdata[endloc:]
+ fdata = fdata[:endloc]
+ if junk.rstrip('\00').strip():
+ log.warning('Extra data at end of file')
+
+ private = self.private
+ private.indirect_objects = {}
+ private.deferred_objects = set()
+ private.special = {'<<': self.readdict,
+ '[': self.readarray,
+ 'endobj': self.empty_obj,
+ }
+ for tok in r'\ ( ) < > { } ] >> %'.split():
+ self.special[tok] = self.badtoken
+
+ startloc, source = self.findxref(fdata)
+ private.source = source
+
+ # Find all the xref tables/streams, and
+ # then deal with them backwards.
+ xref_list = []
+ while 1:
+ source.obj_offsets = {}
+ trailer, is_stream = self.parsexref(source)
+ prev = trailer.Prev
+ if prev is None:
+ token = source.next()
+ if token != 'startxref' and not xref_list:
+ source.warning('Expected "startxref" '
+ 'at end of xref table')
+ break
+ xref_list.append((source.obj_offsets, trailer, is_stream))
+ source.floc = int(prev)
+
+ # Handle document encryption
+ private.crypt_filters = None
+ if decrypt and PdfName.Encrypt in trailer:
+ identity_filter = crypt.IdentityCryptFilter()
+ crypt_filters = {
+ PdfName.Identity: identity_filter
+ }
+ private.crypt_filters = crypt_filters
+ private.stream_crypt_filter = identity_filter
+ private.string_crypt_filter = identity_filter
+
+ if not crypt.HAS_CRYPTO:
+ raise PdfParseError(
+ 'Install PyCrypto to enable encryption support')
+
+ self._parse_encrypt_info(source, password, trailer)
+
+ if is_stream:
+ self.load_stream_objects(trailer.object_streams)
+
+ while xref_list:
+ later_offsets, later_trailer, is_stream = xref_list.pop()
+ source.obj_offsets.update(later_offsets)
+ if is_stream:
+ trailer.update(later_trailer)
+ self.load_stream_objects(later_trailer.object_streams)
+ else:
+ trailer = later_trailer
+
+ trailer.Prev = None
+
+ if (trailer.Version and
+ float(trailer.Version) > float(self.version)):
+ self.private.version = trailer.Version
+
+ if decrypt:
+ self.decrypt_all()
+ trailer.Encrypt = None
+
+ if is_stream:
+ self.Root = trailer.Root
+ self.Info = trailer.Info
+ self.ID = trailer.ID
+ self.Size = trailer.Size
+ self.Encrypt = trailer.Encrypt
+ else:
+ self.update(trailer)
+
+ # self.read_all_indirect(source)
+ private.pages = self.readpages(self.Root)
+ if decompress:
+ self.uncompress()
+
+ # For compatibility with pyPdf
+ private.numPages = len(self.pages)
+ finally:
+ if disable_gc:
+ gc.enable()
+
+ # For compatibility with pyPdf
+ def getPage(self, pagenum):
+ return self.pages[pagenum]
diff --git a/plugin.program.AML/pdfrw/pdfrw/pdfwriter.py b/plugin.program.AML/pdfrw/pdfrw/pdfwriter.py
new file mode 100644
index 0000000000..3c887baa3f
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/pdfwriter.py
@@ -0,0 +1,385 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2006-2015 Patrick Maupin, Austin, Texas
+# MIT license -- See LICENSE.txt for details
+
+'''
+The PdfWriter class writes an entire PDF file out to disk.
+
+The writing process is not at all optimized or organized.
+
+An instance of the PdfWriter class has two methods:
+ addpage(page)
+and
+ write(fname)
+
+addpage() assumes that the pages are part of a valid
+tree/forest of PDF objects.
+'''
+import gc
+
+from .objects import (PdfName, PdfArray, PdfDict, IndirectPdfDict,
+ PdfObject, PdfString)
+from .compress import compress as do_compress
+from .errors import PdfOutputError, log
+from .py23_diffs import iteritems, convert_store
+
+NullObject = PdfObject('null')
+NullObject.indirect = True
+NullObject.Type = 'Null object'
+
+
+def user_fmt(obj, isinstance=isinstance, float=float, str=str,
+ basestring=(type(u''), type(b'')), encode=PdfString.encode):
+ ''' This function may be replaced by the user for
+ specialized formatting requirements.
+ '''
+
+ if isinstance(obj, basestring):
+ return encode(obj)
+
+ # PDFs don't handle exponent notation
+ if isinstance(obj, float):
+ return ('%.9f' % obj).rstrip('0').rstrip('.')
+
+ return str(obj)
+
+
+def FormatObjects(f, trailer, version='1.3', compress=True, killobj=(),
+ user_fmt=user_fmt, do_compress=do_compress,
+ convert_store=convert_store, iteritems=iteritems,
+ id=id, isinstance=isinstance, getattr=getattr, len=len,
+ sum=sum, set=set, str=str, hasattr=hasattr, repr=repr,
+ enumerate=enumerate, list=list, dict=dict, tuple=tuple,
+ PdfArray=PdfArray, PdfDict=PdfDict, PdfObject=PdfObject):
+ ''' FormatObjects performs the actual formatting and disk write.
+ Should be a class, was a class, turned into nested functions
+ for performace (to reduce attribute lookups).
+ '''
+
+ def f_write(s):
+ f.write(convert_store(s))
+
+ def add(obj):
+ ''' Add an object to our list, if it's an indirect
+ object. Just format it if not.
+ '''
+ # Can't hash dicts, so just hash the object ID
+ objid = id(obj)
+
+ # Automatically set stream objects to indirect
+ if isinstance(obj, PdfDict):
+ indirect = obj.indirect or (obj.stream is not None)
+ else:
+ indirect = getattr(obj, 'indirect', False)
+
+ if not indirect:
+ if objid in visited:
+ log.warning('Replicating direct %s object, '
+ 'should be indirect for optimal file size' %
+ type(obj))
+ obj = type(obj)(obj)
+ objid = id(obj)
+ visiting(objid)
+ result = format_obj(obj)
+ leaving(objid)
+ return result
+
+ objnum = indirect_dict_get(objid)
+
+ # If we haven't seen the object yet, we need to
+ # add it to the indirect object list.
+ if objnum is None:
+ swapped = swapobj(objid)
+ if swapped is not None:
+ old_id = objid
+ obj = swapped
+ objid = id(obj)
+ objnum = indirect_dict_get(objid)
+ if objnum is not None:
+ indirect_dict[old_id] = objnum
+ return '%s 0 R' % objnum
+ objnum = len(objlist) + 1
+ objlist_append(None)
+ indirect_dict[objid] = objnum
+ deferred.append((objnum - 1, obj))
+ return '%s 0 R' % objnum
+
+ def format_array(myarray, formatter):
+ # Format array data into semi-readable ASCII
+ if sum([len(x) for x in myarray]) <= 70:
+ return formatter % space_join(myarray)
+ return format_big(myarray, formatter)
+
+ def format_big(myarray, formatter):
+ bigarray = []
+ count = 1000000
+ for x in myarray:
+ lenx = len(x) + 1
+ count += lenx
+ if count > 71:
+ subarray = []
+ bigarray.append(subarray)
+ count = lenx
+ subarray.append(x)
+ return formatter % lf_join([space_join(x) for x in bigarray])
+
+ def format_obj(obj):
+ ''' format PDF object data into semi-readable ASCII.
+ May mutually recurse with add() -- add() will
+ return references for indirect objects, and add
+ the indirect object to the list.
+ '''
+ while 1:
+ if isinstance(obj, (list, dict, tuple)):
+ if isinstance(obj, PdfArray):
+ myarray = [add(x) for x in obj]
+ return format_array(myarray, '[%s]')
+ elif isinstance(obj, PdfDict):
+ if compress and obj.stream:
+ do_compress([obj])
+ pairs = sorted((getattr(x, 'encoded', None) or x, y)
+ for (x, y) in obj.iteritems())
+ myarray = []
+ for key, value in pairs:
+ myarray.append(key)
+ myarray.append(add(value))
+ result = format_array(myarray, '<<%s>>')
+ stream = obj.stream
+ if stream is not None:
+ result = ('%s\nstream\n%s\nendstream' %
+ (result, stream))
+ return result
+ obj = (PdfArray, PdfDict)[isinstance(obj, dict)](obj)
+ continue
+
+ # We assume that an object with an indirect
+ # attribute knows how to represent itself to us.
+ if hasattr(obj, 'indirect'):
+ return str(getattr(obj, 'encoded', None) or obj)
+ return user_fmt(obj)
+
+ def format_deferred():
+ while deferred:
+ index, obj = deferred.pop()
+ objlist[index] = format_obj(obj)
+
+ indirect_dict = {}
+ indirect_dict_get = indirect_dict.get
+ objlist = []
+ objlist_append = objlist.append
+ visited = set()
+ visiting = visited.add
+ leaving = visited.remove
+ space_join = ' '.join
+ lf_join = '\n '.join
+
+ deferred = []
+
+ # Don't reference old catalog or pages objects --
+ # swap references to new ones.
+ type_remap = {PdfName.Catalog: trailer.Root,
+ PdfName.Pages: trailer.Root.Pages, None: trailer}.get
+ swapobj = [(objid, type_remap(obj.Type) if new_obj is None else new_obj)
+ for objid, (obj, new_obj) in iteritems(killobj)]
+ swapobj = dict((objid, obj is None and NullObject or obj)
+ for objid, obj in swapobj).get
+
+ for objid in killobj:
+ assert swapobj(objid) is not None
+
+ # The first format of trailer gets all the information,
+ # but we throw away the actual trailer formatting.
+ format_obj(trailer)
+ # Keep formatting until we're done.
+ # (Used to recurse inside format_obj for this, but
+ # hit system limit.)
+ format_deferred()
+ # Now we know the size, so we update the trailer dict
+ # and get the formatted data.
+ trailer.Size = PdfObject(len(objlist) + 1)
+ trailer = format_obj(trailer)
+
+ # Now we have all the pieces to write out to the file.
+ # Keep careful track of the counts while we do it so
+ # we can correctly build the cross-reference.
+
+ header = '%%PDF-%s\n%%\xe2\xe3\xcf\xd3\n' % version
+ f_write(header)
+ offset = len(header)
+ offsets = [(0, 65535, 'f')]
+ offsets_append = offsets.append
+
+ for i, x in enumerate(objlist):
+ objstr = '%s 0 obj\n%s\nendobj\n' % (i + 1, x)
+ offsets_append((offset, 0, 'n'))
+ offset += len(objstr)
+ f_write(objstr)
+
+ f_write('xref\n0 %s\n' % len(offsets))
+ for x in offsets:
+ f_write('%010d %05d %s\r\n' % x)
+ f_write('trailer\n\n%s\nstartxref\n%s\n%%%%EOF\n' % (trailer, offset))
+
+
+class PdfWriter(object):
+
+ _trailer = None
+ canonicalize = False
+ fname = None
+
+ def __init__(self, fname=None, version='1.3', compress=False, **kwargs):
+ """
+ Parameters:
+ fname -- Output file name, or file-like binary object
+ with a write method
+ version -- PDF version to target. Currently only 1.3
+ supported.
+ compress -- True to do compression on output. Currently
+ compresses stream objects.
+ """
+
+ # Legacy support: fname is new, was added in front
+ if fname is not None:
+ try:
+ float(fname)
+ except (ValueError, TypeError):
+ pass
+ else:
+ if version != '1.3':
+ assert compress == False
+ compress = version
+ version = fname
+ fname = None
+
+ self.fname = fname
+ self.version = version
+ self.compress = compress
+
+ if kwargs:
+ for name, value in iteritems(kwargs):
+ if name not in self.replaceable:
+ raise ValueError("Cannot set attribute %s "
+ "on PdfWriter instance" % name)
+ setattr(self, name, value)
+
+ self.pagearray = PdfArray()
+ self.killobj = {}
+
+ def addpage(self, page):
+ self._trailer = None
+ if page.Type != PdfName.Page:
+ raise PdfOutputError('Bad /Type: Expected %s, found %s'
+ % (PdfName.Page, page.Type))
+ inheritable = page.inheritable # searches for resources
+ self.pagearray.append(
+ IndirectPdfDict(
+ page,
+ Resources=inheritable.Resources,
+ MediaBox=inheritable.MediaBox,
+ CropBox=inheritable.CropBox,
+ Rotate=inheritable.Rotate,
+ )
+ )
+
+ # Add parents in the hierarchy to objects we
+ # don't want to output
+ killobj = self.killobj
+ obj, new_obj = page, self.pagearray[-1]
+ while obj is not None:
+ objid = id(obj)
+ if objid in killobj:
+ break
+ killobj[objid] = obj, new_obj
+ obj = obj.Parent
+ new_obj = None
+ return self
+
+ addPage = addpage # for compatibility with pyPdf
+
+ def addpages(self, pagelist):
+ for page in pagelist:
+ self.addpage(page)
+ return self
+
+ def _get_trailer(self):
+ trailer = self._trailer
+ if trailer is not None:
+ return trailer
+
+ if self.canonicalize:
+ self.make_canonical()
+
+ # Create the basic object structure of the PDF file
+ trailer = PdfDict(
+ Root=IndirectPdfDict(
+ Type=PdfName.Catalog,
+ Pages=IndirectPdfDict(
+ Type=PdfName.Pages,
+ Count=PdfObject(len(self.pagearray)),
+ Kids=self.pagearray
+ )
+ )
+ )
+ # Make all the pages point back to the page dictionary and
+ # ensure they are indirect references
+ pagedict = trailer.Root.Pages
+ for page in pagedict.Kids:
+ page.Parent = pagedict
+ page.indirect = True
+ self._trailer = trailer
+ return trailer
+
+ def _set_trailer(self, trailer):
+ self._trailer = trailer
+
+ trailer = property(_get_trailer, _set_trailer)
+
+ def write(self, fname=None, trailer=None, user_fmt=user_fmt,
+ disable_gc=True):
+
+ trailer = trailer or self.trailer
+
+ # Support fname for legacy applications
+ if (fname is not None) == (self.fname is not None):
+ raise PdfOutputError(
+ "PdfWriter fname must be specified exactly once")
+
+ fname = fname or self.fname
+
+ # Dump the data. We either have a filename or a preexisting
+ # file object.
+ preexisting = hasattr(fname, 'write')
+ f = preexisting and fname or open(fname, 'wb')
+ if disable_gc:
+ gc.disable()
+
+ try:
+ FormatObjects(f, trailer, self.version, self.compress,
+ self.killobj, user_fmt=user_fmt)
+ finally:
+ if not preexisting:
+ f.close()
+ if disable_gc:
+ gc.enable()
+
+ def make_canonical(self):
+ ''' Canonicalizes a PDF. Assumes everything
+ is a Pdf object already.
+ '''
+ visited = set()
+ workitems = list(self.pagearray)
+ while workitems:
+ obj = workitems.pop()
+ objid = id(obj)
+ if objid in visited:
+ continue
+ visited.add(objid)
+ obj.indirect = False
+ if isinstance(obj, (PdfArray, PdfDict)):
+ obj.indirect = True
+ if isinstance(obj, PdfArray):
+ workitems += obj
+ else:
+ workitems += obj.values()
+
+ replaceable = set(vars())
\ No newline at end of file
diff --git a/plugin.program.AML/pdfrw/pdfrw/py23_diffs.py b/plugin.program.AML/pdfrw/pdfrw/py23_diffs.py
new file mode 100644
index 0000000000..b3509d0204
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/py23_diffs.py
@@ -0,0 +1,53 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2006-2015 Patrick Maupin, Austin, Texas
+# MIT license -- See LICENSE.txt for details
+
+# Deal with Python2/3 differences
+
+try:
+ import zlib
+except ImportError:
+ zlib = None
+
+try:
+ unicode = unicode
+except NameError:
+
+ def convert_load(s):
+ if isinstance(s, bytes):
+ return s.decode('Latin-1')
+ return s
+
+ def convert_store(s):
+ return s.encode('Latin-1')
+
+ def from_array(a):
+ return a.tobytes()
+
+else:
+
+ def convert_load(s):
+ return s
+
+ def convert_store(s):
+ return s
+
+ def from_array(a):
+ return a.tostring()
+
+nextattr, = (x for x in dir(iter([])) if 'next' in x)
+
+try:
+ iteritems = dict.iteritems
+except AttributeError:
+ iteritems = dict.items
+
+try:
+ xrange = xrange
+except NameError:
+ xrange = range
+
+try:
+ intern = intern
+except NameError:
+ from sys import intern
diff --git a/plugin.program.AML/pdfrw/pdfrw/tokens.py b/plugin.program.AML/pdfrw/pdfrw/tokens.py
new file mode 100644
index 0000000000..2b69e02c94
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/tokens.py
@@ -0,0 +1,229 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2006-2015 Patrick Maupin, Austin, Texas
+# MIT license -- See LICENSE.txt for details
+
+'''
+A tokenizer for PDF streams.
+
+In general, documentation used was "PDF reference",
+sixth edition, for PDF version 1.7, dated November 2006.
+
+'''
+
+import re
+import itertools
+from .objects import PdfString, PdfObject
+from .objects.pdfname import BasePdfName
+from .errors import log, PdfParseError
+from .py23_diffs import nextattr, intern
+
+
+def linepos(fdata, loc):
+ line = fdata.count('\n', 0, loc) + 1
+ line += fdata.count('\r', 0, loc) - fdata.count('\r\n', 0, loc)
+ col = loc - max(fdata.rfind('\n', 0, loc), fdata.rfind('\r', 0, loc))
+ return line, col
+
+
+class PdfTokens(object):
+
+ # Table 3.1, page 50 of reference, defines whitespace
+ eol = '\n\r'
+ whitespace = '\x00 \t\f' + eol
+
+ # Text on page 50 defines delimiter characters
+ # Escape the ]
+ delimiters = r'()<>{}[\]/%'
+
+ # "normal" stuff is all but delimiters or whitespace.
+
+ p_normal = r'(?:[^\\%s%s]+|\\[^%s])+' % (whitespace, delimiters,
+ whitespace)
+
+ p_comment = r'\%%[^%s]*' % eol
+
+ # This will get the bulk of literal strings.
+ p_literal_string = r'\((?:[^\\()]+|\\.)*[()]?'
+
+ # This will get more pieces of literal strings
+ # (Don't ask me why, but it hangs without the trailing ?.)
+ p_literal_string_extend = r'(?:[^\\()]+|\\.)*[()]?'
+
+ # A hex string. This one's easy.
+ p_hex_string = r'\<[%s0-9A-Fa-f]*\>' % whitespace
+
+ p_dictdelim = r'\<\<|\>\>'
+ p_name = r'/[^%s%s]*' % (delimiters, whitespace)
+
+ p_catchall = '[^%s]' % whitespace
+
+ pattern = '|'.join([p_normal, p_name, p_hex_string, p_dictdelim,
+ p_literal_string, p_comment, p_catchall])
+ findtok = re.compile('(%s)[%s]*' % (pattern, whitespace),
+ re.DOTALL).finditer
+ findparen = re.compile('(%s)[%s]*' % (p_literal_string_extend,
+ whitespace), re.DOTALL).finditer
+
+ def _gettoks(self, startloc, intern=intern,
+ delimiters=delimiters, findtok=findtok,
+ findparen=findparen, PdfString=PdfString,
+ PdfObject=PdfObject, BasePdfName=BasePdfName):
+ ''' Given a source data string and a location inside it,
+ gettoks generates tokens. Each token is a tuple of the form:
+ , ,
+ The ending file loc is past any trailing whitespace.
+
+ The main complication here is the literal strings, which
+ can contain nested parentheses. In order to cope with these
+ we can discard the current iterator and loop back to the
+ top to get a fresh one.
+
+ We could use re.search instead of re.finditer, but that's slower.
+ '''
+ fdata = self.fdata
+ current = self.current = [(startloc, startloc)]
+ cache = {}
+ get_cache = cache.get
+ while 1:
+ for match in findtok(fdata, current[0][1]):
+ current[0] = tokspan = match.span()
+ token = match.group(1)
+ firstch = token[0]
+ toktype = intern
+ if firstch not in delimiters:
+ toktype = PdfObject
+ elif firstch in '/<(%':
+ if firstch == '/':
+ # PDF Name
+ toktype = BasePdfName
+ elif firstch == '<':
+ # << dict delim, or < hex string >
+ if token[1:2] != '<':
+ toktype = PdfString
+ elif firstch == '(':
+ # Literal string
+ # It's probably simple, but maybe not
+ # Nested parentheses are a bear, and if
+ # they are present, we exit the for loop
+ # and get back in with a new starting location.
+ ends = None # For broken strings
+ if fdata[match.end(1) - 1] != ')':
+ nest = 2
+ m_start, loc = tokspan
+ for match in findparen(fdata, loc):
+ loc = match.end(1)
+ ending = fdata[loc - 1] == ')'
+ nest += 1 - ending * 2
+ if not nest:
+ break
+ if ending and ends is None:
+ ends = loc, match.end(), nest
+ token = fdata[m_start:loc]
+ current[0] = m_start, match.end()
+ if nest:
+ # There is one possible recoverable error
+ # seen in the wild -- some stupid generators
+ # don't escape (. If this happens, just
+ # terminate on first unescaped ). The string
+ # won't be quite right, but that's a science
+ # fair project for another time.
+ (self.error, self.exception)[not ends](
+ 'Unterminated literal string')
+ loc, ends, nest = ends
+ token = fdata[m_start:loc] + ')' * nest
+ current[0] = m_start, ends
+ toktype = PdfString
+ elif firstch == '%':
+ # Comment
+ if self.strip_comments:
+ continue
+ else:
+ self.exception(('Tokenizer logic incorrect -- '
+ 'should never get here'))
+
+ newtok = get_cache(token)
+ if newtok is None:
+ newtok = cache[token] = toktype(token)
+ yield newtok
+ if current[0] is not tokspan:
+ break
+ else:
+ if self.strip_comments:
+ break
+ raise StopIteration
+
+ def __init__(self, fdata, startloc=0, strip_comments=True, verbose=True):
+ self.fdata = fdata
+ self.strip_comments = strip_comments
+ self.iterator = iterator = self._gettoks(startloc)
+ self.msgs_dumped = None if verbose else set()
+ self.next = getattr(iterator, nextattr)
+ self.current = [(startloc, startloc)]
+
+ def setstart(self, startloc):
+ ''' Change the starting location.
+ '''
+ current = self.current
+ if startloc != current[0][1]:
+ current[0] = startloc, startloc
+
+ def floc(self):
+ ''' Return the current file position
+ (where the next token will be retrieved)
+ '''
+ return self.current[0][1]
+ floc = property(floc, setstart)
+
+ def tokstart(self):
+ ''' Return the file position of the most
+ recently retrieved token.
+ '''
+ return self.current[0][0]
+ tokstart = property(tokstart, setstart)
+
+ def __iter__(self):
+ return self.iterator
+
+ def multiple(self, count, islice=itertools.islice, list=list):
+ ''' Retrieve multiple tokens
+ '''
+ return list(islice(self, count))
+
+ def next_default(self, default='nope'):
+ for result in self:
+ return result
+ return default
+
+ def msg(self, msg, *arg):
+ dumped = self.msgs_dumped
+ if dumped is not None:
+ if msg in dumped:
+ return
+ dumped.add(msg)
+ if arg:
+ msg %= arg
+ fdata = self.fdata
+ begin, end = self.current[0]
+ if begin >= len(fdata):
+ return '%s (filepos %s past EOF %s)' % (msg, begin, len(fdata))
+ line, col = linepos(fdata, begin)
+ if end > begin:
+ tok = fdata[begin:end].rstrip()
+ if len(tok) > 30:
+ tok = tok[:26] + ' ...'
+ return ('%s (line=%d, col=%d, token=%s)' %
+ (msg, line, col, repr(tok)))
+ return '%s (line=%d, col=%d)' % (msg, line, col)
+
+ def warning(self, *arg):
+ s = self.msg(*arg)
+ if s:
+ log.warning(s)
+
+ def error(self, *arg):
+ s = self.msg(*arg)
+ if s:
+ log.error(s)
+
+ def exception(self, *arg):
+ raise PdfParseError(self.msg(*arg))
diff --git a/plugin.program.AML/pdfrw/pdfrw/toreportlab.py b/plugin.program.AML/pdfrw/pdfrw/toreportlab.py
new file mode 100644
index 0000000000..3434fbf2c1
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/toreportlab.py
@@ -0,0 +1,146 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2006-2015 Patrick Maupin, Austin, Texas
+# MIT license -- See LICENSE.txt for details
+
+'''
+Converts pdfrw objects into reportlab objects.
+
+Designed for and tested with rl 2.3.
+
+Knows too much about reportlab internals.
+What can you do?
+
+The interface to this function is through the makerl() function.
+
+Parameters:
+ canv - a reportlab "canvas" (also accepts a "document")
+ pdfobj - a pdfrw PDF object
+
+Returns:
+ A corresponding reportlab object, or if the
+ object is a PDF Form XObject, the name to
+ use with reportlab for the object.
+
+ Will recursively convert all necessary objects.
+ Be careful when converting a page -- if /Parent is set,
+ will recursively convert all pages!
+
+Notes:
+ 1) Original objects are annotated with a
+ derived_rl_obj attribute which points to the
+ reportlab object. This keeps multiple reportlab
+ objects from being generated for the same pdfobj
+ via repeated calls to makerl. This is great for
+ not putting too many objects into the
+ new PDF, but not so good if you are modifying
+ objects for different pages. Then you
+ need to do your own deep copying (of circular
+ structures). You're on your own.
+
+ 2) ReportLab seems weird about FormXObjects.
+ They pass around a partial name instead of the
+ object or a reference to it. So we have to
+ reach into reportlab and get a number for
+ a unique name. I guess this is to make it
+ where you can combine page streams with
+ impunity, but that's just a guess.
+
+ 3) Updated 1/23/2010 to handle multipass documents
+ (e.g. with a table of contents). These have
+ a different doc object on every pass.
+
+'''
+
+from reportlab.pdfbase import pdfdoc as rldocmodule
+from .objects import PdfDict, PdfArray, PdfName
+from .py23_diffs import convert_store
+
+RLStream = rldocmodule.PDFStream
+RLDict = rldocmodule.PDFDictionary
+RLArray = rldocmodule.PDFArray
+
+
+def _makedict(rldoc, pdfobj):
+ rlobj = rldict = RLDict()
+ if pdfobj.indirect:
+ rlobj.__RefOnly__ = 1
+ rlobj = rldoc.Reference(rlobj)
+ pdfobj.derived_rl_obj[rldoc] = rlobj, None
+
+ for key, value in pdfobj.iteritems():
+ rldict[key[1:]] = makerl_recurse(rldoc, value)
+
+ return rlobj
+
+
+def _makestream(rldoc, pdfobj, xobjtype=PdfName.XObject):
+ rldict = RLDict()
+ rlobj = RLStream(rldict, convert_store(pdfobj.stream))
+
+ if pdfobj.Type == xobjtype:
+ shortname = 'pdfrw_%s' % (rldoc.objectcounter + 1)
+ fullname = rldoc.getXObjectName(shortname)
+ else:
+ shortname = fullname = None
+ result = rldoc.Reference(rlobj, fullname)
+ pdfobj.derived_rl_obj[rldoc] = result, shortname
+
+ for key, value in pdfobj.iteritems():
+ rldict[key[1:]] = makerl_recurse(rldoc, value)
+
+ return result
+
+
+def _makearray(rldoc, pdfobj):
+ rlobj = rlarray = RLArray([])
+ if pdfobj.indirect:
+ rlobj.__RefOnly__ = 1
+ rlobj = rldoc.Reference(rlobj)
+ pdfobj.derived_rl_obj[rldoc] = rlobj, None
+
+ mylist = rlarray.sequence
+ for value in pdfobj:
+ mylist.append(makerl_recurse(rldoc, value))
+
+ return rlobj
+
+
+def _makestr(rldoc, pdfobj):
+ assert isinstance(pdfobj, (float, int, str)), repr(pdfobj)
+ # TODO: Add fix for float like in pdfwriter
+ return str(getattr(pdfobj, 'encoded', None) or pdfobj)
+
+
+def makerl_recurse(rldoc, pdfobj):
+ docdict = getattr(pdfobj, 'derived_rl_obj', None)
+ if docdict is not None:
+ value = docdict.get(rldoc)
+ if value is not None:
+ return value[0]
+ if isinstance(pdfobj, PdfDict):
+ if pdfobj.stream is not None:
+ func = _makestream
+ else:
+ func = _makedict
+ if docdict is None:
+ pdfobj.private.derived_rl_obj = {}
+ elif isinstance(pdfobj, PdfArray):
+ func = _makearray
+ if docdict is None:
+ pdfobj.derived_rl_obj = {}
+ else:
+ func = _makestr
+ return func(rldoc, pdfobj)
+
+
+def makerl(canv, pdfobj):
+ try:
+ rldoc = canv._doc
+ except AttributeError:
+ rldoc = canv
+ rlobj = makerl_recurse(rldoc, pdfobj)
+ try:
+ name = pdfobj.derived_rl_obj[rldoc][1]
+ except AttributeError:
+ name = None
+ return name or rlobj
diff --git a/plugin.program.AML/pdfrw/pdfrw/uncompress.py b/plugin.program.AML/pdfrw/pdfrw/uncompress.py
new file mode 100644
index 0000000000..1921817e35
--- /dev/null
+++ b/plugin.program.AML/pdfrw/pdfrw/uncompress.py
@@ -0,0 +1,194 @@
+# A part of pdfrw (https://github.com/pmaupin/pdfrw)
+# Copyright (C) 2006-2015 Patrick Maupin, Austin, Texas
+# Copyright (C) 2012-2015 Nerijus Mika
+# MIT license -- See LICENSE.txt for details
+# Copyright (c) 2006, Mathieu Fenniak
+# BSD license -- see LICENSE.txt for details
+'''
+A small subset of decompression filters. Should add more later.
+
+I believe, after looking at the code, that portions of the flate
+PNG predictor were originally transcribed from PyPDF2, which is
+probably an excellent source of additional filters.
+'''
+import array
+from .objects import PdfDict, PdfName, PdfArray
+from .errors import log
+from .py23_diffs import zlib, xrange, from_array, convert_load, convert_store
+import math
+
+def streamobjects(mylist, isinstance=isinstance, PdfDict=PdfDict):
+ for obj in mylist:
+ if isinstance(obj, PdfDict) and obj.stream is not None:
+ yield obj
+
+# Hack so we can import if zlib not available
+decompressobj = zlib if zlib is None else zlib.decompressobj
+
+
+def uncompress(mylist, leave_raw=False, warnings=set(),
+ flate=PdfName.FlateDecode, decompress=decompressobj,
+ isinstance=isinstance, list=list, len=len):
+ ok = True
+ for obj in streamobjects(mylist):
+ ftype = obj.Filter
+ if ftype is None:
+ continue
+ if isinstance(ftype, list) and len(ftype) == 1:
+ # todo: multiple filters
+ ftype = ftype[0]
+ parms = obj.DecodeParms or obj.DP
+ if ftype != flate:
+ msg = ('Not decompressing: cannot use filter %s'
+ ' with parameters %s') % (repr(ftype), repr(parms))
+ if msg not in warnings:
+ warnings.add(msg)
+ log.warning(msg)
+ ok = False
+ else:
+ dco = decompress()
+ try:
+ data = dco.decompress(convert_store(obj.stream))
+ except Exception as s:
+ error = str(s)
+ else:
+ error = None
+ if isinstance(parms, PdfArray):
+ oldparms = parms
+ parms = PdfDict()
+ for x in oldparms:
+ parms.update(x)
+ if parms:
+ predictor = int(parms.Predictor or 1)
+ columns = int(parms.Columns or 1)
+ colors = int(parms.Colors or 1)
+ bpc = int(parms.BitsPerComponent or 8)
+ if 10 <= predictor <= 15:
+ data, error = flate_png(data, predictor, columns, colors, bpc)
+ elif predictor != 1:
+ error = ('Unsupported flatedecode predictor %s' %
+ repr(predictor))
+ if error is None:
+ assert not dco.unconsumed_tail
+ if dco.unused_data.strip():
+ error = ('Unconsumed compression data: %s' %
+ repr(dco.unused_data[:20]))
+ if error is None:
+ obj.Filter = None
+ obj.stream = data if leave_raw else convert_load(data)
+ else:
+ log.error('%s %s' % (error, repr(obj.indirect)))
+ ok = False
+ return ok
+
+def flate_png_impl(data, predictor=1, columns=1, colors=1, bpc=8):
+
+ # http://www.libpng.org/pub/png/spec/1.2/PNG-Filters.html
+ # https://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters
+ # Reconstruction functions
+ # x: the byte being filtered;
+ # a: the byte corresponding to x in the pixel immediately before the pixel containing x (or the byte immediately before x, when the bit depth is less than 8);
+ # b: the byte corresponding to x in the previous scanline;
+ # c: the byte corresponding to b in the pixel immediately before the pixel containing b (or the byte immediately before b, when the bit depth is less than 8).
+
+ def subfilter(data, prior_row_data, start, length, pixel_size):
+ # filter type 1: Sub
+ # Recon(x) = Filt(x) + Recon(a)
+ for i in xrange(pixel_size, length):
+ left = data[start + i - pixel_size]
+ data[start + i] = (data[start + i] + left) % 256
+
+ def upfilter(data, prior_row_data, start, length, pixel_size):
+ # filter type 2: Up
+ # Recon(x) = Filt(x) + Recon(b)
+ for i in xrange(length):
+ up = prior_row_data[i]
+ data[start + i] = (data[start + i] + up) % 256
+
+ def avgfilter(data, prior_row_data, start, length, pixel_size):
+ # filter type 3: Avg
+ # Recon(x) = Filt(x) + floor((Recon(a) + Recon(b)) / 2)
+ for i in xrange(length):
+ left = data[start + i - pixel_size] if i >= pixel_size else 0
+ up = prior_row_data[i]
+ floor = math.floor((left + up) / 2)
+ data[start + i] = (data[start + i] + int(floor)) % 256
+
+ def paethfilter(data, prior_row_data, start, length, pixel_size):
+ # filter type 4: Paeth
+ # Recon(x) = Filt(x) + PaethPredictor(Recon(a), Recon(b), Recon(c))
+ def paeth_predictor(a, b, c):
+ p = a + b - c
+ pa = abs(p - a)
+ pb = abs(p - b)
+ pc = abs(p - c)
+ if pa <= pb and pa <= pc:
+ return a
+ elif pb <= pc:
+ return b
+ else:
+ return c
+ for i in xrange(length):
+ left = data[start + i - pixel_size] if i >= pixel_size else 0
+ up = prior_row_data[i]
+ up_left = prior_row_data[i - pixel_size] if i >= pixel_size else 0
+ data[start + i] = (data[start + i] + paeth_predictor(left, up, up_left)) % 256
+
+ columnbytes = ((columns * colors * bpc) + 7) // 8
+ pixel_size = (colors * bpc + 7) // 8
+ data = array.array('B', data)
+ rowlen = columnbytes + 1
+ if predictor == 15:
+ padding = (rowlen - len(data)) % rowlen
+ data.extend([0] * padding)
+ assert len(data) % rowlen == 0
+
+ rows = xrange(0, len(data), rowlen)
+ prior_row_data = [ 0 for i in xrange(columnbytes) ]
+ for row_index in rows:
+
+ filter_type = data[row_index]
+
+ if filter_type == 0: # None filter
+ pass
+
+ elif filter_type == 1: # Sub filter
+ subfilter(data, prior_row_data, row_index + 1, columnbytes, pixel_size)
+
+ elif filter_type == 2: # Up filter
+ upfilter(data, prior_row_data, row_index + 1, columnbytes, pixel_size)
+
+ elif filter_type == 3: # Average filter
+ avgfilter(data, prior_row_data, row_index + 1, columnbytes, pixel_size)
+
+ elif filter_type == 4: # Paeth filter
+ paethfilter(data, prior_row_data, row_index + 1, columnbytes, pixel_size)
+
+ else:
+ return None, 'Unsupported PNG filter %d' % filter_type
+
+ prior_row_data = data[row_index + 1 : row_index + 1 + columnbytes] # without filter_type
+
+ for row_index in reversed(rows):
+ data.pop(row_index)
+
+ return data, None
+
+def flate_png(data, predictor=1, columns=1, colors=1, bpc=8):
+ ''' PNG prediction is used to make certain kinds of data
+ more compressible. Before the compression, each data
+ byte is either left the same, or is set to be a delta
+ from the previous byte, or is set to be a delta from
+ the previous row. This selection is done on a per-row
+ basis, and is indicated by a compression type byte
+ prepended to each row of data.
+
+ Within more recent PDF files, it is normal to use
+ this technique for Xref stream objects, which are
+ quite regular.
+ '''
+ d, e = flate_png_impl(data, predictor, columns, colors, bpc)
+ if d is not None:
+ d = from_array(d)
+ return d, e
+
diff --git a/plugin.program.AML/resources/__init__.py b/plugin.program.AML/resources/__init__.py
new file mode 100644
index 0000000000..a9e5f9b83e
--- /dev/null
+++ b/plugin.program.AML/resources/__init__.py
@@ -0,0 +1 @@
+# Dummy file to make directory a Python package
diff --git a/plugin.program.AML/resources/assets.py b/plugin.program.AML/resources/assets.py
new file mode 100644
index 0000000000..ee0dd7aea1
--- /dev/null
+++ b/plugin.program.AML/resources/assets.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2016-2020 Wintermute0110
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+
+# Advanced MAME Launcher asset (artwork) related stuff.
+
+# -------------------------------------------------------------------------------------------------
+# Asset functions
+# -------------------------------------------------------------------------------------------------
+#
+# Make sure this match the contents of settings.xml!
+# values="Title|Snap|Flyer|Cabinet|PCB"
+#
+def assets_get_asset_key_MAME_icon(asset_index):
+ asset_key = 'title' # Default value
+
+ if asset_index == 0: asset_key = 'title'
+ elif asset_index == 1: asset_key = 'snap'
+ elif asset_index == 2: asset_key = 'flyer'
+ elif asset_index == 3: asset_key = 'cabinet'
+ elif asset_index == 4: asset_key = 'PCB'
+
+ return asset_key
+
+#
+# values="Fanart|Snap|Title|Flyer|CPanel"
+#
+def assets_get_asset_key_MAME_fanart(asset_index):
+ asset_key = 'fanart' # Default value
+
+ if asset_index == 0: asset_key = 'fanart'
+ elif asset_index == 1: asset_key = 'snap'
+ elif asset_index == 2: asset_key = 'title'
+ elif asset_index == 3: asset_key = 'flyer'
+ elif asset_index == 4: asset_key = 'cpanel'
+
+ return asset_key
+
+#
+# values="Boxfront|Title|Snap"
+#
+def assets_get_asset_key_SL_icon(asset_index):
+ asset_key = 'boxfront' # Default value
+
+ if asset_index == 0: asset_key = 'boxfront'
+ elif asset_index == 1: asset_key = 'title'
+ elif asset_index == 2: asset_key = 'snap'
+
+ return asset_key
+
+#
+# values="Fanart|Snap|Title"
+#
+def assets_get_asset_key_SL_fanart(asset_index):
+ asset_key = 'fanart' # Default value
+
+ if asset_index == 0: asset_key = 'fanart'
+ elif asset_index == 1: asset_key = 'snap'
+ elif asset_index == 2: asset_key = 'title'
+
+ return asset_key
diff --git a/plugin.program.AML/resources/constants.py b/plugin.program.AML/resources/constants.py
new file mode 100644
index 0000000000..104fe802ac
--- /dev/null
+++ b/plugin.program.AML/resources/constants.py
@@ -0,0 +1,261 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2016-2021 Wintermute0110
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+
+# Advanced Emulator/MAME Launcher constants and globals.
+# This module has no external dependencies.
+
+# Transitional code from Python 2 to Python 3 (https://github.com/benjaminp/six/blob/master/six.py)
+import sys
+ADDON_RUNNING_PYTHON_2 = sys.version_info[0] == 2
+ADDON_RUNNING_PYTHON_3 = sys.version_info[0] == 3
+if ADDON_RUNNING_PYTHON_3:
+ text_type = str
+ binary_type = bytes
+elif ADDON_RUNNING_PYTHON_2:
+ text_type = unicode
+ binary_type = str
+else:
+ raise TypeError('Unknown Python runtime version')
+
+# -------------------------------------------------------------------------------------------------
+# Addon options and tuneables.
+# -------------------------------------------------------------------------------------------------
+# Compact, smaller size, non-human readable JSON. False forces human-readable JSON for development.
+# In AEL speed is not as critical so False. In AML this must be True when releasing.
+OPTION_COMPACT_JSON = False
+
+# Use less memory when writing big JSON files, but writing is slower.
+# In AEL this can be False when releasing. In AML it must be True.
+OPTION_LOWMEM_WRITE_JSON = False
+
+# The addon name in the GUI. Title of Kodi dialogs (yesno, progress, etc.) and used also in log functions.
+ADDON_LONG_NAME = 'Advanced MAME Launcher'
+ADDON_SHORT_NAME = 'AML'
+
+# These parameters are used in utils_write_JSON_file() when pprint is True or
+# OPTION_COMPACT_JSON is False. Otherwise non-human readable, compact JSON is written.
+# pprint = True function parameter overrides option OPTION_COMPACT_JSON.
+# More compact JSON files (less blanks) load faster because file size is smaller.
+JSON_INDENT = 1
+JSON_SEP = (', ', ': ')
+
+# -------------------------------------------------------------------------------------------------
+# DEBUG/TEST settings
+# -------------------------------------------------------------------------------------------------
+# If True MAME is not launched. Useful to test the Recently Played and Most Played code.
+# This setting must be False when releasing.
+DISABLE_MAME_LAUNCHING = False
+
+# -------------------------------------------------------------------------------------------------
+# This is to ease printing colors in Kodi.
+# -------------------------------------------------------------------------------------------------
+KC_RED = '[COLOR red]'
+KC_ORANGE = '[COLOR orange]'
+KC_GREEN = '[COLOR green]'
+KC_YELLOW = '[COLOR yellow]'
+KC_VIOLET = '[COLOR violet]'
+KC_BLUEVIOLET = '[COLOR blueviolet]'
+KC_END = '[/COLOR]'
+
+# -------------------------------------------------------------------------------------------------
+# Image file constants.
+# -------------------------------------------------------------------------------------------------
+# Supported image files in:
+# 1. misc_identify_image_id_by_contents()
+# 2. misc_identify_image_id_by_ext()
+IMAGE_PNG_ID = 'PNG'
+IMAGE_JPEG_ID = 'JPEG'
+IMAGE_GIF_ID = 'GIF'
+IMAGE_BMP_ID = 'BMP'
+IMAGE_TIFF_ID = 'TIFF'
+IMAGE_UKNOWN_ID = 'Image unknown'
+IMAGE_CORRUPT_ID = 'Image corrupt'
+
+IMAGE_IDS = [
+ IMAGE_PNG_ID,
+ IMAGE_JPEG_ID,
+ IMAGE_GIF_ID,
+ IMAGE_BMP_ID,
+ IMAGE_TIFF_ID,
+]
+
+IMAGE_EXTENSIONS = {
+ IMAGE_PNG_ID : ['png'],
+ IMAGE_JPEG_ID : ['jpg', 'jpeg'],
+ IMAGE_GIF_ID : ['gif'],
+ IMAGE_BMP_ID : ['bmp'],
+ IMAGE_TIFF_ID : ['tif', 'tiff'],
+}
+
+# Image file magic numbers. All at file offset 0.
+# See https://en.wikipedia.org/wiki/List_of_file_signatures
+# b prefix is a byte string in both Python 2 and 3.
+IMAGE_MAGIC_DIC = {
+ IMAGE_PNG_ID : [ b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A' ],
+ IMAGE_JPEG_ID : [
+ b'\xFF\xD8\xFF\xDB',
+ b'\xFF\xD8\xFF\xE0\x00\x10\x4A\x46\x49\x46\x00\x01',
+ b'\xFF\xD8\xFF\xEE',
+ b'\xFF\xD8\xFF\xE1',
+ ],
+ IMAGE_GIF_ID : [
+ b'\x47\x49\x46\x38\x37\x61',
+ b'\x47\x49\x46\x38\x39\x61',
+ ],
+ IMAGE_BMP_ID : [ b'\x42\x4D' ],
+ IMAGE_TIFF_ID : [
+ b'\x49\x49\x2A\x00',
+ b'\x4D\x4D\x00\x2A',
+ ]
+}
+
+# -------------------------------------------------------------------------------------------------
+# Addon constants
+# -------------------------------------------------------------------------------------------------
+# Operational modes
+# This must match setting op_mode_raw in settings.xml or bad things will happen.
+OP_MODE_VANILLA = 'Vanilla MAME'
+OP_MODE_RETRO_MAME2003PLUS = 'Retroarch MAME 2003 Plus'
+OP_MODE_RETRO_MAME2010 = 'Retroarch MAME 2010'
+OP_MODE_RETRO_MAME2014 = 'Retroarch MAME 2014'
+OP_MODE_LIST = [
+ OP_MODE_VANILLA,
+ OP_MODE_RETRO_MAME2003PLUS,
+ OP_MODE_RETRO_MAME2010,
+ OP_MODE_RETRO_MAME2014,
+]
+
+# In MAME 2003 Plus the MAME version is not found on the XML file.
+MAME2003PLUS_VERSION_RAW = '0.78 (RA2003Plus)'
+
+# Make sure these strings are equal to the ones in settings.xml or bad things will happen.
+VIEW_MODE_FLAT = 0 # 'Flat'
+VIEW_MODE_PCLONE = 1 # 'Parent/Clone'
+ROMSET_MAME_MERGED = 0 # 'Merged'
+ROMSET_MAME_SPLIT = 1 # 'Split'
+ROMSET_MAME_NONMERGED = 2 # 'Non-merged'
+ROMSET_MAME_FULLYNONMERGED = 3 # 'Fully non-merged'
+ROMSET_SL_MERGED = 0 # 'Merged'
+ROMSET_SL_SPLIT = 1 # 'Split'
+
+ROMSET_NAME_LIST = ['Merged', 'Split', 'Non-merged', 'Fully non-merged']
+CHDSET_NAME_LIST = ['Merged', 'Split', 'Non-merged']
+
+# -------------------------------------------------------------------------------------------------
+# Advanced MAME Launcher constants
+# -------------------------------------------------------------------------------------------------
+# Database status. Status it determined with timestamps in control_dic
+MAME_MAIN_DB_BUILT = 200
+MAME_AUDIT_DB_BUILT = 300
+MAME_CATALOG_BUILT = 400
+MAME_MACHINES_SCANNED = 500
+MAME_ASSETS_SCANNED = 600
+SL_MAIN_DB_BUILT = 700
+SL_ITEMS_SCANNED = 800
+SL_ASSETS_SCANNED = 900
+
+# INI and DAT files default names.
+ALLTIME_INI = 'Alltime.ini'
+ARTWORK_INI = 'Artwork.ini'
+BESTGAMES_INI = 'bestgames.ini'
+CATEGORY_INI = 'Category.ini'
+CATLIST_INI = 'catlist.ini'
+CATVER_INI = 'catver.ini'
+GENRE_INI = 'genre.ini'
+MATURE_INI = 'mature.ini'
+NPLAYERS_INI = 'nplayers.ini'
+SERIES_INI = 'series.ini'
+COMMAND_DAT = 'command.dat'
+GAMEINIT_DAT = 'gameinit.dat'
+HISTORY_XML = 'history.xml'
+HISTORY_DAT = 'history.dat'
+MAMEINFO_DAT = 'mameinfo.dat'
+
+# --- Used in the addon URLs so mark the location of machines/ROMs ---
+LOCATION_STANDARD = 'STANDARD'
+LOCATION_MAME_FAVS = 'MAME_FAVS'
+LOCATION_MAME_MOST_PLAYED = 'MAME_MOST_PLAYED'
+LOCATION_MAME_RECENT_PLAYED = 'MAME_RECENT_PLAYED'
+LOCATION_SL_FAVS = 'SL_FAVS'
+LOCATION_SL_MOST_PLAYED = 'SL_MOST_PLAYED'
+LOCATION_SL_RECENT_PLAYED = 'SL_RECENT_PLAYED'
+
+# --- ROM flags used by skins to display status icons ---
+AEL_INFAV_BOOL_LABEL = 'AEL_InFav'
+AEL_PCLONE_STAT_LABEL = 'AEL_PClone_stat'
+
+AEL_INFAV_BOOL_VALUE_TRUE = 'InFav_True'
+AEL_INFAV_BOOL_VALUE_FALSE = 'InFav_False'
+AEL_PCLONE_STAT_VALUE_PARENT = 'PClone_Parent'
+AEL_PCLONE_STAT_VALUE_CLONE = 'PClone_Clone'
+AEL_PCLONE_STAT_VALUE_NONE = 'PClone_None'
+
+# --- SL ROM launching cases ---
+SL_LAUNCH_CASE_A = 'Case A'
+SL_LAUNCH_CASE_B = 'Case B'
+SL_LAUNCH_CASE_C = 'Case C'
+SL_LAUNCH_CASE_D = 'Case D'
+SL_LAUNCH_CASE_ERROR = 'Case ERROR!'
+
+# --- ROM types ---
+ROM_TYPE_ROM = 'ROM' # Normal ROM (no merged, no BIOS)
+ROM_TYPE_BROM = 'BROM' # BIOS merged ROM
+ROM_TYPE_XROM = 'XROM' # BIOS non-merged ROM
+ROM_TYPE_MROM = 'MROM' # non-BIOS merged ROM
+ROM_TYPE_DROM = 'DROM' # Device ROM
+ROM_TYPE_DISK = 'DISK'
+ROM_TYPE_SAMPLE = 'SAM'
+ROM_TYPE_ERROR = 'ERR'
+
+# --- ROM audit status ---
+AUDIT_STATUS_OK = 'OK'
+AUDIT_STATUS_OK_INVALID_ROM = 'OK (invalid ROM)'
+AUDIT_STATUS_OK_INVALID_CHD = 'OK (invalid CHD)'
+AUDIT_STATUS_OK_WRONG_NAME_ROM = 'OK (wrong named ROM)'
+AUDIT_STATUS_ZIP_NO_FOUND = 'ZIP not found'
+AUDIT_STATUS_CHD_NO_FOUND = 'CHD not found'
+AUDIT_STATUS_BAD_ZIP_FILE = 'Bad ZIP file'
+AUDIT_STATUS_BAD_CHD_FILE = 'Bad CHD file'
+AUDIT_STATUS_ROM_NOT_IN_ZIP = 'ROM not in ZIP'
+AUDIT_STATUS_ROM_BAD_CRC = 'ROM bad CRC'
+AUDIT_STATUS_ROM_BAD_SIZE = 'ROM bad size'
+AUDIT_STATUS_CHD_BAD_VERSION = 'CHD bad version'
+AUDIT_STATUS_CHD_BAD_SHA1 = 'CHD bad SHA1'
+AUDIT_STATUS_SAMPLE_NOT_IN_ZIP = 'SAMPLE not in ZIP'
+
+# --- File name extensions ---
+MAME_ROM_EXTS = ['zip']
+MAME_CHD_EXTS = ['chd']
+MAME_SAMPLE_EXTS = ['zip']
+SL_ROM_EXTS = ['zip']
+SL_CHD_EXTS = ['chd']
+ASSET_ARTWORK_EXTS = ['zip']
+ASSET_MANUAL_EXTS = ['pdf', 'cbz', 'cbr']
+ASSET_TRAILER_EXTS = ['mp4']
+ASSET_IMAGE_EXTS = ['png']
+
+# Colors for filters and items in the root main menu.
+COLOR_FILTER_MAIN = '[COLOR thistle]'
+COLOR_FILTER_BINARY = '[COLOR lightblue]'
+COLOR_FILTER_CATALOG_DAT = '[COLOR violet]'
+COLOR_FILTER_CATALOG_NODAT = '[COLOR sandybrown]'
+COLOR_MAME_DAT_BROWSER = '[COLOR lightgreen]'
+COLOR_SOFTWARE_LISTS = '[COLOR goldenrod]'
+COLOR_MAME_CUSTOM_FILTERS = '[COLOR darkgray]'
+COLOR_AEL_ROLS = '[COLOR blue]'
+COLOR_MAME_SPECIAL = '[COLOR silver]'
+COLOR_SL_SPECIAL = '[COLOR gold]'
+COLOR_UTILITIES = '[COLOR limegreen]'
+COLOR_GLOBAL_REPORTS = '[COLOR darkorange]'
+COLOR_DEFAULT = '[COLOR white]'
+COLOR_END = '[/COLOR]'
diff --git a/plugin.program.AML/resources/db.py b/plugin.program.AML/resources/db.py
new file mode 100644
index 0000000000..da05e5927b
--- /dev/null
+++ b/plugin.program.AML/resources/db.py
@@ -0,0 +1,1222 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2016-2020 Wintermute0110
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+
+# Advanced MAME Launcher high-level filesystem I/O functions and database model.
+#
+# In the future this module must strictly use the FileName class for all IO operations and
+# not the Python runtime.
+
+# --- AEL packages ---
+from .constants import *
+from .utils import *
+
+# --- Python standard library ---
+import copy
+import hashlib
+import io
+import re
+import subprocess
+import threading
+import time
+import xml.etree.ElementTree as ET
+
+# -------------------------------------------------------------------------------------------------
+# Advanced MAME Launcher data model
+# -------------------------------------------------------------------------------------------------
+# http://xmlwriter.net/xml_guide/attlist_declaration.shtml#CdataEx
+# #REQUIRED The attribute must always be included
+# #IMPLIED The attribute does not have to be included.
+#
+# Example from MAME 0.190:
+#
+#
+#
+#
+#
+#
+#
+#
+#
+#
+#
+#
+# tags. Example of machine aes (Neo Geo AES)
+#
+#
+#
+#
+#
+#
+#
+#
+#
+# This is how it is stored:
+# devices = [
+# {
+# 'att_interface' : string,
+# 'att_mandatory' : bool,
+# 'att_tag' : string,
+# 'att_type' : string,
+# 'ext_names' : [string1, string2],
+# 'instance' : {'name' : string, 'briefname' : string}
+# }, ...
+# ]
+#
+# Rendering on AML Machine Information text window.
+# devices[0]:
+# att_interface: text_type
+# att_mandatory: text_type(bool)
+# att_tag: text_type
+# att_type: text_type
+# ext_names: text_type(string list),
+# instance: text_type(dictionary),
+# devices[1]:
+# ...
+#
+def db_new_machine_dic():
+ return {
+ # --- tag attributes ---
+ 'isMechanical' : False,
+ 'romof' : '',
+ 'sampleof' : '',
+ 'sourcefile' : '',
+ # --- Other tags inside from MAME XML ---
+ #
+ #
+ # Name of the chip when type == 'cpu'
+ # Example
+ 'chip_cpu_name' : [],
+ 'devices' : [], # List of dictionaries. See comments above.
+ 'display_height' : [], #
+ 'display_refresh' : [], #
+ 'display_rotate' : [], #
+ 'display_type' : [], #
+ 'display_width' : [], #
+ 'input' : {},
+ 'softwarelists' : [],
+ # --- Custom AML data (from INI files or generated) ---
+ 'alltime' : '', # MASH Alltime.ini
+ 'artwork' : [], # MASH Artwork.ini
+ 'bestgames' : '', # betsgames.ini
+ 'category' : [], # MASH category.ini
+ 'catlist' : '', # catlist.ini
+ 'catver' : '', # catver.ini
+ 'genre' : '', # genre.ini
+ 'series' : [], # series.ini
+ 'veradded' : '', # catver.ini
+ # --- AML generated field ---
+ 'isDead' : False,
+ }
+
+#
+# Object used in MAME_render_db.json
+#
+def db_new_machine_render_dic():
+ return {
+ # --- attributes ---
+ 'cloneof' : '', # Must be in the render DB to generate the PClone flag
+ 'isBIOS' : False,
+ 'isDevice' : False,
+ # --- Other tags inside from MAME XML ---
+ 'description' : '',
+ 'year' : '',
+ 'manufacturer' : '',
+ 'driver_status' : '',
+ # --- Custom AML data (from INI files or generated) ---
+ 'isMature' : False, # Taken from mature.ini
+ 'nplayers' : '', # Taken from NPlayers.ini
+ # Genre used in AML for the skin
+ # Taken from Genre.ini or Catver.ini or Catlist.ini
+ 'genre' : '',
+ }
+
+#
+# Object used in MAME_DB_roms.json
+# machine_roms = {
+# 'machine_name' : {
+# 'bios' : [ db_new_bios_dic(), ... ],
+# 'disks' : [ db_new_disk_dic(), ... ],
+# 'roms' : [ db_new_rom_dic(), ... ],
+# }
+# }
+#
+def db_new_roms_object():
+ return {
+ 'bios' : [],
+ 'roms' : [],
+ 'disks' : [],
+ 'samples' : [],
+ }
+
+def db_new_bios_dic():
+ return {
+ 'name' : '',
+ 'description' : '',
+ }
+
+def db_new_disk_dic():
+ return {
+ 'name' : '',
+ 'merge' : '',
+ 'sha1' : '', # sha1 allows to know if CHD is valid or not. CHDs don't have crc
+ }
+
+def db_new_rom_dic():
+ return {
+ 'name' : '',
+ 'merge' : '',
+ 'bios' : '',
+ 'size' : 0,
+ 'crc' : '', # crc allows to know if ROM is valid or not
+ }
+
+def db_new_audit_dic():
+ return {
+ 'machine_has_ROMs_or_CHDs' : False,
+ 'machine_has_ROMs' : False,
+ 'machine_has_CHDs' : False,
+ 'machine_is_OK' : True,
+ 'machine_ROMs_are_OK' : True,
+ 'machine_CHDs_are_OK' : True,
+ }
+
+#
+# First element is the database dictionary key of the asset, second element is the subdirectory name.
+# List used in mame_scan_MAME_assets()
+#
+ASSET_MAME_T_LIST = [
+ ('3dbox', '3dboxes'),
+ ('artpreview', 'artpreviews'),
+ ('artwork', 'artwork'),
+ ('cabinet', 'cabinets'),
+ ('clearlogo', 'clearlogos'),
+ ('cpanel', 'cpanels'),
+ ('fanart', 'fanarts'), # Created by AML automatically when building Fanarts.
+ ('flyer', 'flyers'),
+ ('manual', 'manuals'),
+ ('marquee', 'marquees'),
+ ('PCB', 'PCBs'),
+ ('snap', 'snaps'),
+ ('title', 'titles'),
+ ('trailer', 'videosnaps'),
+]
+
+#
+# flags -> ROM, CHD, Samples, SoftwareLists, Pluggable Devices
+#
+# Status flags meaning:
+# - Machine doesn't have ROMs | Machine doesn't have Software Lists
+# ? Machine has own ROMs and ROMs not been scanned
+# r Machine has own ROMs and ROMs doesn't exist
+# R Machine has own ROMs and ROMs exists | Machine has Software Lists
+#
+# Status device flag:
+# - Machine has no devices
+# d Machine has device/s but are not mandatory (can be booted without the device).
+# D Machine has device/s and must be plugged in order to boot.
+#
+def db_new_MAME_asset():
+ return {
+ '3dbox' : '',
+ 'artpreview' : '',
+ 'artwork' : '',
+ 'cabinet' : '',
+ 'clearlogo' : '',
+ 'cpanel' : '',
+ 'fanart' : '',
+ 'flags' : '-----',
+ 'flyer' : '',
+ 'history' : '',
+ 'manual' : '',
+ 'marquee' : '',
+ 'PCB' : '',
+ 'plot' : '',
+ 'snap' : '',
+ 'title' : '',
+ 'trailer' : '',
+ }
+
+# Status flags meaning:
+# ? SL ROM not scanned
+# r Missing ROM
+# R Have ROM
+def db_new_SL_ROM_part():
+ return {
+ 'name' : '',
+ 'interface' : ''
+ }
+
+def db_new_SL_ROM():
+ return {
+ 'description' : '',
+ 'year' : '',
+ 'publisher' : '',
+ 'plot' : '', # Generated from other fields
+ 'cloneof' : '',
+ 'parts' : [],
+ 'hasROMs' : False,
+ 'hasCHDs' : False,
+ 'status_ROM' : '-',
+ 'status_CHD' : '-',
+ }
+
+def db_new_SL_ROM_audit_dic():
+ return {
+ 'type' : '',
+ 'name' : '',
+ 'size' : '',
+ 'crc' : '',
+ 'location' : '',
+ }
+
+def db_new_SL_DISK_audit_dic():
+ return {
+ 'type' : '',
+ 'name' : '',
+ 'sha1' : '',
+ 'location' : '',
+ }
+
+ASSET_SL_T_LIST = [
+ ('3dbox', '3dboxes_SL'),
+ ('title', 'titles_SL'),
+ ('snap', 'snaps_SL'),
+ ('boxfront', 'covers_SL'),
+ ('fanart', 'fanarts_SL'),
+ ('trailer', 'videosnaps_SL'),
+ ('manual', 'manuals_SL'),
+]
+
+def db_new_SL_asset():
+ return {
+ '3dbox' : '',
+ 'title' : '',
+ 'snap' : '',
+ 'boxfront' : '',
+ 'fanart' : '',
+ 'trailer' : '',
+ 'manual' : '',
+ }
+
+# Some fields are used in all working modes.
+# Some fields are used in Vanilla MAME mode.
+# Some fields are used in MAME 2003 Plus mode.
+def db_new_MAME_XML_control_dic():
+ return {
+ 't_XML_extraction' : 0, # Result of time.time() [float]
+ 't_XML_preprocessing' : 0, # Result of time.time() [float]
+ 'total_machines' : 0, # [integer]
+ 'st_size' : 0, # Bytes [integer]
+ 'st_mtime' : 0.0, # seconds [float]
+ 'ver_mame_int' : 0, # Allows version comparisons [integer]
+ 'ver_mame_str' : 'undefined', # [Unicode string]
+ }
+
+def db_new_control_dic():
+ return {
+ # --- Filed in when extracting/preprocessing MAME XML ---
+ # Operation mode when the database is created. If the OP mode is changed database
+ # must be rebuilt.
+ 'op_mode_raw' : 0,
+ 'op_mode' : '',
+ 'stats_total_machines' : 0,
+
+ # --- Timestamps ---
+ # MAME
+ 't_MAME_DB_build' : 0.0,
+ 't_MAME_Audit_DB_build' : 0.0,
+ 't_MAME_Catalog_build' : 0.0,
+ 't_MAME_ROMs_scan' : 0.0,
+ 't_MAME_assets_scan' : 0.0,
+ 't_MAME_plots_build' : 0.0,
+ 't_MAME_fanart_build' : 0.0,
+ 't_MAME_3dbox_build' : 0.0,
+ 't_MAME_machine_hash' : 0.0,
+ 't_MAME_asset_hash' : 0.0,
+ 't_MAME_render_cache_build' : 0.0,
+ 't_MAME_asset_cache_build' : 0.0,
+ # Software Lists
+ 't_SL_DB_build' : 0.0,
+ 't_SL_ROMs_scan' : 0.0,
+ 't_SL_assets_scan' : 0.0,
+ 't_SL_plots_build' : 0.0,
+ 't_SL_fanart_build' : 0.0,
+ 't_SL_3dbox_build' : 0.0,
+ # Misc
+ 't_Custom_Filter_build' : 0.0,
+ 't_MAME_audit' : 0.0,
+ 't_SL_audit' : 0.0,
+
+ # --- Filed in when building main MAME database ---
+ 'ver_AML_int' : 0,
+ 'ver_AML_str' : 'Undefined',
+ # Numerical MAME version. Allows for comparisons like ver_mame >= MAME_VERSION_0190
+ # MAME string version, as reported by the executable stdout. Example: '0.194 (mame0194)'
+ 'ver_mame_int' : 0,
+ 'ver_mame_str' : 'Undefined',
+ # INI files
+ 'ver_alltime' : 'MAME database not built',
+ 'ver_artwork' : 'MAME database not built',
+ 'ver_bestgames' : 'MAME database not built',
+ 'ver_category' : 'MAME database not built',
+ 'ver_catlist' : 'MAME database not built',
+ 'ver_catver' : 'MAME database not built',
+ 'ver_genre' : 'MAME database not built',
+ 'ver_mature' : 'MAME database not built',
+ 'ver_nplayers' : 'MAME database not built',
+ 'ver_series' : 'MAME database not built',
+
+ # DAT files
+ 'ver_command' : 'MAME database not built',
+ 'ver_gameinit' : 'MAME database not built',
+ 'ver_history' : 'MAME database not built',
+ 'ver_mameinfo' : 'MAME database not built',
+
+ # Basic stats
+ 'stats_processed_machines' : 0,
+ 'stats_parents' : 0,
+ 'stats_clones' : 0,
+ # Excluding devices machines (devices are not runnable)
+ 'stats_runnable' : 0,
+ 'stats_runnable_parents' : 0,
+ 'stats_runnable_clones' : 0,
+ # Main filters
+ 'stats_coin' : 0,
+ 'stats_coin_parents' : 0,
+ 'stats_coin_clones' : 0,
+ 'stats_nocoin' : 0,
+ 'stats_nocoin_parents' : 0,
+ 'stats_nocoin_clones' : 0,
+ 'stats_mechanical' : 0,
+ 'stats_mechanical_parents' : 0,
+ 'stats_mechanical_clones' : 0,
+ 'stats_dead' : 0,
+ 'stats_dead_parents' : 0,
+ 'stats_dead_clones' : 0,
+ 'stats_devices' : 0,
+ 'stats_devices_parents' : 0,
+ 'stats_devices_clones' : 0,
+ # Binary filters
+ 'stats_BIOS' : 0,
+ 'stats_BIOS_parents' : 0,
+ 'stats_BIOS_clones' : 0,
+ 'stats_samples' : 0,
+ 'stats_samples_parents' : 0,
+ 'stats_samples_clones' : 0,
+
+ # --- Main filter statistics ---
+ # Filed in when building the MAME catalogs in mame_build_MAME_catalogs()
+ # driver_status for device machines is always the empty string ''
+ 'stats_MF_Normal_Total' : 0, 'stats_MF_Normal_Total_parents' : 0,
+ 'stats_MF_Normal_Good' : 0, 'stats_MF_Normal_Good_parents' : 0,
+ 'stats_MF_Normal_Imperfect' : 0, 'stats_MF_Normal_Imperfect_parents' : 0,
+ 'stats_MF_Normal_Nonworking' : 0, 'stats_MF_Normal_Nonworking_parents' : 0,
+ 'stats_MF_Unusual_Total' : 0, 'stats_MF_Unusual_Total_parents' : 0,
+ 'stats_MF_Unusual_Good' : 0, 'stats_MF_Unusual_Good_parents' : 0,
+ 'stats_MF_Unusual_Imperfect' : 0, 'stats_MF_Unusual_Imperfect_parents' : 0,
+ 'stats_MF_Unusual_Nonworking' : 0, 'stats_MF_Unusual_Nonworking_parents' : 0,
+ 'stats_MF_Nocoin_Total' : 0, 'stats_MF_Nocoin_Total_parents' : 0,
+ 'stats_MF_Nocoin_Good' : 0, 'stats_MF_Nocoin_Good_parents' : 0,
+ 'stats_MF_Nocoin_Imperfect' : 0, 'stats_MF_Nocoin_Imperfect_parents' : 0,
+ 'stats_MF_Nocoin_Nonworking' : 0, 'stats_MF_Nocoin_Nonworking_parents' : 0,
+ 'stats_MF_Mechanical_Total' : 0, 'stats_MF_Mechanical_Total_parents' : 0,
+ 'stats_MF_Mechanical_Good' : 0, 'stats_MF_Mechanical_Good_parents' : 0,
+ 'stats_MF_Mechanical_Imperfect' : 0, 'stats_MF_Mechanical_Imperfect_parents' : 0,
+ 'stats_MF_Mechanical_Nonworking' : 0, 'stats_MF_Mechanical_Nonworking_parents' : 0,
+ 'stats_MF_Dead_Total' : 0, 'stats_MF_Dead_Total_parents' : 0,
+ 'stats_MF_Dead_Good' : 0, 'stats_MF_Dead_Good_parents' : 0,
+ 'stats_MF_Dead_Imperfect' : 0, 'stats_MF_Dead_Imperfect_parents' : 0,
+ 'stats_MF_Dead_Nonworking' : 0, 'stats_MF_Dead_Nonworking_parents' : 0,
+
+ # --- Filed in when building the ROM audit databases ---
+ 'stats_audit_MAME_machines_runnable' : 0,
+ # Number of ROM ZIP files in the Merged, Split or Non-merged sets.
+ 'stats_audit_MAME_ROM_ZIP_files' : 0,
+ # Number of Sample ZIP files.
+ 'stats_audit_MAME_Sample_ZIP_files' : 0,
+ # Number of CHD files in the Merged, Split or Non-merged sets.
+ 'stats_audit_MAME_CHD_files' : 0,
+
+ # Number of machines that require one or more ROM ZIP archives to run
+ 'stats_audit_machine_archives_ROM' : 0,
+ 'stats_audit_machine_archives_ROM_parents' : 0,
+ 'stats_audit_machine_archives_ROM_clones' : 0,
+ # Number of machines that require one or more CHDs to run
+ 'stats_audit_machine_archives_CHD' : 0,
+ 'stats_audit_machine_archives_CHD_parents' : 0,
+ 'stats_audit_machine_archives_CHD_clones' : 0,
+ # Number of machines that require Sample ZIPs
+ 'stats_audit_machine_archives_Samples' : 0,
+ 'stats_audit_machine_archives_Samples_parents' : 0,
+ 'stats_audit_machine_archives_Samples_clones' : 0,
+ # ROM less machines do not need any ZIP archive or CHD to run
+ 'stats_audit_archive_less' : 0,
+ 'stats_audit_archive_less_parents' : 0,
+ 'stats_audit_archive_less_clones' : 0,
+
+ # ROM statistics (not implemented yet)
+ 'stats_audit_ROMs_total' : 0,
+ 'stats_audit_ROMs_valid' : 0,
+ 'stats_audit_ROMs_invalid' : 0,
+ 'stats_audit_ROMs_unique' : 0, # Not implemented
+ 'stats_audit_ROMs_SHA_merged' : 0, # Not implemented
+ 'stats_audit_CHDs_total' : 0,
+ 'stats_audit_CHDs_valid' : 0,
+ 'stats_audit_CHDs_invalid' : 0,
+
+ # --- Filed in when auditing the MAME machines ---
+ # >> Machines with ROMs/CHDs archives that are OK or not
+ 'audit_MAME_machines_with_arch' : 0,
+ 'audit_MAME_machines_with_arch_OK' : 0,
+ 'audit_MAME_machines_with_arch_BAD' : 0,
+ 'audit_MAME_machines_without' : 0,
+ # >> Machines with ROM archives that are OK or not
+ 'audit_MAME_machines_with_ROMs' : 0,
+ 'audit_MAME_machines_with_ROMs_OK' : 0,
+ 'audit_MAME_machines_with_ROMs_BAD' : 0,
+ 'audit_MAME_machines_without_ROMs' : 0,
+ # >> Machines with Samples archives that are OK or not
+ 'audit_MAME_machines_with_SAMPLES' : 0,
+ 'audit_MAME_machines_with_SAMPLES_OK' : 0,
+ 'audit_MAME_machines_with_SAMPLES_BAD' : 0,
+ 'audit_MAME_machines_without_SAMPLES' : 0,
+ # >> Machines with CHDs that are OK or not
+ 'audit_MAME_machines_with_CHDs' : 0,
+ 'audit_MAME_machines_with_CHDs_OK' : 0,
+ 'audit_MAME_machines_with_CHDs_BAD' : 0,
+ 'audit_MAME_machines_without_CHDs' : 0,
+
+ # --- Filed in when building the SL item databases ---
+ # Number of SL databases (equal to the number of XML files).
+ 'stats_SL_XML_files' : 0,
+ 'stats_SL_software_items' : 0,
+ # Number of SL items that require one or more ROM ZIP archives to run
+ 'stats_SL_items_with_ROMs' : 0,
+ # Number of SL items that require one or more CHDs to run
+ 'stats_SL_items_with_CHDs' : 0,
+
+ # --- Filed in when building the SL audit databases ---
+ 'stats_audit_SL_items_runnable' : 0,
+ 'stats_audit_SL_items_with_arch' : 0, # ROM ZIP or CHD or both
+ 'stats_audit_SL_items_with_arch_ROM' : 0, # At least ROM ZIP (and maybe CHD)
+ 'stats_audit_SL_items_with_CHD' : 0, # At least CHD (and maybe ROM ZIP)
+
+ # --- Filed in when auditing the SL items ---
+ 'audit_SL_items_runnable' : 0,
+ 'audit_SL_items_with_arch' : 0,
+ 'audit_SL_items_with_arch_OK' : 0,
+ 'audit_SL_items_with_arch_BAD' : 0,
+ 'audit_SL_items_without_arch' : 0,
+ 'audit_SL_items_with_arch_ROM' : 0,
+ 'audit_SL_items_with_arch_ROM_OK' : 0,
+ 'audit_SL_items_with_arch_ROM_BAD' : 0,
+ 'audit_SL_items_without_arch_ROM' : 0,
+ 'audit_SL_items_with_CHD' : 0,
+ 'audit_SL_items_with_CHD_OK' : 0,
+ 'audit_SL_items_with_CHD_BAD' : 0,
+ 'audit_SL_items_without_CHD' : 0,
+
+ # --- Filed in by the MAME ROM/CHD/Samples scanner ---
+ # ROM_Set_ROM_list.json database
+ # Number of ROM ZIP files, including device ROMs.
+ 'scan_ROM_ZIP_files_total' : 0,
+ 'scan_ROM_ZIP_files_have' : 0,
+ 'scan_ROM_ZIP_files_missing' : 0,
+
+ # ROM_Set_Sample_list.json
+ # Number of Samples ZIP files.
+ 'scan_Samples_ZIP_total' : 0,
+ 'scan_Samples_ZIP_have' : 0,
+ 'scan_Samples_ZIP_missing' : 0,
+
+ # ROM_Set_CHD_list.json database
+ # Number of CHD files.
+ 'scan_CHD_files_total' : 0,
+ 'scan_CHD_files_have' : 0,
+ 'scan_CHD_files_missing' : 0,
+
+ # ROM_Set_machine_files.json database
+ # Number of runnable machines that need one or more ROM ZIP file to run (excluding devices).
+ # Number of machines you can run, excluding devices.
+ # Number of machines you cannot run, excluding devices.
+ 'scan_machine_archives_ROM_total' : 0,
+ 'scan_machine_archives_ROM_have' : 0,
+ 'scan_machine_archives_ROM_missing' : 0,
+
+ # Sames with Samples
+ 'scan_machine_archives_Samples_total' : 0,
+ 'scan_machine_archives_Samples_have' : 0,
+ 'scan_machine_archives_Samples_missing' : 0,
+
+ # Number of machines that need one or more CHDs to run.
+ # Number of machines with CHDs you can run.
+ # Number of machines with CHDs you cannot run.
+ 'scan_machine_archives_CHD_total' : 0,
+ 'scan_machine_archives_CHD_have' : 0,
+ 'scan_machine_archives_CHD_missing' : 0,
+
+ # --- Filed in by the SL ROM/CHD scanner ---
+ 'scan_SL_archives_ROM_total' : 0,
+ 'scan_SL_archives_ROM_have' : 0,
+ 'scan_SL_archives_ROM_missing' : 0,
+ 'scan_SL_archives_CHD_total' : 0,
+ 'scan_SL_archives_CHD_have' : 0,
+ 'scan_SL_archives_CHD_missing' : 0,
+
+ # --- Filed in by the MAME asset scanner ---
+ 'assets_num_MAME_machines' : 0,
+ 'assets_3dbox_have' : 0,
+ 'assets_3dbox_missing' : 0,
+ 'assets_3dbox_alternate' : 0,
+ 'assets_artpreview_have' : 0,
+ 'assets_artpreview_missing' : 0,
+ 'assets_artpreview_alternate' : 0,
+ 'assets_artwork_have' : 0,
+ 'assets_artwork_missing' : 0,
+ 'assets_artwork_alternate' : 0,
+ 'assets_cabinets_have' : 0,
+ 'assets_cabinets_missing' : 0,
+ 'assets_cabinets_alternate' : 0,
+ 'assets_clearlogos_have' : 0,
+ 'assets_clearlogos_missing' : 0,
+ 'assets_clearlogos_alternate' : 0,
+ 'assets_cpanels_have' : 0,
+ 'assets_cpanels_missing' : 0,
+ 'assets_cpanels_alternate' : 0,
+ 'assets_fanarts_have' : 0,
+ 'assets_fanarts_missing' : 0,
+ 'assets_fanarts_alternate' : 0,
+ 'assets_flyers_have' : 0,
+ 'assets_flyers_missing' : 0,
+ 'assets_flyers_alternate' : 0,
+ 'assets_manuals_have' : 0,
+ 'assets_manuals_missing' : 0,
+ 'assets_manuals_alternate' : 0,
+ 'assets_marquees_have' : 0,
+ 'assets_marquees_missing' : 0,
+ 'assets_marquees_alternate' : 0,
+ 'assets_PCBs_have' : 0,
+ 'assets_PCBs_missing' : 0,
+ 'assets_PCBs_alternate' : 0,
+ 'assets_snaps_have' : 0,
+ 'assets_snaps_missing' : 0,
+ 'assets_snaps_alternate' : 0,
+ 'assets_titles_have' : 0,
+ 'assets_titles_missing' : 0,
+ 'assets_titles_alternate' : 0,
+ 'assets_trailers_have' : 0,
+ 'assets_trailers_missing' : 0,
+ 'assets_trailers_alternate' : 0,
+
+ # --- Filed in by the SL asset scanner ---
+ 'assets_SL_num_items' : 0,
+ 'assets_SL_3dbox_have' : 0,
+ 'assets_SL_3dbox_missing' : 0,
+ 'assets_SL_3dbox_alternate' : 0,
+ 'assets_SL_titles_have' : 0,
+ 'assets_SL_titles_missing' : 0,
+ 'assets_SL_titles_alternate' : 0,
+ 'assets_SL_snaps_have' : 0,
+ 'assets_SL_snaps_missing' : 0,
+ 'assets_SL_snaps_alternate' : 0,
+ 'assets_SL_boxfronts_have' : 0,
+ 'assets_SL_boxfronts_missing' : 0,
+ 'assets_SL_boxfronts_alternate' : 0,
+ 'assets_SL_fanarts_have' : 0,
+ 'assets_SL_fanarts_missing' : 0,
+ 'assets_SL_fanarts_alternate' : 0,
+ 'assets_SL_trailers_have' : 0,
+ 'assets_SL_trailers_missing' : 0,
+ 'assets_SL_trailers_alternate' : 0,
+ 'assets_SL_manuals_have' : 0,
+ 'assets_SL_manuals_missing' : 0,
+ 'assets_SL_manuals_alternate' : 0,
+ }
+
+# Safe way of change a dictionary without adding new fields.
+def db_safe_edit(my_dic, field, value):
+ if field in my_dic:
+ my_dic[field] = value
+ else:
+ raise TypeError('Field {} not in dictionary'.format(field))
+
+#
+# Favourite MAME object creation.
+# Simple means the main data and assets are used to created the Favourite.
+# Full means that the main data, the render data and the assets are used to create the Favourite.
+#
+# Both functioncs create a complete Favourite. When simple() is used the machine is taken from
+# the hashed database, which includes both the main and render machine data.
+#
+# Changes introduced in 0.9.6
+# 1) fav_machine['name'] = machine_name
+#
+def db_get_MAME_Favourite_simple(machine_name, machine, assets, control_dic):
+ fav_machine = {}
+
+ fav_machine = copy.deepcopy(machine)
+ fav_machine['name'] = machine_name
+ fav_machine['ver_mame_int'] = control_dic['ver_mame_int']
+ fav_machine['ver_mame_str'] = control_dic['ver_mame_str']
+ fav_machine['assets'] = copy.deepcopy(assets)
+
+ return fav_machine
+
+def db_get_MAME_Favourite_full(machine_name, machine, machine_render, assets, control_dic):
+ fav_machine = {}
+
+ fav_machine = copy.deepcopy(machine)
+ fav_machine.update(machine_render)
+ fav_machine['name'] = machine_name
+ fav_machine['ver_mame_int'] = control_dic['ver_mame_int']
+ fav_machine['ver_mame_str'] = control_dic['ver_mame_str']
+ fav_machine['assets'] = copy.deepcopy(assets)
+
+ return fav_machine
+
+def db_get_SL_Favourite(SL_name, ROM_name, ROM, assets, control_dic):
+ fav_SL_item = {}
+
+ SL_DB_key = SL_name + '-' + ROM_name
+ fav_SL_item = copy.deepcopy(ROM)
+ fav_SL_item['SL_name'] = SL_name
+ fav_SL_item['SL_ROM_name'] = ROM_name
+ fav_SL_item['SL_DB_key'] = SL_DB_key
+ fav_SL_item['ver_mame_int'] = control_dic['ver_mame_int']
+ fav_SL_item['ver_mame_str'] = control_dic['ver_mame_str']
+ fav_SL_item['launch_machine'] = ''
+ fav_SL_item['assets'] = copy.deepcopy(assets)
+
+ return fav_SL_item
+
+# Get Catalog databases
+def db_get_cataloged_dic_parents(cfg, catalog_name):
+ if catalog_name == 'Main':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_MAIN_PARENT_PATH.getPath())
+ elif catalog_name == 'Binary':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_BINARY_PARENT_PATH.getPath())
+ elif catalog_name == 'Catver':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_CATVER_PARENT_PATH.getPath())
+ elif catalog_name == 'Catlist':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_CATLIST_PARENT_PATH.getPath())
+ elif catalog_name == 'Genre':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_GENRE_PARENT_PATH.getPath())
+ elif catalog_name == 'Category':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_CATEGORY_PARENT_PATH.getPath())
+ elif catalog_name == 'NPlayers':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_NPLAYERS_PARENT_PATH.getPath())
+ elif catalog_name == 'Bestgames':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_BESTGAMES_PARENT_PATH.getPath())
+ elif catalog_name == 'Series':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_SERIES_PARENT_PATH.getPath())
+ elif catalog_name == 'Alltime':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_ALLTIME_PARENT_PATH.getPath())
+ elif catalog_name == 'Artwork':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_ARTWORK_PARENT_PATH.getPath())
+ elif catalog_name == 'Version':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_VERADDED_PARENT_PATH.getPath())
+ elif catalog_name == 'Controls_Expanded':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_CONTROL_EXPANDED_PARENT_PATH.getPath())
+ elif catalog_name == 'Controls_Compact':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_CONTROL_COMPACT_PARENT_PATH.getPath())
+ elif catalog_name == 'Devices_Expanded':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_DEVICE_EXPANDED_PARENT_PATH.getPath())
+ elif catalog_name == 'Devices_Compact':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_DEVICE_COMPACT_PARENT_PATH.getPath())
+ elif catalog_name == 'Display_Type':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_DISPLAY_TYPE_PARENT_PATH.getPath())
+ elif catalog_name == 'Display_VSync':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_DISPLAY_VSYNC_PARENT_PATH.getPath())
+ elif catalog_name == 'Display_Resolution':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_DISPLAY_RES_PARENT_PATH.getPath())
+ elif catalog_name == 'CPU':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_CPU_PARENT_PATH.getPath())
+ elif catalog_name == 'Driver':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_DRIVER_PARENT_PATH.getPath())
+ elif catalog_name == 'Manufacturer':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_MANUFACTURER_PARENT_PATH.getPath())
+ elif catalog_name == 'ShortName':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_SHORTNAME_PARENT_PATH.getPath())
+ elif catalog_name == 'LongName':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_LONGNAME_PARENT_PATH.getPath())
+ elif catalog_name == 'BySL':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_SL_PARENT_PATH.getPath())
+ elif catalog_name == 'Year':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_YEAR_PARENT_PATH.getPath())
+ else:
+ log_error('db_get_cataloged_dic_parents() Unknown catalog_name = "{}"'.format(catalog_name))
+
+ return catalog_dic
+
+def db_get_cataloged_dic_all(cfg, catalog_name):
+ if catalog_name == 'Main':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_MAIN_ALL_PATH.getPath())
+ elif catalog_name == 'Binary':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_BINARY_ALL_PATH.getPath())
+ elif catalog_name == 'Catver':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_CATVER_ALL_PATH.getPath())
+ elif catalog_name == 'Catlist':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_CATLIST_ALL_PATH.getPath())
+ elif catalog_name == 'Genre':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_GENRE_ALL_PATH.getPath())
+ elif catalog_name == 'Category':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_CATEGORY_ALL_PATH.getPath())
+ elif catalog_name == 'NPlayers':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_NPLAYERS_ALL_PATH.getPath())
+ elif catalog_name == 'Bestgames':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_BESTGAMES_ALL_PATH.getPath())
+ elif catalog_name == 'Series':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_SERIES_ALL_PATH.getPath())
+ elif catalog_name == 'Alltime':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_ALLTIME_ALL_PATH.getPath())
+ elif catalog_name == 'Artwork':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_ARTWORK_ALL_PATH.getPath())
+ elif catalog_name == 'Version':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_VERADDED_ALL_PATH.getPath())
+ elif catalog_name == 'Controls_Expanded':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_CONTROL_EXPANDED_ALL_PATH.getPath())
+ elif catalog_name == 'Controls_Compact':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_CONTROL_COMPACT_ALL_PATH.getPath())
+ elif catalog_name == 'Devices_Expanded':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_DEVICE_EXPANDED_ALL_PATH.getPath())
+ elif catalog_name == 'Devices_Compact':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_DEVICE_COMPACT_ALL_PATH.getPath())
+ elif catalog_name == 'Display_Type':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_DISPLAY_TYPE_ALL_PATH.getPath())
+ elif catalog_name == 'Display_VSync':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_DISPLAY_VSYNC_ALL_PATH.getPath())
+ elif catalog_name == 'Display_Resolution':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_DISPLAY_RES_ALL_PATH.getPath())
+ elif catalog_name == 'CPU':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_CPU_ALL_PATH.getPath())
+ elif catalog_name == 'Driver':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_DRIVER_ALL_PATH.getPath())
+ elif catalog_name == 'Manufacturer':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_MANUFACTURER_ALL_PATH.getPath())
+ elif catalog_name == 'ShortName':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_SHORTNAME_ALL_PATH.getPath())
+ elif catalog_name == 'LongName':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_LONGNAME_ALL_PATH.getPath())
+ elif catalog_name == 'BySL':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_SL_ALL_PATH.getPath())
+ elif catalog_name == 'Year':
+ catalog_dic = utils_load_JSON_file(cfg.CATALOG_YEAR_ALL_PATH.getPath())
+ else:
+ log_error('db_get_cataloged_dic_all() Unknown catalog_name = "{}"'.format(catalog_name))
+
+ return catalog_dic
+
+#
+# Locates object index in a list of dictionaries by 'name' field.
+# Returns -1 if object cannot be found. Uses a linear search (slow!).
+#
+def db_locate_idx_by_MAME_name(object_list, object_name):
+ object_index = -1
+ for i, machine in enumerate(object_list):
+ if object_name == machine['name']:
+ object_index = i
+ break
+
+ return object_index
+
+#
+# Same as previous function but on a list of Software List items
+#
+def db_locate_idx_by_SL_item_name(object_list, SL_name, SL_ROM_name):
+ SL_fav_DB_key = SL_name + '-' + SL_ROM_name
+ object_index = -1
+ for i, machine in enumerate(object_list):
+ if SL_fav_DB_key == machine['SL_DB_key']:
+ object_index = i
+ break
+
+ return object_index
+
+# Valid ROM: ROM has CRC hash
+# Valid CHD: CHD has SHA1 hash
+def db_initial_flags(machine, machine_render, m_roms):
+ # Machine has own ROMs (at least one ROM is valid and has empty 'merge' attribute)
+ has_own_ROMs = False
+ for rom in m_roms['roms']:
+ if not rom['merge'] and rom['crc']:
+ has_own_ROMs = True
+ break
+ flag_ROM = '?' if has_own_ROMs else '-'
+
+ # Machine has own CHDs
+ has_own_CHDs = False
+ for rom in m_roms['disks']:
+ if not rom['merge'] and rom['sha1']:
+ has_own_CHDs = True
+ break
+ flag_CHD = '?' if has_own_CHDs else '-'
+
+ # Samples flag
+ flag_Samples = '?' if machine['sampleof'] else '-'
+
+ # Software List flag
+ flag_SL = 'L' if machine['softwarelists'] else '-'
+
+ # Pluggable Devices flag
+ if machine['devices']:
+ num_dev_mandatory = 0
+ for device in machine['devices']:
+ if device['att_mandatory']:
+ flag_Devices = 'D'
+ num_dev_mandatory += 1
+ else:
+ flag_Devices = 'd'
+ if num_dev_mandatory > 2:
+ message = 'Machine {} has {} mandatory devices'.format(machine_name, num_dev_mandatory)
+ raise CriticalError(message)
+ else:
+ flag_Devices = '-'
+
+ return '{}{}{}{}{}'.format(flag_ROM, flag_CHD, flag_Samples, flag_SL, flag_Devices)
+
+#
+# Update m_dic using Python pass by assignment.
+# Remember that strings are inmutable!
+#
+def db_set_ROM_flag(m_dic, new_ROM_flag):
+ flag_ROM = m_dic['flags'][0]
+ flag_CHD = m_dic['flags'][1]
+ flag_Samples = m_dic['flags'][2]
+ flag_SL = m_dic['flags'][3]
+ flag_Devices = m_dic['flags'][4]
+ flag_ROM = new_ROM_flag
+ m_dic['flags'] = '{}{}{}{}{}'.format(flag_ROM, flag_CHD, flag_Samples, flag_SL, flag_Devices)
+
+def db_set_CHD_flag(m_dic, new_CHD_flag):
+ flag_ROM = m_dic['flags'][0]
+ flag_CHD = m_dic['flags'][1]
+ flag_Samples = m_dic['flags'][2]
+ flag_SL = m_dic['flags'][3]
+ flag_Devices = m_dic['flags'][4]
+ flag_CHD = new_CHD_flag
+ m_dic['flags'] = '{}{}{}{}{}'.format(flag_ROM, flag_CHD, flag_Samples, flag_SL, flag_Devices)
+
+def db_set_Sample_flag(m_dic, new_Sample_flag):
+ flag_ROM = m_dic['flags'][0]
+ flag_CHD = m_dic['flags'][1]
+ flag_Samples = m_dic['flags'][2]
+ flag_SL = m_dic['flags'][3]
+ flag_Devices = m_dic['flags'][4]
+ flag_Samples = new_Sample_flag
+ m_dic['flags'] = '{}{}{}{}{}'.format(flag_ROM, flag_CHD, flag_Samples, flag_SL, flag_Devices)
+
+# -------------------------------------------------------------------------------------------------
+# MAME hashed databases. Useful when only one item in a big dictionary is required.
+# -------------------------------------------------------------------------------------------------
+# Hash database with 256 elements (2 hex digits)
+def db_build_main_hashed_db(cfg, control_dic, machines, machines_render):
+ log_info('db_build_main_hashed_db() Building main hashed database...')
+
+ # machine_name -> MD5 -> take two letters -> aa.json, ab.json, ...
+ # A) First create an index
+ # db_main_hash_idx = { 'machine_name' : 'aa', ... }
+ # B) Then traverse a list [0, 1, ..., f] and write the machines in that sub database section.
+ pDialog = KodiProgressDialog()
+ pDialog.startProgress('Building main hashed database...', len(machines))
+ db_main_hash_idx = {}
+ for key in machines:
+ pDialog.updateProgressInc()
+ md5_str = hashlib.md5(key.encode('utf-8')).hexdigest()
+ db_name = md5_str[0:2] # WARNING Python slicing does not work like in C/C++!
+ db_main_hash_idx[key] = db_name
+ # log_debug('Machine {:20s} / hash {} / db file {}'.format(key, md5_str, db_name))
+ pDialog.endProgress()
+
+ hex_digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
+ distributed_db_files = []
+ for u in range(len(hex_digits)):
+ for v in range(len(hex_digits)):
+ distributed_db_files.append('{}{}'.format(hex_digits[u], hex_digits[v]))
+ pDialog.startProgress('Building main hashed database JSON files...', len(distributed_db_files))
+ for db_prefix in distributed_db_files:
+ pDialog.updateProgressInc()
+ # log_debug('db prefix {}'.format(db_prefix))
+ # --- Generate dictionary in this JSON file ---
+ hashed_db_dic = {}
+ for key in db_main_hash_idx:
+ if db_main_hash_idx[key] == db_prefix:
+ machine_dic = machines[key].copy()
+ # >> returns None because it mutates machine_dic
+ machine_dic.update(machines_render[key])
+ hashed_db_dic[key] = machine_dic
+ # --- Save JSON file ---
+ hash_DB_FN = cfg.MAIN_DB_HASH_DIR.pjoin(db_prefix + '_machines.json')
+ utils_write_JSON_file(hash_DB_FN.getPath(), hashed_db_dic, verbose = False)
+ pDialog.endProgress()
+
+ # Update timestamp in control_dic.
+ db_safe_edit(control_dic, 't_MAME_machine_hash', time.time())
+ utils_write_JSON_file(cfg.MAIN_CONTROL_PATH.getPath(), control_dic)
+
+#
+# Retrieves machine from distributed database.
+# This is very quick for retrieving individual machines, very slow for multiple machines.
+#
+def db_get_machine_main_hashed_db(cfg, machine_name):
+ log_debug('db_get_machine_main_hashed_db() machine {}'.format(machine_name))
+ md5_str = hashlib.md5(machine_name.encode('utf-8')).hexdigest()
+ # WARNING Python slicing does not work like in C/C++!
+ hash_DB_FN = cfg.MAIN_DB_HASH_DIR.pjoin(md5_str[0:2] + '_machines.json')
+ hashed_db_dic = utils_load_JSON_file(hash_DB_FN.getPath())
+
+ return hashed_db_dic[machine_name]
+
+# MAME hash database with 256 elements (2 hex digits)
+def db_build_asset_hashed_db(cfg, control_dic, assets_dic):
+ log_info('db_build_asset_hashed_db() Building assets hashed database ...')
+
+ # machine_name -> MD5 -> take two letters -> aa.json, ab.json, ...
+ pDialog = KodiProgressDialog()
+ pDialog.startProgress('Building asset hashed database...', len(assets_dic))
+ db_main_hash_idx = {}
+ for key in assets_dic:
+ pDialog.updateProgressInc()
+ md5_str = hashlib.md5(key.encode('utf-8')).hexdigest()
+ db_name = md5_str[0:2] # WARNING Python slicing does not work like in C/C++!
+ db_main_hash_idx[key] = db_name
+ # log_debug('Machine {:20s} / hash {} / db file {}'.format(key, md5_str, db_name))
+ pDialog.endProgress()
+
+ hex_digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
+ distributed_db_files = []
+ for u in range(len(hex_digits)):
+ for v in range(len(hex_digits)):
+ distributed_db_files.append('{}{}'.format(hex_digits[u], hex_digits[v]))
+ pDialog.startProgress('Building asset hashed database JSON files...', len(distributed_db_files))
+ for db_prefix in distributed_db_files:
+ pDialog.updateProgressInc()
+ hashed_db_dic = {}
+ for key in db_main_hash_idx:
+ if db_main_hash_idx[key] == db_prefix:
+ hashed_db_dic[key] = assets_dic[key]
+ hash_DB_FN = cfg.MAIN_DB_HASH_DIR.pjoin(db_prefix + '_assets.json')
+ utils_write_JSON_file(hash_DB_FN.getPath(), hashed_db_dic, verbose = False)
+ pDialog.endProgress()
+
+ # --- Timestamp ---
+ db_safe_edit(control_dic, 't_MAME_asset_hash', time.time())
+ utils_write_JSON_file(cfg.MAIN_CONTROL_PATH.getPath(), control_dic)
+
+#
+# Retrieves machine from distributed hashed database.
+# This is very quick for retrieving individual machines, slow for multiple machines.
+#
+def db_get_machine_assets_hashed_db(cfg, machine_name):
+ log_debug('db_get_machine_assets_hashed_db() machine {}'.format(machine_name))
+ md5_str = hashlib.md5(machine_name.encode('utf-8')).hexdigest()
+ hash_DB_FN = cfg.MAIN_DB_HASH_DIR.pjoin(md5_str[0:2] + '_assets.json')
+ hashed_db_dic = utils_load_JSON_file(hash_DB_FN.getPath())
+
+ return hashed_db_dic[machine_name]
+
+# -------------------------------------------------------------------------------------------------
+# MAME machine render cache
+# Creates a separate MAME render and assets databases for each catalog to speed up
+# access of ListItems when rendering machine lists.
+# -------------------------------------------------------------------------------------------------
+def db_cache_get_key(catalog_name, category_name):
+ return hashlib.md5('{} - {}'.format(catalog_name, category_name).encode('utf-8')).hexdigest()
+
+def db_build_render_cache(cfg, control_dic, cache_index_dic, machines_render, force_build = False):
+ log_info('db_build_render_cache() Initialising...')
+ log_debug('debug_enable_MAME_render_cache is {}'.format(cfg.settings['debug_enable_MAME_render_cache']))
+ log_debug('force_build is {}'.format(force_build))
+ if not cfg.settings['debug_enable_MAME_render_cache'] and not force_build:
+ log_info('db_build_render_cache() Render cache disabled.')
+ return
+ # Notify user this is a forced build.
+ if not cfg.settings['debug_enable_MAME_render_cache'] and force_build:
+ t = 'MAME render cache disabled but forcing rebuilding.'
+ log_info(t)
+ kodi_dialog_OK(t)
+
+ # --- Clean 'cache' directory JSON ROM files ---
+ log_info('Cleaning dir "{}"'.format(cfg.CACHE_DIR.getPath()))
+ pDialog = KodiProgressDialog()
+ pDialog.startProgress('Listing render cache JSON files...')
+ file_list = os.listdir(cfg.CACHE_DIR.getPath())
+ log_info('Found {} files'.format(len(file_list)))
+ deleted_items = 0
+ pDialog.resetProgress('Cleaning render cache JSON files...', len(file_list))
+ for file in file_list:
+ pDialog.updateProgressInc()
+ if not file.endswith('_render.json'): continue
+ full_path = os.path.join(cfg.CACHE_DIR.getPath(), file)
+ # log_debug('UNLINK "{}"'.format(full_path))
+ os.unlink(full_path)
+ deleted_items += 1
+ pDialog.endProgress()
+ log_info('Deleted {} files'.format(deleted_items))
+
+ # --- Build ROM cache ---
+ num_catalogs = len(cache_index_dic)
+ catalog_count = 1
+ pDialog.startProgress('Building MAME render cache')
+ for catalog_name in sorted(cache_index_dic):
+ catalog_index_dic = cache_index_dic[catalog_name]
+ catalog_all = db_get_cataloged_dic_all(cfg, catalog_name)
+ diag_t = 'Building MAME [COLOR orange]{}[/COLOR] render cache ({} of {})...'.format(
+ catalog_name, catalog_count, num_catalogs)
+ pDialog.resetProgress(diag_t, len(catalog_index_dic))
+ for catalog_key in catalog_index_dic:
+ pDialog.updateProgressInc()
+ hash_str = catalog_index_dic[catalog_key]['hash']
+ # log_debug('db_build_ROM_cache() Catalog "{}" --- Key "{}"'.format(catalog_name, catalog_key))
+ # log_debug('db_build_ROM_cache() hash {}'.format(hash_str))
+
+ # Build all machines cache
+ m_render_all_dic = {}
+ for machine_name in catalog_all[catalog_key]:
+ m_render_all_dic[machine_name] = machines_render[machine_name]
+ ROMs_all_FN = cfg.CACHE_DIR.pjoin(hash_str + '_render.json')
+ utils_write_JSON_file(ROMs_all_FN.getPath(), m_render_all_dic, verbose = False)
+ catalog_count += 1
+ pDialog.endProgress()
+
+ # --- Timestamp ---
+ db_safe_edit(control_dic, 't_MAME_render_cache_build', time.time())
+ utils_write_JSON_file(cfg.MAIN_CONTROL_PATH.getPath(), control_dic)
+
+def db_get_render_cache_row(cfg, cache_index_dic, catalog_name, category_name):
+ hash_str = cache_index_dic[catalog_name][category_name]['hash']
+ ROMs_all_FN = cfg.CACHE_DIR.pjoin(hash_str + '_render.json')
+
+ return utils_load_JSON_file(ROMs_all_FN.getPath())
+
+# -------------------------------------------------------------------------------------------------
+# MAME asset cache
+# -------------------------------------------------------------------------------------------------
+def db_build_asset_cache(cfg, control_dic, cache_index_dic, assets_dic, force_build = False):
+ log_info('db_build_asset_cache() Initialising...')
+ log_debug('debug_enable_MAME_asset_cache is {}'.format(cfg.settings['debug_enable_MAME_asset_cache']))
+ log_debug('force_build is {}'.format(force_build))
+ if not cfg.settings['debug_enable_MAME_asset_cache'] and not force_build:
+ log_info('db_build_asset_cache() Asset cache disabled.')
+ return
+ # Notify user this is a forced build.
+ if not cfg.settings['debug_enable_MAME_render_cache'] and force_build:
+ t = 'MAME asset cache disabled but forcing rebuilding.'
+ log_info(t)
+ kodi_dialog_OK(t)
+
+ # --- Clean 'cache' directory JSON Asset files ---
+ log_info('Cleaning dir "{}"'.format(cfg.CACHE_DIR.getPath()))
+ pDialog = KodiProgressDialog()
+ pDialog.startProgress('Listing asset cache JSON files...')
+ file_list = os.listdir(cfg.CACHE_DIR.getPath())
+ log_info('Found {} files'.format(len(file_list)))
+ deleted_items = 0
+ pDialog.resetProgress('Cleaning asset cache JSON files...', len(file_list))
+ for file in file_list:
+ pDialog.updateProgressInc()
+ if not file.endswith('_assets.json'): continue
+ full_path = os.path.join(cfg.CACHE_DIR.getPath(), file)
+ # log_debug('UNLINK "{}"'.format(full_path))
+ os.unlink(full_path)
+ deleted_items += 1
+ pDialog.endProgress()
+ log_info('Deleted {} files'.format(deleted_items))
+
+ # --- Build MAME asset cache ---
+ num_catalogs = len(cache_index_dic)
+ catalog_count = 1
+ pDialog.startProgress('Building MAME asset cache')
+ for catalog_name in sorted(cache_index_dic):
+ catalog_index_dic = cache_index_dic[catalog_name]
+ catalog_all = db_get_cataloged_dic_all(cfg, catalog_name)
+ diag_t = 'Building MAME [COLOR orange]{}[/COLOR] asset cache ({} of {})...'.format(
+ catalog_name, catalog_count, num_catalogs)
+ pDialog.resetProgress(diag_t, len(catalog_index_dic))
+ for catalog_key in catalog_index_dic:
+ pDialog.updateProgressInc()
+ hash_str = catalog_index_dic[catalog_key]['hash']
+ # log_debug('db_build_asset_cache() Catalog "{}" --- Key "{}"'.format(catalog_name, catalog_key))
+ # log_debug('db_build_asset_cache() hash {}'.format(hash_str))
+
+ # Build all machines cache
+ m_assets_all_dic = {}
+ for machine_name in catalog_all[catalog_key]:
+ m_assets_all_dic[machine_name] = assets_dic[machine_name]
+ ROMs_all_FN = cfg.CACHE_DIR.pjoin(hash_str + '_assets.json')
+ utils_write_JSON_file(ROMs_all_FN.getPath(), m_assets_all_dic, verbose = False)
+ catalog_count += 1
+ pDialog.endProgress()
+
+ # Update timestamp and save control_dic.
+ db_safe_edit(control_dic, 't_MAME_asset_cache_build', time.time())
+ utils_write_JSON_file(cfg.MAIN_CONTROL_PATH.getPath(), control_dic)
+
+def db_get_asset_cache_row(cfg, cache_index_dic, catalog_name, category_name):
+ hash_str = cache_index_dic[catalog_name][category_name]['hash']
+ ROMs_all_FN = cfg.CACHE_DIR.pjoin(hash_str + '_assets.json')
+
+ return utils_load_JSON_file(ROMs_all_FN.getPath())
+
+# -------------------------------------------------------------------------------------------------
+# Load and save a bunch of JSON files
+# -------------------------------------------------------------------------------------------------
+#
+# Accepts a list of JSON files to be loaded. Displays a progress dialog.
+# Returns a dictionary with the context of the loaded files.
+#
+def db_load_files(db_files):
+ log_debug('db_load_files() Loading {} JSON database files...'.format(len(db_files)))
+ db_dic = {}
+ d_text = 'Loading databases...'
+ pDialog = KodiProgressDialog()
+ pDialog.startProgress(d_text, len(db_files))
+ for f_item in db_files:
+ dict_key, db_name, db_path = f_item
+ pDialog.updateProgressInc('{}\nDatabase [COLOR orange]{}[/COLOR]'.format(d_text, db_name))
+ db_dic[dict_key] = utils_load_JSON_file(db_path)
+ pDialog.endProgress()
+
+ return db_dic
+
+def db_save_files(db_files, json_write_func = utils_write_JSON_file):
+ log_debug('db_save_files() Saving {} JSON database files...'.format(len(db_files)))
+ d_text = 'Saving databases...'
+ pDialog = KodiProgressDialog()
+ pDialog.startProgress(d_text, len(db_files))
+ for f_item in db_files:
+ dict_data, db_name, db_path = f_item
+ pDialog.updateProgressInc('{}\nDatabase [COLOR orange]{}[/COLOR]'.format(d_text, db_name))
+ json_write_func(db_path, dict_data)
+ pDialog.endProgress()
+
+# -------------------------------------------------------------------------------------------------
+# Export stuff
+# -------------------------------------------------------------------------------------------------
+def db_export_Read_Only_Launcher(export_FN, catalog_dic, machines, machines_render, assets_dic):
+ log_debug('db_export_Read_Only_Launcher() File "{}"'.format(export_FN.getPath()))
+
+ # Create list of strings.
+ sl = []
+ sl.append('')
+ sl.append(''.format(time.strftime("%Y-%m-%d %H:%M:%S")))
+ sl.append('')
+ for m_name, r_name in catalog_dic.items():
+ sl.append('')
+ sl.append(XML_text('name', m_name))
+ sl.append(XML_text('description', machines_render[m_name]['description']))
+ sl.append(XML_text('genre', machines_render[m_name]['genre']))
+ sl.append(XML_text('year', machines_render[m_name]['year']))
+ sl.append(XML_text('cabinet', assets_dic[m_name]['cabinet']))
+ sl.append('')
+ sl.append('')
+ utils_write_str_list_to_file(sl, export_FN)
diff --git a/plugin.program.AML/resources/filters.py b/plugin.program.AML/resources/filters.py
new file mode 100644
index 0000000000..30f698bc93
--- /dev/null
+++ b/plugin.program.AML/resources/filters.py
@@ -0,0 +1,1604 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2016-2020 Wintermute0110
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+
+# Advanced MAME Launcher MAME filter engine.
+
+# --- Modules/packages in this plugin ---
+from .constants import *
+from .utils import *
+from .misc import *
+from .db import *
+from .mame_misc import *
+
+# --- Python standard library ---
+import xml.etree.ElementTree
+
+# -------------------------------------------------------------------------------------------------
+# Constants
+# -------------------------------------------------------------------------------------------------
+OPTIONS_KEYWORK_LIST = [
+ 'NoClones',
+ 'NoCoin',
+ 'NoCoinLess',
+ 'NoROMs',
+ 'NoCHDs',
+ 'NoSamples',
+ 'NoMature',
+ 'NoBIOS',
+ 'NoMechanical',
+ 'NoImperfect',
+ 'NoNonworking',
+ 'NoVertical',
+ 'NoHorizontal',
+ 'NoMissingROMs',
+ 'NoMissingCHDs',
+ 'NoMissingSamples',
+]
+
+# -------------------------------------------------------------------------------------------------
+# Parse filter XML definition
+# -------------------------------------------------------------------------------------------------
+#
+# Strips a list of strings.
+#
+def _strip_str_list(t_list):
+ for i, s_t in enumerate(t_list):
+ t_list[i] = s_t.strip()
+
+ return t_list
+
+#
+# Returns a comma-separated string of values as a list of strings.
+#
+def _get_comma_separated_list(text_t):
+ if not text_t:
+ return []
+ else:
+ return _strip_str_list(text_t.split(','))
+
+#
+# Parse a string 'XXXXXX with YYYYYY' and return a tuple.
+#
+def _get_change_tuple(text_t):
+ if not text_t: return ()
+ # Returns a list of strings or list of tuples.
+ tuple_list = re.findall('(\w+) with (\w+)', text_t)
+ if tuple_list:
+ return tuple_list[0]
+ else:
+ log_error('_get_change_tuple() text_t = "{}"'.format(text_t))
+ m = '(Exception) Cannot parse "{}"'.format(text_t)
+ log_error(m)
+ raise Addon_Error(m)
+
+# -------------------------------------------------------------------------------------------------
+# String Parser (SP) engine. Grammar token objects.
+# Parser inspired by http://effbot.org/zone/simple-top-down-parsing.htm
+#
+# SP operators: and, or, not, has, lacks, literal.
+# -------------------------------------------------------------------------------------------------
+debug_SP_parser = False
+debug_SP_parse_exec = False
+
+class SP_literal_token:
+ def __init__(self, value): self.value = value
+ def nud(self):
+ if debug_SP_parser: log_debug('Call LITERAL token nud()')
+ return self
+ def exec_token(self):
+ if debug_SP_parser: log_debug('Executing LITERAL token value "{}"'.format(self.value))
+ ret = self.value
+ if debug_SP_parser: log_debug('Token LITERAL returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return ''.format(self.value)
+
+class SP_operator_has_token:
+ lbp = 50
+ def __init__(self): pass
+ def nud(self):
+ if debug_SP_parser: log_debug('Call HAS token nud()')
+ self.first = SP_expression(50)
+ return self
+ def exec_token(self):
+ if debug_SP_parser: log_debug('Executing HAS token')
+ literal_str = self.first.exec_token()
+ if type(literal_str) is not text_type:
+ raise SyntaxError("HAS token exec; expected string, got {}".format(type(literal_str)))
+ ret = True if SP_parser_search_string.find(literal_str) >= 0 else False
+ if debug_SP_parser: log_debug('Token HAS returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return ""
+
+class SP_operator_lacks_token:
+ lbp = 50
+ def __init__(self): pass
+ def nud(self):
+ if debug_SP_parser: log_debug('Call LACKS token nud()')
+ self.first = SP_expression(50)
+ return self
+ def exec_token(self):
+ if debug_SP_parser: log_debug('Executing LACKS token')
+ literal_str = self.first.exec_token()
+ if type(literal_str) is not text_type:
+ raise SyntaxError("LACKS token exec; expected string, got {}".format(type(literal_str)))
+ ret = False if SP_parser_search_string.find(literal_str) >= 0 else True
+ if debug_SP_parser: log_debug('Token LACKS returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return ""
+
+class SP_operator_not_token:
+ lbp = 50
+ def __init__(self): pass
+ def nud(self):
+ if debug_SP_parser: log_debug('Call NOT token nud()')
+ self.first = SP_expression(50)
+ return self
+ def exec_token(self):
+ if debug_SP_parser: log_debug('Executing NOT token')
+ exp_bool = self.first.exec_token()
+ if type(exp_bool) is not bool:
+ raise SyntaxError("NOT token exec; expected string, got {}".format(type(exp_bool)))
+ ret = not exp_bool
+ if debug_SP_parser: log_debug('Token NOT returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return ""
+
+class SP_operator_and_token:
+ lbp = 10
+ def __init__(self): pass
+ def led(self, left):
+ if debug_SP_parser: log_debug('Call AND token led()')
+ self.first = left
+ self.second = SP_expression(10)
+ return self
+ def exec_token(self):
+ if debug_SP_parser: log_debug('Executing AND token')
+ ret = self.first.exec_token() and self.second.exec_token()
+ if debug_SP_parser: log_debug('Token AND returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return ""
+
+class SP_operator_or_token:
+ lbp = 10
+ def __init__(self): pass
+ def led(self, left):
+ if debug_SP_parser: log_debug('Call OR token led()')
+ self.first = left
+ self.second = SP_expression(10)
+ return self
+ def exec_token(self):
+ if debug_SP_parser: log_debug('Executing OR token')
+ ret = self.first.exec_token() or self.second.exec_token()
+ if debug_SP_parser: log_debug('Token OR returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return ""
+
+class SP_end_token:
+ lbp = 0
+ def __init__(self): pass
+ def __repr__(self): return ""
+
+# -------------------------------------------------------------------------------------------------
+# String Parser (SP) Tokenizer
+# See http://jeffknupp.com/blog/2013/04/07/improve-your-python-yield-and-generators-explained/
+# -------------------------------------------------------------------------------------------------
+SP_token_pat = re.compile("\s*(?:(and|or|not|has|lacks)|(\"[ \.\w_\-\&\/]+\")|([\.\w_\-\&]+))")
+
+def SP_tokenize(program):
+ # \s* -> Matches any number of blanks [ \t\n\r\f\v].
+ # (?:...) -> A non-capturing version of regular parentheses.
+ # \w -> Matches [a-zA-Z0-9_]
+ for operator, q_string, string in SP_token_pat.findall(program):
+ if string:
+ yield SP_literal_token(string)
+ elif q_string:
+ if q_string[0] == '"': q_string = q_string[1:]
+ if q_string[-1] == '"': q_string = q_string[:-1]
+ yield SP_literal_token(q_string)
+ elif operator == "and":
+ yield SP_operator_and_token()
+ elif operator == "or":
+ yield SP_operator_or_token()
+ elif operator == "not":
+ yield SP_operator_not_token()
+ elif operator == "has":
+ yield SP_operator_has_token()
+ elif operator == "lacks":
+ yield SP_operator_lacks_token()
+ else:
+ raise SyntaxError("Unknown operator: '{}'".format(operator))
+ yield SP_end_token()
+
+# -------------------------------------------------------------------------------------------------
+# String Parser (SP) inspired by http://effbot.org/zone/simple-top-down-parsing.htm
+# -------------------------------------------------------------------------------------------------
+def SP_expression(rbp = 0):
+ global SP_token
+
+ t = SP_token
+ SP_token = SP_next()
+ left = t.nud()
+ while rbp < SP_token.lbp:
+ t = SP_token
+ SP_token = SP_next()
+ left = t.led(left)
+
+ return left
+
+def SP_parse_exec(program, search_string):
+ global SP_token, SP_next, SP_parser_search_string
+
+ if debug_SP_parse_exec:
+ log_debug('SP_parse_exec() Initialising program execution')
+ log_debug('SP_parse_exec() Search string "{}"'.format(search_string))
+ log_debug('SP_parse_exec() Program "{}"'.format(program))
+ SP_parser_search_string = search_string
+ if ADDON_RUNNING_PYTHON_2:
+ SP_next = SP_tokenize(program).next
+ elif ADDON_RUNNING_PYTHON_3:
+ SP_next = SP_tokenize(program).__next__
+ else:
+ raise TypeError('Undefined Python runtime version.')
+ SP_token = SP_next()
+
+ # Old function parse_exec()
+ rbp = 0
+ t = SP_token
+ SP_token = SP_next()
+ left = t.nud()
+ while rbp < SP_token.lbp:
+ t = SP_token
+ SP_token = SP_next()
+ left = t.led(left)
+ if debug_SP_parse_exec:
+ log_debug('SP_parse_exec() Init exec program in token {}'.format(left))
+
+ return left.exec_token()
+
+# -------------------------------------------------------------------------------------------------
+# List of String Parser (LSP) engine. Grammar token objects.
+# Parser inspired by http://effbot.org/zone/simple-top-down-parsing.htm
+# Also see test_parser_SP.py for more documentation.
+#
+# LSP operators: and, or, not, has, lacks, '(', ')', literal.
+# -------------------------------------------------------------------------------------------------
+debug_LSP_parser = False
+debug_LSP_parse_exec = False
+
+class LSP_literal_token:
+ def __init__(self, value): self.value = value
+ def nud(self):
+ if debug_LSP_parser: log_debug('Call LITERAL token nud()')
+ return self
+ def exec_token(self):
+ if debug_LSP_parser: log_debug('Executing LITERAL token value "{}"'.format(self.value))
+ ret = self.value
+ if debug_LSP_parser: log_debug('Token LITERAL returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return ''.format(self.value)
+
+# id is a type object as return by type()
+def LSP_advance(id = None):
+ global LSP_token
+
+ if id and type(LSP_token) != id:
+ raise SyntaxError("Expected {}".format(type(LSP_token)))
+ LSP_token = LSP_next()
+
+class LSP_operator_open_par_token:
+ lbp = 0
+ def __init__(self): pass
+ def nud(self):
+ if debug_LSP_parser: log_debug('Call ( token nud()')
+ expr = LSP_expression()
+ LSP_advance(LSP_operator_close_par_token)
+ return expr
+ def __repr__(self): return ""
+
+class LSP_operator_close_par_token:
+ lbp = 0
+ def __init__(self): pass
+ def __repr__(self): return ""
+
+class LSP_operator_has_token:
+ lbp = 50
+ def __init__(self): pass
+ def nud(self):
+ if debug_LSP_parser: log_debug('Call HAS token nud()')
+ self.first = LSP_expression(50)
+ return self
+ def exec_token(self):
+ if debug_LSP_parser: log_debug('Executing HAS token')
+ literal_str = self.first.exec_token()
+ if type(literal_str) is not text_type:
+ raise SyntaxError("HAS token exec; expected string, got {}".format(type(literal_str)))
+ ret = literal_str in LSP_parser_search_list
+ if debug_LSP_parser: log_debug('Token HAS returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return ""
+
+class LSP_operator_lacks_token:
+ lbp = 50
+ def __init__(self): pass
+ def nud(self):
+ if debug_LSP_parser: log_debug('Call LACKS token nud()')
+ self.first = LSP_expression(50)
+ return self
+ def exec_token(self):
+ if debug_LSP_parser: log_debug('Executing LACKS token')
+ literal_str = self.first.exec_token()
+ if type(literal_str) is not text_type:
+ raise SyntaxError("LACKS token exec; expected string, got {}".format(type(literal_str)))
+ ret = literal_str not in LSP_parser_search_list
+ if debug_LSP_parser: log_debug('Token LACKS returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return ""
+
+class LSP_operator_not_token:
+ lbp = 50
+ def __init__(self): pass
+ def nud(self):
+ if debug_LSP_parser: log_debug('Call NOT token nud()')
+ self.first = LSP_expression(50)
+ return self
+ def exec_token(self):
+ if debug_LSP_parser: log_debug('Executing NOT token')
+ exp_bool = self.first.exec_token()
+ if type(exp_bool) is not bool:
+ raise SyntaxError("NOT token exec; expected string, got {}".format(type(exp_bool)))
+ ret = not exp_bool
+ if debug_LSP_parser: log_debug('Token NOT returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return ""
+
+class LSP_operator_and_token:
+ lbp = 10
+ def __init__(self): pass
+ def led(self, left):
+ if debug_LSP_parser: log_debug('Call AND token led()')
+ self.first = left
+ self.second = LSP_expression(10)
+ return self
+ def exec_token(self):
+ if debug_LSP_parser: log_debug('Executing AND token')
+ ret = self.first.exec_token() and self.second.exec_token()
+ if debug_LSP_parser: log_debug('Token AND returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return ""
+
+class LSP_operator_or_token:
+ lbp = 10
+ def __init__(self): pass
+ def led(self, left):
+ if debug_LSP_parser: log_debug('Call OR token led()')
+ self.first = left
+ self.second = LSP_expression(10)
+ return self
+ def exec_token(self):
+ if debug_LSP_parser: log_debug('Executing OR token')
+ ret = self.first.exec_token() or self.second.exec_token()
+ if debug_LSP_parser: log_debug('Token OR returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return ""
+
+class LSP_end_token:
+ lbp = 0
+ def __init__(self): pass
+ def __repr__(self): return ""
+
+# -------------------------------------------------------------------------------------------------
+# List of String Parser (LSP) Tokenizer
+# See http://jeffknupp.com/blog/2013/04/07/improve-your-python-yield-and-generators-explained/
+# -------------------------------------------------------------------------------------------------
+LSP_token_pat = re.compile("\s*(?:(and|or|not|has|lacks|\(|\))|(\"[ \.\w_\-\&\/]+\")|([\.\w_\-\&]+))")
+
+def LSP_tokenize(program):
+ for operator, q_string, string in LSP_token_pat.findall(program):
+ if string:
+ yield LSP_literal_token(string)
+ elif q_string:
+ if q_string[0] == '"': q_string = q_string[1:]
+ if q_string[-1] == '"': q_string = q_string[:-1]
+ yield LSP_literal_token(q_string)
+ elif operator == "and":
+ yield LSP_operator_and_token()
+ elif operator == "or":
+ yield LSP_operator_or_token()
+ elif operator == "not":
+ yield LSP_operator_not_token()
+ elif operator == "has":
+ yield LSP_operator_has_token()
+ elif operator == "lacks":
+ yield LSP_operator_lacks_token()
+ elif operator == "(":
+ yield LSP_operator_open_par_token()
+ elif operator == ")":
+ yield LSP_operator_close_par_token()
+ else:
+ raise SyntaxError("Unknown operator: '{}'".format(operator))
+ yield LSP_end_token()
+
+# -------------------------------------------------------------------------------------------------
+# List of String Parser (LSP) inspired by http://effbot.org/zone/simple-top-down-parsing.htm
+# -------------------------------------------------------------------------------------------------
+def LSP_expression(rbp = 0):
+ global LSP_token
+
+ t = LSP_token
+ LSP_token = LSP_next()
+ left = t.nud()
+ while rbp < LSP_token.lbp:
+ t = LSP_token
+ LSP_token = LSP_next()
+ left = t.led(left)
+ return left
+
+def LSP_parse_exec(program, search_list):
+ global LSP_token, LSP_next, LSP_parser_search_list
+
+ if debug_LSP_parse_exec:
+ log_debug('LSP_parse_exec() Initialising program execution')
+ log_debug('LSP_parse_exec() Search "{}"'.format(text_type(search_list)))
+ log_debug('LSP_parse_exec() Program "{}"'.format(program))
+ LSP_parser_search_list = search_list
+ if ADDON_RUNNING_PYTHON_2:
+ LSP_next = LSP_tokenize(program).next
+ elif ADDON_RUNNING_PYTHON_3:
+ LSP_next = LSP_tokenize(program).__next__
+ else:
+ raise TypeError('Undefined Python runtime version.')
+ LSP_token = LSP_next()
+
+ # Old function parse_exec().
+ rbp = 0
+ t = LSP_token
+ LSP_token = LSP_next()
+ left = t.nud()
+ while rbp < LSP_token.lbp:
+ t = LSP_token
+ LSP_token = LSP_next()
+ left = t.led(left)
+ if debug_LSP_parse_exec:
+ log_debug('LSP_parse_exec() Init exec program in token {}'.format(left))
+
+ return left.exec_token()
+
+# -------------------------------------------------------------------------------------------------
+# Year Parser (YP) engine. Grammar token objects.
+# Parser inspired by http://effbot.org/zone/simple-top-down-parsing.htm
+# See also test_parser_SP.py for more documentation.
+#
+# YP operators: ==, !=, >, <, >=, <=, and, or, not, '(', ')', literal.
+# literal may be the special variable 'year' or a MAME number.
+# -------------------------------------------------------------------------------------------------
+debug_YP_parser = False
+debug_YP_parse_exec = False
+
+class YP_literal_token:
+ def __init__(self, value): self.value = value
+ def nud(self): return self
+ def exec_token(self):
+ if self.value == 'year':
+ ret = YP_year
+ else:
+ ret = int(self.value)
+ if debug_YP_parser: log_debug('Token LITERAL returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return '[LITERAL "{}"]'.format(self.value)
+
+def YP_advance(id = None):
+ global YP_token
+
+ if id and type(YP_token) != id:
+ raise SyntaxError("Expected {}".format(type(YP_token)))
+ YP_token = YP_next()
+
+class YP_operator_open_par_token:
+ lbp = 0
+ def __init__(self): pass
+ def nud(self):
+ expr = YP_expression()
+ YP_advance(YP_operator_close_par_token)
+ return expr
+ def __repr__(self): return "[OP (]"
+
+class YP_operator_close_par_token:
+ lbp = 0
+ def __init__(self): pass
+ def __repr__(self): return "[OP )]"
+
+class YP_operator_not_token:
+ lbp = 60
+ def __init__(self): pass
+ def nud(self):
+ self.first = YP_expression(50)
+ return self
+ def exec_token(self):
+ ret = not self.first.exec_token()
+ if debug_YP_parser: log_debug('Token NOT returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return "[OP not]"
+
+class YP_operator_and_token:
+ lbp = 10
+ def __init__(self): pass
+ def led(self, left):
+ self.first = left
+ self.second = YP_expression(10)
+ return self
+ def exec_token(self):
+ ret = self.first.exec_token() and self.second.exec_token()
+ if debug_YP_parser: log_debug('Token AND returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return "[OP and]"
+
+class YP_operator_or_token:
+ lbp = 10
+ def __init__(self): pass
+ def led(self, left):
+ self.first = left
+ self.second = YP_expression(10)
+ return self
+ def exec_token(self):
+ ret = self.first.exec_token() or self.second.exec_token()
+ if debug_YP_parser: log_debug('Token OR returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return "[OP or]"
+
+class YP_operator_equal_token:
+ lbp = 50
+ def __init__(self): pass
+ def led(self, left):
+ self.first = left
+ self.second = YP_expression(10)
+ return self
+ def exec_token(self):
+ ret = self.first.exec_token() == self.second.exec_token()
+ if debug_YP_parser: log_debug('Token == returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return "[OP ==]"
+
+class YP_operator_not_equal_token:
+ lbp = 50
+ def __init__(self): pass
+ def led(self, left):
+ self.first = left
+ self.second = YP_expression(10)
+ return self
+ def exec_token(self):
+ ret = self.first.exec_token() != self.second.exec_token()
+ if debug_YP_parser: log_debug('Token != returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return "[OP !=]"
+
+class YP_operator_great_than_token:
+ lbp = 50
+ def __init__(self): pass
+ def led(self, left):
+ self.first = left
+ self.second = YP_expression(10)
+ return self
+ def exec_token(self):
+ ret = self.first.exec_token() > self.second.exec_token()
+ if debug_YP_parser: log_debug('Token > returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return "[OP >]"
+
+class YP_operator_less_than_token:
+ lbp = 50
+ def __init__(self): pass
+ def led(self, left):
+ self.first = left
+ self.second = YP_expression(10)
+ return self
+ def exec_token(self):
+ ret = self.first.exec_token() < self.second.exec_token()
+ if debug_YP_parser: log_debug('Token < returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return "[OP <]"
+
+class YP_operator_great_or_equal_than_token:
+ lbp = 50
+ def __init__(self): pass
+ def led(self, left):
+ self.first = left
+ self.second = YP_expression(10)
+ return self
+ def exec_token(self):
+ ret = self.first.exec_token() >= self.second.exec_token()
+ if debug_YP_parser: log_debug('Token >= returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return "[OP >=]"
+
+class YP_operator_less_or_equal_than_token:
+ lbp = 50
+ def __init__(self): pass
+ def led(self, left):
+ self.first = left
+ self.second = YP_expression(10)
+ return self
+ def exec_token(self):
+ ret = self.first.exec_token() <= self.second.exec_token()
+ if debug_YP_parser: log_debug('Token <= returns {} "{}"'.format(type(ret), text_type(ret)))
+ return ret
+ def __repr__(self): return "[OP <=]"
+
+class YP_end_token:
+ lbp = 0
+ def __init__(self): pass
+ def __repr__(self): return "[END token]"
+
+# -------------------------------------------------------------------------------------------------
+# Year Parser Tokenizer
+# See http://jeffknupp.com/blog/2013/04/07/improve-your-python-yield-and-generators-explained/
+# -------------------------------------------------------------------------------------------------
+YP_token_pat = re.compile("\s*(?:(==|!=|>=|<=|>|<|and|or|not|\(|\))|([\w]+))")
+
+def YP_tokenize(program):
+ for operator, n_string in YP_token_pat.findall(program):
+ if n_string: yield YP_literal_token(n_string)
+ elif operator == "==": yield YP_operator_equal_token()
+ elif operator == "!=": yield YP_operator_not_equal_token()
+ elif operator == ">": yield YP_operator_great_than_token()
+ elif operator == "<": yield YP_operator_less_than_token()
+ elif operator == ">=": yield YP_operator_great_or_equal_than_token()
+ elif operator == "<=": yield YP_operator_less_or_equal_than_token()
+ elif operator == "and": yield YP_operator_and_token()
+ elif operator == "or": yield YP_operator_or_token()
+ elif operator == "not": yield YP_operator_not_token()
+ elif operator == "(": yield YP_operator_open_par_token()
+ elif operator == ")": yield YP_operator_close_par_token()
+ else: raise SyntaxError("Unknown operator: '{}'".format(operator))
+ yield YP_end_token()
+
+# -------------------------------------------------------------------------------------------------
+# Year Parser (YP) inspired by http://effbot.org/zone/simple-top-down-parsing.htm
+# -------------------------------------------------------------------------------------------------
+def YP_expression(rbp = 0):
+ global YP_token
+
+ t = YP_token
+ YP_token = YP_next()
+ left = t.nud()
+ while rbp < YP_token.lbp:
+ t = YP_token
+ YP_token = YP_next()
+ left = t.led(left)
+ return left
+
+def YP_parse_exec(program, year_str):
+ global YP_token, YP_next, YP_year
+
+ # --- Transform year_str to an integer. year_str may be ill formed ---
+ if re.findall(r'^[0-9]{4}$', year_str):
+ year = int(year_str)
+ elif re.findall(r'^[0-9]{4}\?$', year_str):
+ year = int(year_str[0:4])
+ else:
+ year = 0
+
+ if debug_YP_parse_exec:
+ log_debug('YP_parse_exec() Initialising program execution')
+ log_debug('YP_parse_exec() year "{}"'.format(year))
+ log_debug('YP_parse_exec() Program "{}"'.format(program))
+ YP_year = year
+ if ADDON_RUNNING_PYTHON_2:
+ YP_next = YP_tokenize(program).next
+ elif ADDON_RUNNING_PYTHON_3:
+ YP_next = YP_tokenize(program).__next__
+ else:
+ raise TypeError('Undefined Python runtime version.')
+ YP_token = YP_next()
+
+ # Old function parse_exec().
+ rbp = 0
+ t = YP_token
+ YP_token = YP_next()
+ left = t.nud()
+ while rbp < YP_token.lbp:
+ t = YP_token
+ YP_token = YP_next()
+ left = t.led(left)
+ if debug_YP_parse_exec:
+ log_debug('YP_parse_exec() Init exec program in token {}'.format(left))
+
+ return left.exec_token()
+
+# -------------------------------------------------------------------------------------------------
+# MAME machine filters
+# -------------------------------------------------------------------------------------------------
+#
+# Default filter removes device machines.
+#
+def filter_mame_Default(mame_xml_dic):
+ log_debug('filter_mame_Default() Starting ...')
+ initial_num_games = len(mame_xml_dic)
+ filtered_out_games = 0
+ machines_filtered_dic = {}
+ for m_name in sorted(mame_xml_dic):
+ if mame_xml_dic[m_name]['isDevice']:
+ filtered_out_games += 1
+ else:
+ machines_filtered_dic[m_name] = mame_xml_dic[m_name]
+ log_debug('filter_mame_Default() Initial {} | '.format(initial_num_games) + \
+ 'Removed {} | '.format(filtered_out_games) + \
+ 'Remaining {}'.format(len(machines_filtered_dic)))
+
+ return machines_filtered_dic
+
+def filter_mame_Options_tag(mame_xml_dic, f_definition):
+ # log_debug('filter_mame_Options_tag() Starting ...')
+ options_list = f_definition['options']
+
+ if not options_list:
+ log_debug('filter_mame_Options_tag() Option list is empty.')
+ return mame_xml_dic
+ log_debug('Option list "{}"'.format(options_list))
+
+ # --- Compute bool variables ---
+ # This must match OPTIONS_KEYWORK_LIST
+ NoClones_bool = True if 'NoClones' in options_list else False
+ NoCoin_bool = True if 'NoCoin' in options_list else False
+ NoCoinLess_bool = True if 'NoCoinLess' in options_list else False
+ NoROMs_bool = True if 'NoROMs' in options_list else False
+ NoCHDs_bool = True if 'NoCHDs' in options_list else False
+ NoSamples_bool = True if 'NoSamples' in options_list else False
+ NoMature_bool = True if 'NoMature' in options_list else False
+ NoBIOS_bool = True if 'NoBIOS' in options_list else False
+ NoMechanical_bool = True if 'NoMechanical' in options_list else False
+ NoImperfect_bool = True if 'NoImperfect' in options_list else False
+ NoNonWorking_bool = True if 'NoNonworking' in options_list else False
+ NoVertical_bool = True if 'NoVertical' in options_list else False
+ NoHorizontal_bool = True if 'NoHorizontal' in options_list else False
+ # ROM scanner boolean filters.
+ NoMissingROMs_bool = True if 'NoMissingROMs' in options_list else False
+ NoMissingCHDs_bool = True if 'NoMissingCHDs' in options_list else False
+ NoMissingSamples_bool = True if 'NoMissingSamples' in options_list else False
+
+ log_debug('NoClones_bool {}'.format(NoClones_bool))
+ log_debug('NoCoin_bool {}'.format(NoCoin_bool))
+ log_debug('NoCoinLess_bool {}'.format(NoCoinLess_bool))
+ log_debug('NoROMs_bool {}'.format(NoROMs_bool))
+ log_debug('NoCHDs_bool {}'.format(NoCHDs_bool))
+ log_debug('NoSamples_bool {}'.format(NoSamples_bool))
+ log_debug('NoMature_bool {}'.format(NoMature_bool))
+ log_debug('NoBIOS_bool {}'.format(NoBIOS_bool))
+ log_debug('NoMechanical_bool {}'.format(NoMechanical_bool))
+ log_debug('NoImperfect_bool {}'.format(NoImperfect_bool))
+ log_debug('NoNonWorking_bool {}'.format(NoNonWorking_bool))
+ log_debug('NoVertical_bool {}'.format(NoVertical_bool))
+ log_debug('NoHorizontal_bool {}'.format(NoHorizontal_bool))
+ log_debug('NoMissingROMs_bool {}'.format(NoMissingROMs_bool))
+ log_debug('NoMissingCHDs_bool {}'.format(NoMissingCHDs_bool))
+ log_debug('NoMissingSamples_bool {}'.format(NoMissingSamples_bool))
+
+ initial_num_games = len(mame_xml_dic)
+ filtered_out_games = 0
+ machines_filtered_dic = {}
+ for m_name in sorted(mame_xml_dic):
+ # Remove Clone machines
+ if NoClones_bool and mame_xml_dic[m_name]['isClone']:
+ filtered_out_games += 1
+ continue
+ # Remove Coin machines
+ if NoCoin_bool and mame_xml_dic[m_name]['coins'] > 0:
+ filtered_out_games += 1
+ continue
+ # Remove CoinLess machines
+ if NoCoinLess_bool and mame_xml_dic[m_name]['coins'] == 0:
+ filtered_out_games += 1
+ continue
+ # Remove ROM machines
+ if NoROMs_bool and mame_xml_dic[m_name]['hasROMs']:
+ filtered_out_games += 1
+ continue
+ # Remove CHD machines
+ if NoCHDs_bool and mame_xml_dic[m_name]['hasCHDs']:
+ filtered_out_games += 1
+ continue
+ # Remove Samples machines
+ if NoSamples_bool and mame_xml_dic[m_name]['hasSamples']:
+ filtered_out_games += 1
+ continue
+ # Remove Mature machines
+ if NoMature_bool and mame_xml_dic[m_name]['isMature']:
+ filtered_out_games += 1
+ continue
+ # Remove BIOS machines
+ if NoBIOS_bool and mame_xml_dic[m_name]['isBIOS']:
+ filtered_out_games += 1
+ continue
+ # Remove Mechanical machines
+ if NoMechanical_bool and mame_xml_dic[m_name]['isMechanical']:
+ filtered_out_games += 1
+ continue
+ # Remove Imperfect machines
+ if NoImperfect_bool and mame_xml_dic[m_name]['isImperfect']:
+ filtered_out_games += 1
+ continue
+ # Remove NonWorking machines
+ if NoNonWorking_bool and mame_xml_dic[m_name]['isNonWorking']:
+ filtered_out_games += 1
+ continue
+ # Remove Vertical machines
+ if NoVertical_bool and mame_xml_dic[m_name]['isVertical']:
+ filtered_out_games += 1
+ continue
+ # Remove Horizontal machines
+ if NoHorizontal_bool and mame_xml_dic[m_name]['isHorizontal']:
+ filtered_out_games += 1
+ continue
+ # Remove machines with Missing ROMs
+ if NoMissingROMs_bool and mame_xml_dic[m_name]['missingROMs']:
+ filtered_out_games += 1
+ continue
+ # Remove machines with Missing CHDs
+ if NoMissingCHDs_bool and mame_xml_dic[m_name]['missingCHDs']:
+ filtered_out_games += 1
+ continue
+ # Remove machines with Missing Samples
+ if NoMissingSamples_bool and mame_xml_dic[m_name]['missingSamples']:
+ filtered_out_games += 1
+ continue
+ # If machine was not removed then add it
+ machines_filtered_dic[m_name] = mame_xml_dic[m_name]
+ log_debug(
+ 'filter_mame_Options_tag() Initial {} | '.format(initial_num_games) + \
+ 'Removed {} | '.format(filtered_out_games) + \
+ 'Remaining {}'.format(len(machines_filtered_dic)))
+
+ return machines_filtered_dic
+
+def filter_mame_Driver_tag(mame_xml_dic, f_definition):
+ # log_debug('filter_mame_Driver_tag() Starting ...')
+ filter_expression = f_definition['driver']
+
+ if not filter_expression:
+ log_debug('filter_mame_Driver_tag() User wants all drivers')
+ return mame_xml_dic
+ log_debug('Expression "{}"'.format(filter_expression))
+
+ initial_num_games = len(mame_xml_dic)
+ filtered_out_games = 0
+ machines_filtered_dic = {}
+ for m_name in sorted(mame_xml_dic):
+ search_list = [ mame_xml_dic[m_name]['driver'] ]
+ bool_result = LSP_parse_exec(filter_expression, search_list)
+ if not bool_result:
+ filtered_out_games += 1
+ else:
+ machines_filtered_dic[m_name] = mame_xml_dic[m_name]
+ log_debug('filter_mame_Driver_tag() Initial {} | '.format(initial_num_games) + \
+ 'Removed {} | '.format(filtered_out_games) + \
+ 'Remaining {}'.format(len(machines_filtered_dic)))
+
+ return machines_filtered_dic
+
+def filter_mame_Manufacturer_tag(mame_xml_dic, f_definition):
+ # log_debug('filter_mame_Manufacturer_tag() Starting ...')
+ filter_expression = f_definition['manufacturer']
+
+ if not filter_expression:
+ log_debug('filter_mame_Manufacturer_tag() User wants all manufacturers')
+ return mame_xml_dic
+ log_debug('Expression "{}"'.format(filter_expression))
+
+ initial_num_games = len(mame_xml_dic)
+ filtered_out_games = 0
+ machines_filtered_dic = {}
+ for m_name in sorted(mame_xml_dic):
+ bool_result = SP_parse_exec(filter_expression, mame_xml_dic[m_name]['manufacturer'])
+ if not bool_result:
+ filtered_out_games += 1
+ else:
+ machines_filtered_dic[m_name] = mame_xml_dic[m_name]
+ log_debug('filter_mame_Manufacturer_tag() Initial {} | '.format(initial_num_games) + \
+ 'Removed {} | '.format(filtered_out_games) + \
+ 'Remaining {}'.format(len(machines_filtered_dic)))
+
+ return machines_filtered_dic
+
+def filter_mame_Genre_tag(mame_xml_dic, f_definition):
+ # log_debug('filter_mame_Genre_tag() Starting ...')
+ filter_expression = f_definition['genre']
+
+ if not filter_expression:
+ log_debug('filter_mame_Genre_tag() User wants all genres')
+ return mame_xml_dic
+ log_debug('Expression "{}"'.format(filter_expression))
+
+ initial_num_games = len(mame_xml_dic)
+ filtered_out_games = 0
+ machines_filtered_dic = {}
+ for m_name in sorted(mame_xml_dic):
+ search_list = [ mame_xml_dic[m_name]['genre'] ]
+ bool_result = LSP_parse_exec(filter_expression, search_list)
+ if not bool_result:
+ filtered_out_games += 1
+ else:
+ machines_filtered_dic[m_name] = mame_xml_dic[m_name]
+ log_debug('filter_mame_Genre_tag() Initial {} | '.format(initial_num_games) + \
+ 'Removed {} | '.format(filtered_out_games) + \
+ 'Remaining {}'.format(len(machines_filtered_dic)))
+
+ return machines_filtered_dic
+
+def filter_mame_Controls_tag(mame_xml_dic, f_definition):
+ # log_debug('filter_mame_Controls_tag() Starting ...')
+ filter_expression = f_definition['controls']
+
+ if not filter_expression:
+ log_debug('filter_mame_Controls_tag() User wants all genres')
+ return mame_xml_dic
+ log_debug('Expression "{}"'.format(filter_expression))
+
+ initial_num_games = len(mame_xml_dic)
+ filtered_out_games = 0
+ machines_filtered_dic = {}
+ for m_name in sorted(mame_xml_dic):
+ bool_result = LSP_parse_exec(filter_expression, mame_xml_dic[m_name]['control_list'])
+ if not bool_result:
+ filtered_out_games += 1
+ else:
+ machines_filtered_dic[m_name] = mame_xml_dic[m_name]
+ log_debug('filter_mame_Controls_tag() Initial {} | '.format(initial_num_games) + \
+ 'Removed {} | '.format(filtered_out_games) + \
+ 'Remaining {}'.format(len(machines_filtered_dic)))
+
+ return machines_filtered_dic
+
+def filter_mame_PluggableDevices_tag(mame_xml_dic, f_definition):
+ # log_debug('filter_mame_PluggableDevices_tag() Starting ...')
+ filter_expression = f_definition['pluggabledevices']
+
+ if not filter_expression:
+ log_debug('filter_mame_PluggableDevices_tag() User wants all genres')
+ return mame_xml_dic
+ log_debug('Expression "{}"'.format(filter_expression))
+
+ initial_num_games = len(mame_xml_dic)
+ filtered_out_games = 0
+ machines_filtered_dic = {}
+ for m_name in sorted(mame_xml_dic):
+ # --- Update search list variable and call parser to evaluate expression ---
+ bool_result = LSP_parse_exec(filter_expression, mame_xml_dic[m_name]['pluggable_device_list'])
+ if not bool_result:
+ filtered_out_games += 1
+ else:
+ machines_filtered_dic[m_name] = mame_xml_dic[m_name]
+ log_debug(
+ 'filter_mame_PluggableDevices_tag() Initial {} | '.format(initial_num_games) + \
+ 'Removed {} | '.format(filtered_out_games) + \
+ 'Remaining {}'.format(len(machines_filtered_dic)))
+
+ return machines_filtered_dic
+
+def filter_mame_Year_tag(mame_xml_dic, f_definition):
+ # log_debug('filter_mame_Year_tag() Starting ...')
+ filter_expression = f_definition['year']
+
+ if not filter_expression:
+ log_debug('filter_mame_Year_tag() User wants all genres')
+ return mame_xml_dic
+ log_debug('Expression "{}"'.format(filter_expression))
+
+ initial_num_games = len(mame_xml_dic)
+ filtered_out_games = 0
+ machines_filtered_dic = {}
+ for m_name in sorted(mame_xml_dic):
+ # --- Update search int variable and call parser to evaluate expression ---
+ bool_result = YP_parse_exec(filter_expression, mame_xml_dic[m_name]['year'])
+ if not bool_result:
+ filtered_out_games += 1
+ else:
+ machines_filtered_dic[m_name] = mame_xml_dic[m_name]
+ log_debug('filter_mame_Year_tag() Initial {} | '.format(initial_num_games) + \
+ 'Removed {} | '.format(filtered_out_games) + \
+ 'Remaining {}'.format(len(machines_filtered_dic)))
+
+ return machines_filtered_dic
+
+def filter_mame_Include_tag(mame_xml_dic, f_definition, machines_dic):
+ # log_debug('filter_mame_Include_tag() Starting ...')
+ log_debug('filter_mame_Include_tag() Include machines {}'.format(text_type(f_definition['include'])))
+ added_machines = 0
+ machines_filtered_dic = mame_xml_dic.copy()
+ # If no machines to include then skip processing
+ if not f_definition['include']:
+ log_debug('filter_mame_Include_tag() No machines to include. Exiting.')
+ return machines_filtered_dic
+ # First traverse all MAME machines, then traverse list of strings to include.
+ for m_name in sorted(machines_dic):
+ for f_name in f_definition['include']:
+ if f_name == m_name:
+ log_debug('filter_mame_Include_tag() Matched machine {}'.format(f_name))
+ if f_name in machines_filtered_dic:
+ log_debug('filter_mame_Include_tag() Machine {} already in filtered list'.format(f_name))
+ else:
+ log_debug('filter_mame_Include_tag() Adding machine {}'.format(f_name))
+ machines_filtered_dic[m_name] = machines_dic[m_name]
+ added_machines += 1
+ log_debug('filter_mame_Include_tag() Initial {} | '.format(len(mame_xml_dic)) + \
+ 'Added {} | '.format(added_machines) + \
+ 'Remaining {}'.format(len(machines_filtered_dic)))
+
+ return machines_filtered_dic
+
+def filter_mame_Exclude_tag(mame_xml_dic, f_definition):
+ # log_debug('filter_mame_Exclude_tag() Starting ...')
+ log_debug('filter_mame_Exclude_tag() Exclude machines {}'.format(text_type(f_definition['exclude'])))
+ initial_num_games = len(mame_xml_dic)
+ filtered_out_games = 0
+ machines_filtered_dic = mame_xml_dic.copy()
+ # If no machines to exclude then skip processing
+ if not f_definition['exclude']:
+ log_debug('filter_mame_Exclude_tag() No machines to exclude. Exiting.')
+ return machines_filtered_dic
+ # First traverse current set of machines, then traverse list of strings to include.
+ for m_name in sorted(mame_xml_dic):
+ for f_name in f_definition['exclude']:
+ if f_name == m_name:
+ log_debug('filter_mame_Exclude_tag() Matched machine {}'.format(f_name))
+ log_debug('filter_mame_Exclude_tag() Deleting machine {}'.format(f_name))
+ del machines_filtered_dic[f_name]
+ filtered_out_games += 1
+ log_debug('filter_mame_Exclude_tag() Initial {} | '.format(initial_num_games) + \
+ 'Removed {} | '.format(filtered_out_games) + \
+ 'Remaining {}'.format(len(machines_filtered_dic)))
+
+ return machines_filtered_dic
+
+def filter_mame_Change_tag(mame_xml_dic, f_definition, machines_dic):
+ # log_debug('filter_mame_Change_tag() Starting ...')
+ log_debug('filter_mame_Change_tag() Change machines {}'.format(text_type(f_definition['change'])))
+ initial_num_games = len(mame_xml_dic)
+ changed_machines = 0
+ machines_filtered_dic = mame_xml_dic.copy()
+ # If no machines to change then skip processing
+ if not f_definition['change']:
+ log_debug('filter_mame_Change_tag() No machines to swap. Exiting.')
+ return machines_filtered_dic
+ # First traverse current set of machines, then traverse list of strings to include.
+ for m_name in sorted(mame_xml_dic):
+ for (f_name, new_name) in f_definition['change']:
+ if f_name == m_name:
+ log_debug('filter_mame_Change_tag() Matched machine {}'.format(f_name))
+ if new_name in machines_dic:
+ log_debug('filter_mame_Change_tag() Changing machine {} with {}'.format(f_name, new_name))
+ del machines_filtered_dic[f_name]
+ machines_filtered_dic[new_name] = machines_dic[new_name]
+ changed_machines += 1
+ else:
+ log_warning('filter_mame_Change_tag() New machine {} not found on MAME machines.'.format(new_name))
+ log_debug('filter_mame_Change_tag() Initial {} | '.format(initial_num_games) + \
+ 'Changed {} | '.format(changed_machines) + \
+ 'Remaining {}'.format(len(machines_filtered_dic)))
+
+ return machines_filtered_dic
+
+# -------------------------------------------------------------------------------------------------
+# Build MAME custom filters
+# -------------------------------------------------------------------------------------------------
+#
+# Returns a list of dictionaries, each dictionary has the filter definition.
+#
+def filter_parse_XML(fname_str):
+ __debug_xml_parser = False
+
+ # If XML has errors (invalid characters, etc.) this will rais exception 'err'
+ XML_FN = FileName(fname_str)
+ if not XML_FN.exists():
+ kodi_dialog_OK('Custom filter XML file not found.')
+ return
+ log_debug('filter_parse_XML() Reading XML OP "{}"'.format(XML_FN.getOriginalPath()))
+ log_debug('filter_parse_XML() Reading XML P "{}"'.format(XML_FN.getPath()))
+ try:
+ xml_tree = ET.parse(XML_FN.getPath())
+ except IOError as ex:
+ log_error('(Exception) {}'.format(ex))
+ log_error('(Exception) Syntax error in the XML file definition')
+ raise Addon_Error('(Exception) ET.parse(XML_FN.getPath()) failed.')
+ xml_root = xml_tree.getroot()
+ define_dic = {}
+ filters_list = []
+ for root_element in xml_root:
+ if __debug_xml_parser: log_debug('Root child {}'.format(root_element.tag))
+
+ if root_element.tag == 'DEFINE':
+ name_str = root_element.attrib['name']
+ define_str = root_element.text if root_element.text else ''
+ log_debug('DEFINE "{}" := "{}"'.format(name_str, define_str))
+ define_dic[name_str] = define_str
+ elif root_element.tag == 'MAMEFilter':
+ this_filter_dic = {
+ 'name' : '',
+ 'plot' : '',
+ 'options' : [], # List of strings
+ 'driver' : '',
+ 'manufacturer' : '',
+ 'genre' : '',
+ 'controls' : '',
+ 'pluggabledevices' : '',
+ 'year' : '',
+ 'include' : [], # List of strings
+ 'exclude' : [], # List of strings
+ 'change' : [], # List of tuples (change_orig string, change_dest string)
+ }
+ for filter_element in root_element:
+ if ADDON_RUNNING_PYTHON_2:
+ # In Python 2 filter_element.text has type str and not Unicode.
+ text_t = text_type(filter_element.text if filter_element.text else '')
+ elif ADDON_RUNNING_PYTHON_3:
+ text_t = filter_element.text if filter_element.text else ''
+ else:
+ raise TypeError('Undefined Python runtime version.')
+ # log_debug('text_t "{}" type "{}"'.format(text_t, type(text_t)))
+ if filter_element.tag == 'Name':
+ this_filter_dic['name'] = text_t
+ elif filter_element.tag == 'Plot':
+ this_filter_dic['plot'] = text_t
+ elif filter_element.tag == 'Options':
+ t_list = _get_comma_separated_list(text_t)
+ if t_list:
+ this_filter_dic['options'].extend(t_list)
+ elif filter_element.tag == 'Driver':
+ this_filter_dic['driver'] = text_t
+ elif filter_element.tag == 'Manufacturer':
+ this_filter_dic['manufacturer'] = text_t
+ elif filter_element.tag == 'Genre':
+ this_filter_dic['genre'] = text_t
+ elif filter_element.tag == 'Controls':
+ this_filter_dic['controls'] = text_t
+ elif filter_element.tag == 'PluggableDevices':
+ this_filter_dic['pluggabledevices'] = text_t
+ elif filter_element.tag == 'Year':
+ this_filter_dic['year'] = text_t
+ elif filter_element.tag == 'Include':
+ t_list = _get_comma_separated_list(text_t)
+ if t_list: this_filter_dic['include'].extend(t_list)
+ elif filter_element.tag == 'Exclude':
+ t_list = _get_comma_separated_list(text_t)
+ if t_list: this_filter_dic['exclude'].extend(t_list)
+ elif filter_element.tag == 'Change':
+ tuple_t = _get_change_tuple(text_t)
+ if tuple_t: this_filter_dic['change'].append(tuple_t)
+ else:
+ m = '(Exception) Unrecognised tag <{}>'.format(filter_element.tag)
+ log_debug(m)
+ raise Addon_Error(m)
+ log_debug('Adding filter "{}"'.format(this_filter_dic['name']))
+ filters_list.append(this_filter_dic)
+
+ # Resolve DEFINE tags (substitute by the defined value)
+ for f_definition in filters_list:
+ for initial_str in define_dic:
+ final_str = define_dic[initial_str]
+ f_definition['driver'] = f_definition['driver'].replace(initial_str, final_str)
+ f_definition['manufacturer'] = f_definition['manufacturer'].replace(initial_str, final_str)
+ f_definition['genre'] = f_definition['genre'].replace(initial_str, final_str)
+ f_definition['controls'] = f_definition['controls'].replace(initial_str, final_str)
+ f_definition['pluggabledevices'] = f_definition['pluggabledevices'].replace(initial_str, final_str)
+ # Replace strings in list of strings.
+ for i, s_t in enumerate(f_definition['include']):
+ f_definition['include'][i] = s_t.replace(initial_str, final_str)
+ for i, s_t in enumerate(f_definition['exclude']):
+ f_definition['exclude'][i] = s_t.replace(initial_str, final_str)
+ # for i, s_t in enumerate(f_definition['change']):
+ # f_definition['change'][i] = s_t.replace(initial_str, final_str)
+
+ return filters_list
+
+# Makes a list of all machines and add flags for easy filtering.
+# Returns a dictionary of dictionaries, indexed by the machine name.
+# This includes all MAME machines, including parents and clones.
+def filter_get_filter_DB(cfg, db_dic_in):
+ machine_main_dic = db_dic_in['machines']
+ renderdb_dic = db_dic_in['renderdb']
+ assetdb_dic = db_dic_in['assetdb']
+ machine_archives_dic = db_dic_in['machine_archives']
+
+ main_filter_dic = {}
+ # Sets are used to check the integrity of the filters defined in the XML.
+ drivers_set = set()
+ genres_set = set()
+ controls_set = set()
+ pdevices_set = set()
+ # Histograms
+ # The driver histogram is too big and unuseful.
+ genres_drivers_dic = {}
+ controls_drivers_dic = {}
+ pdevices_drivers_dic = {}
+ pDialog = KodiProgressDialog()
+ pDialog.startProgress('Building filter database...', len(machine_main_dic))
+ for m_name in machine_main_dic:
+ pDialog.updateProgressInc()
+ if 'att_coins' in machine_main_dic[m_name]['input']:
+ coins = machine_main_dic[m_name]['input']['att_coins']
+ else:
+ coins = 0
+ if m_name in machine_archives_dic:
+ hasROMs = True if machine_archives_dic[m_name]['ROMs'] else False
+ else:
+ hasROMs = False
+ if m_name in machine_archives_dic:
+ hasCHDs = True if machine_archives_dic[m_name]['CHDs'] else False
+ else:
+ hasCHDs = False
+ if m_name in machine_archives_dic:
+ hasSamples = True if machine_archives_dic[m_name]['Samples'] else False
+ else:
+ hasSamples = False
+ # If the machine has no displays then both isVertical and isHorizontal are False.
+ isVertical, isHorizontal = False, False
+ for drotate in machine_main_dic[m_name]['display_rotate']:
+ if drotate == '0' or drotate == '180':
+ isHorizontal = True
+ elif drotate == '90' or drotate == '270':
+ isVertical = True
+
+ # ROM/CHD/Sample scanner flags. See funcion db_initial_flags()
+ missingROMs = True if assetdb_dic[m_name]['flags'][0] == 'r' else False
+ missingCHDs = True if assetdb_dic[m_name]['flags'][1] == 'c' else False
+ missingSamples = True if assetdb_dic[m_name]['flags'][2] == 's' else False
+
+ # Fix controls to match "Machines by Controls (Compact)" filter
+ if machine_main_dic[m_name]['input']:
+ raw_control_list = [
+ ctrl_dic['type'] for ctrl_dic in machine_main_dic[m_name]['input']['control_list']
+ ]
+ else:
+ raw_control_list = []
+ pretty_control_type_list = misc_improve_mame_control_type_list(raw_control_list)
+ control_list = misc_compress_mame_item_list_compact(pretty_control_type_list)
+ if not control_list: control_list = [ 'None' ]
+
+ # Fix this to match "Machines by Pluggable Devices (Compact)" filter
+ raw_device_list = [ device['att_type'] for device in machine_main_dic[m_name]['devices'] ]
+ pretty_device_list = misc_improve_mame_device_list(raw_device_list)
+ pluggable_device_list = misc_compress_mame_item_list_compact(pretty_device_list)
+ if not pluggable_device_list: pluggable_device_list = [ 'None' ]
+
+ # --- Build filtering dictionary ---
+ main_filter_dic[m_name] = {
+ # --- Default filters ---
+ 'isDevice' : renderdb_dic[m_name]['isDevice'],
+ # ---