diff --git a/.codeclimate.yml b/.codeclimate.yml index f3f07da52..8eec6c427 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,39 +1,37 @@ ---- -engines: +version: "2" +plugins: pep8: enabled: true - checks: - E402: - # Disable module order check because we need to work around this - enabled: false radon: enabled: true - config: - python_version: 2 markdownlint: enabled: true +checks: + file-lines: + # There is no a real limit imposed, PyLint set it to 1000 lines and raccommanding a refactoring + # A good development should be between 250 (default of CodeClimate) and 500 lines + config: + threshold: 400 + method-complexity: + config: + threshold: 10 ratings: paths: - "**.py" - "**.md" exclude_paths: - - "docs" - - "resources/test/" + - "docs/" + - "LICENSES/" - "resources/language/" - "resources/media/" - "resources/skins/" - - "resources/fanart.jpg" - - "resources/icon.png" - - "resources/screenshot-01.jpg" - - "resources/screenshot-02.jpg" - - "resources/screenshot-03.jpg" - "resources/settings.xml" - - "resources/__init__.py" - - "resources/lib/__init__.py" - - "__init__.py" + - "tests/" - "addon.xml" - - "LICENSE.txt" - - "makefile" + - "changelog.txt" + - "Contributing.md" + - "Code_of_Conduct.md" + - "LICENSE.md" - "requirements.txt" - - "ISSUE_TEMPLATE.md" - - "PULL_REQUEST_TEMPLATE.md" + - "Makefile" + - "tox.ini" diff --git a/.editorconfig b/.editorconfig index dca80abd4..ab2b975e8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,8 +14,6 @@ charset = utf-8 trim_trailing_whitespace = true # A file must end with an empty line - this is good for version control systems insert_final_newline = true -# A line should not have more than this amount of chars (not supported by all plugins) -max_line_length = 100 [*.{py,md,txt}] indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..80be53949 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,17 @@ +.github/ export-ignore +docs/ export-ignore +LICENSES/ export-ignore +tests/ export-ignore +.codeclimate.yml export-ignore +.editorconfig export-ignore +.flake8 export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.pylintrc export-ignore +changelog.txt export-ignore +Code_of_Conduct.md export-ignore +codecov.yml export-ignore +Contributing.md export-ignore +Makefile export-ignore +requirements.txt export-ignore +tox.ini export-ignore diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..777a08636 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: castagnait +open_collective: # Replace with a single Open Collective username +ko_fi: stefive +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: beacons.ai/castagnait diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index afb95c846..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -name: Problem report -about: If something doesn't work - ---- -## Bug report - -#### Your Environment -- Netflix add-on version: -- Operating system version/name: -- Device model: - -Used Operating system: -* [ ] Android -* [ ] iOS -* [ ] Linux -* [ ] OSX -* [ ] Raspberry-Pi -* [ ] Windows - -### Describe the bug - - - - -#### Expected behavior - - - -#### Actual behavior - - - -#### Steps to reproduce the behavior - -1. -2. -3. - -#### Possible fix - - - -### Debug log - -The debug log can be found here: - -### Additional context or screenshots (if appropriate) - -#### Installation -* [ ] I'm using other Netflix Repo -* [ ] I'm using a different source - -#### Other information - - - -#### Screenshots - - - - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..1a6c68851 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,96 @@ +name: Bug Report +description: To report errors or wrong behaviour +labels: ["Bug", "Triage: Needed"] + +body: + - type: markdown + attributes: + value: | + Thanks for filing an issue, this help us to determine what is causing your issue, please take your time to read and fill out everything with as much detail as you can. + We would like to remind you that if your problem not concern bugs, instead open an bug report please use channels like Kodi forum, Github Discussions tab or Github Wiki pages." + + - type: input + id: addon-version + validations: + required: true + attributes: + label: Netflix add-on version + description: Specify the add-on version where you have encountered the problem + + - type: dropdown + id: operative-systems + validations: + required: true + attributes: + label: Operative systems used + multiple: true + description: Select the operative systems where you have encountered the problem + options: + - Android + - Linux (Ubuntu / Mint / ...) + - Raspbian + - CoreELEC + - LibreELEC + - OSMC + - Mac OSX + - Windows + - Other (specify in description) + + - type: dropdown + id: kodi-versions + validations: + required: true + attributes: + label: Kodi version used + options: + - Kodi 18 (Leia) + - Kodi 19 (Matrix) + - Kodi 20 (Nexus) + - Other (specify in description) + + - type: textarea + id: description + validations: + required: true + attributes: + label: Description of the bug + description: Give a clear detailed description of the problem + + - type: textarea + id: steps + attributes: + label: Steps to reproduce the behavior + placeholder: | + Example: + 1. Navigate to menu X + 2. Select context menu Y + 3. Play Z + + - type: input + id: log-file + validations: + required: true + attributes: + label: Debug log - mandatory + description: | + Refer to the Github Readme instructions on how to get the log, + then save it to http://paste.kodi.tv/ and paste the link here. + If the log size is too large drag-n-drop the file on description field. + + - type: textarea + id: possible-fix + attributes: + label: Possible fix + description: If you know a possible fix to the problem describe it + + - type: textarea + id: additional-context + attributes: + label: Additional context + description: Anything else related that might be useful (related issues, suggestions, links, ...) + + - type: textarea + id: screenshots + attributes: + label: Screenshots + description: Add some screenshots if that helps understanding your problem diff --git a/.github/ISSUE_TEMPLATE/support_request.md b/.github/ISSUE_TEMPLATE/support_request.md deleted file mode 100644 index 652e34fdf..000000000 --- a/.github/ISSUE_TEMPLATE/support_request.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: Help request -about: If you need help on how to use the addon - ---- -## Help request - -#### Your Environment -- Netflix add-on version: -- Operating system version/name: -- Device model: - -Used Operating system: -* [ ] Android -* [ ] iOS -* [ ] Linux -* [ ] OSX -* [ ] Raspberry-Pi -* [ ] Windows - -### Describe your help request - - - - -#### Screenshots - - - - diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 9a8a9fe1c..848077ef9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,15 +1,16 @@ ### Check if this PR fulfills these requirements: * [ ] My changes respect the [Kodi add-on development rules](https://kodi.wiki/view/Add-on_rules) -* [ ] I have read the [**CONTRIBUTING**](Contributing.md) document. -* [ ] I made sure there wasn't another one [Pull Requests](../../pulls) opened for the same update/change +* [ ] I have read the [**CONTRIBUTING**](../../master/Contributing.md) document. +* [ ] I made sure there wasn't another [Pull Request](../../../pulls) opened for the same update/change * [ ] I have successfully tested my changes locally #### Types of changes - [ ] New feature (non-breaking change which adds functionality) -- [ ] Feature change (non-breaking change which change behaviour of an existing functionality) -- [ ] Improvement (non-breaking change which improve functionality) +- [ ] Feature change (non-breaking change which changes behaviour of an existing functionality) +- [ ] Improvement (non-breaking change which improves functionality) - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] Breaking change (fix or feature that would cause existing functionality to change) +- [ ] Refactor (non-breaking performance or readability improvements) ### Description diff --git a/.github/workflows/addon-check.yml b/.github/workflows/addon-check.yml new file mode 100644 index 000000000..74c47a120 --- /dev/null +++ b/.github/workflows/addon-check.yml @@ -0,0 +1,21 @@ +name: Kodi Addon-Check +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + kodi-addon-checker: + runs-on: ubuntu-latest + name: Kodi addon checker + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Kodi addon checker validation + id: kodi-addon-checker + uses: xbmc/action-kodi-addon-checker@master + with: + kodi-version: matrix + addon-id: ${{ github.event.repository.name }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..efc05cec0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,41 @@ +name: CI +on: [push, pull_request] +jobs: + tests: + name: Add-on testing + runs-on: ubuntu-latest + env: + PYTHONIOENCODING: utf-8 + PYTHONPATH: ${{ github.workspace }}/resources/lib:${{ github.workspace }}/tests + strategy: + fail-fast: false + matrix: + python-version: ['3.10', '3.11', '3.12', '3.13'] + steps: + - name: Check out ${{ github.sha }} from repository ${{ github.repository }} + uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Run tox + run: python -m tox -q -e flake8,py + if: always() + - name: Run pylint + run: python -m pylint resources/lib/ tests/ + if: always() + - name: Analyze with SonarCloud + uses: SonarSource/sonarcloud-github-action@v1.4 + with: + args: > + -Dsonar.organization=add-ons + -Dsonar.projectKey=add-ons_plugin.video.netflix + -Dsonar.python.version=3.10,3.11,3.12,3.13 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + continue-on-error: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..e33079ba2 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,52 @@ +name: Release +on: + push: + tags: + - 'v*' +jobs: + build: + if: github.event.base_ref == 'refs/heads/master' + name: Publish release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Build zip files + run: | + sudo apt-get update + sudo apt-get install libxml2-utils + make build release=1 + - name: Get zip filename + id: get-zip-filename + run: | + echo ::set-output name=zip-filename::$(cd ..;ls plugin.video.netflix*.zip | head -1) + - name: Get body + id: get-body + run: | + description=$(sed '/v[0-9\.]*\s([0-9-]*)/d;/^$/,$d' changelog.txt) + echo $description + description="${description//'%'/'%25'}" + description="${description//$'\n'/'%0A'}" + description="${description//$'\r'/'%0D'}" + echo ::set-output name=body::$description + - name: Create GitHub Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + body: ${{ steps.get-body.outputs.body }} + draft: false + prerelease: false + - name: Upload zip file + id: upload-zip + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_name: ${{ steps.get-zip-filename.outputs.zip-filename }} + asset_path: ../${{ steps.get-zip-filename.outputs.zip-filename }} + asset_content_type: application/zip diff --git a/.github/workflows/translations.yml b/.github/workflows/translations.yml new file mode 100644 index 000000000..05d7232a9 --- /dev/null +++ b/.github/workflows/translations.yml @@ -0,0 +1,32 @@ +name: Translations +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + tests: + name: Check translations + runs-on: ubuntu-latest + env: + PYTHONIOENCODING: utf-8 + strategy: + fail-fast: false + matrix: + kodi-branch: [matrix] + steps: + - uses: actions/checkout@v2 + with: + path: ${{ github.repository }} + - name: Set up Python 3.10 + uses: actions/setup-python@v1 + with: + python-version: '3.10' + - name: Install dependencies + run: | + sudo apt-get install gettext + - name: Checking language translations + run: make check-translations + working-directory: ${{ github.repository }} diff --git a/.gitignore b/.gitignore index 305184549..7719ece32 100644 --- a/.gitignore +++ b/.gitignore @@ -104,6 +104,9 @@ ENV/ # mypy .mypy_cache/ +# PyCharm +.idea + # Visual Studio Code .vscode @@ -120,15 +123,15 @@ kodi-addon-checker-report.log kodi-addon-checker.log # Credentials -test/userdata/*.ndb -test/userdata/COOKIE_*= -test/userdata/_*= -test/userdata/addon_settings.json -test/userdata/credentials.json -test/userdata/msl_data.json -test/userdata/cache/ -test/userdata/database/ -test/userdata/imagecache/ -test/userdata/metadata/ -test/userdata/movies/ -test/userdata/shows/ +tests/userdata/*.ndb +tests/userdata/COOKIE_*= +tests/userdata/_*= +tests/userdata/addon_settings.json +tests/userdata/credentials.json +tests/userdata/msl_data.json +tests/userdata/cache/ +tests/userdata/database/ +tests/userdata/imagecache/ +tests/userdata/metadata/ +tests/userdata/movies/ +tests/userdata/shows/ diff --git a/.pylintrc b/.pylintrc index 8e810653c..9ee0c2126 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,15 +1,18 @@ [MESSAGES CONTROL] disable= bad-option-value, + comparison-with-callable, cyclic-import, duplicate-code, fixme, + import-outside-toplevel, invalid-name, line-too-long, locally-disabled, missing-docstring, no-self-use, old-style-class, + super-with-arguments, too-few-public-methods, too-many-arguments, too-many-instance-attributes, @@ -17,3 +20,6 @@ disable= too-many-public-methods, too-many-return-statements, too-many-statements, + too-many-positional-arguments, + useless-object-inheritance, + consider-using-from-import diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e79a02469..000000000 --- a/.travis.yml +++ /dev/null @@ -1,36 +0,0 @@ ---- -sudo: false -language: python - -python: -- '2.7' -#- '3.6' -#- '3.7' - -env: - PYTHONPATH: .:modules/enum:modules/mysql-connector-python:resources/lib:test - PYTHONIOENCODING: utf-8 - -install: -- pip install -r requirements.txt - -script: -- tox -- tox -e flake8 -- pylint resources/lib/ test/ -#- coverage run -m unittest discover -#- coverage run -a service.py -#- sleep 10 -#- coverage run -a test/run.py /directory/root -#- coverage run -a test/run.py /directory/profiles -#- coverage run -a test/run.py /directory/home -#- coverage run -a test/run.py /directory/video_list_sorted/myList/queue -#- coverage run -a test/run.py /directory/video_list_sorted/newRelease/newRelease -#- coverage run -a test/run.py /directory/video_list/continueWatching/continueWatching -#- coverage run -a test/run.py /directory/video_list/chosenForYou/topTen -#- coverage run -a test/run.py /directory/video_list/recentlyAdded/1592210 -#- coverage run -a test/run.py /directory/show/80057281/ -#- pkill -f service.py - -after_success: -- codecov diff --git a/LICENSE.txt b/LICENSE.md similarity index 100% rename from LICENSE.txt rename to LICENSE.md diff --git a/LICENSES/GPL-3.0-only.md b/LICENSES/GPL-3.0-only.md new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/LICENSES/GPL-3.0-only.md @@ -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/LICENSES/MIT.md b/LICENSES/MIT.md new file mode 100644 index 000000000..e3fa8b2e7 --- /dev/null +++ b/LICENSES/MIT.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Sebastian Golasch + +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/Makefile b/Makefile index 81895296c..a64581ba7 100644 --- a/Makefile +++ b/Makefile @@ -1,75 +1,107 @@ -ENVS := flake8,py27,py36 -export PYTHONPATH := .:$(CURDIR)/modules/enum:$(CURDIR)/modules/mysql-connector-python:$(CURDIR)/resources/lib:$(CURDIR)/test -addon_xml := addon.xml +export PYTHONPATH := .:$(CURDIR)/test +PYTHON := python +KODI_PYTHON_ABIS := 3.0.0 2.26.0 -# Collect information to build as sensible package name -name = $(shell xmllint --xpath 'string(/addon/@id)' $(addon_xml)) -version = $(shell xmllint --xpath 'string(/addon/@version)' $(addon_xml)) +name = $(shell xmllint --xpath 'string(/addon/@id)' addon.xml) +version = $(shell xmllint --xpath 'string(/addon/@version)' addon.xml) git_branch = $(shell git rev-parse --abbrev-ref HEAD) git_hash = $(shell git rev-parse --short HEAD) +matrix = $(findstring $(shell xmllint --xpath 'string(/addon/requires/import[@addon="xbmc.python"]/@version)' addon.xml), $(word 1,$(KODI_PYTHON_ABIS))) -zip_name = $(name)-$(version)-$(git_branch)-$(git_hash).zip -include_files = addon.py addon.xml LICENSE.txt modules/ README.md resources/ service.py +ifdef release + zip_name = $(name)-$(version).zip +else + zip_name = $(name)-$(version)-$(git_branch)-$(git_hash).zip +endif + +include_files = addon.py addon.xml LICENSE.txt README.md resources/ service.py include_paths = $(patsubst %,$(name)/%,$(include_files)) exclude_files = \*.new \*.orig \*.pyc \*.pyo zip_dir = $(name)/ +languages = $(filter-out en_gb, $(patsubst resources/language/resource.language.%, %, $(wildcard resources/language/*))) + blue = \e[1;34m white = \e[1;37m reset = \e[0m -all: clean test zip - -clean: - find . -name '*.pyc' -type f -delete - find . -name '*.pyo' -type f -delete - find . -name __pycache__ -type d -delete - rm -rf .pytest_cache/ .tox/ - -test: sanity unit +all: check test build +zip: build +test: check test-unit test-service test-run -sanity: tox pylint +check: check-tox check-pylint check-translations -tox: +check-tox: @echo -e "$(white)=$(blue) Starting sanity tox test$(reset)" - tox -q -e $(ENVS) + $(PYTHON) -m tox -q -e -pylint: +check-pylint: @echo -e "$(white)=$(blue) Starting sanity pylint test$(reset)" - pylint resources/lib/ test/ + $(PYTHON) -m pylint resources/lib/ tests/ + +check-translations: + @echo -e "$(white)=$(blue) Starting language test$(reset)" + @-$(foreach lang,$(languages), \ + msgcmp resources/language/resource.language.$(lang)/strings.po resources/language/resource.language.en_gb/strings.po; \ + ) -addon: clean +update-translations: + @echo -e "$(white)=$(blue) Updating languages$(reset)" + @-$(foreach lang,$(languages), \ + tests/update_translations.py resources/language/resource.language.$(lang)/strings.po resources/language/resource.language.en_gb/strings.po; \ + ) + +check-addon: clean @echo -e "$(white)=$(blue) Starting sanity addon tests$(reset)" - kodi-addon-checker . --branch=leia + kodi-addon-checker --branch=leia + +unit: test-unit +run: test-run -unit: +test-unit: clean @echo -e "$(white)=$(blue) Starting unit tests$(reset)" - python -m unittest discover + $(PYTHON) -m unittest discover -run: +test-run: @echo -e "$(white)=$(blue) Run CLI$(reset)" + coverage run -a tests/run.py /action/purge_cache/ + coverage run -a tests/run.py /action/purge_cache/?on_disk=True coverage run -a service.py & sleep 10 - coverage run -a test/run.py /action/purge_cache/ - coverage run -a test/run.py /action/purge_cache/?on_disk=True - coverage run -a test/run.py /directory/root - coverage run -a test/run.py /directory/profiles - coverage run -a test/run.py /directory/home - coverage run -a test/run.py /directory/video_list_sorted/myList/queue - coverage run -a test/run.py /directory/video_list_sorted/newRelease/newRelease - coverage run -a test/run.py /directory/video_list/continueWatching/continueWatching - coverage run -a test/run.py /directory/video_list/chosenForYou/topTen - coverage run -a test/run.py /directory/video_list/recentlyAdded/1592210 - coverage run -a test/run.py /directory/show/80057281/ - coverage run -a test/run.py /directory/show/80057281/season/80186799/ - coverage run -a test/run.py /directory/genres/tvshows/83/ - coverage run -a test/run.py /directory/genres/movies/34399/ - coverage run -a test/run.py /directory/search/search/cool - coverage run -a test/run.py /directory/exported/exported + coverage run -a tests/run.py /directory/root + coverage run -a tests/run.py /directory/profiles + coverage run -a tests/run.py /directory/home + coverage run -a tests/run.py /directory/video_list_sorted/myList/queue + coverage run -a tests/run.py /directory/video_list_sorted/newRelease/newRelease + coverage run -a tests/run.py /directory/video_list/continueWatching/continueWatching + coverage run -a tests/run.py /directory/video_list/chosenForYou/topTen + coverage run -a tests/run.py /directory/video_list/recentlyAdded/1592210 + coverage run -a tests/run.py /directory/show/80057281/ + coverage run -a tests/run.py /directory/show/80057281/season/80186799/ + coverage run -a tests/run.py /directory/genres/tvshows/83/ + coverage run -a tests/run.py /directory/genres/movies/34399/ + coverage run -a tests/run.py /directory/search/search/cool + coverage run -a tests/run.py /directory/exported/exported pkill -ef service.py -zip: clean +build: clean @echo -e "$(white)=$(blue) Building new package$(reset)" @rm -f ../$(zip_name) cd ..; zip -r $(zip_name) $(include_paths) -x $(exclude_files) @echo -e "$(white)=$(blue) Successfully wrote package as: $(white)../$(zip_name)$(reset)" + +multizip: clean + @-$(foreach abi,$(KODI_PYTHON_ABIS), \ + echo "cd /addon/requires/import[@addon='xbmc.python']/@version\nset $(abi)\nsave\nbye" | xmllint --shell addon.xml; \ + matrix=$(findstring $(abi), $(word 1,$(KODI_PYTHON_ABIS))); \ + if [ $$matrix ]; then version=$(version)+matrix.1; else version=$(version); fi; \ + echo "cd /addon/@version\nset $$version\nsave\nbye" | xmllint --shell addon.xml; \ + make build; \ + ) + +clean: + @echo -e "$(white)=$(blue) Cleaning up$(reset)" + find . -name '*.py[cod]' -type f -delete + find . -name __pycache__ -type d -delete + rm -rf .pytest_cache/ .tox/ + rm -f *.log diff --git a/README.md b/README.md index 258b7c3ca..dc728fe1b 100644 --- a/README.md +++ b/README.md @@ -1,147 +1,146 @@ +# Netflix Plugin for Kodi (plugin.video.netflix) + +[![Kodi version](https://img.shields.io/badge/kodi%20versions-19--20--21--22-blue)](https://kodi.tv/) [![GitHub release](https://img.shields.io/github/release/castagnait/plugin.video.netflix.svg)](https://github.com/castagnait/plugin.video.netflix/releases) -[![Build Status](https://travis-ci.org/castagnait/plugin.video.netflix.svg?branch=master)](https://travis-ci.org/castagnait/plugin.video.netflix) -[![Codecov status](https://img.shields.io/codecov/c/github/castagnait/plugin.video.netflix/master)](https://codecov.io/gh/castagnait/plugin.video.netflix/branch/master) +[![CI](https://github.com/castagnait/plugin.video.netflix/workflows/CI/badge.svg)](https://github.com/castagnait/plugin.video.netflix/actions?query=workflow:CI) +[![Code Climate - Maintainability](https://api.codeclimate.com/v1/badges/9fbe3ac732f86c05ff00/maintainability)](https://codeclimate.com/github/CastagnaIT/plugin.video.netflix/maintainability) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Contributors](https://img.shields.io/github/contributors/castagnait/plugin.video.netflix.svg)](https://github.com/castagnait/plugin.video.netflix/graphs/contributors) -# Netflix Plugin for Kodi 18 (plugin.video.netflix) -This source code comes from the [caphm repository](https://github.com/caphm/plugin.video.netflix) given the discontinuity of his work, i'm trying to keep the project alive, help from skilled people are welcome. -The initial project is on the [repository of asciidisco](https://github.com/asciidisco/plugin.video.netflix) no longer maintained but used as a reference. - ## Disclaimer -This plugin is not officially commisioned/supported by Netflix. +This plugin is not officially commissioned/supported by Netflix. The trademark "Netflix" is registered by "Netflix, Inc." -## Prerequisites +## Main features -- Kodi 18 [official download](https://kodi.tv/download) -- Inputstream.adaptive [>=v2.0.0](https://github.com/peak3d/inputstream.adaptive) - (with Kodi 18 should be installed automatically, otherwise you will be notified) -- Cryptdome python library, with Kodi 18 will be installed automatically -(for Linux systems, install using `pip install --user pycryptodomex` as the user that will run Kodi) +> [!WARNING] +> **DUE TO CHANGES TO THE WEBSITE THAT PREVENT THE PROPER PLAYBACK OF VIDEOS, THE DEVELOPMENT OF THE ADD-ON HAS BEEN SUSPENDED. +> THE VIDEO PLAYBACK, WHEN IT WORKS, IS LIMITED TO _SD QUALITY AND ONLY ON SOME LINUX/ANDROID DEVICES_. +> WHO WANTS TO HELP FIND A SOLUTION TO THE PLAYBACK PROBLEM CAN FIND SOME INFO ON THE ISSUE [#1627](https://github.com/CastagnaIT/plugin.video.netflix/issues/1627).** -- Widevine DRM -For non-Android devices, will automatically be installed (by inputstream.helper). -Please make sure to read the licence agreement that is presented upon Widevine installation, so you know what you´re getting yourself into. +- Access to all profiles and relative My list management +- Show the most used lists such as New releases, Recently added, Netflix originals, ... +- Show trailers lists (by context menu) +- Synchronize the watched status with Netflix service - [How works and limitations](https://github.com/CastagnaIT/plugin.video.netflix/wiki/Sync-of-watched-status-with-Netflix) +- Export and synchronize Kodi library with Netflix +- Share/Sync a Kodi library with multiple devices that using Kodi (requires a MySQL server) +- Capability of 1080P and 4K resolutions (see high resolutions table) +- Dolby Digital Plus and Dolby Digital Atmos (requires a premium account) +- HDR / HDR10 / Dolby Vision only on capable Android devices (requires a premium account) +- Support integration with Up Next add-on (proposes to play the next episode automatically) ## Installation & Updates -Repository that provides automatic updates for release builds: -[repository.castagnait-1.0.0.zip](https://github.com/castagnait/repository.castagnait/raw/master/repository.castagnait-1.0.0.zip) - -- First download the repository zip -- Open Kodi, go to menu Add-ons, select "Install from zip file", and select the downloaded zip -- Last step, go to "Install from repository", select CastagnaIT repository and Netflix addon - -For those who prefer to stay up to date with the daily build should do the manual installation, or use other repositories -[Daily builds](http://www.mediafire.com/folder/vifnw8ve44bi7/KodiNetflixAddon) - -## Functionality - -- Multiple profiles -- Search Netflix (incl. suggestions) -- Netflix categories, recommendations, "my list" & continue watching -- Browse all movies and all TV shows Netflix style -- Rate show/movie -- Add & remove to/from "my list" -- Export of complete shows & movies in local database -- Keep My List and local database in sync -- Export new seasons/episodes to local database when they become available on Netflix - -## FAQ - -### Does it work with Kodi 17? -No. Netflix's DRM is incompatible with inpustream from Kodi 17. - -### Does it work on a RPI? -Yes, but you most likely won't get 1080p playback to work properly (see next FAQ). - -### Can it play 1080p videos? -- On Android devices -Yes, as long as they are available from Netflix and your hardware can handle it. -To understand if your device can handle them, you need to check if it has support for the Widewine L1 DRM - -- Other platform (Windows, Linux, ...) -The video is always software decoded due to Netflix licensing restrictions, so **you'll need a CPU that can handle the load of software decoding 1080p video** otherwise you'll have the result of stuttering video playback. -Which is what happens with certain RPI, 720p is maximum for those devices, and even then you need to make sure to properly cool your RPI or you'll have stuttering playback as well. -You can limit the resolution in this way: In the addon settings open Expert page and change `Limit video stream resolution to` value to 720p. - -### It only plays videos in 480p/720p, why is that? -inputstream.adaptive selects the stream to play based on an initial bandwidth measurement and your screen resolution. -If you want to force 1080p playback, set Min Bandwidth to 8,000,000 in inputstream.adaptive settings. -Also make sure your display resolution is at least 1080p or enable `Ignore display resolution` in inputstream.adaptive settings. -If it's still not playing 1080p, the title most probably just isn't available in 1080p. - -### Can it play 4K videos? -Yes, but only on Android devices with Widevine L1, and you need to set the following parameters: -- In the addon settings, Expert page: -`Enable VP9 profiles` to OFF -`Enable HEVC profiles` to ON -`Force support to HDCP 2.2` to ON -- In the Inputstream Addon settings, Account page: -`Override HDCP status` to ON - -If you don't get 4k resolution when you play: -Try to enter the ESN from your Netflix App (can be found unter Settings => About). - -### Can it play HDR? -Yes, as long as the 4K prerequisites are met. Additionally, you must enabled HDR and/or DolbyVision profiles -in addon settings. -Depending on your setup, there may be some tinkering required to get HDR to work. This depends on your TV, -if you are using an AV-Receiver, which device Kodi is running on, etc. Please make sure to search the issues and available forum threads for a solution before opening an issue! - -### Does it support 5.1 audio? -Yes, enable the option `Enable Dolby Digital Plus` in addon settings (is enabled by default). - -### Is Dolby Atmos supported? -Yes. It's enabled by default, when option `Enable Dolby Digital Plus` is enabled. -But only some videos have Atmos, they can be distinguished from the skin media-flag "Dolby-HD". -Note: Need a premium netflix account. - -### Are image based subtitles (Hebrew, Arabic, ...) supported? -No. They are provided in a different format, which requires some work to support, either on Kodi or the addon side. -It's on the roadmap but doesn't have an ETA. - -### Why do i always see subtitles in every video? -Just change how Kodi handles subtitles by choosing forced only. -In Kodi Settings -> Player -> Language -set: `Preferred subtitle language` to `Forced only` - -### I added/removed something to My List on PC/in the Netflix App but it doesn't show up in my Kodi library? -Only add/remove to My List from within the addon keeps the Kodi library in sync. Changes made in other clients (PC, App, ...) are not recognized because it's unclear how to handle those actions with multiple profiles. - -### My watched status is not being updated?! -The addon does not report watched status back to Netflix (yet). This is a top priority on our roadmap, but we haven't been able to figure this out just yet. - -### Can i share the exported content in the library with multiple devices? -Yes it is possible share the same library with multiple devices that using netflix addon. -In order to work it is necessary use Kodi with a MySQL server. -You can follow the official Kodi MySQL setup instructions at [Kodi Wiki](https://kodi.wiki/view/MySQL). -When done, in each device that use this addon, open the addon settings and under Library page: -- Check "Enable custom library folder", and choose a shared "Custom library path". The path must be the exact same on all devices. -- Enable "Use MySQL shared library database", then set the same connection parameters used in Kodi MySQL setup. - -### Auto-update of exported content -WARNING: AN INTENSIVE USE of AUTO-UPDATE function due to many exported tv shows MAY CAUSE A TEMPORARY BAN of the ACCOUNT that varies starting from 24/48 hours. Use at your own risk. -If it happens often, there is the possibility to exclude the auto-updates from the tv shows, by open context menu on a tv show and selecting "Exclude from auto update". -- If you want to use the auto-update with a shared exported content (to multiple devices), you need to set up one of the devices as the main library update manager, by using the menu "Set this device as main auto-updates manager" from the chosen device under Library page. +**[How to install with automatic updates](https://github.com/CastagnaIT/plugin.video.netflix/wiki/How-install-the-addon)** + +#### Quick download links + +Install add-on via repository - provide automatic installation of updates:
+[CastagnaIT Repository for Kodi - repository.castagnait-2.0.1.zip](https://github.com/castagnait/repository.castagnait/raw/kodi/repository.castagnait-2.0.1.zip)
+_**KODI v18 is not supported.**_ + +Install add-on manually - updates should always be installed manually:
+https://castagnait.github.io/repository.castagnait/ (url to add in the Kodi file manager)
+Note: The release files in to "kodi19" folder are compatible with Kodi v19, v20, v21, v22. + +## Login with Authentication key + +An alternative login method to avoid "incorrect password" error +* [How to login with Authentication key - wiki](https://github.com/CastagnaIT/plugin.video.netflix/wiki/Login-with-Authentication-key) + +## Reference table of high resolutions + +This table explains in brief the availability of high resolutions between devices and operating systems. This may change over time based on changes made by Netflix itself. + +Unlike official apps (on Smart TV or certified TV Boxes) in some cases using this add-on there are some limitations. +Here Netflix could provide the same TV shows/movies with lower resolutions, this mostly depends on the type of system/device in use. +Devices with more limited resolutions are all those that use Linux operating system (certified Android excluded). Even between different Linux machines there may be differences. + +| System | 1080P | 4K | Video Decoding | +| ----------------------------------- | --------- | ------- | -------------------------- | +| Windows | ❌\*1 | ❌\*2 | Software | +| Linux (Android) \*5 | ✔️\*1, \*3| ✔️\*4 | Software \\ Hardware \*4 | +| Linux (Distributions) | ✔️\*1 | ❌\*2 | Software | +| Linux (OSMC-CoreElec-LibreELEC-...) | ✔️\*1 | ❌\*2 | Software | +| MacOS | ❌\*1 | ❌\*2 | Software | +| iOS / tvOS | ❌ | ❌ | Not supported | + +
+*1 With Software decoding 1080P is not guaranteed.
+*2 Currently not available due to widevine limitations.
+*3 To to have a chance to have all the videos at 1080P you must meet \*4 requirements.
+*4 Hardware decoding and 4k are supported only to devices with Netflix certification, Widevine Security Level L1 and HDCP 2.2 hardware.
+*5 Some android devices do not work properly, this is due to restrictions implemented by netflix with devices with false certifications (often with some Chinese boxes) in rare cases even happened to not being able to play the videos. +
+ +In order to have a better chance to have high resolutions, we suggest to use the following operating systems:
+Windows (x86/x64), MacOS, Certified Android (better with Netflix certification) + +[List of known and tested android devices for 1080P and 4K playback](https://github.com/CastagnaIT/plugin.video.netflix/wiki/List-of-1080P-4k-Android-tested-devices) + +#### For video playback problems or 4K problems, BEFORE open an Issue: + +- [Try read the FAQ on Wiki page for the common playback problems](https://github.com/CastagnaIT/plugin.video.netflix/wiki/FAQ-%28Audio%2C-Video%2C-Subtitle%2C-Other%29) +- [Try ask for help to the official Kodi forum](https://forum.kodi.tv/showthread.php?tid=329767) + +## YOU NEED OTHER HELP? Read the Wiki page! + +What you can find? + +FAQs: + +- [FAQ with how to for common problems with Audio, Video, Subtitles and other](https://github.com/CastagnaIT/plugin.video.netflix/wiki/FAQ-%28Audio%2C-Video%2C-Subtitle%2C-Other%29) +- [FAQ with how to for common errors](https://github.com/CastagnaIT/plugin.video.netflix/wiki/FAQ-%28Errors%29) + +Some guides like: +- [How to export to Kodi library and use auto-sync](https://github.com/CastagnaIT/plugin.video.netflix/wiki/How-to-export-and-sync-tv-shows-and-movies-in-Kodi-library) +- [How to share the exported content in the library with multiple devices](https://github.com/CastagnaIT/plugin.video.netflix/wiki/Share-STRM-library-with-multiple-devices) +- [How works and limitations of the synchronisation of watched status with Netflix](https://github.com/CastagnaIT/plugin.video.netflix/wiki/Sync-of-watched-status-with-Netflix) + +And much more... + +[***Click here to open the Wiki page or click on Wiki button***](https://github.com/CastagnaIT/plugin.video.netflix/wiki) + +## Notice for the use of auto-update and auto-sync with Netflix "My List" feature + +AN INTENSIVE USE OF THIS FEATURES due to many exported tv shows MAY CAUSE A TEMPORARY BAN OF THE ACCOUNT that varies starting from 24/48 hours. Use at your own risk. + +If it happens often, there is the possibility to exclude the auto update from the tv shows, by open context menu on a tv show and selecting `Exclude from auto update`. ## Something doesn't work -If something doesn't work for you, please: -- Make sure all prerequisites are met -- Enable the Debug log in your Kodi settings -- Open an issue with a title that summarises your problems and **attach the full debug log** +***Before open a new Issue and engage the developers, please try to find your answer on other channels like: +old closed Issues (on Issue tab), the Wiki pages or ask in the Kodi forum.*** + +If you have encountered an error or misbehaviour: +1. Open add-on `Expert` settings and turn on `Enable debug logging` setting, then press OK button +2. Enable Kodi debug, go to Kodi `Settings` > `System Settings` > `Logging` and enable `Enable debug logging` +3. Perform the actions that cause the error, so they are written in the log file +4. Open a new GitHub Issue (of type *Bug report*) and fill in the page with detailed information +5. Attach/link in your Issue thread the log file is mandatory (follow rules below) + +Rules for the log: +- You can attach the log file or use a service like [Kodi paste](http://paste.kodi.tv) to make a link +- Do not paste the content of the log directly into a Issue or message +- Do not cut, edit or remove parts of the log (there are no sensitive data) + +When the problem will be solved, remember to disable the debug logging, to avoid unnecessary slowing down in your device. + +**Why my Issue is labeled with ![Ignored rules](https://img.shields.io/badge/-Ignored%20rules-red) ?** + +This happens when the guidelines for compiling the Issue thread have not been followed. Therefore if the information will not be filled and or changed in the right way, the Issue post will be closed in the next days. -We can't help you if you don't provide detailed information (i.e. explanation and full debug log) on your issue. -Please also use a service like pastebin to provide logs and refrain from uploading them to where they'll be hidden behind an ad-wall or any other sketchy services. ## Code of Conduct [Contributor Code of Conduct](Code_of_Conduct.md) By participating in this project you agree to abide by its terms. - ## License + Licensed under The MIT License. + +## Support the project + +[Info for contribute and donations](https://github.com/CastagnaIT/plugin.video.netflix/wiki/Contribute-and-donations) diff --git a/addon.py b/addon.py index c73256381..007af606f 100644 --- a/addon.py +++ b/addon.py @@ -1,122 +1,13 @@ # -*- coding: utf-8 -*- -# Author: asciidisco -# Module: default -# Created on: 13.01.2017 -# License: MIT https://goo.gl/5bMj3H -# pylint: disable=wrong-import-position -"""Kodi plugin for Netflix (https://netflix.com)""" -from __future__ import absolute_import, division, unicode_literals +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Default module - Kodi plugin for Netflix + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" import sys -from functools import wraps -import xbmcplugin +from resources.lib.run_addon import run -# Import and initialize globals right away to avoid stale values from the last -# addon invocation. Otherwise Kodi's reuseLanguageInvoker will cause some -# really quirky behavior! -from resources.lib.globals import g -g.init_globals(sys.argv) - -import resources.lib.common as common -import resources.lib.upgrade_controller as upgrade_ctrl -import resources.lib.api.shakti as api -import resources.lib.kodi.ui as ui -import resources.lib.navigation as nav -import resources.lib.navigation.directory as directory -import resources.lib.navigation.hub as hub -import resources.lib.navigation.player as player -import resources.lib.navigation.actions as actions -import resources.lib.navigation.library as library - -from resources.lib.api.exceptions import (NotLoggedInError, MissingCredentialsError) - -NAV_HANDLERS = { - g.MODE_DIRECTORY: directory.DirectoryBuilder, - g.MODE_ACTION: actions.AddonActionExecutor, - g.MODE_LIBRARY: library.LibraryActionExecutor, - g.MODE_HUB: hub.HubBrowser -} - - -def lazy_login(func): - """ - Decorator to ensure that a valid login is present when calling a method - """ - # pylint: disable=protected-access, missing-docstring - @wraps(func) - def lazy_login_wrapper(*args, **kwargs): - try: - return func(*args, **kwargs) - except NotLoggedInError: - common.debug('Tried to perform an action without being logged in') - try: - api.login() - common.debug('Now that we\'re logged in, let\'s try again') - return func(*args, **kwargs) - except MissingCredentialsError: - # Aborted from user or left an empty field - xbmcplugin.endOfDirectory(handle=g.PLUGIN_HANDLE, - succeeded=False) - return lazy_login_wrapper - - -@lazy_login -def route(pathitems): - """Route to the appropriate handler""" - common.debug('Routing navigation request') - root_handler = pathitems[0] if pathitems else g.MODE_DIRECTORY - if root_handler == g.MODE_PLAY: - player.play(pathitems=pathitems[1:]) - elif root_handler == 'extrafanart': - common.debug('Ignoring extrafanart invocation') - xbmcplugin.endOfDirectory(handle=g.PLUGIN_HANDLE, succeeded=False) - elif root_handler not in NAV_HANDLERS: - raise nav.InvalidPathError( - 'No root handler for path {}'.format('/'.join(pathitems))) - else: - nav.execute(NAV_HANDLERS[root_handler], pathitems[1:], - g.REQUEST_PARAMS) - - -def check_valid_credentials(): - """Check that credentials are valid otherwise request user credentials""" - # This function check only if credentials exist, instead lazy_login - # only works in conjunction with nfsession and also performs other checks - if not common.check_credentials(): - try: - if not api.login(): - # Wrong login try again - return check_valid_credentials() - except MissingCredentialsError: - # Aborted from user or left an empty field - return False - return True - - -if __name__ == '__main__': - # pylint: disable=broad-except - # Initialize variables in common module scope - # (necessary when reusing language invoker) - common.info('Started (Version {})'.format(g.VERSION)) - common.info('URL is {}'.format(g.URL)) - success = False - - try: - if check_valid_credentials(): - upgrade_ctrl.check_addon_upgrade() - g.initial_addon_configuration() - route(filter(None, g.PATH.split('/'))) - success = True - except common.BackendNotReady: - ui.show_backend_not_ready() - except Exception as exc: - import traceback - common.error(traceback.format_exc()) - ui.show_addon_error_info(exc) - - if not success: - xbmcplugin.endOfDirectory(g.PLUGIN_HANDLE, succeeded=success) - - g.CACHE.commit() - common.log_time_trace() +run(sys.argv) diff --git a/addon.xml b/addon.xml index 44ba5ba1e..0b6b39d58 100644 --- a/addon.xml +++ b/addon.xml @@ -1,97 +1,94 @@ - + - - - + + + - + + video - - true + Netflix + Plugin pro sledování filmů, seriálů online z Netflixu + Použití tohoto doplňku nemusí být ve vaší zemi pobytu legální - před instalací dbejte na vaše zákony. Netflix - Addon für Netflix VOD Services - Möglicherweise sind einge Teile dieses Addons in Ihrem Land illegal, Sie sollten dies unbedingt vor der Installation überprüfen. + Addon für Netflix VOD-Dienste + Möglicherweise ist die Verwendung dieses Addons in Ihrem Land illegal, Sie sollten dies unbedingt vor der Installation überprüfen. Netflix - Netflix VOD Services Addon - Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing. + Netflix VOD Services Add-on + The use of this add-on may not be legal in your country of residence - please check with your local laws before installing. + Νέτφλιξ + Πρόσθετο υπηρεσιών κατά παραγγελία του Νέτφλιξ + Η χρήση αυτού του προσθέτου μπορεί να μην είναι νόμιμη στην χώρα που κατοικείτε - παρακαλώ ελέγξτε τους τοπικούς νόμους πριν την εγκατάσταση. Netflix Bekijk Netflix films en tvprogramma's in Kodi. Sommige delen van deze add-on zijn mogelijk onwettig in jouw land - raadpleeg de lokale wetgeving alvorens deze add-on te installeren. - Netflix Addon servizi VOD - Alcune parti di questo addon potrebbero non essere legali nel proprio paese di residenza - si prega di verificare le leggi locali prima dell'installazione. - + Netflix + Netflix VOD サービスアドオン + 一部の国では、このアドオンを使用するのが不法でありますので、設置する前に必ず関連法律を確認してください + Netflix + Netflix VOD 서비스애드온 + 일부 국가에서는 이 애드온을 사용하는 것이 불법일 수도 있으니, 설치하기전 관련 법을 확인하기 바랍니다. + Netflix Add-on servizi VOD + L'utilizzo di questo add-on potrebbe non essere legale nel vostro paese di residenza - si prega di verificare le leggi in vigore prima dell'installazione. + Netflix + Complemento para los Servicios VOD de Netflix + El uso de este complemento puede no ser legal en su país de residencia - Por favor, consulte las leyes locales antes de instalarlo. + Netflix + Netflix VOD kiegészítő a Kodihoz + Előfordulhat, hogy a kiegészítő használata nem törvényes az Ön országában - telepítés előtt tájékozódjon a helyi törvényekről + Netflix + Add-on para os serviços VOD do Netflix + O uso deste addon pode não ser legal no seu país de residência - por favor, verifique as suas leis locais antes de instalar. + Netflix + Netflix VOD Hizmetleri Eklentisi + Bu eklentinin bazı bölümleri ikamet ettiğiniz ülkede yasal olmayabilir - lütfen yüklemeden önce yerel yasalarınıza bakın. + Netflix + Add-on pentru servicii video la cerere Netflix + Utilizarea acestui add-on ar putea fi ilegală în țara în care locuiți - vă rugăm să verificați legile locale înainte de instalare. + Netflix + Netflix VOD Service-tillägg + Användandet av detta tillägg kanske inte är lagligt i ditt hemland - kontrollera med dina lokala lagar innan du installerar. + Netflix + Netflix VOD服务附加组件 + 此附加组件在您的居住国可能不合法-请在安装前与您当地的法律核对。 + Netflix + Extension Netflix SVoD + L'utilisation de cette extension n'est peut-être pas légale dans votre pays de résidence - renseignez-vous avant de l'installer + Netflix + Wtyczka dla usług VOD serwisu Netflix + Korzystanie z tego dodatku może być niezgodne z prawem w twoim kraju zamieszkania - przed zainstalowaniem zapoznaj się z lokalnymi przepisami. + Netflix + תוסף VOD לשירות של Netflix + השימוש בתוסף זה עלול להיות לא חוקי במדינת מגוריך - אנא בדוק את החוק ברשות המקומית לפני ההתקנה + Netflix + Netflix VOD服務套件 + 在您居住的國家使用此套件可能並不合法 - 請在安裝前與您當地的法律核對 + resources/media/icon.png resources/media/fanart.jpg resources/media/screenshot-01.jpg resources/media/screenshot-02.jpg resources/media/screenshot-03.jpg + resources/media/screenshot-04.jpg + resources/media/screenshot-05.jpg - en de es he hr it nl pl pt sk sv + en cs de es fr he hr hu it ja ko nl pl pt ro sv tr zh all MIT https://www.netflix.com https://forum.kodi.tv/showthread.php?tid=329767 https://github.com/CastagnaIT/plugin.video.netflix - -v0.15.5 (2019-10-12) --Speedup loading lists due to the new login validity check --Cookies expires are now used to check a valid login --Fixed an issue introduced with previous version causing login error --Fixed double handshake request on first run - -v0.15.4 (2019-10-11) --Added InputStream Helper settings in settings menu --Fixed add/remove to mylist on huge lists --Fixed expired mastertoken key issue --Fixed skipping sections due to netflix changes --Manifests now are requested only once --Updated pt-br, de, es translations --Minor improvements - -v0.15.3 (2019-09-19) --Initial conversion to python 3 --Initial integration tests --Implemented device uuid to avoid always asking credentials --Fixed a problem when library source is special:// or a direct path --Fixed run library update on slow system like RPI --Updated dutch language --Minor fixes - -v0.15.2 (2019-08-30) --Fixed key handshake at addon first run --Fixed library update service at first run --Local database now dynamically created by code --Profile data is not deleted if an problem occurred --Minor fixes - -v0.15.1 (2019-08-25) --Fixed wrong path to linux systems - -v0.15.0 (2019-08-20) --Implemented data management through database --Implemented automatic export of new episodes --Implemented a version upgrade system --Implemented management of login errors --Added a new context menu "Export new episodes" for a single tv show --Added ability to exclude (and re-include) a tv show from library auto update --Added possibility to share the same library with multiple devices (MySQL server is required) --No more concurrency and data loss problems of previous "persistent storage" --Fixed continuous "new access" email notification from netflix --Fixed locale id error --Fixed automatic library updates --Fixed logout now the profiles are no longer accessible and you can enter your credentials --Fixed exporting tvshow.nfo for a single episode --Fixed UpNext watched status from Kodi library --Fixed issue with library items that containing % in the path --Other minor improvements and fixes + v1.23.5 (2025-08-24) +- Fix esn error on login due to website changes +- Fix Nonetype error on startup due to website changes diff --git a/changelog.txt b/changelog.txt index fd72185a7..9aad190ce 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,760 @@ +v1.23.5 (2025-08-24) +- Fix esn error on login due to website changes +- Fix Nonetype error on startup due to website changes + +v1.23.4 (2025-05-15) +- Fix authUrl not valid error +- Better InputStream Adaptive support on Kodi 22 +- Other minor fixes + +v1.23.3 (2024-04-16) +- Disabled login with e-mail/password (new website protections) +- Add workaround for missing images to tvshow/movies +- Fixed login with Auth key due to e-mail parsing problem + +v1.23.2 (2024-01-17) +- Fix/workaround for Kodi crash when play a promo trailer +- Fix wrong ESN generation for Xiaomi tv boxes +- Fix missing video zoom when video starts after ADS +- Fix "TypeError: Actor" error that prevent to show video list +- Fix wrong "Next page" shown when there are no more videos +- Fix missing "Browse subgenres" folder when returning from a next page +- Update translation zh_tw + +v1.23.1 (2023-12-16) +- Fix missing Add/Remove to my list context menu on Search results +- Fix misbehavior of Add/Remove context menu in my list on search pages that ask for new search +- Fix titles wrongly marked as watched due to no watched status data + +v1.23.0 (2023-11-19) +- Add experimental support to ADS plan, for Kodi 20 and higher only, please note add-on features may not work correctly +- Fix search menus (sort order dont work yet) +- Audio description menu is broken, atm cannot be fixed, website problems + +v1.22.3 (2023-09-20) +- Removed HTTPX module (http2) to fix compatibility with python >= 3.11 +- Restored Requests module (details https://github.com/CastagnaIT/plugin.video.netflix/pull/1622) +- Fix cookies errors of 1.22.2 + +v1.22.2 (2023-09-20) +- Removed HTTPX module (http2) to fix compatibility with python >= 3.11 +- Restored Requests module (details https://github.com/CastagnaIT/plugin.video.netflix/pull/1622) + +v1.22.1 (2023-06-26) +- NOTICE FOR ANDROID: Has been readd an old workaround to fix "incorrect password" error, + If you are using a L1 device you have to login again, however some L1 devices may still not works. + If you are using a L3 device you have, you have to disable ESN 1080p workaround from Expert setting after login again. + After the upgrade the add-on should disable the ESN 1080p workaround automatically but better check before re-login. +- NOTICE FOR MAC: Video playback still not works. No solution has been found yet. +- NOTICE FOR WINDOWS: Video playback still not works. No solution has been found yet. +- ADS PLAN: Is officially not supported, this until support for ads is implemented appropriately. +- Re-add workaround for "invalid password" error on android L1 devices +- Add warning message for unsupported videos with ads contents +- Update translations po, de, gl_es + +v1.22.0 (2023-04-12) +- New minimise black bars settings (Kodi 20 and above) +- Kodi "View mode" setting is no longer reset by default +- Update translations hu, pl, de, it, zh_tw, ro + +v1.21.0 (2023-03-12) +- Fix TypeError that prevent to load menu video contents (due to website bug) +- Fix possible Server disconnected error on playback start that cause broken watched status sync +- Cleanup video codec settings +- Add support to AV1 codec, atm available on non-Android systems (Kodi 21 only, available on future nightlies) +- Add support to VP9 Profile 2 (Kodi 21 only, available on future nightlies) +- Add Android HDR detection at first addon start-up, ask to enable it (Kodi 20) +- Updated NFAuthenticationKey software/script +- Update translations it + +v1.20.8 (2023-02-24) +- Fix Kodi library update when you add/remove tv shows and movies +- Fix wrong path added to Kodi sources from library setting + +v1.20.7 (2023-02-21) +- Workaround/fix for wrong plot language due to website bug +- Cleanup/fix ListItem deprecated methods/properties (Kodi 20) + +v1.20.6 (2023-02-09) +- Fix SQLite error on startup using some linux systems +- Update translations po, cs_cz + +v1.20.5 (2023-01-16) +- NOTICE FOR ANDROID DEVICES: The website has fixed the recent bug for "MSL: Email or password is incorrect" error, + so video playback is now restored for all Android devices. +- Disabled update loco context, is not more used to update watched status +- Minor changes +- Update translations zh_cn, zh_tw + +v1.20.4 (2023-01-14) +- NOTICE FOR ANDROID DEVICES: Due to new changes to the website, many Android devices will not be able to play videos + with this addon, at the moment there are no solutions, we hope this is a temporary problem. +- On android, replaced MSL idtoken auth with netflixid auth (for "Email or password is incorrect" error problem) +- Add new audio offset setting +- Fix Kodi crash on Android when play a video +- Fix STRM resume workaround for Kodi v19.5 +- Fix wrong mediaflag info on episodes +- Update translations it, de, pt_br, sv_se + +v1.20.3 (2022-12-17) +- IMPORTANT NOTICE: New changes to website is causing video quality to drop to 540p after about 20h, +this add-on update implements a workaround to fix this problem by changing ESNs every 20h. +If necessary, you can open "Widevine / ESN settings" on the Expert settings, to manually generate new ESN's +by selecting the "Reset" button, or you can also disable the 540p workaround. +- IMPORTANT NOTICE FOR ANDROID: Do NOT USE the ESN of the original app with the add-on, or will cause video quality drop +to 540p also on the original android app for an unknown period of time. This problem cannot be solved by the add-on, +there are no know solutions yet, so you can only report the 540p problem to customer service. +- FOR ANDROID, KNOWN SIDE EFFECTS: You will receive a notification that a new device has logged in every time ESN changes. +- Add new 540p workaround setting to "Widevine / ESN settings" on the Expert settings +- Supplemental plot info are now consistent in all use cases +- Fix a possible website JSON parsing problem on login/refresh session +- Fix "hdrType" TypeError (Kodi 20) +- Update translations hu, zh_cn, ro, it, de + +v1.20.2 (2022-11-12) +- Fixed 4k HDR +- Fixed missing plot +- Fix/workaround for error "ReadError [Errno 11] Try again" +- Fixed broken Clean cache menu +- Fix year/premiered date on NFO files. +Only the year is available, required optional full resync for existing files, on new files will be add by default. +- Update translations pl, it, de, hu, zh_tw + +v1.20.1 (2022-10-30) +- Fixed playback on linux devices +- Protected Logout setting with a confirmation dialog +- Wait for service startup without notification +- Add support info on add-on settings + +v1.20.0 (2022-10-27) +- Fixed limitation on Android devices to playback from main profile only +- Fixed trailers context menu and list +- Add color setting for videos set as "Remind me" +- Add "Remember PIN" feature, can be set by opening profile context menu +- Updated "Remove watched status" context menu (continue watching menu) +- Cleanup add-on library settings +- Cleanup profiles context menus +- Removed Expert setting "Force MSL with idtoken authentication" not more needed +- Removed language code descriptions workaround to Kodi 20 only, for details see Wiki FAQ: +"Audio/Subtitles language codes have a wrong/incomplete descriptions" +- Update translations it, de, fr, zh_cn + +v1.19.1 (2022-10-09) +- NOTE: DUE TO WEBSITE CHANGES ANDROID L3 DEVICES CAN PLAY VIDEOS FROM MAIN PROFILE ONLY +- Fixed error "License update not successful (no keys)" on linux systems +- Fixed add/remove the remind me to the videos +- Fixed IPC timeout error when you share STRM library with MySQL +- Speeded up database access +- Updated Android ESN generation +- Add Expert setting "Force MSL with idtoken authentication" +can be used to try solve error: "User authentication data does not match entity identity" +- Add AV1 codec support, for development test purpose only (Kodi 20) +- Update translations el_gr, it + +v1.19.0 (2022-10-04) +- NOTE: DUE TO WEBSITE CHANGES ANDROID L3 DEVICES CAN PLAY VIDEOS FROM MAIN PROFILE ONLY +- Rework of the addon due to website API change +- Fix HTTP error 404 Not Found for url +- Fix error missing 2 required keyword-only arguments: 'request' and 'response' +- Fix MSL error user auth data does not match entity identity on android L3 devices +- Update translation sv_se + +v1.18.10 (2022-09-26) +- Fixed MSL error when playing videos from non-owner Netflix profiles +- Fixed setting type error at first plugin startup on Kodi 20 +- Fixed error when you add a tvshow/movie on an empty my list +- Removed STRM resume workaround for Kodi v19.5 and v20 (fixed on Kodi) +- Update translations zh_cn, cs_cz, gl_es + +v1.18.9 (2022-08-30) +- Add new expert setting to override "Stream selection type" setting of InputStream Adaptive (Kodi 20) +- Add new experimental setting "Limit maximum height of black bars" +- Fixed recommendations menu +- Fixed "Remind me" context menu error +- Fixed MSL error "User entity association record ..." +- Fixed "double click" problem on Skip button (Kodi 20) +- Better match for video codec with skin mediaflag +- Update translations it, de, zh_tw, hu, ro, fr, jp, kr + +v1.18.8 (2022-06-19) +- REPOSITORY CHANGED: The repository has been changed, to receive future updates +the add-on will try update it automatically to new v2.0, if for some reason this not +happens you can update it manually by downloading: repository.castagnait-2.0.0.zip +from Github Readme: https://github.com/CastagnaIT/plugin.video.netflix +- Fix broken addon startup due to update error +- Update translations zh_cn, ro, pt_br + +v1.18.7 (2022-06-14) +- REPOSITORY CHANGED: The repository has been changed, to receive future updates +the add-on will try update it automatically to new v2.0, if for some reason this not +happens you can update it manually by downloading: repository.castagnait-2.0.0.zip +from Github Readme: https://github.com/CastagnaIT/plugin.video.netflix +- Started tv shows are no longer marked as watched by default, this to prevent problems +with Skin filters, if you prefer it enable "Marks started tv shows as watched" from settings. +- Fix genres/subgenres menus due to website changes +- Add support to HDR/DolbyVision skin media flags (Kodi 20) +- Update translations pl, it, cs_cz, hu, de, zh_tw, fr, gl_es + +v1.18.6 (2022-05-21) +- Fix regression to allow 1080p on ARM + +v1.18.5 (2022-05-19) +- Updates to get/handle licensed manifest +- Update translations pt_br, pl + +v1.18.4 (2022-02-05) +- Fix error "This title is not available to watch instantly" when using MSL manifest v1 +- Fixed tvshow.nfo not exported on library auto-update +- Update translations gl_es, jp, kr, de + +v1.18.3 (2021-12-30) +- Add 1080P workaround for linux ARM devices +To enable it: On Expert settings, set "MSL manifest version" to "Version 1" + +v1.18.2 (2021-12-12) +- Fix "This title is not available to watch instantly" error due to website changes + +v1.18.1 (2021-11-26) +- Fix problems to watched status sync due to website changes +- Fix error when trying to play an episode not yet available +- Update translations zh_cn, jp, kr + +v1.18.0 (2021-10-23) +- Add support to make search by text from Json-RPC requests (see wiki) +- Add expert setting to disable system-based encryption (for persistent login request problem) +- Fixed bug in Kodi 20 which led to possible crashes when playing +- Fixed bug in search by subtitle/audio language +- Fixed problems with scrapers due to slash-backslash +- Update translations po, zh_tw, it, hr, hu, cz, de, fr, pt_br, ro +- Minor changes + +v1.17.0 (2021-08-13) +- Migration to HTTP2 protocol +- Fixed login with username/password for more than 90% of cases + +v1.16.2 (2021-07-16) +- Implemented new licensed manifest request (to have HD is mandatory to update InputStream Adaptive to last version) +- Fixed "This title is not available to watch instantly" error on some ARM devices incorrectly identified +- Fixed empty lists on menu/submenus due to website changes +- Update translation zh_cn +- Minor changes + +v1.16.1 (2021-06-20) +- Updated video profiles for HEVC/HEVC HDR +- Add support to browse network paths with open file/folder dialog +- Fixed missing items in episodes lists and others submenus +- Fixed broken add-on services when play videos from playlist (only Kodi 20) +- Update translations gl_es +- Minor changes + +v1.16.0 (2021-06-03) +- Add new menu "New and Popular" +- Add feature to play promo trailer on unavailable videos +- Add "Remind me" feature on unavailable videos +- Norwegian macrolanguages are now played/selected as the main Norwegian language +- Watched status sync is now enabled by default (on new installs) +- VP9 codec profile is now enabled by default (on new installs for non-android devices) +- "Top 10" menu is now under "New and Popular" menu +- Re-added Up Next add-on install menu +- Unplayable videos no longer cause playback error +- Minor changes +- Update translations cs_cz, it, hu, de, cz, fr, ro, jp, kr, pt-br, zh_tw + +v1.15.1 (2021-04-21) +- Fixed regression to ESN/Widevine window +- Fixed wrong selection of audio track for impaired +- Add latest changes to IPC +- Minor changes + +v1.15.0 (2021-04-02) +NOTICE TO PARENTAL CONTROL: +THE OLD PARENTAL CONTROL WITH PIN IS NO LONGER SUPPORTED, +HAS BEEN DEPRECATED BY NETFLIX SINCE LAST YEAR, IF YOU KEEP USING IT +YOU MUST UPDATE YOUR ACCOUNT PARENTAL CONTROL SETTINGS. +- Fixes due to website changes (fix KeyError loco) +- Removed the deprecated Parental Control (with PIN protection) +- Improved playback startup speed +- Enabled IPC over HTTP by default, IPC over AddonSignals lead to a memory leak (Kodi bug) +- IPC now use only Pickle +- Add missing support to IPC over AddonSignals for the cache +- Joined all HTTP servers to one multithreaded to save resources +- Used a single web session instead of two distinct (nfsession/MSL) +- Cleaned many part of source code (nfsession/MSL/directory/IPC/...) +- Update translations zh_tw, gr + +v1.14.1 (2021-02-25) +- Fixed error caused by installation from scratch +- Update translations pt_br, zh,tw, ro, fr, zh_cn, jp, kr, gl_es, hu + +v1.14.0 (2021-02-21) +- Converted settings xml to the new Kodi settings format type +- Debug logging setting is now an on/off switch +- Fixed stream continuity errors after Kodi migration (Kodi 18 to 19) +- Fixed HTTP ReadTimeout/ConnectionError errors +- Fixed incorrect handling of MSL errors +- Fixed AttributeError on playback when "User interface language" is set as preferred audio +- Updated translations de, it +- Minor fixes/changes + +v1.13.1 (2021-01-25) +- Add images to seasons list items +- Implemented support to Kodi language setting case "User interface language" +- Fixed "Prefer the audio/subtitle language with country code" feature +- Fixed possible dependencies errors on new installations +- Fixed possible service crash on slower devices like RPI +- Better managed data chunks in some cases could cause errors on starting playback +- Tried to better handle HTTP ReadTimeout/ConnectionError errors +- Updated translations fr, hu, pt-br, zh-cn + +v1.13.0 (2021-01-02) +- Add new ESN/Widevine setting dialog +- MySQL Connector/Python library is now a Kodi dependency +- Removed all Python 2 code / Kodi 18 compatibility +- Fixed an issue that not allow to use same profile to play videos with UpNext +- Disabled UpNext when play STRM of a shared path (Wiki: Share STRM library with multiple devices - OPTION 2) +- Minor code fixes/improvements +- Add Taiwan transation +- Updated translations it, de, es, jp, kr, ro + +v1.12.0 (2020-12-13) +- >> END OF THE ADD-ON DEVELOPMENT ON KODI 18.x +- >> ON KODI 18.x THE ADD-ON WILL RECEIVE ONLY BUG FIX OR MINOR CHANGES +- Add more options to force Widevine L3 (android) +- Improved errors messages to resolve: + - This title is not available to watch instantly + - Request failed validation during key exchange +- Reverted "reworked Audio/Subtitles language features" on Kodi 18 +- Fix missing default audio track for impaired (Kodi 19) +- Fixed "Prefer audio/subtitles language with country code" (Kodi 19) +- Parental control moved to profiles list context menu +- Fixed ESN generation with modelgroup +- Fixed build_media_tag exception due to video pause +- Blocked profile switching while playing, can cause issues +- Managed wrong password case on login that caused http error 500 +- Updated translations it, de, gr, pt-br, hu, ro, es, jp, kr, fr + + +v1.11.0 (2020-11-14) +- Reworked Audio/Subtitles language features + - Add support to Kodi player audio setting "Media default" (use NF profile language) + - Add setting to force the display subtitles only with the audio language set + - Add setting to always show subtitles when the preferred audio language is not available (Kodi 19 only) + - Add setting to prefer audio/subtitles with country code (Kodi 19 only) + - Add setting to prefer stereo audio tracks (instead of multichannels) +- Add check for video availability +- Add support to NFAuthentication for MacOS +- Add workaround to HTTP connection problem blocking the add-on service +- Fixed wrong resume time after manual seek (when nf sync enabled) +- Fixed login/addon open error due to json parsing error +- Updated translations it, jp, kr, tr, gr, ro, fr, hu, pl, de, zh-cn, pt-br +- Others minor fixes + +v1.10.1 (2020-10-24) +- Improved watched status, now videos will be marked as watched correctly (Kodi 19 all cases/Kodi 18 library only) +- Improved loading video lists speed on slow hdd/sdcard +- Updated some MSL endpoints +- Implemented clean library by directory (Kodi 19) +- Fixed problems with skins that cause add-on problems due to extrafanart +- Fixed broken profile switch when profile autoselection was enabled +- Fixed wrong time position sent to netflix server when player seek +- Fixed notification error when playback a video with no subtitles (Kodi 19) +- Add Galicial translation + +v1.10.0 (2020-10-02) +- Add initial support to keymapping (details to Wiki) +- Fixed regression causing PIN request each time the main menu is loaded +- Fixed an issue that caused build_media_tag exception while watching video +- Fixed an issue that caused build_media_tag exception while watching a non-netflix content +- Improvements to english language and related translations + +v1.9.1 (2020-09-18) +- Add TV Shows/Movies Top 10 +- Add support to NFAuthenticationKey for linux +- Fixed search results via JSON-RPC +- Fixed an issue that cause to ask to clear search history again when you return to previous menu +- Fixed possible wrong results on Top 10 menu due to cache TTL +- Updated translations ro, nl + +v1.9.0 (2020-09-06) +- New login method "Authentication key" details on GitHub readme +- Add possibility to import library from a different folder +- Add flatted tvshow for single season (depends from kodi setting) +- Big speedup improvement for Kodi 18 in service loading and opening profiles page +- Fixed a problem that could cause the addon to not start after login unexpected errors +- Fixed a wrong behaviour that caused timeout error after credentials login fails +- Fixed context menus issues after add a new search +- Updated translations de, it, fr, pl, zh_ch, pt_br, hu, jp, kr, el_gr, es + +v1.8.0 (2020-08-20) +- Attempt to fix the login (not full solved) +- Reimplemented menus "All movies", "All TV Shows", "Browse subgenres" +- Added new search option "By genre/subgenre ID" +- Added sort order setting for search history (Last used or A-Z) +- Fixed Previous/Next page buttons on alphabetically sorted lists +- Fixed regression in http IPC switch setting +- Updated translations it, de, pt_br, el_gr, hu, zh_ch, jp, kr, ro, fr, pl +- Minor fixes + +v1.7.1 (2020-08-12) +- Added Profiles menu with new profiles context menus: + - Set for auto-selection + - Set for library playback + These two options now are managed only from the Profiles list +- Added setting to enable nf watched status sync with library (one way) +- Added expert setting to customize results per page +- Fixed add-on inaccessibility when the current used profile no more exists +- Fixed feature "Select first unwatched" on Kodi 18.x +- Fixed failure to update nf watched status (seem works better now) +- Little speed improvement on add-on sequential executions +- Better handled types of exceptions with IPC +- Removed disable_modal_error_display expert setting +- Some code cleaning with other improvements +- Add Hebrew translation +- Updated translations it, el, fr, pr_br, jp, kr, hu, po, de, ro, cz, zh_cn + +v1.7.0 (2020-07-28) +- Big improvement in loading lists when you have many titles to My list +- Refactor of nfsession +- Refactor/improved library code: + - Improved speed and cpu use in autoupdate/sync in background + - Suppressed continuous appearance of loading screen when autoupdate/sync in background + - Widgets/Favourites managed without profile selection + - New Import existing library feature + - Play From Here context menu is fixed + - Reintroduced UpNext fast video playback feature + - Very long list of improvements/fixes full list on GitHub PR-756, PR-761 +- Managed error to account not reactivacted +- New Chinese (simple) language translation +- Updated translations it, sv-se, fr, de, jp, kr, hu, pl, es, pt-br, ru +- Many other changes + +v1.6.1 (2020-07-08) +- Fixed broken search menu on fresh installations +- Updated el-gr translation + +v1.6.0 (2020-07-04) +- New search menu +- New search types (by term, by audio lang, by subtitles lang) +- Implemented option to remove titles from Continue watching list +- Updated translations it, de, hu, tr, ro, jk, kr, pt-br, sv-se, fr, el-gr + +v1.5.1 (2020-06-27) +- Implemented fix/workaround to get resolutions until to 1080p with ARM devices +- Updated translations tr + +v1.5.0 (2020-06-21) +- Add support to the new LoCo Netflix pages +- Fixed KeyError 'lolomo' (LoLoMo seem now deprecated) +- Fixed incorrect user/password issue when spaces are inserted unintentionally to credentials +- New attempt to fix http error 401 +- Updated translations fr +- Minor changes/fixes + +v1.4.0 (2020-05-30) +- Implemented profile selection to library (preferences in the settings/playback) + This also fixes following related issues when play from library: + - Missing audio/subtitle languages + - Missing metadata + - Wrong age restrictions + - Video content not available +- Initial adaptions for the new NF changes (cause of KeyError "lolomo") +- Fixed profile switch for PIN protected profiles +- Fixed forced subtitles sel. issues when Kodi audio lang setting is Original/Media default (Kodi 19) +- Fixed regression for missing skin codec media-flag +- Fixed an issue that caused loss of skin view type after switch profile +- Fixed KeyError issue when performed the scheduled clear expired cache +- Fixed unicodedecode error with Kodi 18 and MSL errors +- Updated translations de, es, hu, ro, sv-se, tr, pl +- Other minor fixes + +v1.3.2 (2020-05-20) +- Fixed some issues on chinese systems +- Fixed errors on loading lists +- Fixed missing plot in some skins +- Fixed ESN generation (lower case issue) +- Fixed UpNext regressions +- Updated translations es, sv-se + +v1.3.1 (2020-05-17) +Note to RPI devices: credentials will be asked again a storage problem has been fixed +- Fixed KeyError issue + +v1.3.0 (2020-05-17) +Note to RPI devices: credentials will be asked again a storage problem has been fixed +- Reimplemented parental control +- Add new "Top 10" menu list +- Add support to promo video trailer for custom skins +- Add metadata Season and Episode value to ListItem's +- Add metadata PlotOutline and Trailer values to ListItem's +- Continue watching is now working with Netflix watched status sync +- Fixed wrong video framerate, caused Dolby Vision signal loss and wrong colors +- Fixed an issue that caused a wrong watched status sync at end of playback +- Fixed an issue that caused missing to export some new seasons +- Fixed an issue that caused the library sync to crash in some situations +- Fixed a possible issue that can cause a wrong audio language selection on the episodes +- Fixed a possible issue that can cause streamcontinuity to crash due to empty subtitle data +- Fixed non visible media-flags to tvshows +- Updated translations en, it, pt-br, de, sv-se, hu, nl, pl +- Minor improvements/fixes + +v1.2.2 (2020-05-07) +- Fixed a issue regression caused problems to 1080P/4K playback + +v1.2.1 (2020-05-05) +- Reviewed access and profile selection to mitigate http error 401 (not resolved) +- Add Open Connect CDN setting +- Set time limit for paused playback +- Fixed a issue caused wrong date in viewing activity +- Fixed dash content protection data +- Fixed a issue caused UpNext to crash +- Fixed a issue caused KeyError when play videos +- Fixed a issue caused TypeError on more addon rollback +- Fixed a issue caused a double profile switch on selection +- Fixed a issue caused slow playback video starts (on windows) +- Fixed a issue caused missing of h265 media flag +- Fixed broken "force the update of my list" context menu +- Some changes for Kodi 19 due to API changes +- Updated translations de, it +- Other minor improvements/changes + +v1.2.0 (2020-04-22) +- Parental control temporary disabled due to Netflix changes +- New cache management +- Add-on now is ready to works also on systems with special chars in the system path +- Implemented profile PIN protection +- Implemented masked PIN input (Kodi 19) +- Implemented auto purge cache of expired items +- Implemented paginated list of seasons +- Implemented cache to search menu +- Implemented Up Next also with "Sync of watched status with Netflix" feature +- Add support to Python 3.8 +- Add title to search page +- Add profile name to homepage title +- Add a better handling of lolomo error with sync of watched status enabled +- Add romanian, swedish translation +- Improved watched status threshold +- Improved watched status update in the GUI display +- Improved catch of possible errors in events module +- EventHandler thread now is running only when sync of watched status is enabled +- Fixed an issue caused of disabling the Up Next setting +- Fixed an issue which prevented a correctly add/remove operations to paginated My list +- Fixed an issue caused mixing images with other languages of profiles +- Fixed an issue caused error when art data was missing +- Fixed an issue caused generic wrong error messages of BackendNotReady with HTTP IPC +- Fixed an issue caused ProfilesMissing with profiles without avatar +- Fixed an issue that show a wrong title in sub genres menus +- Fixed a possible cases of service breakage due to server init fails +- Fixed a possible cases of service controller breakage +- Fixed an issue caused not update of watched status when play video inside the add-on with Up Next +- Fixed a particular issue of wrong file url passed to Up Next +- Fixed a rare case of ImportError error at add-on start +- Fixed cases of CacheMiss error with Remember audio-subtitle or Sync of watched status +- Fixed possible issues with internal upgrade function and version rollback +- Fixed add-on not starts due to possible outdated MySQL or possible MySQL errors +- Updated translations de, es, hu, it, pl, tr, hr, pt-br +- A lot of other improvements and fixes + +v1.1.1 (2020-03-24) +- Fixed critical bug caused continuous profile switching +- Fixed unicode decode error to profile auto-selection + +v1.1.0 (2020-03-20) +- Added more supplemental info to plot info +- Added customizable color to suppl. plot info +- Added Greek language +- Added more results to recommendations menu +- Improved auto-login feature (now called "profile auto-selection") +- Improved password request in particular circumstances +- Improvements to recognize external calls to add-on +- Fixed msl issues with login with different account after a logout +- Fixed an issue in the strm resume workaround +- Fixed an issue caused broken configuration wizard on L1 android devices +- Fixed an issue which prevented the loading of profile list with a backslash on the names +- Fixed an issue which prevented the loading of suppl. info to plot info +- Fixed an issue caused interruption of sending video status progress after video pause or seek +- Updated it, de, nl translations +- Other minor fixes + +v1.0.0 (2020-03-06) +- Added watched status sync with Netflix services WIP (details on github wiki) +- Reworked add-on/kodi auto-configuration based on device and system characteristics +- Reworked MSL service +- Added support to MSL switch profile, this fixes very old issues: + - Requests are now performed with the right msl profile + - No more missing audio languages on non-owner profiles + - No more missing subtitle languages on non-owner profiles + - No more video not available error when main profile has age limits and other profile are set as adults + - You can update watched status on all profiles +- Added workaround to fix 4k media flag with android device 4k capable +- Added turkish language +- Changes on nfsession profile switch in order to get the profiles cookies +- Improved ESN generation on android devices +- Automatically turn off omx player on raspberry (not compatible) +- Fixed the error when opening the Export menu caused by too many exported items +- Fixed an issue that causing delayed start of services features after start playback +- Fixed issues with ios/tvos restrictions (but due to missing widevine library for now are not supported) +- Fixed service databases upgrade +- Fixed an issue that caused wrong behaviour with Reset ESN +- Fixed an issue that caused wrong behaviour with highlighted titles +- Fixed a rare issue that caused "stream continuity" to stop working +- Updated jp, kr, it, pl, hu, hr, de, fr, es translations +- A lot of other code changes + +v0.16.4 (2020-01-28) +- Up Next is now supported only from version 1.1.0 and up +- Added czech language +- Added "because you liked" to recommendations menu +- Added Up Next install option +- Implemented Up Next feature, end time information +- Implemented Up Next feature, fast start next video +- Fixed the error "Request failed validation during key exchange" +- Fixed an issue that causing opening Up Next notification in wrong position +- Fixed html tags in profiles names +- Fixed STRM files resume workaround +- Fixed retrieving infolabels from library on python 3 +- Managed user id token key expiration +- Manifest is saved to hdd with enabled verbose debugging only +- Updated de, es, hu, it translations +- Some minor changes, fixes + +v0.16.3 (2020-01-18) +- Fixed an issue that causing addon freeze on export/update to library +- Fixed an regression issue that causing http error 401 +- Fixed an issue that causing unicodedecode error at startup +- Fixed an issue that in some cases prevented the export of a tv show season +- Generally optimized addon speed +- Many improvements to the code + +v0.16.2 (2020-01-07) +- Improved add-on startup +- Improved loading of profiles list +- Added in expert setting a choice to speed up service or addon startup +- Fixed an issue that causing addon misbehaviour using multiple Kodi profiles +- Fixed an issue that causing addon breakage with sqlite connections +- Fixed some python 3 issues on Android +- Handled cases of metadata not available +- Permanently removed sharing Kodi videos settings between profiles +- Updated de, es, it translations +- Minor improvements + +v0.16.1 (2019-12-14) +- Allowed to export individual seasons to the library (manual mode) +- Dolby atmos audio streams are now specified (Kodi 19) +- Added workaround to fix skin widgets +- Handled subtitle properties for next version of InputStream Adaptive +- Introduced accurate handling of subtitles (Kodi 19) +- Improved handling of subtitles (Kodi 18) +- Improved return to main page from search dialog +- Improved cancel login dialog after the logout +- Improved timeout on IPC over HTTP +- Fixed an issue that showed the wrong label while browsing movies +- Fixed ParentalControl/Rating GUI on custom skins +- Fixed an issue that cause unicodedecode error on some android devices +- Fixed an issue that can cause unicodedecode error in user/password +- Added japanese language +- Updated kr, hu, pt_br, fr +- Many improvements to the code + +v0.16.0 (2019-11-29) +- Added new parental control settings +- Added new thumb rating to movies/tv shows +- Started migrating to watched status marks by profile +- Optimized startup code +- Better handled no internet connection +- Fixed an issue that breaks the service when there is no internet connection +- Fixed an issue in some specific cases request at startup credentials even if already saved +- Fixed an issue did not show any error to the user when the loading of profiles fails +- Fixed an issue that did not allow the display of the skip button in the Kodi library +- New Hungarian language +- Updated de, hr, it, pl, pt_br translations +- Other minor improvements/fixes + +v0.15.11 (2019-11-20) +- Fixed a critical error on auto-update +- Fixed some error on py3 +- Fix to handle dolby vision on Kodi 19 +- Updated fr_fr, nl_nl translations +- Minor fixes + +v0.15.10 (2019-11-17) +- Fixed error in exporting to Kodi library due to wrong settings name +- Updated de_de, pt_br translations + +v0.15.9 (2019-11-16) +- Removed limit to perform auto-update and auto-sync with my list only with main profile +- Added possibility to choose the profile to perform auto-sync with my list +- Auto-sync with my list now sync also the movies +- Auto-update now can be performed in manual and scheduled mode +- Purge library now ensures complete cleaning of the database and files +- Added possibility to disable sort order of my list from addon settings +- Updated user agents +- Modified debug logging in order to save cpu load +- Fixed pagination of episodes +- Fixed unhandled error of membership user account status +- When set to one profile the Kodi library is no longer modified by other profiles +- A lot of fixes/improvements to compatibility between py2 and py3 +- Updated it, pl, pt_BR, hr_HR translations +- Minor fixes + +v0.15.8 (2019-10-31) +- Fixed addon open issue caused to a broken cookies +- Updated de translations +- Fixed an issue that cause UpNext sometimes to fail +- Minor fixes + +v0.15.7 (2019-10-26) +- Do not start auto-sync if disabled +- Updated polish translation + +v0.15.6 (2019-10-24) +- Added customizable color to titles already contained in mylist +- Added menu to mylist menu to force update of the list +- Added trailers video length +- Added supplemental info to tvshow/movie plot (shown in green) +- Added add/remove to my-list also in childrens profiles +- Added owner/kids account profile infos to profiles menu items +- Added notification when library auto-sync is completed (customizable) +- Library auto-sync now take in account of changes made to mylist from other apps +- Library auto-sync now can export automatically NFOs +- Increased default cache ttl from 10m to 120m +- Improved speed of add/remove operations to mylist +- More intuitive settings menus +- Updated addon screenshots +- Fixed generate ESN on android +- Fixed "Perform full sync" setting now work without close settings window +- Fixed HTTPError 401 on add/remove to mylist +- Fixed cache on sorted lists +- Fixed library full sync when mylist have huge list +- Fixed wrong cache identifier +- Fixed videoid error when play a trailer +- Fixed purge cache that didn't delete the files +- Fixed mixed language of plot/titles when more profiles have different languages +- Other fixes + +v0.15.5 (2019-10-12) +- Speedup loading lists due to the new login validity check +- Cookies expires are now used to check a valid login +- Fixed an issue introduced with previous version causing login error +- Fixed double handshake request on first run + +v0.15.4 (2019-10-11) +- Added InputStream Helper settings in settings menu +- Fixed add/remove to mylist on huge lists +- Fixed expired mastertoken key issue +- Fixed skipping sections due to netflix changes +- Manifests now are requested only once +- Updated pt-br, de, es translations +- Minor improvements + +v0.15.3 (2019-09-19) +- Initial conversion to python 3 +- Initial integration tests +- Implemented device uuid to avoid always asking credentials +- Fixed a problem when library source is special:// or a direct path +- Fixed run library update on slow system like RPI +- Updated dutch language +- Minor fixes + v0.15.2 (2019-08-30) - Fixed key handshake at addon first run - Fixed library update service at first run diff --git a/.github/codecov.yml b/codecov.yml similarity index 90% rename from .github/codecov.yml rename to codecov.yml index 01fdc532e..04f82dff0 100644 --- a/.github/codecov.yml +++ b/codecov.yml @@ -9,5 +9,4 @@ coverage: patch: false comment: false ignore: -- modules/ -- test/ +- tests/ diff --git a/docs/conf.py b/docs/conf.py index 831698cfa..cb87c91e3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,7 +1,12 @@ # -*- coding: utf-8 -*- -# -# plugin.video.netflix documentation build configuration file, created by -# sphinx-quickstart on Wed Apr 26 16:27:25 2017. +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2017 sphinx-quickstart (original implementation module) + Documentation build configuration file + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" from __future__ import absolute_import, division, unicode_literals diff --git a/modules/enum/MANIFEST.in b/modules/enum/MANIFEST.in deleted file mode 100644 index 98fe77f55..000000000 --- a/modules/enum/MANIFEST.in +++ /dev/null @@ -1,9 +0,0 @@ -exclude enum/* -include setup.py -include README -include enum/__init__.py -include enum/test.py -include enum/LICENSE -include enum/README -include enum/doc/enum.pdf -include enum/doc/enum.rst diff --git a/modules/enum/PKG-INFO b/modules/enum/PKG-INFO deleted file mode 100644 index 98927c4d9..000000000 --- a/modules/enum/PKG-INFO +++ /dev/null @@ -1,62 +0,0 @@ -Metadata-Version: 1.1 -Name: enum34 -Version: 1.1.6 -Summary: Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4 -Home-page: https://bitbucket.org/stoneleaf/enum34 -Author: Ethan Furman -Author-email: ethan@stoneleaf.us -License: BSD License -Description: enum --- support for enumerations - ======================================== - - An enumeration is a set of symbolic names (members) bound to unique, constant - values. Within an enumeration, the members can be compared by identity, and - the enumeration itself can be iterated over. - - from enum import Enum - - class Fruit(Enum): - apple = 1 - banana = 2 - orange = 3 - - list(Fruit) - # [, , ] - - len(Fruit) - # 3 - - Fruit.banana - # - - Fruit['banana'] - # - - Fruit(2) - # - - Fruit.banana is Fruit['banana'] is Fruit(2) - # True - - Fruit.banana.name - # 'banana' - - Fruit.banana.value - # 2 - - Repository and Issue Tracker at https://bitbucket.org/stoneleaf/enum34. - -Platform: UNKNOWN -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: BSD License -Classifier: Programming Language :: Python -Classifier: Topic :: Software Development -Classifier: Programming Language :: Python :: 2.4 -Classifier: Programming Language :: Python :: 2.5 -Classifier: Programming Language :: Python :: 2.6 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3.3 -Classifier: Programming Language :: Python :: 3.4 -Classifier: Programming Language :: Python :: 3.5 -Provides: enum diff --git a/modules/enum/README b/modules/enum/README deleted file mode 100644 index aa2333d8d..000000000 --- a/modules/enum/README +++ /dev/null @@ -1,3 +0,0 @@ -enum34 is the new Python stdlib enum module available in Python 3.4 -backported for previous versions of Python from 2.4 to 3.3. -tested on 2.6, 2.7, and 3.3+ diff --git a/modules/enum/enum/LICENSE b/modules/enum/enum/LICENSE deleted file mode 100644 index 9003b8850..000000000 --- a/modules/enum/enum/LICENSE +++ /dev/null @@ -1,32 +0,0 @@ -Copyright (c) 2013, Ethan Furman. -All rights reserved. - -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. - - Neither the name Ethan Furman nor the names of any - contributors may 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 HOLDER 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/modules/enum/enum/README b/modules/enum/enum/README deleted file mode 100644 index aa2333d8d..000000000 --- a/modules/enum/enum/README +++ /dev/null @@ -1,3 +0,0 @@ -enum34 is the new Python stdlib enum module available in Python 3.4 -backported for previous versions of Python from 2.4 to 3.3. -tested on 2.6, 2.7, and 3.3+ diff --git a/modules/enum/enum/__init__.py b/modules/enum/enum/__init__.py deleted file mode 100644 index d6ffb3a40..000000000 --- a/modules/enum/enum/__init__.py +++ /dev/null @@ -1,837 +0,0 @@ -"""Python Enumerations""" - -import sys as _sys - -__all__ = ['Enum', 'IntEnum', 'unique'] - -version = 1, 1, 6 - -pyver = float('%s.%s' % _sys.version_info[:2]) - -try: - any -except NameError: - def any(iterable): - for element in iterable: - if element: - return True - return False - -try: - from collections import OrderedDict -except ImportError: - OrderedDict = None - -try: - basestring -except NameError: - # In Python 2 basestring is the ancestor of both str and unicode - # in Python 3 it's just str, but was missing in 3.1 - basestring = str - -try: - unicode -except NameError: - # In Python 3 unicode no longer exists (it's just str) - unicode = str - -class _RouteClassAttributeToGetattr(object): - """Route attribute access on a class to __getattr__. - - This is a descriptor, used to define attributes that act differently when - accessed through an instance and through a class. Instance access remains - normal, but access to an attribute through a class will be routed to the - class's __getattr__ method; this is done by raising AttributeError. - - """ - def __init__(self, fget=None): - self.fget = fget - - def __get__(self, instance, ownerclass=None): - if instance is None: - raise AttributeError() - return self.fget(instance) - - def __set__(self, instance, value): - raise AttributeError("can't set attribute") - - def __delete__(self, instance): - raise AttributeError("can't delete attribute") - - -def _is_descriptor(obj): - """Returns True if obj is a descriptor, False otherwise.""" - return ( - hasattr(obj, '__get__') or - hasattr(obj, '__set__') or - hasattr(obj, '__delete__')) - - -def _is_dunder(name): - """Returns True if a __dunder__ name, False otherwise.""" - return (name[:2] == name[-2:] == '__' and - name[2:3] != '_' and - name[-3:-2] != '_' and - len(name) > 4) - - -def _is_sunder(name): - """Returns True if a _sunder_ name, False otherwise.""" - return (name[0] == name[-1] == '_' and - name[1:2] != '_' and - name[-2:-1] != '_' and - len(name) > 2) - - -def _make_class_unpicklable(cls): - """Make the given class un-picklable.""" - def _break_on_call_reduce(self, protocol=None): - raise TypeError('%r cannot be pickled' % self) - cls.__reduce_ex__ = _break_on_call_reduce - cls.__module__ = '' - - -class _EnumDict(dict): - """Track enum member order and ensure member names are not reused. - - EnumMeta will use the names found in self._member_names as the - enumeration member names. - - """ - def __init__(self): - super(_EnumDict, self).__init__() - self._member_names = [] - - def __setitem__(self, key, value): - """Changes anything not dundered or not a descriptor. - - If a descriptor is added with the same name as an enum member, the name - is removed from _member_names (this may leave a hole in the numerical - sequence of values). - - If an enum member name is used twice, an error is raised; duplicate - values are not checked for. - - Single underscore (sunder) names are reserved. - - Note: in 3.x __order__ is simply discarded as a not necessary piece - leftover from 2.x - - """ - if pyver >= 3.0 and key in ('_order_', '__order__'): - return - elif key == '__order__': - key = '_order_' - if _is_sunder(key): - if key != '_order_': - raise ValueError('_names_ are reserved for future Enum use') - elif _is_dunder(key): - pass - elif key in self._member_names: - # descriptor overwriting an enum? - raise TypeError('Attempted to reuse key: %r' % key) - elif not _is_descriptor(value): - if key in self: - # enum overwriting a descriptor? - raise TypeError('Key already defined as: %r' % self[key]) - self._member_names.append(key) - super(_EnumDict, self).__setitem__(key, value) - - -# Dummy value for Enum as EnumMeta explicity checks for it, but of course until -# EnumMeta finishes running the first time the Enum class doesn't exist. This -# is also why there are checks in EnumMeta like `if Enum is not None` -Enum = None - - -class EnumMeta(type): - """Metaclass for Enum""" - @classmethod - def __prepare__(metacls, cls, bases): - return _EnumDict() - - def __new__(metacls, cls, bases, classdict): - # an Enum class is final once enumeration items have been defined; it - # cannot be mixed with other types (int, float, etc.) if it has an - # inherited __new__ unless a new __new__ is defined (or the resulting - # class will fail). - if type(classdict) is dict: - original_dict = classdict - classdict = _EnumDict() - for k, v in original_dict.items(): - classdict[k] = v - - member_type, first_enum = metacls._get_mixins_(bases) - __new__, save_new, use_args = metacls._find_new_(classdict, member_type, - first_enum) - # save enum items into separate mapping so they don't get baked into - # the new class - members = dict((k, classdict[k]) for k in classdict._member_names) - for name in classdict._member_names: - del classdict[name] - - # py2 support for definition order - _order_ = classdict.get('_order_') - if _order_ is None: - if pyver < 3.0: - try: - _order_ = [name for (name, value) in sorted(members.items(), key=lambda item: item[1])] - except TypeError: - _order_ = [name for name in sorted(members.keys())] - else: - _order_ = classdict._member_names - else: - del classdict['_order_'] - if pyver < 3.0: - _order_ = _order_.replace(',', ' ').split() - aliases = [name for name in members if name not in _order_] - _order_ += aliases - - # check for illegal enum names (any others?) - invalid_names = set(members) & set(['mro']) - if invalid_names: - raise ValueError('Invalid enum member name(s): %s' % ( - ', '.join(invalid_names), )) - - # save attributes from super classes so we know if we can take - # the shortcut of storing members in the class dict - base_attributes = set([a for b in bases for a in b.__dict__]) - # create our new Enum type - enum_class = super(EnumMeta, metacls).__new__(metacls, cls, bases, classdict) - enum_class._member_names_ = [] # names in random order - if OrderedDict is not None: - enum_class._member_map_ = OrderedDict() - else: - enum_class._member_map_ = {} # name->value map - enum_class._member_type_ = member_type - - # Reverse value->name map for hashable values. - enum_class._value2member_map_ = {} - - # instantiate them, checking for duplicates as we go - # we instantiate first instead of checking for duplicates first in case - # a custom __new__ is doing something funky with the values -- such as - # auto-numbering ;) - if __new__ is None: - __new__ = enum_class.__new__ - for member_name in _order_: - value = members[member_name] - if not isinstance(value, tuple): - args = (value, ) - else: - args = value - if member_type is tuple: # special case for tuple enums - args = (args, ) # wrap it one more time - if not use_args or not args: - enum_member = __new__(enum_class) - if not hasattr(enum_member, '_value_'): - enum_member._value_ = value - else: - enum_member = __new__(enum_class, *args) - if not hasattr(enum_member, '_value_'): - enum_member._value_ = member_type(*args) - value = enum_member._value_ - enum_member._name_ = member_name - enum_member.__objclass__ = enum_class - enum_member.__init__(*args) - # If another member with the same value was already defined, the - # new member becomes an alias to the existing one. - for name, canonical_member in enum_class._member_map_.items(): - if canonical_member.value == enum_member._value_: - enum_member = canonical_member - break - else: - # Aliases don't appear in member names (only in __members__). - enum_class._member_names_.append(member_name) - # performance boost for any member that would not shadow - # a DynamicClassAttribute (aka _RouteClassAttributeToGetattr) - if member_name not in base_attributes: - setattr(enum_class, member_name, enum_member) - # now add to _member_map_ - enum_class._member_map_[member_name] = enum_member - try: - # This may fail if value is not hashable. We can't add the value - # to the map, and by-value lookups for this value will be - # linear. - enum_class._value2member_map_[value] = enum_member - except TypeError: - pass - - - # If a custom type is mixed into the Enum, and it does not know how - # to pickle itself, pickle.dumps will succeed but pickle.loads will - # fail. Rather than have the error show up later and possibly far - # from the source, sabotage the pickle protocol for this class so - # that pickle.dumps also fails. - # - # However, if the new class implements its own __reduce_ex__, do not - # sabotage -- it's on them to make sure it works correctly. We use - # __reduce_ex__ instead of any of the others as it is preferred by - # pickle over __reduce__, and it handles all pickle protocols. - unpicklable = False - if '__reduce_ex__' not in classdict: - if member_type is not object: - methods = ('__getnewargs_ex__', '__getnewargs__', - '__reduce_ex__', '__reduce__') - if not any(m in member_type.__dict__ for m in methods): - _make_class_unpicklable(enum_class) - unpicklable = True - - - # double check that repr and friends are not the mixin's or various - # things break (such as pickle) - for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'): - class_method = getattr(enum_class, name) - obj_method = getattr(member_type, name, None) - enum_method = getattr(first_enum, name, None) - if name not in classdict and class_method is not enum_method: - if name == '__reduce_ex__' and unpicklable: - continue - setattr(enum_class, name, enum_method) - - # method resolution and int's are not playing nice - # Python's less than 2.6 use __cmp__ - - if pyver < 2.6: - - if issubclass(enum_class, int): - setattr(enum_class, '__cmp__', getattr(int, '__cmp__')) - - elif pyver < 3.0: - - if issubclass(enum_class, int): - for method in ( - '__le__', - '__lt__', - '__gt__', - '__ge__', - '__eq__', - '__ne__', - '__hash__', - ): - setattr(enum_class, method, getattr(int, method)) - - # replace any other __new__ with our own (as long as Enum is not None, - # anyway) -- again, this is to support pickle - if Enum is not None: - # if the user defined their own __new__, save it before it gets - # clobbered in case they subclass later - if save_new: - setattr(enum_class, '__member_new__', enum_class.__dict__['__new__']) - setattr(enum_class, '__new__', Enum.__dict__['__new__']) - return enum_class - - def __bool__(cls): - """ - classes/types should always be True. - """ - return True - - def __call__(cls, value, names=None, module=None, type=None, start=1): - """Either returns an existing member, or creates a new enum class. - - This method is used both when an enum class is given a value to match - to an enumeration member (i.e. Color(3)) and for the functional API - (i.e. Color = Enum('Color', names='red green blue')). - - When used for the functional API: `module`, if set, will be stored in - the new class' __module__ attribute; `type`, if set, will be mixed in - as the first base class. - - Note: if `module` is not set this routine will attempt to discover the - calling module by walking the frame stack; if this is unsuccessful - the resulting class will not be pickleable. - - """ - if names is None: # simple value lookup - return cls.__new__(cls, value) - # otherwise, functional API: we're creating a new Enum type - return cls._create_(value, names, module=module, type=type, start=start) - - def __contains__(cls, member): - return isinstance(member, cls) and member.name in cls._member_map_ - - def __delattr__(cls, attr): - # nicer error message when someone tries to delete an attribute - # (see issue19025). - if attr in cls._member_map_: - raise AttributeError( - "%s: cannot delete Enum member." % cls.__name__) - super(EnumMeta, cls).__delattr__(attr) - - def __dir__(self): - return (['__class__', '__doc__', '__members__', '__module__'] + - self._member_names_) - - @property - def __members__(cls): - """Returns a mapping of member name->value. - - This mapping lists all enum members, including aliases. Note that this - is a copy of the internal mapping. - - """ - return cls._member_map_.copy() - - def __getattr__(cls, name): - """Return the enum member matching `name` - - We use __getattr__ instead of descriptors or inserting into the enum - class' __dict__ in order to support `name` and `value` being both - properties for enum members (which live in the class' __dict__) and - enum members themselves. - - """ - if _is_dunder(name): - raise AttributeError(name) - try: - return cls._member_map_[name] - except KeyError: - raise AttributeError(name) - - def __getitem__(cls, name): - return cls._member_map_[name] - - def __iter__(cls): - return (cls._member_map_[name] for name in cls._member_names_) - - def __reversed__(cls): - return (cls._member_map_[name] for name in reversed(cls._member_names_)) - - def __len__(cls): - return len(cls._member_names_) - - __nonzero__ = __bool__ - - def __repr__(cls): - return "" % cls.__name__ - - def __setattr__(cls, name, value): - """Block attempts to reassign Enum members. - - A simple assignment to the class namespace only changes one of the - several possible ways to get an Enum member from the Enum class, - resulting in an inconsistent Enumeration. - - """ - member_map = cls.__dict__.get('_member_map_', {}) - if name in member_map: - raise AttributeError('Cannot reassign members.') - super(EnumMeta, cls).__setattr__(name, value) - - def _create_(cls, class_name, names=None, module=None, type=None, start=1): - """Convenience method to create a new Enum class. - - `names` can be: - - * A string containing member names, separated either with spaces or - commas. Values are auto-numbered from 1. - * An iterable of member names. Values are auto-numbered from 1. - * An iterable of (member name, value) pairs. - * A mapping of member name -> value. - - """ - if pyver < 3.0: - # if class_name is unicode, attempt a conversion to ASCII - if isinstance(class_name, unicode): - try: - class_name = class_name.encode('ascii') - except UnicodeEncodeError: - raise TypeError('%r is not representable in ASCII' % class_name) - metacls = cls.__class__ - if type is None: - bases = (cls, ) - else: - bases = (type, cls) - classdict = metacls.__prepare__(class_name, bases) - _order_ = [] - - # special processing needed for names? - if isinstance(names, basestring): - names = names.replace(',', ' ').split() - if isinstance(names, (tuple, list)) and isinstance(names[0], basestring): - names = [(e, i+start) for (i, e) in enumerate(names)] - - # Here, names is either an iterable of (name, value) or a mapping. - item = None # in case names is empty - for item in names: - if isinstance(item, basestring): - member_name, member_value = item, names[item] - else: - member_name, member_value = item - classdict[member_name] = member_value - _order_.append(member_name) - # only set _order_ in classdict if name/value was not from a mapping - if not isinstance(item, basestring): - classdict['_order_'] = ' '.join(_order_) - enum_class = metacls.__new__(metacls, class_name, bases, classdict) - - # TODO: replace the frame hack if a blessed way to know the calling - # module is ever developed - if module is None: - try: - module = _sys._getframe(2).f_globals['__name__'] - except (AttributeError, ValueError): - pass - if module is None: - _make_class_unpicklable(enum_class) - else: - enum_class.__module__ = module - - return enum_class - - @staticmethod - def _get_mixins_(bases): - """Returns the type for creating enum members, and the first inherited - enum class. - - bases: the tuple of bases that was given to __new__ - - """ - if not bases or Enum is None: - return object, Enum - - - # double check that we are not subclassing a class with existing - # enumeration members; while we're at it, see if any other data - # type has been mixed in so we can use the correct __new__ - member_type = first_enum = None - for base in bases: - if (base is not Enum and - issubclass(base, Enum) and - base._member_names_): - raise TypeError("Cannot extend enumerations") - # base is now the last base in bases - if not issubclass(base, Enum): - raise TypeError("new enumerations must be created as " - "`ClassName([mixin_type,] enum_type)`") - - # get correct mix-in type (either mix-in type of Enum subclass, or - # first base if last base is Enum) - if not issubclass(bases[0], Enum): - member_type = bases[0] # first data type - first_enum = bases[-1] # enum type - else: - for base in bases[0].__mro__: - # most common: (IntEnum, int, Enum, object) - # possible: (, , - # , , - # ) - if issubclass(base, Enum): - if first_enum is None: - first_enum = base - else: - if member_type is None: - member_type = base - - return member_type, first_enum - - if pyver < 3.0: - @staticmethod - def _find_new_(classdict, member_type, first_enum): - """Returns the __new__ to be used for creating the enum members. - - classdict: the class dictionary given to __new__ - member_type: the data type whose __new__ will be used by default - first_enum: enumeration to check for an overriding __new__ - - """ - # now find the correct __new__, checking to see of one was defined - # by the user; also check earlier enum classes in case a __new__ was - # saved as __member_new__ - __new__ = classdict.get('__new__', None) - if __new__: - return None, True, True # __new__, save_new, use_args - - N__new__ = getattr(None, '__new__') - O__new__ = getattr(object, '__new__') - if Enum is None: - E__new__ = N__new__ - else: - E__new__ = Enum.__dict__['__new__'] - # check all possibles for __member_new__ before falling back to - # __new__ - for method in ('__member_new__', '__new__'): - for possible in (member_type, first_enum): - try: - target = possible.__dict__[method] - except (AttributeError, KeyError): - target = getattr(possible, method, None) - if target not in [ - None, - N__new__, - O__new__, - E__new__, - ]: - if method == '__member_new__': - classdict['__new__'] = target - return None, False, True - if isinstance(target, staticmethod): - target = target.__get__(member_type) - __new__ = target - break - if __new__ is not None: - break - else: - __new__ = object.__new__ - - # if a non-object.__new__ is used then whatever value/tuple was - # assigned to the enum member name will be passed to __new__ and to the - # new enum member's __init__ - if __new__ is object.__new__: - use_args = False - else: - use_args = True - - return __new__, False, use_args - else: - @staticmethod - def _find_new_(classdict, member_type, first_enum): - """Returns the __new__ to be used for creating the enum members. - - classdict: the class dictionary given to __new__ - member_type: the data type whose __new__ will be used by default - first_enum: enumeration to check for an overriding __new__ - - """ - # now find the correct __new__, checking to see of one was defined - # by the user; also check earlier enum classes in case a __new__ was - # saved as __member_new__ - __new__ = classdict.get('__new__', None) - - # should __new__ be saved as __member_new__ later? - save_new = __new__ is not None - - if __new__ is None: - # check all possibles for __member_new__ before falling back to - # __new__ - for method in ('__member_new__', '__new__'): - for possible in (member_type, first_enum): - target = getattr(possible, method, None) - if target not in ( - None, - None.__new__, - object.__new__, - Enum.__new__, - ): - __new__ = target - break - if __new__ is not None: - break - else: - __new__ = object.__new__ - - # if a non-object.__new__ is used then whatever value/tuple was - # assigned to the enum member name will be passed to __new__ and to the - # new enum member's __init__ - if __new__ is object.__new__: - use_args = False - else: - use_args = True - - return __new__, save_new, use_args - - -######################################################## -# In order to support Python 2 and 3 with a single -# codebase we have to create the Enum methods separately -# and then use the `type(name, bases, dict)` method to -# create the class. -######################################################## -temp_enum_dict = {} -temp_enum_dict['__doc__'] = "Generic enumeration.\n\n Derive from this class to define new enumerations.\n\n" - -def __new__(cls, value): - # all enum instances are actually created during class construction - # without calling this method; this method is called by the metaclass' - # __call__ (i.e. Color(3) ), and by pickle - if type(value) is cls: - # For lookups like Color(Color.red) - value = value.value - #return value - # by-value search for a matching enum member - # see if it's in the reverse mapping (for hashable values) - try: - if value in cls._value2member_map_: - return cls._value2member_map_[value] - except TypeError: - # not there, now do long search -- O(n) behavior - for member in cls._member_map_.values(): - if member.value == value: - return member - raise ValueError("%s is not a valid %s" % (value, cls.__name__)) -temp_enum_dict['__new__'] = __new__ -del __new__ - -def __repr__(self): - return "<%s.%s: %r>" % ( - self.__class__.__name__, self._name_, self._value_) -temp_enum_dict['__repr__'] = __repr__ -del __repr__ - -def __str__(self): - return "%s.%s" % (self.__class__.__name__, self._name_) -temp_enum_dict['__str__'] = __str__ -del __str__ - -if pyver >= 3.0: - def __dir__(self): - added_behavior = [ - m - for cls in self.__class__.mro() - for m in cls.__dict__ - if m[0] != '_' and m not in self._member_map_ - ] - return (['__class__', '__doc__', '__module__', ] + added_behavior) - temp_enum_dict['__dir__'] = __dir__ - del __dir__ - -def __format__(self, format_spec): - # mixed-in Enums should use the mixed-in type's __format__, otherwise - # we can get strange results with the Enum name showing up instead of - # the value - - # pure Enum branch - if self._member_type_ is object: - cls = str - val = str(self) - # mix-in branch - else: - cls = self._member_type_ - val = self.value - return cls.__format__(val, format_spec) -temp_enum_dict['__format__'] = __format__ -del __format__ - - -#################################### -# Python's less than 2.6 use __cmp__ - -if pyver < 2.6: - - def __cmp__(self, other): - if type(other) is self.__class__: - if self is other: - return 0 - return -1 - return NotImplemented - raise TypeError("unorderable types: %s() and %s()" % (self.__class__.__name__, other.__class__.__name__)) - temp_enum_dict['__cmp__'] = __cmp__ - del __cmp__ - -else: - - def __le__(self, other): - raise TypeError("unorderable types: %s() <= %s()" % (self.__class__.__name__, other.__class__.__name__)) - temp_enum_dict['__le__'] = __le__ - del __le__ - - def __lt__(self, other): - raise TypeError("unorderable types: %s() < %s()" % (self.__class__.__name__, other.__class__.__name__)) - temp_enum_dict['__lt__'] = __lt__ - del __lt__ - - def __ge__(self, other): - raise TypeError("unorderable types: %s() >= %s()" % (self.__class__.__name__, other.__class__.__name__)) - temp_enum_dict['__ge__'] = __ge__ - del __ge__ - - def __gt__(self, other): - raise TypeError("unorderable types: %s() > %s()" % (self.__class__.__name__, other.__class__.__name__)) - temp_enum_dict['__gt__'] = __gt__ - del __gt__ - - -def __eq__(self, other): - if type(other) is self.__class__: - return self is other - return NotImplemented -temp_enum_dict['__eq__'] = __eq__ -del __eq__ - -def __ne__(self, other): - if type(other) is self.__class__: - return self is not other - return NotImplemented -temp_enum_dict['__ne__'] = __ne__ -del __ne__ - -def __hash__(self): - return hash(self._name_) -temp_enum_dict['__hash__'] = __hash__ -del __hash__ - -def __reduce_ex__(self, proto): - return self.__class__, (self._value_, ) -temp_enum_dict['__reduce_ex__'] = __reduce_ex__ -del __reduce_ex__ - -# _RouteClassAttributeToGetattr is used to provide access to the `name` -# and `value` properties of enum members while keeping some measure of -# protection from modification, while still allowing for an enumeration -# to have members named `name` and `value`. This works because enumeration -# members are not set directly on the enum class -- __getattr__ is -# used to look them up. - -@_RouteClassAttributeToGetattr -def name(self): - return self._name_ -temp_enum_dict['name'] = name -del name - -@_RouteClassAttributeToGetattr -def value(self): - return self._value_ -temp_enum_dict['value'] = value -del value - -@classmethod -def _convert(cls, name, module, filter, source=None): - """ - Create a new Enum subclass that replaces a collection of global constants - """ - # convert all constants from source (or module) that pass filter() to - # a new Enum called name, and export the enum and its members back to - # module; - # also, replace the __reduce_ex__ method so unpickling works in - # previous Python versions - module_globals = vars(_sys.modules[module]) - if source: - source = vars(source) - else: - source = module_globals - members = dict((name, value) for name, value in source.items() if filter(name)) - cls = cls(name, members, module=module) - cls.__reduce_ex__ = _reduce_ex_by_name - module_globals.update(cls.__members__) - module_globals[name] = cls - return cls -temp_enum_dict['_convert'] = _convert -del _convert - -Enum = EnumMeta('Enum', (object, ), temp_enum_dict) -del temp_enum_dict - -# Enum has now been created -########################### - -class IntEnum(int, Enum): - """Enum where members are also (and must be) ints""" - -def _reduce_ex_by_name(self, proto): - return self.name - -def unique(enumeration): - """Class decorator that ensures only unique members exist in an enumeration.""" - duplicates = [] - for name, member in enumeration.__members__.items(): - if name != member.name: - duplicates.append((name, member.name)) - if duplicates: - duplicate_names = ', '.join( - ["%s -> %s" % (alias, name) for (alias, name) in duplicates] - ) - raise ValueError('duplicate names found in %r: %s' % - (enumeration, duplicate_names) - ) - return enumeration diff --git a/modules/enum/enum/doc/enum.pdf b/modules/enum/enum/doc/enum.pdf deleted file mode 100644 index 3fb6ec264..000000000 Binary files a/modules/enum/enum/doc/enum.pdf and /dev/null differ diff --git a/modules/enum/enum/doc/enum.rst b/modules/enum/enum/doc/enum.rst deleted file mode 100644 index 3afc23821..000000000 --- a/modules/enum/enum/doc/enum.rst +++ /dev/null @@ -1,735 +0,0 @@ -``enum`` --- support for enumerations -======================================== - -.. :synopsis: enumerations are sets of symbolic names bound to unique, constant - values. -.. :moduleauthor:: Ethan Furman -.. :sectionauthor:: Barry Warsaw , -.. :sectionauthor:: Eli Bendersky , -.. :sectionauthor:: Ethan Furman - ----------------- - -An enumeration is a set of symbolic names (members) bound to unique, constant -values. Within an enumeration, the members can be compared by identity, and -the enumeration itself can be iterated over. - - -Module Contents ---------------- - -This module defines two enumeration classes that can be used to define unique -sets of names and values: ``Enum`` and ``IntEnum``. It also defines -one decorator, ``unique``. - -``Enum`` - -Base class for creating enumerated constants. See section `Functional API`_ -for an alternate construction syntax. - -``IntEnum`` - -Base class for creating enumerated constants that are also subclasses of ``int``. - -``unique`` - -Enum class decorator that ensures only one name is bound to any one value. - - -Creating an Enum ----------------- - -Enumerations are created using the ``class`` syntax, which makes them -easy to read and write. An alternative creation method is described in -`Functional API`_. To define an enumeration, subclass ``Enum`` as -follows:: - - >>> from enum import Enum - >>> class Color(Enum): - ... red = 1 - ... green = 2 - ... blue = 3 - -Note: Nomenclature - - - The class ``Color`` is an *enumeration* (or *enum*) - - The attributes ``Color.red``, ``Color.green``, etc., are - *enumeration members* (or *enum members*). - - The enum members have *names* and *values* (the name of - ``Color.red`` is ``red``, the value of ``Color.blue`` is - ``3``, etc.) - -Note: - - Even though we use the ``class`` syntax to create Enums, Enums - are not normal Python classes. See `How are Enums different?`_ for - more details. - -Enumeration members have human readable string representations:: - - >>> print(Color.red) - Color.red - -...while their ``repr`` has more information:: - - >>> print(repr(Color.red)) - - -The *type* of an enumeration member is the enumeration it belongs to:: - - >>> type(Color.red) - - >>> isinstance(Color.green, Color) - True - >>> - -Enum members also have a property that contains just their item name:: - - >>> print(Color.red.name) - red - -Enumerations support iteration. In Python 3.x definition order is used; in -Python 2.x the definition order is not available, but class attribute -``__order__`` is supported; otherwise, value order is used:: - - >>> class Shake(Enum): - ... __order__ = 'vanilla chocolate cookies mint' # only needed in 2.x - ... vanilla = 7 - ... chocolate = 4 - ... cookies = 9 - ... mint = 3 - ... - >>> for shake in Shake: - ... print(shake) - ... - Shake.vanilla - Shake.chocolate - Shake.cookies - Shake.mint - -The ``__order__`` attribute is always removed, and in 3.x it is also ignored -(order is definition order); however, in the stdlib version it will be ignored -but not removed. - -Enumeration members are hashable, so they can be used in dictionaries and sets:: - - >>> apples = {} - >>> apples[Color.red] = 'red delicious' - >>> apples[Color.green] = 'granny smith' - >>> apples == {Color.red: 'red delicious', Color.green: 'granny smith'} - True - - -Programmatic access to enumeration members and their attributes ---------------------------------------------------------------- - -Sometimes it's useful to access members in enumerations programmatically (i.e. -situations where ``Color.red`` won't do because the exact color is not known -at program-writing time). ``Enum`` allows such access:: - - >>> Color(1) - - >>> Color(3) - - -If you want to access enum members by *name*, use item access:: - - >>> Color['red'] - - >>> Color['green'] - - -If have an enum member and need its ``name`` or ``value``:: - - >>> member = Color.red - >>> member.name - 'red' - >>> member.value - 1 - - -Duplicating enum members and values ------------------------------------ - -Having two enum members (or any other attribute) with the same name is invalid; -in Python 3.x this would raise an error, but in Python 2.x the second member -simply overwrites the first:: - - >>> # python 2.x - >>> class Shape(Enum): - ... square = 2 - ... square = 3 - ... - >>> Shape.square - - - >>> # python 3.x - >>> class Shape(Enum): - ... square = 2 - ... square = 3 - Traceback (most recent call last): - ... - TypeError: Attempted to reuse key: 'square' - -However, two enum members are allowed to have the same value. Given two members -A and B with the same value (and A defined first), B is an alias to A. By-value -lookup of the value of A and B will return A. By-name lookup of B will also -return A:: - - >>> class Shape(Enum): - ... __order__ = 'square diamond circle alias_for_square' # only needed in 2.x - ... square = 2 - ... diamond = 1 - ... circle = 3 - ... alias_for_square = 2 - ... - >>> Shape.square - - >>> Shape.alias_for_square - - >>> Shape(2) - - - -Allowing aliases is not always desirable. ``unique`` can be used to ensure -that none exist in a particular enumeration:: - - >>> from enum import unique - >>> @unique - ... class Mistake(Enum): - ... __order__ = 'one two three four' # only needed in 2.x - ... one = 1 - ... two = 2 - ... three = 3 - ... four = 3 - Traceback (most recent call last): - ... - ValueError: duplicate names found in : four -> three - -Iterating over the members of an enum does not provide the aliases:: - - >>> list(Shape) - [, , ] - -The special attribute ``__members__`` is a dictionary mapping names to members. -It includes all names defined in the enumeration, including the aliases:: - - >>> for name, member in sorted(Shape.__members__.items()): - ... name, member - ... - ('alias_for_square', ) - ('circle', ) - ('diamond', ) - ('square', ) - -The ``__members__`` attribute can be used for detailed programmatic access to -the enumeration members. For example, finding all the aliases:: - - >>> [name for name, member in Shape.__members__.items() if member.name != name] - ['alias_for_square'] - -Comparisons ------------ - -Enumeration members are compared by identity:: - - >>> Color.red is Color.red - True - >>> Color.red is Color.blue - False - >>> Color.red is not Color.blue - True - -Ordered comparisons between enumeration values are *not* supported. Enum -members are not integers (but see `IntEnum`_ below):: - - >>> Color.red < Color.blue - Traceback (most recent call last): - File "", line 1, in - TypeError: unorderable types: Color() < Color() - -.. warning:: - - In Python 2 *everything* is ordered, even though the ordering may not - make sense. If you want your enumerations to have a sensible ordering - check out the `OrderedEnum`_ recipe below. - - -Equality comparisons are defined though:: - - >>> Color.blue == Color.red - False - >>> Color.blue != Color.red - True - >>> Color.blue == Color.blue - True - -Comparisons against non-enumeration values will always compare not equal -(again, ``IntEnum`` was explicitly designed to behave differently, see -below):: - - >>> Color.blue == 2 - False - - -Allowed members and attributes of enumerations ----------------------------------------------- - -The examples above use integers for enumeration values. Using integers is -short and handy (and provided by default by the `Functional API`_), but not -strictly enforced. In the vast majority of use-cases, one doesn't care what -the actual value of an enumeration is. But if the value *is* important, -enumerations can have arbitrary values. - -Enumerations are Python classes, and can have methods and special methods as -usual. If we have this enumeration:: - - >>> class Mood(Enum): - ... funky = 1 - ... happy = 3 - ... - ... def describe(self): - ... # self is the member here - ... return self.name, self.value - ... - ... def __str__(self): - ... return 'my custom str! {0}'.format(self.value) - ... - ... @classmethod - ... def favorite_mood(cls): - ... # cls here is the enumeration - ... return cls.happy - -Then:: - - >>> Mood.favorite_mood() - - >>> Mood.happy.describe() - ('happy', 3) - >>> str(Mood.funky) - 'my custom str! 1' - -The rules for what is allowed are as follows: _sunder_ names (starting and -ending with a single underscore) are reserved by enum and cannot be used; -all other attributes defined within an enumeration will become members of this -enumeration, with the exception of *__dunder__* names and descriptors (methods -are also descriptors). - -Note: - - If your enumeration defines ``__new__`` and/or ``__init__`` then - whatever value(s) were given to the enum member will be passed into - those methods. See `Planet`_ for an example. - - -Restricted subclassing of enumerations --------------------------------------- - -Subclassing an enumeration is allowed only if the enumeration does not define -any members. So this is forbidden:: - - >>> class MoreColor(Color): - ... pink = 17 - Traceback (most recent call last): - ... - TypeError: Cannot extend enumerations - -But this is allowed:: - - >>> class Foo(Enum): - ... def some_behavior(self): - ... pass - ... - >>> class Bar(Foo): - ... happy = 1 - ... sad = 2 - ... - -Allowing subclassing of enums that define members would lead to a violation of -some important invariants of types and instances. On the other hand, it makes -sense to allow sharing some common behavior between a group of enumerations. -(See `OrderedEnum`_ for an example.) - - -Pickling --------- - -Enumerations can be pickled and unpickled:: - - >>> from enum.test_enum import Fruit - >>> from pickle import dumps, loads - >>> Fruit.tomato is loads(dumps(Fruit.tomato, 2)) - True - -The usual restrictions for pickling apply: picklable enums must be defined in -the top level of a module, since unpickling requires them to be importable -from that module. - -Note: - - With pickle protocol version 4 (introduced in Python 3.4) it is possible - to easily pickle enums nested in other classes. - - - -Functional API --------------- - -The ``Enum`` class is callable, providing the following functional API:: - - >>> Animal = Enum('Animal', 'ant bee cat dog') - >>> Animal - - >>> Animal.ant - - >>> Animal.ant.value - 1 - >>> list(Animal) - [, , , ] - -The semantics of this API resemble ``namedtuple``. The first argument -of the call to ``Enum`` is the name of the enumeration. - -The second argument is the *source* of enumeration member names. It can be a -whitespace-separated string of names, a sequence of names, a sequence of -2-tuples with key/value pairs, or a mapping (e.g. dictionary) of names to -values. The last two options enable assigning arbitrary values to -enumerations; the others auto-assign increasing integers starting with 1. A -new class derived from ``Enum`` is returned. In other words, the above -assignment to ``Animal`` is equivalent to:: - - >>> class Animals(Enum): - ... ant = 1 - ... bee = 2 - ... cat = 3 - ... dog = 4 - -Pickling enums created with the functional API can be tricky as frame stack -implementation details are used to try and figure out which module the -enumeration is being created in (e.g. it will fail if you use a utility -function in separate module, and also may not work on IronPython or Jython). -The solution is to specify the module name explicitly as follows:: - - >>> Animals = Enum('Animals', 'ant bee cat dog', module=__name__) - -Derived Enumerations --------------------- - -IntEnum -^^^^^^^ - -A variation of ``Enum`` is provided which is also a subclass of -``int``. Members of an ``IntEnum`` can be compared to integers; -by extension, integer enumerations of different types can also be compared -to each other:: - - >>> from enum import IntEnum - >>> class Shape(IntEnum): - ... circle = 1 - ... square = 2 - ... - >>> class Request(IntEnum): - ... post = 1 - ... get = 2 - ... - >>> Shape == 1 - False - >>> Shape.circle == 1 - True - >>> Shape.circle == Request.post - True - -However, they still can't be compared to standard ``Enum`` enumerations:: - - >>> class Shape(IntEnum): - ... circle = 1 - ... square = 2 - ... - >>> class Color(Enum): - ... red = 1 - ... green = 2 - ... - >>> Shape.circle == Color.red - False - -``IntEnum`` values behave like integers in other ways you'd expect:: - - >>> int(Shape.circle) - 1 - >>> ['a', 'b', 'c'][Shape.circle] - 'b' - >>> [i for i in range(Shape.square)] - [0, 1] - -For the vast majority of code, ``Enum`` is strongly recommended, -since ``IntEnum`` breaks some semantic promises of an enumeration (by -being comparable to integers, and thus by transitivity to other -unrelated enumerations). It should be used only in special cases where -there's no other choice; for example, when integer constants are -replaced with enumerations and backwards compatibility is required with code -that still expects integers. - - -Others -^^^^^^ - -While ``IntEnum`` is part of the ``enum`` module, it would be very -simple to implement independently:: - - class IntEnum(int, Enum): - pass - -This demonstrates how similar derived enumerations can be defined; for example -a ``StrEnum`` that mixes in ``str`` instead of ``int``. - -Some rules: - -1. When subclassing ``Enum``, mix-in types must appear before - ``Enum`` itself in the sequence of bases, as in the ``IntEnum`` - example above. -2. While ``Enum`` can have members of any type, once you mix in an - additional type, all the members must have values of that type, e.g. - ``int`` above. This restriction does not apply to mix-ins which only - add methods and don't specify another data type such as ``int`` or - ``str``. -3. When another data type is mixed in, the ``value`` attribute is *not the - same* as the enum member itself, although it is equivalant and will compare - equal. -4. %-style formatting: ``%s`` and ``%r`` call ``Enum``'s ``__str__`` and - ``__repr__`` respectively; other codes (such as ``%i`` or ``%h`` for - IntEnum) treat the enum member as its mixed-in type. - - Note: Prior to Python 3.4 there is a bug in ``str``'s %-formatting: ``int`` - subclasses are printed as strings and not numbers when the ``%d``, ``%i``, - or ``%u`` codes are used. -5. ``str.__format__`` (or ``format``) will use the mixed-in - type's ``__format__``. If the ``Enum``'s ``str`` or - ``repr`` is desired use the ``!s`` or ``!r`` ``str`` format codes. - - -Decorators ----------- - -unique -^^^^^^ - -A ``class`` decorator specifically for enumerations. It searches an -enumeration's ``__members__`` gathering any aliases it finds; if any are -found ``ValueError`` is raised with the details:: - - >>> @unique - ... class NoDupes(Enum): - ... first = 'one' - ... second = 'two' - ... third = 'two' - Traceback (most recent call last): - ... - ValueError: duplicate names found in : third -> second - - -Interesting examples --------------------- - -While ``Enum`` and ``IntEnum`` are expected to cover the majority of -use-cases, they cannot cover them all. Here are recipes for some different -types of enumerations that can be used directly, or as examples for creating -one's own. - - -AutoNumber -^^^^^^^^^^ - -Avoids having to specify the value for each enumeration member:: - - >>> class AutoNumber(Enum): - ... def __new__(cls): - ... value = len(cls.__members__) + 1 - ... obj = object.__new__(cls) - ... obj._value_ = value - ... return obj - ... - >>> class Color(AutoNumber): - ... __order__ = "red green blue" # only needed in 2.x - ... red = () - ... green = () - ... blue = () - ... - >>> Color.green.value == 2 - True - -Note: - - The `__new__` method, if defined, is used during creation of the Enum - members; it is then replaced by Enum's `__new__` which is used after - class creation for lookup of existing members. Due to the way Enums are - supposed to behave, there is no way to customize Enum's `__new__`. - - -UniqueEnum -^^^^^^^^^^ - -Raises an error if a duplicate member name is found instead of creating an -alias:: - - >>> class UniqueEnum(Enum): - ... def __init__(self, *args): - ... cls = self.__class__ - ... if any(self.value == e.value for e in cls): - ... a = self.name - ... e = cls(self.value).name - ... raise ValueError( - ... "aliases not allowed in UniqueEnum: %r --> %r" - ... % (a, e)) - ... - >>> class Color(UniqueEnum): - ... red = 1 - ... green = 2 - ... blue = 3 - ... grene = 2 - Traceback (most recent call last): - ... - ValueError: aliases not allowed in UniqueEnum: 'grene' --> 'green' - - -OrderedEnum -^^^^^^^^^^^ - -An ordered enumeration that is not based on ``IntEnum`` and so maintains -the normal ``Enum`` invariants (such as not being comparable to other -enumerations):: - - >>> class OrderedEnum(Enum): - ... def __ge__(self, other): - ... if self.__class__ is other.__class__: - ... return self._value_ >= other._value_ - ... return NotImplemented - ... def __gt__(self, other): - ... if self.__class__ is other.__class__: - ... return self._value_ > other._value_ - ... return NotImplemented - ... def __le__(self, other): - ... if self.__class__ is other.__class__: - ... return self._value_ <= other._value_ - ... return NotImplemented - ... def __lt__(self, other): - ... if self.__class__ is other.__class__: - ... return self._value_ < other._value_ - ... return NotImplemented - ... - >>> class Grade(OrderedEnum): - ... __ordered__ = 'A B C D F' - ... A = 5 - ... B = 4 - ... C = 3 - ... D = 2 - ... F = 1 - ... - >>> Grade.C < Grade.A - True - - -Planet -^^^^^^ - -If ``__new__`` or ``__init__`` is defined the value of the enum member -will be passed to those methods:: - - >>> class Planet(Enum): - ... MERCURY = (3.303e+23, 2.4397e6) - ... VENUS = (4.869e+24, 6.0518e6) - ... EARTH = (5.976e+24, 6.37814e6) - ... MARS = (6.421e+23, 3.3972e6) - ... JUPITER = (1.9e+27, 7.1492e7) - ... SATURN = (5.688e+26, 6.0268e7) - ... URANUS = (8.686e+25, 2.5559e7) - ... NEPTUNE = (1.024e+26, 2.4746e7) - ... def __init__(self, mass, radius): - ... self.mass = mass # in kilograms - ... self.radius = radius # in meters - ... @property - ... def surface_gravity(self): - ... # universal gravitational constant (m3 kg-1 s-2) - ... G = 6.67300E-11 - ... return G * self.mass / (self.radius * self.radius) - ... - >>> Planet.EARTH.value - (5.976e+24, 6378140.0) - >>> Planet.EARTH.surface_gravity - 9.802652743337129 - - -How are Enums different? ------------------------- - -Enums have a custom metaclass that affects many aspects of both derived Enum -classes and their instances (members). - - -Enum Classes -^^^^^^^^^^^^ - -The ``EnumMeta`` metaclass is responsible for providing the -``__contains__``, ``__dir__``, ``__iter__`` and other methods that -allow one to do things with an ``Enum`` class that fail on a typical -class, such as ``list(Color)`` or ``some_var in Color``. ``EnumMeta`` is -responsible for ensuring that various other methods on the final ``Enum`` -class are correct (such as ``__new__``, ``__getnewargs__``, -``__str__`` and ``__repr__``). - -.. note:: - - ``__dir__`` is not changed in the Python 2 line as it messes up some - of the decorators included in the stdlib. - - -Enum Members (aka instances) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The most interesting thing about Enum members is that they are singletons. -``EnumMeta`` creates them all while it is creating the ``Enum`` -class itself, and then puts a custom ``__new__`` in place to ensure -that no new ones are ever instantiated by returning only the existing -member instances. - - -Finer Points -^^^^^^^^^^^^ - -``Enum`` members are instances of an ``Enum`` class, and even though they -are accessible as `EnumClass.member1.member2`, they should not be -accessed directly from the member as that lookup may fail or, worse, -return something besides the ``Enum`` member you were looking for -(changed in version 1.1.1):: - - >>> class FieldTypes(Enum): - ... name = 1 - ... value = 2 - ... size = 3 - ... - >>> FieldTypes.value.size - - >>> FieldTypes.size.value - 3 - -The ``__members__`` attribute is only available on the class. - -In Python 3.x ``__members__`` is always an ``OrderedDict``, with the order being -the definition order. In Python 2.7 ``__members__`` is an ``OrderedDict`` if -``__order__`` was specified, and a plain ``dict`` otherwise. In all other Python -2.x versions ``__members__`` is a plain ``dict`` even if ``__order__`` was specified -as the ``OrderedDict`` type didn't exist yet. - -If you give your ``Enum`` subclass extra methods, like the `Planet`_ -class above, those methods will show up in a `dir` of the member, -but not of the class:: - - >>> dir(Planet) - ['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', - 'VENUS', '__class__', '__doc__', '__members__', '__module__'] - >>> dir(Planet.EARTH) - ['__class__', '__doc__', '__module__', 'name', 'surface_gravity', 'value'] - -A ``__new__`` method will only be used for the creation of the -``Enum`` members -- after that it is replaced. This means if you wish to -change how ``Enum`` members are looked up you either have to write a -helper function or a ``classmethod``. diff --git a/modules/enum/enum/test.py b/modules/enum/enum/test.py deleted file mode 100644 index d9edfaee4..000000000 --- a/modules/enum/enum/test.py +++ /dev/null @@ -1,1820 +0,0 @@ -from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL -import sys -import unittest -pyver = float('%s.%s' % sys.version_info[:2]) -if pyver < 2.5: - sys.path.insert(0, '.') -import enum -from enum import Enum, IntEnum, unique, EnumMeta - -if pyver < 2.6: - from __builtin__ import enumerate as bltin_enumerate - def enumerate(thing, start=0): - result = [] - for i, item in bltin_enumerate(thing): - i = i + start - result.append((i, item)) - return result - -try: - any -except NameError: - def any(iterable): - for element in iterable: - if element: - return True - return False - -try: - unicode -except NameError: - unicode = str - -try: - from collections import OrderedDict -except ImportError: - OrderedDict = None - -# for pickle tests -try: - class Stooges(Enum): - LARRY = 1 - CURLY = 2 - MOE = 3 -except Exception: - Stooges = sys.exc_info()[1] - -try: - class IntStooges(int, Enum): - LARRY = 1 - CURLY = 2 - MOE = 3 -except Exception: - IntStooges = sys.exc_info()[1] - -try: - class FloatStooges(float, Enum): - LARRY = 1.39 - CURLY = 2.72 - MOE = 3.142596 -except Exception: - FloatStooges = sys.exc_info()[1] - -# for pickle test and subclass tests -try: - class StrEnum(str, Enum): - 'accepts only string values' - class Name(StrEnum): - BDFL = 'Guido van Rossum' - FLUFL = 'Barry Warsaw' -except Exception: - Name = sys.exc_info()[1] - -try: - Question = Enum('Question', 'who what when where why', module=__name__) -except Exception: - Question = sys.exc_info()[1] - -try: - Answer = Enum('Answer', 'him this then there because') -except Exception: - Answer = sys.exc_info()[1] - -try: - Theory = Enum('Theory', 'rule law supposition', qualname='spanish_inquisition') -except Exception: - Theory = sys.exc_info()[1] - -# for doctests -try: - class Fruit(Enum): - tomato = 1 - banana = 2 - cherry = 3 -except Exception: - pass - -def test_pickle_dump_load(assertion, source, target=None, - protocol=(0, HIGHEST_PROTOCOL)): - start, stop = protocol - failures = [] - for protocol in range(start, stop+1): - try: - if target is None: - assertion(loads(dumps(source, protocol=protocol)) is source) - else: - assertion(loads(dumps(source, protocol=protocol)), target) - except Exception: - exc, tb = sys.exc_info()[1:] - failures.append('%2d: %s' %(protocol, exc)) - if failures: - raise ValueError('Failed with protocols: %s' % ', '.join(failures)) - -def test_pickle_exception(assertion, exception, obj, - protocol=(0, HIGHEST_PROTOCOL)): - start, stop = protocol - failures = [] - for protocol in range(start, stop+1): - try: - assertion(exception, dumps, obj, protocol=protocol) - except Exception: - exc = sys.exc_info()[1] - failures.append('%d: %s %s' % (protocol, exc.__class__.__name__, exc)) - if failures: - raise ValueError('Failed with protocols: %s' % ', '.join(failures)) - - -class TestHelpers(unittest.TestCase): - # _is_descriptor, _is_sunder, _is_dunder - - def test_is_descriptor(self): - class foo: - pass - for attr in ('__get__','__set__','__delete__'): - obj = foo() - self.assertFalse(enum._is_descriptor(obj)) - setattr(obj, attr, 1) - self.assertTrue(enum._is_descriptor(obj)) - - def test_is_sunder(self): - for s in ('_a_', '_aa_'): - self.assertTrue(enum._is_sunder(s)) - - for s in ('a', 'a_', '_a', '__a', 'a__', '__a__', '_a__', '__a_', '_', - '__', '___', '____', '_____',): - self.assertFalse(enum._is_sunder(s)) - - def test_is_dunder(self): - for s in ('__a__', '__aa__'): - self.assertTrue(enum._is_dunder(s)) - for s in ('a', 'a_', '_a', '__a', 'a__', '_a_', '_a__', '__a_', '_', - '__', '___', '____', '_____',): - self.assertFalse(enum._is_dunder(s)) - - -class TestEnum(unittest.TestCase): - def setUp(self): - class Season(Enum): - SPRING = 1 - SUMMER = 2 - AUTUMN = 3 - WINTER = 4 - self.Season = Season - - class Konstants(float, Enum): - E = 2.7182818 - PI = 3.1415926 - TAU = 2 * PI - self.Konstants = Konstants - - class Grades(IntEnum): - A = 5 - B = 4 - C = 3 - D = 2 - F = 0 - self.Grades = Grades - - class Directional(str, Enum): - EAST = 'east' - WEST = 'west' - NORTH = 'north' - SOUTH = 'south' - self.Directional = Directional - - from datetime import date - class Holiday(date, Enum): - NEW_YEAR = 2013, 1, 1 - IDES_OF_MARCH = 2013, 3, 15 - self.Holiday = Holiday - - if pyver >= 3.0: # do not specify custom `dir` on previous versions - def test_dir_on_class(self): - Season = self.Season - self.assertEqual( - set(dir(Season)), - set(['__class__', '__doc__', '__members__', '__module__', - 'SPRING', 'SUMMER', 'AUTUMN', 'WINTER']), - ) - - def test_dir_on_item(self): - Season = self.Season - self.assertEqual( - set(dir(Season.WINTER)), - set(['__class__', '__doc__', '__module__', 'name', 'value']), - ) - - def test_dir_with_added_behavior(self): - class Test(Enum): - this = 'that' - these = 'those' - def wowser(self): - return ("Wowser! I'm %s!" % self.name) - self.assertEqual( - set(dir(Test)), - set(['__class__', '__doc__', '__members__', '__module__', 'this', 'these']), - ) - self.assertEqual( - set(dir(Test.this)), - set(['__class__', '__doc__', '__module__', 'name', 'value', 'wowser']), - ) - - def test_dir_on_sub_with_behavior_on_super(self): - # see issue22506 - class SuperEnum(Enum): - def invisible(self): - return "did you see me?" - class SubEnum(SuperEnum): - sample = 5 - self.assertEqual( - set(dir(SubEnum.sample)), - set(['__class__', '__doc__', '__module__', 'name', 'value', 'invisible']), - ) - - if pyver >= 2.7: # OrderedDict first available here - def test_members_is_ordereddict_if_ordered(self): - class Ordered(Enum): - __order__ = 'first second third' - first = 'bippity' - second = 'boppity' - third = 'boo' - self.assertTrue(type(Ordered.__members__) is OrderedDict) - - def test_members_is_ordereddict_if_not_ordered(self): - class Unordered(Enum): - this = 'that' - these = 'those' - self.assertTrue(type(Unordered.__members__) is OrderedDict) - - if pyver >= 3.0: # all objects are ordered in Python 2.x - def test_members_is_always_ordered(self): - class AlwaysOrdered(Enum): - first = 1 - second = 2 - third = 3 - self.assertTrue(type(AlwaysOrdered.__members__) is OrderedDict) - - def test_comparisons(self): - def bad_compare(): - Season.SPRING > 4 - Season = self.Season - self.assertNotEqual(Season.SPRING, 1) - self.assertRaises(TypeError, bad_compare) - - class Part(Enum): - SPRING = 1 - CLIP = 2 - BARREL = 3 - - self.assertNotEqual(Season.SPRING, Part.SPRING) - def bad_compare(): - Season.SPRING < Part.CLIP - self.assertRaises(TypeError, bad_compare) - - def test_enum_in_enum_out(self): - Season = self.Season - self.assertTrue(Season(Season.WINTER) is Season.WINTER) - - def test_enum_value(self): - Season = self.Season - self.assertEqual(Season.SPRING.value, 1) - - def test_intenum_value(self): - self.assertEqual(IntStooges.CURLY.value, 2) - - def test_enum(self): - Season = self.Season - lst = list(Season) - self.assertEqual(len(lst), len(Season)) - self.assertEqual(len(Season), 4, Season) - self.assertEqual( - [Season.SPRING, Season.SUMMER, Season.AUTUMN, Season.WINTER], lst) - - for i, season in enumerate('SPRING SUMMER AUTUMN WINTER'.split()): - i += 1 - e = Season(i) - self.assertEqual(e, getattr(Season, season)) - self.assertEqual(e.value, i) - self.assertNotEqual(e, i) - self.assertEqual(e.name, season) - self.assertTrue(e in Season) - self.assertTrue(type(e) is Season) - self.assertTrue(isinstance(e, Season)) - self.assertEqual(str(e), 'Season.' + season) - self.assertEqual( - repr(e), - '' % (season, i), - ) - - def test_value_name(self): - Season = self.Season - self.assertEqual(Season.SPRING.name, 'SPRING') - self.assertEqual(Season.SPRING.value, 1) - def set_name(obj, new_value): - obj.name = new_value - def set_value(obj, new_value): - obj.value = new_value - self.assertRaises(AttributeError, set_name, Season.SPRING, 'invierno', ) - self.assertRaises(AttributeError, set_value, Season.SPRING, 2) - - def test_attribute_deletion(self): - class Season(Enum): - SPRING = 1 - SUMMER = 2 - AUTUMN = 3 - WINTER = 4 - - def spam(cls): - pass - - self.assertTrue(hasattr(Season, 'spam')) - del Season.spam - self.assertFalse(hasattr(Season, 'spam')) - - self.assertRaises(AttributeError, delattr, Season, 'SPRING') - self.assertRaises(AttributeError, delattr, Season, 'DRY') - self.assertRaises(AttributeError, delattr, Season.SPRING, 'name') - - def test_bool_of_class(self): - class Empty(Enum): - pass - self.assertTrue(bool(Empty)) - - def test_bool_of_member(self): - class Count(Enum): - zero = 0 - one = 1 - two = 2 - for member in Count: - self.assertTrue(bool(member)) - - def test_invalid_names(self): - def create_bad_class_1(): - class Wrong(Enum): - mro = 9 - def create_bad_class_2(): - class Wrong(Enum): - _reserved_ = 3 - self.assertRaises(ValueError, create_bad_class_1) - self.assertRaises(ValueError, create_bad_class_2) - - def test_contains(self): - Season = self.Season - self.assertTrue(Season.AUTUMN in Season) - self.assertTrue(3 not in Season) - - val = Season(3) - self.assertTrue(val in Season) - - class OtherEnum(Enum): - one = 1; two = 2 - self.assertTrue(OtherEnum.two not in Season) - - if pyver >= 2.6: # when `format` came into being - - def test_format_enum(self): - Season = self.Season - self.assertEqual('{0}'.format(Season.SPRING), - '{0}'.format(str(Season.SPRING))) - self.assertEqual( '{0:}'.format(Season.SPRING), - '{0:}'.format(str(Season.SPRING))) - self.assertEqual('{0:20}'.format(Season.SPRING), - '{0:20}'.format(str(Season.SPRING))) - self.assertEqual('{0:^20}'.format(Season.SPRING), - '{0:^20}'.format(str(Season.SPRING))) - self.assertEqual('{0:>20}'.format(Season.SPRING), - '{0:>20}'.format(str(Season.SPRING))) - self.assertEqual('{0:<20}'.format(Season.SPRING), - '{0:<20}'.format(str(Season.SPRING))) - - def test_format_enum_custom(self): - class TestFloat(float, Enum): - one = 1.0 - two = 2.0 - def __format__(self, spec): - return 'TestFloat success!' - self.assertEqual('{0}'.format(TestFloat.one), 'TestFloat success!') - - def assertFormatIsValue(self, spec, member): - self.assertEqual(spec.format(member), spec.format(member.value)) - - def test_format_enum_date(self): - Holiday = self.Holiday - self.assertFormatIsValue('{0}', Holiday.IDES_OF_MARCH) - self.assertFormatIsValue('{0:}', Holiday.IDES_OF_MARCH) - self.assertFormatIsValue('{0:20}', Holiday.IDES_OF_MARCH) - self.assertFormatIsValue('{0:^20}', Holiday.IDES_OF_MARCH) - self.assertFormatIsValue('{0:>20}', Holiday.IDES_OF_MARCH) - self.assertFormatIsValue('{0:<20}', Holiday.IDES_OF_MARCH) - self.assertFormatIsValue('{0:%Y %m}', Holiday.IDES_OF_MARCH) - self.assertFormatIsValue('{0:%Y %m %M:00}', Holiday.IDES_OF_MARCH) - - def test_format_enum_float(self): - Konstants = self.Konstants - self.assertFormatIsValue('{0}', Konstants.TAU) - self.assertFormatIsValue('{0:}', Konstants.TAU) - self.assertFormatIsValue('{0:20}', Konstants.TAU) - self.assertFormatIsValue('{0:^20}', Konstants.TAU) - self.assertFormatIsValue('{0:>20}', Konstants.TAU) - self.assertFormatIsValue('{0:<20}', Konstants.TAU) - self.assertFormatIsValue('{0:n}', Konstants.TAU) - self.assertFormatIsValue('{0:5.2}', Konstants.TAU) - self.assertFormatIsValue('{0:f}', Konstants.TAU) - - def test_format_enum_int(self): - Grades = self.Grades - self.assertFormatIsValue('{0}', Grades.C) - self.assertFormatIsValue('{0:}', Grades.C) - self.assertFormatIsValue('{0:20}', Grades.C) - self.assertFormatIsValue('{0:^20}', Grades.C) - self.assertFormatIsValue('{0:>20}', Grades.C) - self.assertFormatIsValue('{0:<20}', Grades.C) - self.assertFormatIsValue('{0:+}', Grades.C) - self.assertFormatIsValue('{0:08X}', Grades.C) - self.assertFormatIsValue('{0:b}', Grades.C) - - def test_format_enum_str(self): - Directional = self.Directional - self.assertFormatIsValue('{0}', Directional.WEST) - self.assertFormatIsValue('{0:}', Directional.WEST) - self.assertFormatIsValue('{0:20}', Directional.WEST) - self.assertFormatIsValue('{0:^20}', Directional.WEST) - self.assertFormatIsValue('{0:>20}', Directional.WEST) - self.assertFormatIsValue('{0:<20}', Directional.WEST) - - def test_hash(self): - Season = self.Season - dates = {} - dates[Season.WINTER] = '1225' - dates[Season.SPRING] = '0315' - dates[Season.SUMMER] = '0704' - dates[Season.AUTUMN] = '1031' - self.assertEqual(dates[Season.AUTUMN], '1031') - - def test_enum_duplicates(self): - _order_ = "SPRING SUMMER AUTUMN WINTER" - class Season(Enum): - SPRING = 1 - SUMMER = 2 - AUTUMN = FALL = 3 - WINTER = 4 - ANOTHER_SPRING = 1 - lst = list(Season) - self.assertEqual( - lst, - [Season.SPRING, Season.SUMMER, - Season.AUTUMN, Season.WINTER, - ]) - self.assertTrue(Season.FALL is Season.AUTUMN) - self.assertEqual(Season.FALL.value, 3) - self.assertEqual(Season.AUTUMN.value, 3) - self.assertTrue(Season(3) is Season.AUTUMN) - self.assertTrue(Season(1) is Season.SPRING) - self.assertEqual(Season.FALL.name, 'AUTUMN') - self.assertEqual( - set([k for k,v in Season.__members__.items() if v.name != k]), - set(['FALL', 'ANOTHER_SPRING']), - ) - - if pyver >= 3.0: - cls = vars() - result = {'Enum':Enum} - exec("""def test_duplicate_name(self): - with self.assertRaises(TypeError): - class Color(Enum): - red = 1 - green = 2 - blue = 3 - red = 4 - - with self.assertRaises(TypeError): - class Color(Enum): - red = 1 - green = 2 - blue = 3 - def red(self): - return 'red' - - with self.assertRaises(TypeError): - class Color(Enum): - @property - - def red(self): - return 'redder' - red = 1 - green = 2 - blue = 3""", - result) - cls['test_duplicate_name'] = result['test_duplicate_name'] - - def test_enum_with_value_name(self): - class Huh(Enum): - name = 1 - value = 2 - self.assertEqual( - list(Huh), - [Huh.name, Huh.value], - ) - self.assertTrue(type(Huh.name) is Huh) - self.assertEqual(Huh.name.name, 'name') - self.assertEqual(Huh.name.value, 1) - - def test_intenum_from_scratch(self): - class phy(int, Enum): - pi = 3 - tau = 2 * pi - self.assertTrue(phy.pi < phy.tau) - - def test_intenum_inherited(self): - class IntEnum(int, Enum): - pass - class phy(IntEnum): - pi = 3 - tau = 2 * pi - self.assertTrue(phy.pi < phy.tau) - - def test_floatenum_from_scratch(self): - class phy(float, Enum): - pi = 3.1415926 - tau = 2 * pi - self.assertTrue(phy.pi < phy.tau) - - def test_floatenum_inherited(self): - class FloatEnum(float, Enum): - pass - class phy(FloatEnum): - pi = 3.1415926 - tau = 2 * pi - self.assertTrue(phy.pi < phy.tau) - - def test_strenum_from_scratch(self): - class phy(str, Enum): - pi = 'Pi' - tau = 'Tau' - self.assertTrue(phy.pi < phy.tau) - - def test_strenum_inherited(self): - class StrEnum(str, Enum): - pass - class phy(StrEnum): - pi = 'Pi' - tau = 'Tau' - self.assertTrue(phy.pi < phy.tau) - - def test_intenum(self): - class WeekDay(IntEnum): - SUNDAY = 1 - MONDAY = 2 - TUESDAY = 3 - WEDNESDAY = 4 - THURSDAY = 5 - FRIDAY = 6 - SATURDAY = 7 - - self.assertEqual(['a', 'b', 'c'][WeekDay.MONDAY], 'c') - self.assertEqual([i for i in range(WeekDay.TUESDAY)], [0, 1, 2]) - - lst = list(WeekDay) - self.assertEqual(len(lst), len(WeekDay)) - self.assertEqual(len(WeekDay), 7) - target = 'SUNDAY MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY SATURDAY' - target = target.split() - for i, weekday in enumerate(target): - i += 1 - e = WeekDay(i) - self.assertEqual(e, i) - self.assertEqual(int(e), i) - self.assertEqual(e.name, weekday) - self.assertTrue(e in WeekDay) - self.assertEqual(lst.index(e)+1, i) - self.assertTrue(0 < e < 8) - self.assertTrue(type(e) is WeekDay) - self.assertTrue(isinstance(e, int)) - self.assertTrue(isinstance(e, Enum)) - - def test_intenum_duplicates(self): - class WeekDay(IntEnum): - __order__ = 'SUNDAY MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY SATURDAY' - SUNDAY = 1 - MONDAY = 2 - TUESDAY = TEUSDAY = 3 - WEDNESDAY = 4 - THURSDAY = 5 - FRIDAY = 6 - SATURDAY = 7 - self.assertTrue(WeekDay.TEUSDAY is WeekDay.TUESDAY) - self.assertEqual(WeekDay(3).name, 'TUESDAY') - self.assertEqual([k for k,v in WeekDay.__members__.items() - if v.name != k], ['TEUSDAY', ]) - - def test_pickle_enum(self): - if isinstance(Stooges, Exception): - raise Stooges - test_pickle_dump_load(self.assertTrue, Stooges.CURLY) - test_pickle_dump_load(self.assertTrue, Stooges) - - def test_pickle_int(self): - if isinstance(IntStooges, Exception): - raise IntStooges - test_pickle_dump_load(self.assertTrue, IntStooges.CURLY) - test_pickle_dump_load(self.assertTrue, IntStooges) - - def test_pickle_float(self): - if isinstance(FloatStooges, Exception): - raise FloatStooges - test_pickle_dump_load(self.assertTrue, FloatStooges.CURLY) - test_pickle_dump_load(self.assertTrue, FloatStooges) - - def test_pickle_enum_function(self): - if isinstance(Answer, Exception): - raise Answer - test_pickle_dump_load(self.assertTrue, Answer.him) - test_pickle_dump_load(self.assertTrue, Answer) - - def test_pickle_enum_function_with_module(self): - if isinstance(Question, Exception): - raise Question - test_pickle_dump_load(self.assertTrue, Question.who) - test_pickle_dump_load(self.assertTrue, Question) - - if pyver == 3.4: - def test_class_nested_enum_and_pickle_protocol_four(self): - # would normally just have this directly in the class namespace - class NestedEnum(Enum): - twigs = 'common' - shiny = 'rare' - - self.__class__.NestedEnum = NestedEnum - self.NestedEnum.__qualname__ = '%s.NestedEnum' % self.__class__.__name__ - test_pickle_exception( - self.assertRaises, PicklingError, self.NestedEnum.twigs, - protocol=(0, 3)) - test_pickle_dump_load(self.assertTrue, self.NestedEnum.twigs, - protocol=(4, HIGHEST_PROTOCOL)) - - elif pyver == 3.5: - def test_class_nested_enum_and_pickle_protocol_four(self): - # would normally just have this directly in the class namespace - class NestedEnum(Enum): - twigs = 'common' - shiny = 'rare' - - self.__class__.NestedEnum = NestedEnum - self.NestedEnum.__qualname__ = '%s.NestedEnum' % self.__class__.__name__ - test_pickle_dump_load(self.assertTrue, self.NestedEnum.twigs, - protocol=(0, HIGHEST_PROTOCOL)) - - def test_exploding_pickle(self): - BadPickle = Enum('BadPickle', 'dill sweet bread-n-butter') - enum._make_class_unpicklable(BadPickle) - globals()['BadPickle'] = BadPickle - test_pickle_exception(self.assertRaises, TypeError, BadPickle.dill) - test_pickle_exception(self.assertRaises, PicklingError, BadPickle) - - def test_string_enum(self): - class SkillLevel(str, Enum): - master = 'what is the sound of one hand clapping?' - journeyman = 'why did the chicken cross the road?' - apprentice = 'knock, knock!' - self.assertEqual(SkillLevel.apprentice, 'knock, knock!') - - def test_getattr_getitem(self): - class Period(Enum): - morning = 1 - noon = 2 - evening = 3 - night = 4 - self.assertTrue(Period(2) is Period.noon) - self.assertTrue(getattr(Period, 'night') is Period.night) - self.assertTrue(Period['morning'] is Period.morning) - - def test_getattr_dunder(self): - Season = self.Season - self.assertTrue(getattr(Season, '__hash__')) - - def test_iteration_order(self): - class Season(Enum): - _order_ = 'SUMMER WINTER AUTUMN SPRING' - SUMMER = 2 - WINTER = 4 - AUTUMN = 3 - SPRING = 1 - self.assertEqual( - list(Season), - [Season.SUMMER, Season.WINTER, Season.AUTUMN, Season.SPRING], - ) - - def test_iteration_order_reversed(self): - self.assertEqual( - list(reversed(self.Season)), - [self.Season.WINTER, self.Season.AUTUMN, self.Season.SUMMER, - self.Season.SPRING] - ) - - def test_iteration_order_with_unorderable_values(self): - class Complex(Enum): - a = complex(7, 9) - b = complex(3.14, 2) - c = complex(1, -1) - d = complex(-77, 32) - self.assertEqual( - list(Complex), - [Complex.a, Complex.b, Complex.c, Complex.d], - ) - - def test_programatic_function_string(self): - SummerMonth = Enum('SummerMonth', 'june july august') - lst = list(SummerMonth) - self.assertEqual(len(lst), len(SummerMonth)) - self.assertEqual(len(SummerMonth), 3, SummerMonth) - self.assertEqual( - [SummerMonth.june, SummerMonth.july, SummerMonth.august], - lst, - ) - for i, month in enumerate('june july august'.split()): - i += 1 - e = SummerMonth(i) - self.assertEqual(int(e.value), i) - self.assertNotEqual(e, i) - self.assertEqual(e.name, month) - self.assertTrue(e in SummerMonth) - self.assertTrue(type(e) is SummerMonth) - - def test_programatic_function_string_with_start(self): - SummerMonth = Enum('SummerMonth', 'june july august', start=10) - lst = list(SummerMonth) - self.assertEqual(len(lst), len(SummerMonth)) - self.assertEqual(len(SummerMonth), 3, SummerMonth) - self.assertEqual( - [SummerMonth.june, SummerMonth.july, SummerMonth.august], - lst, - ) - for i, month in enumerate('june july august'.split(), 10): - e = SummerMonth(i) - self.assertEqual(int(e.value), i) - self.assertNotEqual(e, i) - self.assertEqual(e.name, month) - self.assertTrue(e in SummerMonth) - self.assertTrue(type(e) is SummerMonth) - - def test_programatic_function_string_list(self): - SummerMonth = Enum('SummerMonth', ['june', 'july', 'august']) - lst = list(SummerMonth) - self.assertEqual(len(lst), len(SummerMonth)) - self.assertEqual(len(SummerMonth), 3, SummerMonth) - self.assertEqual( - [SummerMonth.june, SummerMonth.july, SummerMonth.august], - lst, - ) - for i, month in enumerate('june july august'.split()): - i += 1 - e = SummerMonth(i) - self.assertEqual(int(e.value), i) - self.assertNotEqual(e, i) - self.assertEqual(e.name, month) - self.assertTrue(e in SummerMonth) - self.assertTrue(type(e) is SummerMonth) - - def test_programatic_function_string_list_with_start(self): - SummerMonth = Enum('SummerMonth', ['june', 'july', 'august'], start=20) - lst = list(SummerMonth) - self.assertEqual(len(lst), len(SummerMonth)) - self.assertEqual(len(SummerMonth), 3, SummerMonth) - self.assertEqual( - [SummerMonth.june, SummerMonth.july, SummerMonth.august], - lst, - ) - for i, month in enumerate('june july august'.split(), 20): - e = SummerMonth(i) - self.assertEqual(int(e.value), i) - self.assertNotEqual(e, i) - self.assertEqual(e.name, month) - self.assertTrue(e in SummerMonth) - self.assertTrue(type(e) is SummerMonth) - - def test_programatic_function_iterable(self): - SummerMonth = Enum( - 'SummerMonth', - (('june', 1), ('july', 2), ('august', 3)) - ) - lst = list(SummerMonth) - self.assertEqual(len(lst), len(SummerMonth)) - self.assertEqual(len(SummerMonth), 3, SummerMonth) - self.assertEqual( - [SummerMonth.june, SummerMonth.july, SummerMonth.august], - lst, - ) - for i, month in enumerate('june july august'.split()): - i += 1 - e = SummerMonth(i) - self.assertEqual(int(e.value), i) - self.assertNotEqual(e, i) - self.assertEqual(e.name, month) - self.assertTrue(e in SummerMonth) - self.assertTrue(type(e) is SummerMonth) - - def test_programatic_function_from_dict(self): - SummerMonth = Enum( - 'SummerMonth', - dict((('june', 1), ('july', 2), ('august', 3))) - ) - lst = list(SummerMonth) - self.assertEqual(len(lst), len(SummerMonth)) - self.assertEqual(len(SummerMonth), 3, SummerMonth) - if pyver < 3.0: - self.assertEqual( - [SummerMonth.june, SummerMonth.july, SummerMonth.august], - lst, - ) - for i, month in enumerate('june july august'.split()): - i += 1 - e = SummerMonth(i) - self.assertEqual(int(e.value), i) - self.assertNotEqual(e, i) - self.assertEqual(e.name, month) - self.assertTrue(e in SummerMonth) - self.assertTrue(type(e) is SummerMonth) - - def test_programatic_function_type(self): - SummerMonth = Enum('SummerMonth', 'june july august', type=int) - lst = list(SummerMonth) - self.assertEqual(len(lst), len(SummerMonth)) - self.assertEqual(len(SummerMonth), 3, SummerMonth) - self.assertEqual( - [SummerMonth.june, SummerMonth.july, SummerMonth.august], - lst, - ) - for i, month in enumerate('june july august'.split()): - i += 1 - e = SummerMonth(i) - self.assertEqual(e, i) - self.assertEqual(e.name, month) - self.assertTrue(e in SummerMonth) - self.assertTrue(type(e) is SummerMonth) - - def test_programatic_function_type_with_start(self): - SummerMonth = Enum('SummerMonth', 'june july august', type=int, start=30) - lst = list(SummerMonth) - self.assertEqual(len(lst), len(SummerMonth)) - self.assertEqual(len(SummerMonth), 3, SummerMonth) - self.assertEqual( - [SummerMonth.june, SummerMonth.july, SummerMonth.august], - lst, - ) - for i, month in enumerate('june july august'.split(), 30): - e = SummerMonth(i) - self.assertEqual(e, i) - self.assertEqual(e.name, month) - self.assertTrue(e in SummerMonth) - self.assertTrue(type(e) is SummerMonth) - - def test_programatic_function_type_from_subclass(self): - SummerMonth = IntEnum('SummerMonth', 'june july august') - lst = list(SummerMonth) - self.assertEqual(len(lst), len(SummerMonth)) - self.assertEqual(len(SummerMonth), 3, SummerMonth) - self.assertEqual( - [SummerMonth.june, SummerMonth.july, SummerMonth.august], - lst, - ) - for i, month in enumerate('june july august'.split()): - i += 1 - e = SummerMonth(i) - self.assertEqual(e, i) - self.assertEqual(e.name, month) - self.assertTrue(e in SummerMonth) - self.assertTrue(type(e) is SummerMonth) - - def test_programatic_function_type_from_subclass_with_start(self): - SummerMonth = IntEnum('SummerMonth', 'june july august', start=40) - lst = list(SummerMonth) - self.assertEqual(len(lst), len(SummerMonth)) - self.assertEqual(len(SummerMonth), 3, SummerMonth) - self.assertEqual( - [SummerMonth.june, SummerMonth.july, SummerMonth.august], - lst, - ) - for i, month in enumerate('june july august'.split(), 40): - e = SummerMonth(i) - self.assertEqual(e, i) - self.assertEqual(e.name, month) - self.assertTrue(e in SummerMonth) - self.assertTrue(type(e) is SummerMonth) - - def test_programatic_function_unicode(self): - SummerMonth = Enum('SummerMonth', unicode('june july august')) - lst = list(SummerMonth) - self.assertEqual(len(lst), len(SummerMonth)) - self.assertEqual(len(SummerMonth), 3, SummerMonth) - self.assertEqual( - [SummerMonth.june, SummerMonth.july, SummerMonth.august], - lst, - ) - for i, month in enumerate(unicode('june july august').split()): - i += 1 - e = SummerMonth(i) - self.assertEqual(int(e.value), i) - self.assertNotEqual(e, i) - self.assertEqual(e.name, month) - self.assertTrue(e in SummerMonth) - self.assertTrue(type(e) is SummerMonth) - - def test_programatic_function_unicode_list(self): - SummerMonth = Enum('SummerMonth', [unicode('june'), unicode('july'), unicode('august')]) - lst = list(SummerMonth) - self.assertEqual(len(lst), len(SummerMonth)) - self.assertEqual(len(SummerMonth), 3, SummerMonth) - self.assertEqual( - [SummerMonth.june, SummerMonth.july, SummerMonth.august], - lst, - ) - for i, month in enumerate(unicode('june july august').split()): - i += 1 - e = SummerMonth(i) - self.assertEqual(int(e.value), i) - self.assertNotEqual(e, i) - self.assertEqual(e.name, month) - self.assertTrue(e in SummerMonth) - self.assertTrue(type(e) is SummerMonth) - - def test_programatic_function_unicode_iterable(self): - SummerMonth = Enum( - 'SummerMonth', - ((unicode('june'), 1), (unicode('july'), 2), (unicode('august'), 3)) - ) - lst = list(SummerMonth) - self.assertEqual(len(lst), len(SummerMonth)) - self.assertEqual(len(SummerMonth), 3, SummerMonth) - self.assertEqual( - [SummerMonth.june, SummerMonth.july, SummerMonth.august], - lst, - ) - for i, month in enumerate(unicode('june july august').split()): - i += 1 - e = SummerMonth(i) - self.assertEqual(int(e.value), i) - self.assertNotEqual(e, i) - self.assertEqual(e.name, month) - self.assertTrue(e in SummerMonth) - self.assertTrue(type(e) is SummerMonth) - - def test_programatic_function_from_unicode_dict(self): - SummerMonth = Enum( - 'SummerMonth', - dict(((unicode('june'), 1), (unicode('july'), 2), (unicode('august'), 3))) - ) - lst = list(SummerMonth) - self.assertEqual(len(lst), len(SummerMonth)) - self.assertEqual(len(SummerMonth), 3, SummerMonth) - if pyver < 3.0: - self.assertEqual( - [SummerMonth.june, SummerMonth.july, SummerMonth.august], - lst, - ) - for i, month in enumerate(unicode('june july august').split()): - i += 1 - e = SummerMonth(i) - self.assertEqual(int(e.value), i) - self.assertNotEqual(e, i) - self.assertEqual(e.name, month) - self.assertTrue(e in SummerMonth) - self.assertTrue(type(e) is SummerMonth) - - def test_programatic_function_unicode_type(self): - SummerMonth = Enum('SummerMonth', unicode('june july august'), type=int) - lst = list(SummerMonth) - self.assertEqual(len(lst), len(SummerMonth)) - self.assertEqual(len(SummerMonth), 3, SummerMonth) - self.assertEqual( - [SummerMonth.june, SummerMonth.july, SummerMonth.august], - lst, - ) - for i, month in enumerate(unicode('june july august').split()): - i += 1 - e = SummerMonth(i) - self.assertEqual(e, i) - self.assertEqual(e.name, month) - self.assertTrue(e in SummerMonth) - self.assertTrue(type(e) is SummerMonth) - - def test_programatic_function_unicode_type_from_subclass(self): - SummerMonth = IntEnum('SummerMonth', unicode('june july august')) - lst = list(SummerMonth) - self.assertEqual(len(lst), len(SummerMonth)) - self.assertEqual(len(SummerMonth), 3, SummerMonth) - self.assertEqual( - [SummerMonth.june, SummerMonth.july, SummerMonth.august], - lst, - ) - for i, month in enumerate(unicode('june july august').split()): - i += 1 - e = SummerMonth(i) - self.assertEqual(e, i) - self.assertEqual(e.name, month) - self.assertTrue(e in SummerMonth) - self.assertTrue(type(e) is SummerMonth) - - def test_programmatic_function_unicode_class(self): - if pyver < 3.0: - class_names = unicode('SummerMonth'), 'S\xfcmm\xe9rM\xf6nth'.decode('latin1') - else: - class_names = 'SummerMonth', 'S\xfcmm\xe9rM\xf6nth' - for i, class_name in enumerate(class_names): - if pyver < 3.0 and i == 1: - self.assertRaises(TypeError, Enum, class_name, unicode('june july august')) - else: - SummerMonth = Enum(class_name, unicode('june july august')) - lst = list(SummerMonth) - self.assertEqual(len(lst), len(SummerMonth)) - self.assertEqual(len(SummerMonth), 3, SummerMonth) - self.assertEqual( - [SummerMonth.june, SummerMonth.july, SummerMonth.august], - lst, - ) - for i, month in enumerate(unicode('june july august').split()): - i += 1 - e = SummerMonth(i) - self.assertEqual(e.value, i) - self.assertEqual(e.name, month) - self.assertTrue(e in SummerMonth) - self.assertTrue(type(e) is SummerMonth) - - def test_subclassing(self): - if isinstance(Name, Exception): - raise Name - self.assertEqual(Name.BDFL, 'Guido van Rossum') - self.assertTrue(Name.BDFL, Name('Guido van Rossum')) - self.assertTrue(Name.BDFL is getattr(Name, 'BDFL')) - test_pickle_dump_load(self.assertTrue, Name.BDFL) - - def test_extending(self): - def bad_extension(): - class Color(Enum): - red = 1 - green = 2 - blue = 3 - class MoreColor(Color): - cyan = 4 - magenta = 5 - yellow = 6 - self.assertRaises(TypeError, bad_extension) - - def test_exclude_methods(self): - class whatever(Enum): - this = 'that' - these = 'those' - def really(self): - return 'no, not %s' % self.value - self.assertFalse(type(whatever.really) is whatever) - self.assertEqual(whatever.this.really(), 'no, not that') - - def test_wrong_inheritance_order(self): - def wrong_inherit(): - class Wrong(Enum, str): - NotHere = 'error before this point' - self.assertRaises(TypeError, wrong_inherit) - - def test_intenum_transitivity(self): - class number(IntEnum): - one = 1 - two = 2 - three = 3 - class numero(IntEnum): - uno = 1 - dos = 2 - tres = 3 - self.assertEqual(number.one, numero.uno) - self.assertEqual(number.two, numero.dos) - self.assertEqual(number.three, numero.tres) - - def test_introspection(self): - class Number(IntEnum): - one = 100 - two = 200 - self.assertTrue(Number.one._member_type_ is int) - self.assertTrue(Number._member_type_ is int) - class String(str, Enum): - yarn = 'soft' - rope = 'rough' - wire = 'hard' - self.assertTrue(String.yarn._member_type_ is str) - self.assertTrue(String._member_type_ is str) - class Plain(Enum): - vanilla = 'white' - one = 1 - self.assertTrue(Plain.vanilla._member_type_ is object) - self.assertTrue(Plain._member_type_ is object) - - def test_wrong_enum_in_call(self): - class Monochrome(Enum): - black = 0 - white = 1 - class Gender(Enum): - male = 0 - female = 1 - self.assertRaises(ValueError, Monochrome, Gender.male) - - def test_wrong_enum_in_mixed_call(self): - class Monochrome(IntEnum): - black = 0 - white = 1 - class Gender(Enum): - male = 0 - female = 1 - self.assertRaises(ValueError, Monochrome, Gender.male) - - def test_mixed_enum_in_call_1(self): - class Monochrome(IntEnum): - black = 0 - white = 1 - class Gender(IntEnum): - male = 0 - female = 1 - self.assertTrue(Monochrome(Gender.female) is Monochrome.white) - - def test_mixed_enum_in_call_2(self): - class Monochrome(Enum): - black = 0 - white = 1 - class Gender(IntEnum): - male = 0 - female = 1 - self.assertTrue(Monochrome(Gender.male) is Monochrome.black) - - def test_flufl_enum(self): - class Fluflnum(Enum): - def __int__(self): - return int(self.value) - class MailManOptions(Fluflnum): - option1 = 1 - option2 = 2 - option3 = 3 - self.assertEqual(int(MailManOptions.option1), 1) - - def test_no_such_enum_member(self): - class Color(Enum): - red = 1 - green = 2 - blue = 3 - self.assertRaises(ValueError, Color, 4) - self.assertRaises(KeyError, Color.__getitem__, 'chartreuse') - - def test_new_repr(self): - class Color(Enum): - red = 1 - green = 2 - blue = 3 - def __repr__(self): - return "don't you just love shades of %s?" % self.name - self.assertEqual( - repr(Color.blue), - "don't you just love shades of blue?", - ) - - def test_inherited_repr(self): - class MyEnum(Enum): - def __repr__(self): - return "My name is %s." % self.name - class MyIntEnum(int, MyEnum): - this = 1 - that = 2 - theother = 3 - self.assertEqual(repr(MyIntEnum.that), "My name is that.") - - def test_multiple_mixin_mro(self): - class auto_enum(EnumMeta): - def __new__(metacls, cls, bases, classdict): - original_dict = classdict - classdict = enum._EnumDict() - for k, v in original_dict.items(): - classdict[k] = v - temp = type(classdict)() - names = set(classdict._member_names) - i = 0 - for k in classdict._member_names: - v = classdict[k] - if v == (): - v = i - else: - i = v - i += 1 - temp[k] = v - for k, v in classdict.items(): - if k not in names: - temp[k] = v - return super(auto_enum, metacls).__new__( - metacls, cls, bases, temp) - - AutoNumberedEnum = auto_enum('AutoNumberedEnum', (Enum,), {}) - - AutoIntEnum = auto_enum('AutoIntEnum', (IntEnum,), {}) - - class TestAutoNumber(AutoNumberedEnum): - a = () - b = 3 - c = () - - class TestAutoInt(AutoIntEnum): - a = () - b = 3 - c = () - - def test_subclasses_with_getnewargs(self): - class NamedInt(int): - __qualname__ = 'NamedInt' # needed for pickle protocol 4 - def __new__(cls, *args): - _args = args - if len(args) < 1: - raise TypeError("name and value must be specified") - name, args = args[0], args[1:] - self = int.__new__(cls, *args) - self._intname = name - self._args = _args - return self - def __getnewargs__(self): - return self._args - @property - def __name__(self): - return self._intname - def __repr__(self): - # repr() is updated to include the name and type info - return "%s(%r, %s)" % (type(self).__name__, - self.__name__, - int.__repr__(self)) - def __str__(self): - # str() is unchanged, even if it relies on the repr() fallback - base = int - base_str = base.__str__ - if base_str.__objclass__ is object: - return base.__repr__(self) - return base_str(self) - # for simplicity, we only define one operator that - # propagates expressions - def __add__(self, other): - temp = int(self) + int( other) - if isinstance(self, NamedInt) and isinstance(other, NamedInt): - return NamedInt( - '(%s + %s)' % (self.__name__, other.__name__), - temp ) - else: - return temp - - class NEI(NamedInt, Enum): - __qualname__ = 'NEI' # needed for pickle protocol 4 - x = ('the-x', 1) - y = ('the-y', 2) - - self.assertTrue(NEI.__new__ is Enum.__new__) - self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)") - globals()['NamedInt'] = NamedInt - globals()['NEI'] = NEI - NI5 = NamedInt('test', 5) - self.assertEqual(NI5, 5) - test_pickle_dump_load(self.assertTrue, NI5, 5) - self.assertEqual(NEI.y.value, 2) - test_pickle_dump_load(self.assertTrue, NEI.y) - - if pyver >= 3.4: - def test_subclasses_with_getnewargs_ex(self): - class NamedInt(int): - __qualname__ = 'NamedInt' # needed for pickle protocol 4 - def __new__(cls, *args): - _args = args - if len(args) < 2: - raise TypeError("name and value must be specified") - name, args = args[0], args[1:] - self = int.__new__(cls, *args) - self._intname = name - self._args = _args - return self - def __getnewargs_ex__(self): - return self._args, {} - @property - def __name__(self): - return self._intname - def __repr__(self): - # repr() is updated to include the name and type info - return "{}({!r}, {})".format(type(self).__name__, - self.__name__, - int.__repr__(self)) - def __str__(self): - # str() is unchanged, even if it relies on the repr() fallback - base = int - base_str = base.__str__ - if base_str.__objclass__ is object: - return base.__repr__(self) - return base_str(self) - # for simplicity, we only define one operator that - # propagates expressions - def __add__(self, other): - temp = int(self) + int( other) - if isinstance(self, NamedInt) and isinstance(other, NamedInt): - return NamedInt( - '({0} + {1})'.format(self.__name__, other.__name__), - temp ) - else: - return temp - - class NEI(NamedInt, Enum): - __qualname__ = 'NEI' # needed for pickle protocol 4 - x = ('the-x', 1) - y = ('the-y', 2) - - - self.assertIs(NEI.__new__, Enum.__new__) - self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)") - globals()['NamedInt'] = NamedInt - globals()['NEI'] = NEI - NI5 = NamedInt('test', 5) - self.assertEqual(NI5, 5) - test_pickle_dump_load(self.assertEqual, NI5, 5, protocol=(4, HIGHEST_PROTOCOL)) - self.assertEqual(NEI.y.value, 2) - test_pickle_dump_load(self.assertTrue, NEI.y, protocol=(4, HIGHEST_PROTOCOL)) - - def test_subclasses_with_reduce(self): - class NamedInt(int): - __qualname__ = 'NamedInt' # needed for pickle protocol 4 - def __new__(cls, *args): - _args = args - if len(args) < 1: - raise TypeError("name and value must be specified") - name, args = args[0], args[1:] - self = int.__new__(cls, *args) - self._intname = name - self._args = _args - return self - def __reduce__(self): - return self.__class__, self._args - @property - def __name__(self): - return self._intname - def __repr__(self): - # repr() is updated to include the name and type info - return "%s(%r, %s)" % (type(self).__name__, - self.__name__, - int.__repr__(self)) - def __str__(self): - # str() is unchanged, even if it relies on the repr() fallback - base = int - base_str = base.__str__ - if base_str.__objclass__ is object: - return base.__repr__(self) - return base_str(self) - # for simplicity, we only define one operator that - # propagates expressions - def __add__(self, other): - temp = int(self) + int( other) - if isinstance(self, NamedInt) and isinstance(other, NamedInt): - return NamedInt( - '(%s + %s)' % (self.__name__, other.__name__), - temp ) - else: - return temp - - class NEI(NamedInt, Enum): - __qualname__ = 'NEI' # needed for pickle protocol 4 - x = ('the-x', 1) - y = ('the-y', 2) - - - self.assertTrue(NEI.__new__ is Enum.__new__) - self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)") - globals()['NamedInt'] = NamedInt - globals()['NEI'] = NEI - NI5 = NamedInt('test', 5) - self.assertEqual(NI5, 5) - test_pickle_dump_load(self.assertEqual, NI5, 5) - self.assertEqual(NEI.y.value, 2) - test_pickle_dump_load(self.assertTrue, NEI.y) - - def test_subclasses_with_reduce_ex(self): - class NamedInt(int): - __qualname__ = 'NamedInt' # needed for pickle protocol 4 - def __new__(cls, *args): - _args = args - if len(args) < 1: - raise TypeError("name and value must be specified") - name, args = args[0], args[1:] - self = int.__new__(cls, *args) - self._intname = name - self._args = _args - return self - def __reduce_ex__(self, proto): - return self.__class__, self._args - @property - def __name__(self): - return self._intname - def __repr__(self): - # repr() is updated to include the name and type info - return "%s(%r, %s)" % (type(self).__name__, - self.__name__, - int.__repr__(self)) - def __str__(self): - # str() is unchanged, even if it relies on the repr() fallback - base = int - base_str = base.__str__ - if base_str.__objclass__ is object: - return base.__repr__(self) - return base_str(self) - # for simplicity, we only define one operator that - # propagates expressions - def __add__(self, other): - temp = int(self) + int( other) - if isinstance(self, NamedInt) and isinstance(other, NamedInt): - return NamedInt( - '(%s + %s)' % (self.__name__, other.__name__), - temp ) - else: - return temp - - class NEI(NamedInt, Enum): - __qualname__ = 'NEI' # needed for pickle protocol 4 - x = ('the-x', 1) - y = ('the-y', 2) - - - self.assertTrue(NEI.__new__ is Enum.__new__) - self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)") - globals()['NamedInt'] = NamedInt - globals()['NEI'] = NEI - NI5 = NamedInt('test', 5) - self.assertEqual(NI5, 5) - test_pickle_dump_load(self.assertEqual, NI5, 5) - self.assertEqual(NEI.y.value, 2) - test_pickle_dump_load(self.assertTrue, NEI.y) - - def test_subclasses_without_direct_pickle_support(self): - class NamedInt(int): - __qualname__ = 'NamedInt' - def __new__(cls, *args): - _args = args - name, args = args[0], args[1:] - if len(args) == 0: - raise TypeError("name and value must be specified") - self = int.__new__(cls, *args) - self._intname = name - self._args = _args - return self - @property - def __name__(self): - return self._intname - def __repr__(self): - # repr() is updated to include the name and type info - return "%s(%r, %s)" % (type(self).__name__, - self.__name__, - int.__repr__(self)) - def __str__(self): - # str() is unchanged, even if it relies on the repr() fallback - base = int - base_str = base.__str__ - if base_str.__objclass__ is object: - return base.__repr__(self) - return base_str(self) - # for simplicity, we only define one operator that - # propagates expressions - def __add__(self, other): - temp = int(self) + int( other) - if isinstance(self, NamedInt) and isinstance(other, NamedInt): - return NamedInt( - '(%s + %s)' % (self.__name__, other.__name__), - temp ) - else: - return temp - - class NEI(NamedInt, Enum): - __qualname__ = 'NEI' - x = ('the-x', 1) - y = ('the-y', 2) - - self.assertTrue(NEI.__new__ is Enum.__new__) - self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)") - globals()['NamedInt'] = NamedInt - globals()['NEI'] = NEI - NI5 = NamedInt('test', 5) - self.assertEqual(NI5, 5) - self.assertEqual(NEI.y.value, 2) - test_pickle_exception(self.assertRaises, TypeError, NEI.x) - test_pickle_exception(self.assertRaises, PicklingError, NEI) - - def test_subclasses_without_direct_pickle_support_using_name(self): - class NamedInt(int): - __qualname__ = 'NamedInt' - def __new__(cls, *args): - _args = args - name, args = args[0], args[1:] - if len(args) == 0: - raise TypeError("name and value must be specified") - self = int.__new__(cls, *args) - self._intname = name - self._args = _args - return self - @property - def __name__(self): - return self._intname - def __repr__(self): - # repr() is updated to include the name and type info - return "%s(%r, %s)" % (type(self).__name__, - self.__name__, - int.__repr__(self)) - def __str__(self): - # str() is unchanged, even if it relies on the repr() fallback - base = int - base_str = base.__str__ - if base_str.__objclass__ is object: - return base.__repr__(self) - return base_str(self) - # for simplicity, we only define one operator that - # propagates expressions - def __add__(self, other): - temp = int(self) + int( other) - if isinstance(self, NamedInt) and isinstance(other, NamedInt): - return NamedInt( - '(%s + %s)' % (self.__name__, other.__name__), - temp ) - else: - return temp - - class NEI(NamedInt, Enum): - __qualname__ = 'NEI' - x = ('the-x', 1) - y = ('the-y', 2) - def __reduce_ex__(self, proto): - return getattr, (self.__class__, self._name_) - - self.assertTrue(NEI.__new__ is Enum.__new__) - self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)") - globals()['NamedInt'] = NamedInt - globals()['NEI'] = NEI - NI5 = NamedInt('test', 5) - self.assertEqual(NI5, 5) - self.assertEqual(NEI.y.value, 2) - test_pickle_dump_load(self.assertTrue, NEI.y) - test_pickle_dump_load(self.assertTrue, NEI) - - def test_tuple_subclass(self): - class SomeTuple(tuple, Enum): - __qualname__ = 'SomeTuple' - first = (1, 'for the money') - second = (2, 'for the show') - third = (3, 'for the music') - self.assertTrue(type(SomeTuple.first) is SomeTuple) - self.assertTrue(isinstance(SomeTuple.second, tuple)) - self.assertEqual(SomeTuple.third, (3, 'for the music')) - globals()['SomeTuple'] = SomeTuple - test_pickle_dump_load(self.assertTrue, SomeTuple.first) - - def test_duplicate_values_give_unique_enum_items(self): - class AutoNumber(Enum): - __order__ = 'enum_m enum_d enum_y' - enum_m = () - enum_d = () - enum_y = () - def __new__(cls): - value = len(cls.__members__) + 1 - obj = object.__new__(cls) - obj._value_ = value - return obj - def __int__(self): - return int(self._value_) - self.assertEqual(int(AutoNumber.enum_d), 2) - self.assertEqual(AutoNumber.enum_y.value, 3) - self.assertTrue(AutoNumber(1) is AutoNumber.enum_m) - self.assertEqual( - list(AutoNumber), - [AutoNumber.enum_m, AutoNumber.enum_d, AutoNumber.enum_y], - ) - - def test_inherited_new_from_enhanced_enum(self): - class AutoNumber2(Enum): - def __new__(cls): - value = len(cls.__members__) + 1 - obj = object.__new__(cls) - obj._value_ = value - return obj - def __int__(self): - return int(self._value_) - class Color(AutoNumber2): - _order_ = 'red green blue' - red = () - green = () - blue = () - self.assertEqual(len(Color), 3, "wrong number of elements: %d (should be %d)" % (len(Color), 3)) - self.assertEqual(list(Color), [Color.red, Color.green, Color.blue]) - if pyver >= 3.0: - self.assertEqual(list(map(int, Color)), [1, 2, 3]) - - def test_inherited_new_from_mixed_enum(self): - class AutoNumber3(IntEnum): - def __new__(cls): - value = len(cls.__members__) + 1 - obj = int.__new__(cls, value) - obj._value_ = value - return obj - class Color(AutoNumber3): - red = () - green = () - blue = () - self.assertEqual(len(Color), 3, "wrong number of elements: %d (should be %d)" % (len(Color), 3)) - Color.red - Color.green - Color.blue - - def test_equality(self): - class AlwaysEqual: - def __eq__(self, other): - return True - class OrdinaryEnum(Enum): - a = 1 - self.assertEqual(AlwaysEqual(), OrdinaryEnum.a) - self.assertEqual(OrdinaryEnum.a, AlwaysEqual()) - - def test_ordered_mixin(self): - class OrderedEnum(Enum): - def __ge__(self, other): - if self.__class__ is other.__class__: - return self._value_ >= other._value_ - return NotImplemented - def __gt__(self, other): - if self.__class__ is other.__class__: - return self._value_ > other._value_ - return NotImplemented - def __le__(self, other): - if self.__class__ is other.__class__: - return self._value_ <= other._value_ - return NotImplemented - def __lt__(self, other): - if self.__class__ is other.__class__: - return self._value_ < other._value_ - return NotImplemented - class Grade(OrderedEnum): - __order__ = 'A B C D F' - A = 5 - B = 4 - C = 3 - D = 2 - F = 1 - self.assertEqual(list(Grade), [Grade.A, Grade.B, Grade.C, Grade.D, Grade.F]) - self.assertTrue(Grade.A > Grade.B) - self.assertTrue(Grade.F <= Grade.C) - self.assertTrue(Grade.D < Grade.A) - self.assertTrue(Grade.B >= Grade.B) - - def test_extending2(self): - def bad_extension(): - class Shade(Enum): - def shade(self): - print(self.name) - class Color(Shade): - red = 1 - green = 2 - blue = 3 - class MoreColor(Color): - cyan = 4 - magenta = 5 - yellow = 6 - self.assertRaises(TypeError, bad_extension) - - def test_extending3(self): - class Shade(Enum): - def shade(self): - return self.name - class Color(Shade): - def hex(self): - return '%s hexlified!' % self.value - class MoreColor(Color): - cyan = 4 - magenta = 5 - yellow = 6 - self.assertEqual(MoreColor.magenta.hex(), '5 hexlified!') - - def test_no_duplicates(self): - def bad_duplicates(): - class UniqueEnum(Enum): - def __init__(self, *args): - cls = self.__class__ - if any(self.value == e.value for e in cls): - a = self.name - e = cls(self.value).name - raise ValueError( - "aliases not allowed in UniqueEnum: %r --> %r" - % (a, e) - ) - class Color(UniqueEnum): - red = 1 - green = 2 - blue = 3 - class Color(UniqueEnum): - red = 1 - green = 2 - blue = 3 - grene = 2 - self.assertRaises(ValueError, bad_duplicates) - - def test_init(self): - class Planet(Enum): - MERCURY = (3.303e+23, 2.4397e6) - VENUS = (4.869e+24, 6.0518e6) - EARTH = (5.976e+24, 6.37814e6) - MARS = (6.421e+23, 3.3972e6) - JUPITER = (1.9e+27, 7.1492e7) - SATURN = (5.688e+26, 6.0268e7) - URANUS = (8.686e+25, 2.5559e7) - NEPTUNE = (1.024e+26, 2.4746e7) - def __init__(self, mass, radius): - self.mass = mass # in kilograms - self.radius = radius # in meters - @property - def surface_gravity(self): - # universal gravitational constant (m3 kg-1 s-2) - G = 6.67300E-11 - return G * self.mass / (self.radius * self.radius) - self.assertEqual(round(Planet.EARTH.surface_gravity, 2), 9.80) - self.assertEqual(Planet.EARTH.value, (5.976e+24, 6.37814e6)) - - def test_nonhash_value(self): - class AutoNumberInAList(Enum): - def __new__(cls): - value = [len(cls.__members__) + 1] - obj = object.__new__(cls) - obj._value_ = value - return obj - class ColorInAList(AutoNumberInAList): - _order_ = 'red green blue' - red = () - green = () - blue = () - self.assertEqual(list(ColorInAList), [ColorInAList.red, ColorInAList.green, ColorInAList.blue]) - self.assertEqual(ColorInAList.red.value, [1]) - self.assertEqual(ColorInAList([1]), ColorInAList.red) - - def test_conflicting_types_resolved_in_new(self): - class LabelledIntEnum(int, Enum): - def __new__(cls, *args): - value, label = args - obj = int.__new__(cls, value) - obj.label = label - obj._value_ = value - return obj - - class LabelledList(LabelledIntEnum): - unprocessed = (1, "Unprocessed") - payment_complete = (2, "Payment Complete") - - self.assertEqual(list(LabelledList), [LabelledList.unprocessed, LabelledList.payment_complete]) - self.assertEqual(LabelledList.unprocessed, 1) - self.assertEqual(LabelledList(1), LabelledList.unprocessed) - - def test_empty_with_functional_api(self): - empty = enum.IntEnum('Foo', {}) - self.assertEqual(len(empty), 0) - - -class TestUnique(unittest.TestCase): - """2.4 doesn't allow class decorators, use function syntax.""" - - def test_unique_clean(self): - class Clean(Enum): - one = 1 - two = 'dos' - tres = 4.0 - unique(Clean) - class Cleaner(IntEnum): - single = 1 - double = 2 - triple = 3 - unique(Cleaner) - - def test_unique_dirty(self): - try: - class Dirty(Enum): - __order__ = 'one two tres' - one = 1 - two = 'dos' - tres = 1 - unique(Dirty) - except ValueError: - exc = sys.exc_info()[1] - message = exc.args[0] - self.assertTrue('tres -> one' in message) - - try: - class Dirtier(IntEnum): - _order_ = 'single double triple turkey' - single = 1 - double = 1 - triple = 3 - turkey = 3 - unique(Dirtier) - except ValueError: - exc = sys.exc_info()[1] - message = exc.args[0] - self.assertTrue('double -> single' in message) - self.assertTrue('turkey -> triple' in message) - - -class TestMe(unittest.TestCase): - - pass - -if __name__ == '__main__': - unittest.main() diff --git a/modules/enum/setup.cfg b/modules/enum/setup.cfg deleted file mode 100644 index 861a9f554..000000000 --- a/modules/enum/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[egg_info] -tag_build = -tag_date = 0 -tag_svn_revision = 0 - diff --git a/modules/enum/setup.py b/modules/enum/setup.py deleted file mode 100644 index fc67110a4..000000000 --- a/modules/enum/setup.py +++ /dev/null @@ -1,99 +0,0 @@ -import os -import sys -import setuptools -from distutils.core import setup - - -if sys.version_info[:2] < (2, 7): - required = ['ordereddict'] -else: - required = [] - -long_desc = '''\ -enum --- support for enumerations -======================================== - -An enumeration is a set of symbolic names (members) bound to unique, constant -values. Within an enumeration, the members can be compared by identity, and -the enumeration itself can be iterated over. - - from enum import Enum - - class Fruit(Enum): - apple = 1 - banana = 2 - orange = 3 - - list(Fruit) - # [, , ] - - len(Fruit) - # 3 - - Fruit.banana - # - - Fruit['banana'] - # - - Fruit(2) - # - - Fruit.banana is Fruit['banana'] is Fruit(2) - # True - - Fruit.banana.name - # 'banana' - - Fruit.banana.value - # 2 - -Repository and Issue Tracker at https://bitbucket.org/stoneleaf/enum34. -''' - -py2_only = () -py3_only = () -make = [ - 'rst2pdf enum/doc/enum.rst --output=enum/doc/enum.pdf', - ] - - -data = dict( - name='enum34', - version='1.1.6', - url='https://bitbucket.org/stoneleaf/enum34', - packages=['enum'], - package_data={ - 'enum' : [ - 'LICENSE', - 'README', - 'doc/enum.rst', - 'doc/enum.pdf', - 'test.py', - ] - }, - license='BSD License', - description='Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4', - long_description=long_desc, - provides=['enum'], - install_requires=required, - author='Ethan Furman', - author_email='ethan@stoneleaf.us', - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Programming Language :: Python', - 'Topic :: Software Development', - 'Programming Language :: Python :: 2.4', - 'Programming Language :: Python :: 2.5', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - ], - ) - -if __name__ == '__main__': - setup(**data) diff --git a/modules/mysql-connector-python/CHANGES.txt b/modules/mysql-connector-python/CHANGES.txt deleted file mode 100644 index 5f1c9b600..000000000 --- a/modules/mysql-connector-python/CHANGES.txt +++ /dev/null @@ -1,175 +0,0 @@ -==================================================== -MySQL Connector/Python 8.0 - Release Notes & Changes -==================================================== - -MySQL Connector/Python -Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved. - -Full release notes: - http://dev.mysql.com/doc/relnotes/connector-python/en/ - -v8.0.17 -======= - -- WL#13155: Support new utf8mb4 bin collation -- WL#12737: Add overlaps and not_overlaps as operator -- WL#12735: Add README.rst and CONTRIBUTING.rst files -- WL#12227: Indexing array fields -- WL#12085: Support cursor prepared statements with C extension -- BUG#29855733: Fix error during connection using charset and collation combination -- BUG#29833590: Calling execute() should fetch active results -- BUG#21072758: Support for connection attributes classic - -v8.0.16 -======= - -- WL#12864: Upgrade of Protobuf version to 3.6.1 -- WL#12863: Drop support for Django versions older than 1.11 -- WL#12489: Support new session reset functionality -- WL#12488: Support for session-connect-attributes -- WL#12297: Expose metadata about the source and binaries -- WL#12225: Prepared statement support -- BUG#29324966: Add missing username connection argument for driver compatibility -- BUG#29278489: Fix wrong user and group for Solaris packages -- BUG#29001628: Fix access by column label in Table.select() -- BUG#28479054: Fix Python interpreter crash due to memory corruption -- BUG#27897881: Empty LONG BLOB throws an IndexError - -v8.0.15 -======= - -- BUG#29260128: Disable load data local infile by default - -v8.0.14 -======= - -- WL#12607: Handling of Default Schema -- WL#12493: Standardize count method -- WL#12492: Be prepared for initial notice on connection -- BUG#28646344: Remove expression parsing on values -- BUG#28280321: Fix segmentation fault when using unicode characters in tables -- BUG#27794178: Using use_pure=False should raise an error if cext is not available -- BUG#27434751: Add a TLS/SSL option to verify server name - -v8.0.13 -======= - -- WL#12239: Add support for Python 3.7 -- WL#12226: Implement connect timeout -- WL#11897: Implement connection pooling for xprotocol -- BUG#28278352: C extension mysqlx Collection.add() leaks memory in sequential calls -- BUG#28037275: Missing bind parameters causes segfault or unclear error message -- BUG#27528819: Support special characters in the user and password using URI - -v8.0.12 -======= - -- WL#11951: Consolidate discrepancies between pure and c extension -- WL#11932: Remove Fabric support -- WL#11898: Core API v1 alignment -- BUG#28188883: Use utf8mb4 as the default character set -- BUG#28133321: Fix incorrect columns names representing aggregate functions -- BUG#27962293: Fix Django 2.0 and MySQL 8.0 compatibility issues -- BUG#27567999: Fix wrong docstring in ModifyStatement.patch() -- BUG#27277937: Fix confusing error message when using an unsupported collation -- BUG#26834200: Deprecate Row.get_string() method -- BUG#26660624: Fix missing install option in documentation - -v8.0.11 -======= - -- WL#11668: Add SHA256_MEMORY authentication mechanism -- WL#11614: Enable C extension by default -- WL#11448: New document _id generation support -- WL#11282: Support new locking modes NOWAIT and SKIP LOCKED -- BUG#27639119: Use a list of dictionaries to store warnings -- BUG#27634885: Update error codes for MySQL 8.0.11 -- BUG#27589450: Remove upsert functionality from WriteStatement class -- BUG#27528842: Fix internal queries open for SQL injection -- BUG#27364914: Cursor prepared statements do not convert strings -- BUG#24953913: Fix failing unittests -- BUG#24948205: Results from JSON_TYPE() are returned as bytearray -- BUG#24948186: JSON type results are bytearray instead of corresponding python type - -v8.0.6 -====== - -- WL#11372: Remove configuration API -- WL#11303: Remove CreateTable and CreateView -- WL#11281: Transaction savepoints -- WL#11278: Collection.create_index -- WL#11149: Create Pylint test for mysqlx -- WL#11142: Modify/MergePatch -- WL#11079: Add support for Python 3.6 - -v8.0.5 -====== - -- WL#11073: Add caching_sha2_password authentication plugin -- WL#10975: Add Single document operations -- WL#10974: Add Row locking methods to find and select operations -- WL#10973: Allow JSON types as operands for IN operator -- WL#10899: Add support for pure Python implementation of Protobuf -- WL#10771: Add SHA256 authentication -- WL#10053: Configuration handling interface - -v8.0.4 -====== - -- WL#10772: Cleanup Drop APIs -- WL#10770: Ensure all Session connections are secure by default -- WL#10754: Forbid modify() and remove() with no condition -- WL#10659: Support utf8mb4 as default charset -- WL#10658: Remove concept of NodeSession -- WL#10657: Move version number to 8.0 -- WL#10198: Add Protobuf C++ extension implementation -- WL#10004: Document UUID generation -- BUG#26175003: Fix Session.sql() when using unicode SQL statements with Python 2.7 -- BUG#26161838: Dropping an non-existing index should succeed silently -- BUG#26160876: Fix issue when using empty condition in Collection.remove() and Table.delete() -- BUG#26029811: Improve error thrown when using an invalid parameter in bind() -- BUG#25991574: Fix Collection.remove() and Table.delete() missing filters - -v2.2.3 -====== - -- WL#10452: Add Protobuf C++ extension for Linux variants and Mac OSX -- WL#10081: DevAPI: IPv6 support -- BUG#25614860: Fix defined_as method in the view creation -- BUG#25519251: SelectStatement does not implement order_by() method -- BUG#25436568: Update available operators for XPlugin -- BUG#24954006: Add missing items in CHANGES.txt -- BUG#24578507: Fix import error using Python 2.6 -- BUG#23636962: Fix improper error message when creating a Session -- BUG#23568207: Fix default aliases for projection fields -- BUG#23567724: Fix operator names - -v2.2.2 -====== - -- DevAPI: Schema.create_table -- DevAPI: Flexible Parameter Lists -- DevAPI: New transports: Unix domain socket -- DevAPI: Core TLS/SSL options for the mysqlx URI scheme -- DevAPI: View DDL with support for partitioning in a cluster / sharding -- BUG#24520850: Fix unexpected behavior when using an empty collection name - -v2.2.1 -====== - -- Add support for Protocol Buffers 3 -- Add View support (without DDL) -- Implement get_default_schema() method in BaseSchema -- DevAPI: Per ReplicaSet SQL execution -- DevAPI: XSession accepts a list of routers -- DevAPI: Define action on adding empty list of documents -- BUG#23729357: Fix fetching BIT datatype -- BUG#23583381: Add who_am_i and am_i_real methods to DatabaseObject -- BUG#23568257: Add fetch_one method to mysqlx.result -- BUG#23550743: Add close method to XSession and NodeSession -- BUG#23550057: Add support for URI as connection data - -v2.2.0 -====== - -- Provide initial implementation of new DevAPI diff --git a/modules/mysql-connector-python/CONTRIBUTING.rst b/modules/mysql-connector-python/CONTRIBUTING.rst deleted file mode 100644 index 9f861c70e..000000000 --- a/modules/mysql-connector-python/CONTRIBUTING.rst +++ /dev/null @@ -1,71 +0,0 @@ -Contributing Guidelines -======================= - -We love getting feedback from our users. Bugs and code contributions are great forms of feedback and we thank you for any bugs you report or code you contribute. - -Reporting Issues ----------------- - -Before reporting a new bug, please `check first `_ to see if a similar bug already exists. - -Bug reports should be as complete as possible. Please try and include the following: - -- Complete steps to reproduce the issue. -- Any information about platform and environment that could be specific to the bug. -- Specific version of the product you are using. -- Specific version of the server being used. -- Sample code to help reproduce the issue if possible. - -Contributing Code ------------------ - -Contributing to this project is easy. You just need to follow these steps. - -- Make sure you have a user account at `bugs.mysql.com `_. You will need to reference this user account when you submit your Oracle Contributor Agreement (OCA). -- Sign the Oracle Contributor Agreement. You can find instructions for doing that at the `OCA Page `_. -- Develop your pull request. Make sure you are aware of the `requirements `_ for the project. -- Validate your pull request by including tests that sufficiently cover the functionality you are adding. -- Verify that the entire test suite passes with your code applied. -- Submit your pull request. While you can submit the pull request via `GitHub `_, you can also submit it directly via `bugs.mysql.com `_. - -Thanks again for your wish to contribute to MySQL. We truly believe in the principles of open source development and appreciate any contributions to our projects. - -Running Tests -------------- - -Any code you contribute needs to pass our test suite. Please follow these steps to run our tests and validate your contributed code. - -1) Make sure you have the necessary `prerequisites `_ for building the project and `Pylint `_ for code analysis and style - -2) Clone MySQL Connector/Python - - .. code-block:: bash - - shell> git clone https://github.com/mysql/mysql-connector-python.git - -3) Run the entire test suite - - .. code-block:: bash - - shell> python unittests.py --with-mysql= --with-mysql-capi= --with-protobuf-include-dir= --with-protobuf-lib-dir= --with-protoc= --extra-link-args="-L -lssl -lcrypto" - - Example: - - .. code-block:: sh - - shell> python unittests.py --with-mysql=/usr/local/mysql --with-mysql-capi=/usr/local/mysql --with-protobuf-include-dir=/usr/local/protobuf/include --with-protobuf-lib-dir=/usr/local/protobuf/lib --with-protoc=/usr/local/protobuf/bin/protoc --extra-link-args="-L/usr/local/mysql/lib -lssl -lcrypto" - - -Getting Help ------------- - -If you need help or just want to get in touch with us, please use the following resources: - -- `MySQL Connector/Python Developer Guide `_ -- `MySQL Connector/Python X DevAPI Reference `_ -- `MySQL Connector/Python Forum `_ -- `MySQL Public Bug Tracker `_ -- `Slack `_ (`Sign-up `_ required if you do not have an Oracle account) -- `Stack Overflow `_ -- `InsideMySQL.com Connectors Blog `_ - diff --git a/modules/mysql-connector-python/LICENSE.txt b/modules/mysql-connector-python/LICENSE.txt deleted file mode 100644 index 62d838092..000000000 --- a/modules/mysql-connector-python/LICENSE.txt +++ /dev/null @@ -1,2088 +0,0 @@ -Licensing Information User Manual - -MySQL Connector/Python 8.0 - __________________________________________________________________ - -Introduction - - This License Information User Manual contains Oracle's product license - and other licensing information, including licensing information for - third-party software which may be included in this distribution of - MySQL Connector/Python 8.0. - - Last updated: June 2019 - -Licensing Information - - This is a release of MySQL Connector/Python 8.0, brought to you by the - MySQL team at Oracle. This software is released under version 2 of the - GNU General Public License (GPLv2), as set forth below, with the - following additional permissions: - - This distribution of MySQL Connector/Python 8.0 is distributed with - certain software (including but not limited to OpenSSL) that is - licensed under separate terms, as designated in a particular file or - component or in the license documentation. Without limiting your rights - under the GPLv2, the authors of MySQL hereby grant you an additional - permission to link the program and your derivative works with the - separately licensed software that they have included with the program. - - Without limiting the foregoing grant of rights under the GPLv2 and - additional permission as to separately licensed software, this - Connector is also subject to the Universal FOSS Exception, version 1.0, - a copy of which is reproduced below and can also be found along with - its FAQ at http://oss.oracle.com/licenses/universal-foss-exception. - - Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights - reserved. - -Election of GPLv2 - - For the avoidance of doubt, except that if any license choice other - than GPL or LGPL is available it will apply instead, Oracle elects to - use only the General Public License version 2 (GPLv2) at this time for - any software where a choice of GPL license versions is made available - with the language indicating that GPLv2 or any later version may be - used, or where a choice of which version of the GPL is applied is - otherwise unspecified. - -GNU General Public License Version 2.0, June 1991 - -The following applies to all products licensed under the GNU General -Public License, Version 2.0: You may not use the identified files -except in compliance with the GNU General Public License, Version -2.0 (the "License.") You may obtain a copy of the License at -http://www.gnu.org/licenses/gpl-2.0.txt. A copy of the license is -also reproduced below. Unless required by applicable law or agreed -to in writing, software distributed under the License is distributed -on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, -either express or implied. See the License for the specific language -governing permissions and limitations under the License. - -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. - -The Universal FOSS Exception, Version 1.0 - - In addition to the rights set forth in the other license(s) included in - the distribution for this software, data, and/or documentation - (collectively the "Software", and such licenses collectively with this - additional permission the "Software License"), the copyright holders - wish to facilitate interoperability with other software, data, and/or - documentation distributed with complete corresponding source under a - license that is OSI-approved and/or categorized by the FSF as free - (collectively "Other FOSS"). We therefore hereby grant the following - additional permission with respect to the use and distribution of the - Software with Other FOSS, and the constants, function signatures, data - structures and other invocation methods used to run or interact with - each of them (as to each, such software's "Interfaces"): - i. The Software's Interfaces may, to the extent permitted by the - license of the Other FOSS, be copied into, used and distributed in - the Other FOSS in order to enable interoperability, without - requiring a change to the license of the Other FOSS other than as - to any Interfaces of the Software embedded therein. The Software's - Interfaces remain at all times under the Software License, - including without limitation as used in the Other FOSS (which upon - any such use also then contains a portion of the Software under the - Software License). - ii. The Other FOSS's Interfaces may, to the extent permitted by the - license of the Other FOSS, be copied into, used and distributed in - the Software in order to enable interoperability, without requiring - that such Interfaces be licensed under the terms of the Software - License or otherwise altering their original terms, if this does - not require any portion of the Software other than such Interfaces - to be licensed under the terms other than the Software License. - iii. If only Interfaces and no other code is copied between the - Software and the Other FOSS in either direction, the use and/or - distribution of the Software with the Other FOSS shall not be - deemed to require that the Other FOSS be licensed under the license - of the Software, other than as to any Interfaces of the Software - copied into the Other FOSS. This includes, by way of example and - without limitation, statically or dynamically linking the Software - together with Other FOSS after enabling interoperability using the - Interfaces of one or both, and distributing the resulting - combination under different licenses for the respective portions - thereof. For avoidance of doubt, a license which is OSI-approved or - categorized by the FSF as free, includes, for the purpose of this - permission, such licenses with additional permissions, and any - license that has previously been so approved or categorized as - free, even if now deprecated or otherwise no longer recognized as - approved or free. Nothing in this additional permission grants any - right to distribute any portion of the Software on terms other than - those of the Software License or grants any additional permission - of any kind for use or distribution of the Software in conjunction - with software other than Other FOSS. - -Licenses for Third-Party Components - - The following sections contain licensing information for libraries that - we have included with the MySQL Connector/Python 8.0 source and - components used to test MySQL Connector/Python 8.0. Commonly used - licenses referenced herein can be found in Commonly Used Licenses. We - are thankful to all individuals that have created these. - -Django 1.5.1 - - The following software may be included in this product: -Copyright (c) Django Software Foundation and individual contributors. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - - 2. 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. - - - 3. Neither the name of Django nor the names of its contributors may 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. - -Google Protocol Buffers - - The following software may be included in this product: -Protocol Buffers (aka Google protobuf) - -Google Protocol Buffers - protobuf -Copyright 2008, Google Inc. -All rights reserved. - -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. -* Neither the name of Google Inc. nor the names of its - contributors may 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. - -Code generated by the Protocol Buffer compiler is owned by -the owner of the input file used when generating it. This -code is not standalone and requires a support library to be -linked with it. This support library is itself covered by -the above license. - -OpenSSL License - - You are receiving a copy of OpenSSL as part of this product in object - code form. The terms of the Oracle license do NOT apply to OpenSSL. - OpenSSL is licensed under a double license, of the OpenSSL License and - the original SSLeay license, separate from the Oracle product. If you - do not wish to install this library, you may remove it, but the Oracle - program might not operate properly or at all without it. - LICENSE ISSUES - ============== - - The OpenSSL toolkit stays under a double license, i.e. both the conditions of - the OpenSSL License and the original SSLeay license apply to the toolkit. - See below for the actual license texts. Actually both licenses are BSD-style - Open Source licenses. In case of any license issues related to OpenSSL - please contact openssl-core@openssl.org. - - OpenSSL License - --------------- - -/* ==================================================================== - - * Copyright (c) 1998-2017 The OpenSSL Project. All rights reserved. - * - - * Redistribution and use in source and binary forms, with or without - - * modification, are permitted provided that the following conditions - - * are met: - * - - * 1. Redistributions of source code must retain the above copyright - - * notice, this list of conditions and the following disclaimer. - * - - * 2. 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. - * - - * 3. All advertising materials mentioning features or use of this - - * software must display the following acknowledgment: - - * "This product includes software developed by the OpenSSL Project - - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - - * endorse or promote products derived from this software without - - * prior written permission. For written permission, please contact - - * openssl-core@openssl.org. - * - - * 5. Products derived from this software may not be called "OpenSSL" - - * nor may "OpenSSL" appear in their names without prior written - - * permission of the OpenSSL Project. - * - - * 6. Redistributions of any form whatsoever must retain the following - - * acknowledgment: - - * "This product includes software developed by the OpenSSL Project - - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - - * EXPRESSED 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 OpenSSL PROJECT OR - - * ITS 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. - - * ==================================================================== - * - - * This product includes cryptographic software written by Eric Young - - * (eay@cryptsoft.com). This product includes software written by Tim - - * Hudson (tjh@cryptsoft.com). - * - */ - - Original SSLeay License - ----------------------- - -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - - * All rights reserved. - * - - * This package is an SSL implementation written - - * by Eric Young (eay@cryptsoft.com). - - * The implementation was written so as to conform with Netscapes SSL. - * - - * This library is free for commercial and non-commercial use as long as - - * the following conditions are aheared to. The following conditions - - * apply to all code found in this distribution, be it the RC4, RSA, - - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - - * included with this distribution is covered by the same copyright terms - - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - - * Copyright remains Eric Young's, and as such any Copyright notices in - - * the code are not to be removed. - - * If this package is used in a product, Eric Young should be given attribution - - * as the author of the parts of the library used. - - * This can be in the form of a textual message at program startup or - - * in documentation (online or textual) provided with the package. - * - - * Redistribution and use in source and binary forms, with or without - - * modification, are permitted provided that the following conditions - - * are met: - - * 1. Redistributions of source code must retain the copyright - - * notice, this list of conditions and the following disclaimer. - - * 2. 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. - - * 3. All advertising materials mentioning features or use of this software - - * must display the following acknowledgement: - - * "This product includes cryptographic software written by - - * Eric Young (eay@cryptsoft.com)" - - * The word 'cryptographic' can be left out if the rouines from the library - - * being used are not cryptographic related :-). - - * 4. If you include any Windows specific code (or a derivative thereof) from - - * the apps directory (application code) you must include an acknowledgement: - - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. - * - - * The licence and distribution terms for any publically available version or - - * derivative of this code cannot be changed. i.e. this code cannot simply be - - * copied and put under another distribution licence - - * [including the GNU Public Licence.] - */ - -python-lz4 - - The following software may be included in this product: -Copyright (c) 2012-2013, Steeve Morin -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -2. 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. - -3. Neither the name of Steeve Morin nor the names of its contributors may 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 HOLDER 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. - -============================================================ - -Additional 4th party: -py3c ------- -from pyc3.h file in code directory: - -/* -The MIT License (MIT) - -Copyright (c) 2015, Red Hat, Inc. and/or its affiliates - -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. -*/ - ------------------------------------------------------------------------------- - -lz4 ---- -from lz4.h file in lz4libs directory - -/* - - * LZ4 - Fast LZ compression algorithm - - * Header File - - * Copyright (C) 2011-present, Yann Collet. - - BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) - - 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. - - 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. - - You can contact the author at : - - LZ4 homepage : http://www.lz4.org - - LZ4 source repository : https://github.com/lz4/lz4 -*/ - -** Future 4th party -(https://files.pythonhosted.org/packages/90/52/e20466b85000a181e1e144fd8305caf -2cf475e2f9674e797b222f8105f5f/future-0.17.1.tar.gz) -Copyright (c) 2013-2018 Python Charmers Pty Ltd, Australia -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. - -Commonly Used Licenses - -Artistic License (Perl) 1.0 - -The "Artistic License" - -Preamble - -The intent of this document is to state the conditions under which a -Package may be copied, such that the Copyright Holder maintains some -semblance of artistic control over the development of the package, -while giving the users of the package the right to use and distribute -the Package in a more-or-less customary fashion, plus the right to make -reasonable modifications. - -Definitions: - - "Package" refers to the collection of files distributed by the - Copyright Holder, and derivatives of that collection of files - created through textual modification. - - "Standard Version" refers to such a Package if it has not been - modified, or has been modified in accordance with the wishes - of the Copyright Holder as specified below. - - "Copyright Holder" is whoever is named in the copyright or - copyrights for the package. - - "You" is you, if you're thinking about copying or distributing - this Package. - - "Reasonable copying fee" is whatever you can justify on the - basis of media cost, duplication charges, time of people involved, - and so on. (You will not be required to justify it to the - Copyright Holder, but only to the computing community at large - as a market that must bear the fee.) - - "Freely Available" means that no fee is charged for the item - itself, though there may be fees involved in handling the item. - It also means that recipients of the item may redistribute it - under the same conditions they received it. - -1. You may make and give away verbatim copies of the source form of the -Standard Version of this Package without restriction, provided that you -duplicate all of the original copyright notices and associated disclaimers. - -2. You may apply bug fixes, portability fixes and other modifications -derived from the Public Domain or from the Copyright Holder. A Package -modified in such a way shall still be considered the Standard Version. - -3. You may otherwise modify your copy of this Package in any way, provided -that you insert a prominent notice in each changed file stating how and -when you changed that file, and provided that you do at least ONE of the -following: - - a) place your modifications in the Public Domain or otherwise make them - Freely Available, such as by posting said modifications to Usenet or - an equivalent medium, or placing the modifications on a major archive - site such as uunet.uu.net, or by allowing the Copyright Holder to include - your modifications in the Standard Version of the Package. - - b) use the modified Package only within your corporation or organization. - - c) rename any non-standard executables so the names do not conflict - with standard executables, which must also be provided, and provide - a separate manual page for each non-standard executable that clearly - documents how it differs from the Standard Version. - - d) make other distribution arrangements with the Copyright Holder. - -4. You may distribute the programs of this Package in object code or -executable form, provided that you do at least ONE of the following: - - a) distribute a Standard Version of the executables and library files, - together with instructions (in the manual page or equivalent) on where - to get the Standard Version. - - b) accompany the distribution with the machine-readable source of - the Package with your modifications. - - c) give non-standard executables non-standard names, and clearly - document the differences in manual pages (or equivalent), together - with instructions on where to get the Standard Version. - - d) make other distribution arrangements with the Copyright Holder. - -5. You may charge a reasonable copying fee for any distribution of this -Package. You may charge any fee you choose for support of this -Package. You may not charge a fee for this Package itself. However, -you may distribute this Package in aggregate with other (possibly -commercial) programs as part of a larger (possibly commercial) software -distribution provided that you do not advertise this Package as a -product of your own. You may embed this Package's interpreter within -an executable of yours (by linking); this shall be construed as a mere -form of aggregation, provided that the complete Standard Version of the -interpreter is so embedded. - -6. The scripts and library files supplied as input to or produced as -output from the programs of this Package do not automatically fall -under the copyright of this Package, but belong to whoever generated -them, and may be sold commercially, and may be aggregated with this -Package. If such scripts or library files are aggregated with this -Package via the so-called "undump" or "unexec" methods of producing a -binary executable image, then distribution of such an image shall -neither be construed as a distribution of this Package nor shall it -fall under the restrictions of Paragraphs 3 and 4, provided that you do -not represent such an executable image as a Standard Version of this -Package. - -7. C subroutines (or comparably compiled subroutines in other -languages) supplied by you and linked into this Package in order to -emulate subroutines and variables of the language defined by this -Package shall not be considered part of this Package, but are the -equivalent of input as in Paragraph 6, provided these subroutines do -not change the language in any way that would cause it to fail the -regression tests for the language. - -8. Aggregation of this Package with a commercial distribution is always -permitted provided that the use of this Package is embedded; that is, -when no overt attempt is made to make this Package's interfaces visible -to the end user of the commercial distribution. Such use shall not be -construed as a distribution of this Package. - -9. The name of the Copyright Holder may not be used to endorse or promote -products derived from this software without specific prior written -permission. - -10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED -WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. - - The End - -GNU Lesser General Public License Version 2.1, February 1999 - -The following applies to all products licensed under the -GNU Lesser General Public License, Version 2.1: You may -not use the identified files except in compliance with -the GNU Lesser General Public License, Version 2.1 (the -"License"). You may obtain a copy of the License at -http://www.gnu.org/licenses/lgpl-2.1.html. A copy of the -license is also reproduced below. Unless required by -applicable law or agreed to in writing, software distributed -under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -or implied. See the License for the specific language governing -permissions and limitations under the License. - - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 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. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -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 and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it -becomes a de-facto standard. To achieve this, non-free programs -must be allowed to use the library. A more frequent case is that -a free library does the same job as widely used non-free libraries. -In this case, there is little to gain by limiting the free library -to free software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, 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 library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - - 1. You may copy and distribute verbatim copies of the Library's -complete 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 distribute a copy of this License along with the -Library. - - 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 Library or any portion -of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -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 Library, 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 Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you 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. - - If distribution of 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 satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be 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. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library 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. - - - 9. 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 Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -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 with -this License. - - 11. 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 Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library 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 Library. - -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. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library 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. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser 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 Library -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 Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -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 - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "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 -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. 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 LIBRARY 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 -LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms -of the ordinary General Public License). - - To apply these terms, attach the following notices to the library. -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 library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; 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. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James - Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - -GNU Lesser General Public License Version 2, June 1991 - -GNU LIBRARY GENERAL PUBLIC LICENSE - -Version 2, June 1991 - -Copyright (C) 1991 Free Software Foundation, Inc. -51 Franklin St, 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. - -[This is the first released version of the library GPL. It is numbered 2 -because it goes with version 2 of the ordinary GPL.] - -Preamble - -The licenses for most software are designed to take away your freedom to -share and change it. By contrast, the GNU General Public Licenses are -intended to guarantee your freedom to share and change free software--to make -sure the software is free for all its users. - -This license, the Library General Public License, applies to some specially -designated Free Software Foundation software, and to any other libraries -whose authors decide to use it. You can use it for your libraries, 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 library, or if you modify it. - -For example, if you distribute copies of the library, whether gratis or for a -fee, you must give the recipients all the rights that we gave you. You must -make sure that they, too, receive or can get the source code. If you link a -program with the library, you must provide complete object files to the -recipients so that they can relink them with the library, after making -changes to the library and recompiling it. And you must show them these terms -so they know their rights. - -Our method of protecting your rights has two steps: (1) copyright the -library, and (2) offer you this license which gives you legal permission to -copy, distribute and/or modify the library. - -Also, for each distributor's protection, we want to make certain that -everyone understands that there is no warranty for this free library. If the -library is modified by someone else and passed on, we want its recipients to -know that what they have is not the original version, 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 companies distributing free software will -individually obtain patent licenses, thus in effect transforming the program -into proprietary software. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - -Most GNU software, including some libraries, is covered by the ordinary GNU -General Public License, which was designed for utility programs. This -license, the GNU Library General Public License, applies to certain -designated libraries. This license is quite different from the ordinary one; -be sure to read it in full, and don't assume that anything in it is the same -as in the ordinary license. - -The reason we have a separate public license for some libraries is that they -blur the distinction we usually make between modifying or adding to a program -and simply using it. Linking a program with a library, without changing the -library, is in some sense simply using the library, and is analogous to -running a utility program or application program. However, in a textual and -legal sense, the linked executable is a combined work, a derivative of the -original library, and the ordinary General Public License treats it as such. - -Because of this blurred distinction, using the ordinary General Public -License for libraries did not effectively promote software sharing, because -most developers did not use the libraries. We concluded that weaker -conditions might promote sharing better. - -However, unrestricted linking of non-free programs would deprive the users of -those programs of all benefit from the free status of the libraries -themselves. This Library General Public License is intended to permit -developers of non-free programs to use free libraries, while preserving your -freedom as a user of such programs to change the free libraries that are -incorporated in them. (We have not seen how to achieve this as regards -changes in header files, but we have achieved it as regards changes in the -actual functions of the Library.) The hope is that this will lead to faster -development of free libraries. - -The precise terms and conditions for copying, distribution and modification -follow. Pay close attention to the difference between a "work based on the -library" and a "work that uses the library". The former contains code derived -from the library, while the latter only works together with the library. - -Note that it is possible for a library to be covered by the ordinary General -Public License rather than by this special one. - -TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - -0. This License Agreement applies to any software library which contains a -notice placed by the copyright holder or other authorized party saying it may -be distributed under the terms of this Library General Public License (also -called "this License"). Each licensee is addressed as "you". - -A "library" means a collection of software functions and/or data prepared so -as to be conveniently linked with application programs (which use some of -those functions and data) to form executables. - -The "Library", below, refers to any such software library or work which has -been distributed under these terms. A "work based on the Library" means -either the Library or any derivative work under copyright law: that is to -say, a work containing the Library or a portion of it, either verbatim or -with modifications and/or translated straightforwardly into another language. -(Hereinafter, translation is included without limitation in the term -"modification".) - -"Source code" for a work means the preferred form of the work for making -modifications to it. For a library, 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 -library. - -Activities other than copying, distribution and modification are not covered -by this License; they are outside its scope. The act of running a program -using the Library is not restricted, and output from such a program is -covered only if its contents constitute a work based on the Library -(independent of the use of the Library in a tool for writing it). Whether -that is true depends on what the Library does and what the program that uses -the Library does. - -1. You may copy and distribute verbatim copies of the Library's complete -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 distribute a copy of this -License along with the Library. - -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 Library or any portion of it, -thus forming a work based on the Library, 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) The modified work must itself be a software library. - b) You must cause the files modified to carry prominent notices stating -that you changed the files and the date of any change. - c) You must cause the whole of the work to be licensed at no charge to -all third parties under the terms of this License. - d) If a facility in the modified Library refers to a function or a table -of data to be supplied by an application program that uses the facility, -other than as an argument passed when the facility is invoked, then you must -make a good faith effort to ensure that, in the event an application does not -supply such function or table, the facility still operates, and performs -whatever part of its purpose remains meaningful. - - (For example, a function in a library to compute square roots has a -purpose that is entirely well-defined independent of the application. -Therefore, Subsection 2d requires that any application-supplied function or -table used by this function must be optional: if the application does not -supply it, the square root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If identifiable -sections of that work are not derived from the Library, 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 Library, 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 Library. - -In addition, mere aggregation of another work not based on the Library with -the Library (or with a work based on the Library) on a volume of a storage or -distribution medium does not bring the other work under the scope of this -License. - -3. You may opt to apply the terms of the ordinary GNU General Public License -instead of this License to a given copy of the Library. To do this, you must -alter all the notices that refer to this License, so that they refer to the -ordinary GNU General Public License, version 2, instead of to this License. -(If a newer version than version 2 of the ordinary GNU General Public License -has appeared, then you can specify that version instead if you wish.) Do not -make any other change in these notices. - -Once this change is made in a given copy, it is irreversible for that copy, -so the ordinary GNU General Public License applies to all subsequent copies -and derivative works made from that copy. - -This option is useful when you wish to copy part of the code of the Library -into a program that is not a library. - -4. You may copy and distribute the Library (or a portion or derivative of it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you 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. - -If distribution of 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 satisfies the requirement to distribute the source code, -even though third parties are not compelled to copy the source along with the -object code. - -5. A program that contains no derivative of any portion of the Library, but -is designed to work with the Library by being compiled or linked with it, is -called a "work that uses the Library". Such a work, in isolation, is not a -derivative work of the Library, and therefore falls outside the scope of this -License. - -However, linking a "work that uses the Library" with the Library creates an -executable that is a derivative of the Library (because it contains portions -of the Library), rather than a "work that uses the library". The executable -is therefore covered by this License. Section 6 states terms for distribution -of such executables. - -When a "work that uses the Library" uses material from a header file that is -part of the Library, the object code for the work may be a derivative work of -the Library even though the source code is not. Whether this is true is -especially significant if the work can be linked without the Library, or if -the work is itself a library. The threshold for this to be true is not -precisely defined by law. - -If such an object file uses only numerical parameters, data structure layouts -and accessors, and small macros and small inline functions (ten lines or less -in length), then the use of the object file is unrestricted, regardless of -whether it is legally a derivative work. (Executables containing this object -code plus portions of the Library will still fall under Section 6.) - -Otherwise, if the work is a derivative of the Library, you may distribute the -object code for the work under the terms of Section 6. Any executables -containing that work also fall under Section 6, whether or not they are -linked directly with the Library itself. - -6. As an exception to the Sections above, you may also compile or link a -"work that uses the Library" with the Library to produce a work containing -portions of the Library, and distribute that work under terms of your choice, -provided that the terms permit modification of the work for the customer's -own use and reverse engineering for debugging such modifications. - -You must give prominent notice with each copy of the work that the Library is -used in it and that the Library and its use are covered by this License. You -must supply a copy of this License. If the work during execution displays -copyright notices, you must include the copyright notice for the Library -among them, as well as a reference directing the user to the copy of this -License. Also, you must do one of these things: - - a) Accompany the work with the complete corresponding machine-readable -source code for the Library including whatever changes were used in the work -(which must be distributed under Sections 1 and 2 above); and, if the work is -an executable linked with the Library, with the complete machine-readable -"work that uses the Library", as object code and/or source code, so that the -user can modify the Library and then relink to produce a modified executable -containing the modified Library. (It is understood that the user who changes -the contents of definitions files in the Library will not necessarily be able -to recompile the application to use the modified definitions.) - b) Accompany the work with a written offer, valid for at least three -years, to give the same user the materials specified in Subsection 6a, above, -for a charge no more than the cost of performing this distribution. - c) If distribution of the work is made by offering access to copy from a -designated place, offer equivalent access to copy the above specified -materials from the same place. - d) Verify that the user has already received a copy of these materials or -that you have already sent this user a copy. - -For an executable, the required form of the "work that uses the Library" must -include any data and utility programs needed for reproducing the executable -from it. 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. - -It may happen that this requirement contradicts the license restrictions of -other proprietary libraries that do not normally accompany the operating -system. Such a contradiction means you cannot use both them and the Library -together in an executable that you distribute. - -7. You may place library facilities that are a work based on the Library -side-by-side in a single library together with other library facilities not -covered by this License, and distribute such a combined library, provided -that the separate distribution of the work based on the Library and of the -other library facilities is otherwise permitted, and provided that you do -these two things: - - a) Accompany the combined library with a copy of the same work based on -the Library, uncombined with any other library facilities. This must be -distributed under the terms of the Sections above. - b) Give prominent notice with the combined library of the fact that part -of it is a work based on the Library, and explaining where to find the -accompanying uncombined form of the same work. - -8. You may not copy, modify, sublicense, link with, or distribute the Library -except as expressly provided under this License. Any attempt otherwise to -copy, modify, sublicense, link with, or distribute the Library 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. - -9. 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 -Library or its derivative works. These actions are prohibited by law if you -do not accept this License. Therefore, by modifying or distributing the -Library (or any work based on the Library), you indicate your acceptance of -this License to do so, and all its terms and conditions for copying, -distributing or modifying the Library or works based on it. - -10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the original -licensor to copy, distribute, link with or modify the Library 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. - -11. 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 -Library at all. For example, if a patent license would not permit -royalty-free redistribution of the Library 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 -Library. - -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. - -12. If the distribution and/or use of the Library is restricted in certain -countries either by patents or by copyrighted interfaces, the original -copyright holder who places the Library 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. - -13. The Free Software Foundation may publish revised and/or new versions of -the Library 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 Library -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 Library does not specify a license version number, you may -choose any version ever published by the Free Software Foundation. - -14. If you wish to incorporate parts of the Library into other free programs -whose distribution conditions are incompatible with these, 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 - -15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR -THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE -STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE -LIBRARY "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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, -YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. 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 LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER -SOFTWARE), 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 Libraries - -If you develop a new library, and you want it to be of the greatest possible -use to the public, we recommend making it free software that everyone can -redistribute and change. You can do so by permitting redistribution under -these terms (or, alternatively, under the terms of the ordinary General -Public License). - -To apply these terms, attach the following notices to the library. 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. - -one line to give the library's name and an idea of what it does. -Copyright (C) year name of author - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This library 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 -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public -License along with this library; if not, write to the -Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, -Boston, MA 02110-1301, USA. - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - -Yoyodyne, Inc., hereby disclaims all copyright interest in -the library `Frob' (a library for tweaking knobs) written -by James Random Hacker. - -signature of Ty Coon, 1 April 1990 -Ty Coon, President of Vice - -That's all there is to it! - -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. - -Written Offer for Source Code - - For any software that you receive from Oracle in binary form which is - licensed under an open source license that gives you the right to - receive the source code for that binary, you can obtain a copy of the - applicable source code by visiting - http://www.oracle.com/goto/opensourcecode. If the source code for the - binary was not provided to you with the binary, you can also receive a - copy of the source code on physical media by submitting a written - request to the address listed below or by sending an email to Oracle - using the following link: - http://www.oracle.com/goto/opensourcecode/request. - Oracle America, Inc. - Attn: Senior Vice President - Development and Engineering Legal - 500 Oracle Parkway, 10th Floor - Redwood Shores, CA 94065 - - Your request should include: - - * The name of the binary for which you are requesting the source code - - * The name and version number of the Oracle product containing the - binary - - * The date you received the Oracle product - - * Your name - - * Your company name (if applicable) - - * Your return mailing address and email, and - - * A telephone number in the event we need to reach you. - - We may charge you a fee to cover the cost of physical media and - processing. - - Your request must be sent - a. within three (3) years of the date you received the Oracle product - that included the binary that is the subject of your request, or - b. in the case of code licensed under the GPL v3 for as long as Oracle - offers spare parts or customer support for that product model. diff --git a/modules/mysql-connector-python/MANIFEST.in b/modules/mysql-connector-python/MANIFEST.in deleted file mode 100644 index 4e6968fee..000000000 --- a/modules/mysql-connector-python/MANIFEST.in +++ /dev/null @@ -1,17 +0,0 @@ -include README.txt -include README.rst -include LICENSE.txt -include CONTRIBUTING.rst -include CHANGES.txt -include setup.py -include setupinfo.py -include unittests.py -include MANIFEST.in - -recursive-include examples *.py -recursive-include lib *.py -recursive-include tests *.py *.csv *.pem *.cnf -recursive-include src *.c *.h *.cc *.proto - -include docs/INFO_SRC -include docs/INFO_BIN diff --git a/modules/mysql-connector-python/PKG-INFO b/modules/mysql-connector-python/PKG-INFO deleted file mode 100644 index bd5648faf..000000000 --- a/modules/mysql-connector-python/PKG-INFO +++ /dev/null @@ -1,33 +0,0 @@ -Metadata-Version: 1.1 -Name: mysql-connector-python -Version: 8.0.17 -Summary: MySQL driver written in Python -Home-page: http://dev.mysql.com/doc/connector-python/en/index.html -Author: Oracle and/or its affiliates -Author-email: UNKNOWN -License: GNU GPLv2 (with FOSS License Exception) -Download-URL: http://dev.mysql.com/downloads/connector/python/ -Description: - MySQL driver written in Python which does not depend on MySQL C client - libraries and implements the DB API v2.0 specification (PEP-249). - -Keywords: mysql db -Platform: UNKNOWN -Classifier: Development Status :: 5 - Production/Stable -Classifier: Environment :: Other Environment -Classifier: Intended Audience :: Developers -Classifier: Intended Audience :: Education -Classifier: Intended Audience :: Information Technology -Classifier: Intended Audience :: System Administrators -Classifier: License :: OSI Approved :: GNU General Public License (GPL) -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.4 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Topic :: Database -Classifier: Topic :: Software Development -Classifier: Topic :: Software Development :: Libraries :: Application Frameworks -Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/modules/mysql-connector-python/README.rst b/modules/mysql-connector-python/README.rst deleted file mode 100644 index 2d847aa60..000000000 --- a/modules/mysql-connector-python/README.rst +++ /dev/null @@ -1,107 +0,0 @@ -MySQL Connector/Python -====================== - -.. image:: https://img.shields.io/pypi/v/mysql-connector-python.svg - :target: https://pypi.org/project/mysql-connector-python/ -.. image:: https://img.shields.io/pypi/pyversions/mysql-connector-python.svg - :target: https://pypi.org/project/mysql-connector-python/ -.. image:: https://img.shields.io/pypi/l/mysql-connector-python.svg - :target: https://pypi.org/project/mysql-connector-python/ - -MySQL Connector/Python enables Python programs to access MySQL databases, using an API that is compliant with the `Python Database API Specification v2.0 (PEP 249) `_. It also contains an implementation of the `X DevAPI `_, an Application Programming Interface for working with the `MySQL Document Store `_. - -Installation ------------- - -The recommended way to install Connector/Python is via `pip `_. - -Make sure you have a recent `pip `_ version installed on your system. If your system already has ``pip`` installed, you might need to update it. Or you can use the `standalone pip installer `_. - -.. code-block:: bash - - shell> pip install mysql-connector-python - -Please refer to the `installation tutorial `_ for installation alternatives. - -Getting Started ---------------- - -Using the MySQL classic protocol: - -.. code:: python - - import mysql.connector - - # Connect to server - cnx = mysql.connector.connect( - host="127.0.0.1", - port=3306, - user="mike", - password="s3cre3t!") - - # Get a cursor - cur = cnx.cursor() - - # Execute a query - cur.execute("SELECT CURDATE()") - - # Fetch one result - row = cur.fetchone() - print("Current date is: {0}".format(row[0])) - - # Close connection - cnx.close() - -Using the MySQL X DevAPI: - -.. code:: python - - import mysqlx - - # Connect to server - session = mysqlx.get_session( - host="127.0.0.1", - port=33060, - user="mike", - password="s3cr3t!") - schema = session.get_schema("test") - - # Use the collection "my_collection" - collection = schema.get_collection("my_collection") - - # Specify which document to find with Collection.find() - result = collection.find("name like :param") \ - .bind("param", "S%") \ - .limit(1) \ - .execute() - - # Print document - docs = result.fetch_all() - print(r"Name: {0}".format(docs[0]["name"])) - - # Close session - session.close() - - -Please refer to the `MySQL Connector/Python Developer Guide `_ and the `MySQL Connector/Python X DevAPI Reference `_ for a complete usage guide. - -Additional Resources --------------------- - -- `MySQL Connector/Python Developer Guide `_ -- `MySQL Connector/Python X DevAPI Reference `_ -- `MySQL Connector/Python Forum `_ -- `MySQL Public Bug Tracker `_ -- `Slack `_ (`Sign-up `_ required if you do not have an Oracle account) -- `Stack Overflow `_ -- `InsideMySQL.com Connectors Blog `_ - -Contributing ------------- - -There are a few ways to contribute to the Connector/Python code. Please refer to the `contributing guidelines `_ for additional information. - -License -------- - -Please refer to the `README.txt `_ and `LICENSE.txt `_ files, available in this repository, for further details. diff --git a/modules/mysql-connector-python/README.txt b/modules/mysql-connector-python/README.txt deleted file mode 100644 index 4526ea9db..000000000 --- a/modules/mysql-connector-python/README.txt +++ /dev/null @@ -1,47 +0,0 @@ -========================== -MySQL Connector/Python 8.0 -========================== - -MySQL Connector/Python -Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved. - -License information can be found in the LICENSE.txt file. - - -Requirements -============ - -Protobuf C++ (version >= 2.6.0) and Python Protobuf (version >= 3.0.0) -https://developers.google.com/protocol-buffers/docs/downloads - - -Documentation & Examples -======================== - -Documentation for all Connector/Python versions can be found online here: - - http://dev.mysql.com/doc/connector-python/en/index.html - -The source distribution of Connector/Python also contains example scripts. -They can be found in the examples/ directory. - - -License -======= - -Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. - -This is a release of MySQL Connector/Python, Oracle's Python driver for MySQL. - -License information can be found in the LICENSE.txt file. - -This distribution may include materials developed by third parties. -For license and attribution notices for these materials, please refer to the LICENSE.txt file. - -For more information on MySQL Connector/Python, visit - https://dev.mysql.com/doc/dev/connector-python/ - -For additional downloads and the source of MySQL Connector/Python, visit - http://dev.mysql.com/downloads - -MySQL Connector/Python is brought to you by the MySQL team at Oracle. diff --git a/modules/mysql-connector-python/mysql/__init__.py b/modules/mysql-connector-python/mysql/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/modules/mysql-connector-python/mysql/connector/__init__.py b/modules/mysql-connector-python/mysql/connector/__init__.py deleted file mode 100644 index 93dbad1ea..000000000 --- a/modules/mysql-connector-python/mysql/connector/__init__.py +++ /dev/null @@ -1,201 +0,0 @@ -# Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -""" -MySQL Connector/Python - MySQL driver written in Python -""" - -try: - import _mysql_connector # pylint: disable=F0401 - from .connection_cext import CMySQLConnection -except ImportError: - HAVE_CEXT = False -else: - HAVE_CEXT = True - -from . import version -from .connection import MySQLConnection -from .errors import ( # pylint: disable=W0622 - Error, Warning, InterfaceError, DatabaseError, - NotSupportedError, DataError, IntegrityError, ProgrammingError, - OperationalError, InternalError, custom_error_exception, PoolError) -from .constants import FieldFlag, FieldType, CharacterSet, \ - RefreshOption, ClientFlag -from .dbapi import ( - Date, Time, Timestamp, Binary, DateFromTicks, - TimestampFromTicks, TimeFromTicks, - STRING, BINARY, NUMBER, DATETIME, ROWID, - apilevel, threadsafety, paramstyle) -from .optionfiles import read_option_files - -_CONNECTION_POOLS = {} - -def _get_pooled_connection(**kwargs): - """Return a pooled MySQL connection""" - # If no pool name specified, generate one - from .pooling import ( - MySQLConnectionPool, generate_pool_name, - CONNECTION_POOL_LOCK) - - try: - pool_name = kwargs['pool_name'] - except KeyError: - pool_name = generate_pool_name(**kwargs) - - # Setup the pool, ensuring only 1 thread can update at a time - with CONNECTION_POOL_LOCK: - if pool_name not in _CONNECTION_POOLS: - _CONNECTION_POOLS[pool_name] = MySQLConnectionPool(**kwargs) - elif isinstance(_CONNECTION_POOLS[pool_name], MySQLConnectionPool): - # pool_size must be the same - check_size = _CONNECTION_POOLS[pool_name].pool_size - if ('pool_size' in kwargs - and kwargs['pool_size'] != check_size): - raise PoolError("Size can not be changed " - "for active pools.") - - # Return pooled connection - try: - return _CONNECTION_POOLS[pool_name].get_connection() - except AttributeError: - raise InterfaceError( - "Failed getting connection from pool '{0}'".format(pool_name)) - - -def _get_failover_connection(**kwargs): - """Return a MySQL connection and try to failover if needed - - An InterfaceError is raise when no MySQL is available. ValueError is - raised when the failover server configuration contains an illegal - connection argument. Supported arguments are user, password, host, port, - unix_socket and database. ValueError is also raised when the failover - argument was not provided. - - Returns MySQLConnection instance. - """ - config = kwargs.copy() - try: - failover = config['failover'] - except KeyError: - raise ValueError('failover argument not provided') - del config['failover'] - - support_cnx_args = set( - ['user', 'password', 'host', 'port', 'unix_socket', - 'database', 'pool_name', 'pool_size']) - - # First check if we can add all use the configuration - for server in failover: - diff = set(server.keys()) - support_cnx_args - if diff: - raise ValueError( - "Unsupported connection argument {0} in failover: {1}".format( - 's' if len(diff) > 1 else '', - ', '.join(diff))) - - for server in failover: - new_config = config.copy() - new_config.update(server) - try: - return connect(**new_config) - except Error: - # If we failed to connect, we try the next server - pass - - raise InterfaceError("Could not failover: no MySQL server available") - - -def connect(*args, **kwargs): - """Create or get a MySQL connection object - - In its simpliest form, Connect() will open a connection to a - MySQL server and return a MySQLConnection object. - - When any connection pooling arguments are given, for example pool_name - or pool_size, a pool is created or a previously one is used to return - a PooledMySQLConnection. - - Returns MySQLConnection or PooledMySQLConnection. - """ - # Option files - if 'option_files' in kwargs: - new_config = read_option_files(**kwargs) - return connect(**new_config) - - # Failover - if 'failover' in kwargs: - return _get_failover_connection(**kwargs) - - # Pooled connections - try: - from .constants import CNX_POOL_ARGS - if any([key in kwargs for key in CNX_POOL_ARGS]): - return _get_pooled_connection(**kwargs) - except NameError: - # No pooling - pass - - # Use C Extension by default - use_pure = kwargs.get('use_pure', False) - if 'use_pure' in kwargs: - del kwargs['use_pure'] # Remove 'use_pure' from kwargs - if not use_pure and not HAVE_CEXT: - raise ImportError("MySQL Connector/Python C Extension not " - "available") - - if HAVE_CEXT and not use_pure: - return CMySQLConnection(*args, **kwargs) - return MySQLConnection(*args, **kwargs) -Connect = connect # pylint: disable=C0103 - -__version_info__ = version.VERSION -__version__ = version.VERSION_TEXT - -__all__ = [ - 'MySQLConnection', 'Connect', 'custom_error_exception', - - # Some useful constants - 'FieldType', 'FieldFlag', 'ClientFlag', 'CharacterSet', 'RefreshOption', - 'HAVE_CEXT', - - # Error handling - 'Error', 'Warning', - 'InterfaceError', 'DatabaseError', - 'NotSupportedError', 'DataError', 'IntegrityError', 'ProgrammingError', - 'OperationalError', 'InternalError', - - # DBAPI PEP 249 required exports - 'connect', 'apilevel', 'threadsafety', 'paramstyle', - 'Date', 'Time', 'Timestamp', 'Binary', - 'DateFromTicks', 'DateFromTicks', 'TimestampFromTicks', 'TimeFromTicks', - 'STRING', 'BINARY', 'NUMBER', - 'DATETIME', 'ROWID', - - # C Extension - 'CMySQLConnection', - ] diff --git a/modules/mysql-connector-python/mysql/connector/abstracts.py b/modules/mysql-connector-python/mysql/connector/abstracts.py deleted file mode 100644 index ce97a8a8b..000000000 --- a/modules/mysql-connector-python/mysql/connector/abstracts.py +++ /dev/null @@ -1,1223 +0,0 @@ -# Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Module gathering all abstract base classes""" - -from abc import ABCMeta, abstractmethod, abstractproperty -import re -import time -import weakref - -from .catch23 import make_abc, BYTE_TYPES, STRING_TYPES -from .conversion import MySQLConverterBase -from .constants import (ClientFlag, CharacterSet, CONN_ATTRS_DN, - DEFAULT_CONFIGURATION) -from .optionfiles import MySQLOptionsParser -from . import errors - -NAMED_TUPLE_CACHE = weakref.WeakValueDictionary() - -@make_abc(ABCMeta) -class MySQLConnectionAbstract(object): - - """Abstract class for classes connecting to a MySQL server""" - - def __init__(self, **kwargs): - """Initialize""" - self._client_flags = ClientFlag.get_default() - self._charset_id = 45 - self._sql_mode = None - self._time_zone = None - self._autocommit = False - self._server_version = None - self._handshake = None - self._conn_attrs = {} - - self._user = '' - self._password = '' - self._database = '' - self._host = '127.0.0.1' - self._port = 3306 - self._unix_socket = None - self._client_host = '' - self._client_port = 0 - self._ssl = {} - self._ssl_disabled = DEFAULT_CONFIGURATION["ssl_disabled"] - self._force_ipv6 = False - - self._use_unicode = True - self._get_warnings = False - self._raise_on_warnings = False - self._connection_timeout = DEFAULT_CONFIGURATION["connect_timeout"] - self._buffered = False - self._unread_result = False - self._have_next_result = False - self._raw = False - self._in_transaction = False - - self._prepared_statements = None - - self._ssl_active = False - self._auth_plugin = None - self._pool_config_version = None - self.converter = None - self._converter_class = None - self._compress = False - - self._consume_results = False - - def _get_self(self): - """Return self for weakref.proxy - - This method is used when the original object is needed when using - weakref.proxy. - """ - return self - - def _read_option_files(self, config): - """ - Read option files for connection parameters. - - Checks if connection arguments contain option file arguments, and then - reads option files accordingly. - """ - if 'option_files' in config: - try: - if isinstance(config['option_groups'], str): - config['option_groups'] = [config['option_groups']] - groups = config['option_groups'] - del config['option_groups'] - except KeyError: - groups = ['client', 'connector_python'] - - if isinstance(config['option_files'], str): - config['option_files'] = [config['option_files']] - option_parser = MySQLOptionsParser(list(config['option_files']), - keep_dashes=False) - del config['option_files'] - - config_from_file = option_parser.get_groups_as_dict_with_priority( - *groups) - config_options = {} - for group in groups: - try: - for option, value in config_from_file[group].items(): - try: - if option == 'socket': - option = 'unix_socket' - # pylint: disable=W0104 - DEFAULT_CONFIGURATION[option] - # pylint: enable=W0104 - - if (option not in config_options or - config_options[option][1] <= value[1]): - config_options[option] = value - except KeyError: - if group == 'connector_python': - raise AttributeError("Unsupported argument " - "'{0}'".format(option)) - except KeyError: - continue - - for option, value in config_options.items(): - if option not in config: - try: - config[option] = eval(value[0]) # pylint: disable=W0123 - except (NameError, SyntaxError): - config[option] = value[0] - return config - - @property - def user(self): - """User used while connecting to MySQL""" - return self._user - - @property - def server_host(self): - """MySQL server IP address or name""" - return self._host - - @property - def server_port(self): - "MySQL server TCP/IP port" - return self._port - - @property - def unix_socket(self): - "MySQL Unix socket file location" - return self._unix_socket - - @abstractproperty - def database(self): - """Get the current database""" - pass - - @database.setter - def database(self, value): - """Set the current database""" - self.cmd_query("USE %s" % value) - - @property - def can_consume_results(self): - """Returns whether to consume results""" - return self._consume_results - - def config(self, **kwargs): - """Configure the MySQL Connection - - This method allows you to configure the MySQLConnection instance. - - Raises on errors. - """ - config = kwargs.copy() - if 'dsn' in config: - raise errors.NotSupportedError("Data source name is not supported") - - # Read option files - self._read_option_files(config) - - # Configure how we handle MySQL warnings - try: - self.get_warnings = config['get_warnings'] - del config['get_warnings'] - except KeyError: - pass # Leave what was set or default - try: - self.raise_on_warnings = config['raise_on_warnings'] - del config['raise_on_warnings'] - except KeyError: - pass # Leave what was set or default - - # Configure client flags - try: - default = ClientFlag.get_default() - self.set_client_flags(config['client_flags'] or default) - del config['client_flags'] - except KeyError: - pass # Missing client_flags-argument is OK - - try: - if config['compress']: - self._compress = True - self.set_client_flags([ClientFlag.COMPRESS]) - except KeyError: - pass # Missing compress argument is OK - - allow_local_infile = config.get( - 'allow_local_infile', DEFAULT_CONFIGURATION['allow_local_infile']) - if allow_local_infile: - self.set_client_flags([ClientFlag.LOCAL_FILES]) - else: - self.set_client_flags([-ClientFlag.LOCAL_FILES]) - - try: - if not config['consume_results']: - self._consume_results = False - else: - self._consume_results = True - except KeyError: - self._consume_results = False - - # Configure auth_plugin - try: - self._auth_plugin = config['auth_plugin'] - del config['auth_plugin'] - except KeyError: - self._auth_plugin = '' - - # Configure character set and collation - if 'charset' in config or 'collation' in config: - try: - charset = config['charset'] - del config['charset'] - except KeyError: - charset = None - try: - collation = config['collation'] - del config['collation'] - except KeyError: - collation = None - self._charset_id = CharacterSet.get_charset_info(charset, - collation)[0] - - # Set converter class - try: - self.set_converter_class(config['converter_class']) - except KeyError: - pass # Using default converter class - except TypeError: - raise AttributeError("Converter class should be a subclass " - "of conversion.MySQLConverterBase.") - - # Compatible configuration with other drivers - compat_map = [ - # (,) - ('db', 'database'), - ('username', 'user'), - ('passwd', 'password'), - ('connect_timeout', 'connection_timeout'), - ] - for compat, translate in compat_map: - try: - if translate not in config: - config[translate] = config[compat] - del config[compat] - except KeyError: - pass # Missing compat argument is OK - - # Configure login information - if 'user' in config or 'password' in config: - try: - user = config['user'] - del config['user'] - except KeyError: - user = self._user - try: - password = config['password'] - del config['password'] - except KeyError: - password = self._password - self.set_login(user, password) - - # Configure host information - if 'host' in config and config['host']: - self._host = config['host'] - - # Check network locations - try: - self._port = int(config['port']) - del config['port'] - except KeyError: - pass # Missing port argument is OK - except ValueError: - raise errors.InterfaceError( - "TCP/IP port number should be an integer") - - if "ssl_disabled" in config: - self._ssl_disabled = config.pop("ssl_disabled") - - # Other configuration - set_ssl_flag = False - for key, value in config.items(): - try: - DEFAULT_CONFIGURATION[key] - except KeyError: - raise AttributeError("Unsupported argument '{0}'".format(key)) - # SSL Configuration - if key.startswith('ssl_'): - set_ssl_flag = True - self._ssl.update({key.replace('ssl_', ''): value}) - else: - attribute = '_' + key - try: - setattr(self, attribute, value.strip()) - except AttributeError: - setattr(self, attribute, value) - - if set_ssl_flag: - if 'verify_cert' not in self._ssl: - self._ssl['verify_cert'] = \ - DEFAULT_CONFIGURATION['ssl_verify_cert'] - if 'verify_identity' not in self._ssl: - self._ssl['verify_identity'] = \ - DEFAULT_CONFIGURATION['ssl_verify_identity'] - # Make sure both ssl_key/ssl_cert are set, or neither (XOR) - if 'ca' not in self._ssl or self._ssl['ca'] is None: - raise AttributeError( - "Missing ssl_ca argument.") - if bool('key' in self._ssl) != bool('cert' in self._ssl): - raise AttributeError( - "ssl_key and ssl_cert need to be both " - "specified, or neither." - ) - # Make sure key/cert are set to None - elif not set(('key', 'cert')) <= set(self._ssl): - self._ssl['key'] = None - self._ssl['cert'] = None - elif (self._ssl['key'] is None) != (self._ssl['cert'] is None): - raise AttributeError( - "ssl_key and ssl_cert need to be both " - "set, or neither." - ) - - if self._conn_attrs is None: - self._conn_attrs = {} - elif not isinstance(self._conn_attrs, dict): - raise errors.InterfaceError('conn_attrs must be of type dict.') - else: - for attr_name in self._conn_attrs: - if attr_name in CONN_ATTRS_DN: - continue - # Validate name type - if not isinstance(attr_name, STRING_TYPES): - raise errors.InterfaceError( - "Attribute name should be a string, found: '{}' in '{}'" - "".format(attr_name, self._conn_attrs)) - # Validate attribute name limit 32 characters - if len(attr_name) > 32: - raise errors.InterfaceError( - "Attribute name '{}' exceeds 32 characters limit size." - "".format(attr_name)) - # Validate names in connection attributes cannot start with "_" - if attr_name.startswith("_"): - raise errors.InterfaceError( - "Key names in connection attributes cannot start with " - "'_', found: '{}'".format(attr_name)) - # Validate value type - attr_value = self._conn_attrs[attr_name] - if not isinstance(attr_value, STRING_TYPES): - raise errors.InterfaceError( - "Attribute '{}' value: '{}' must be a string type." - "".format(attr_name, attr_value)) - # Validate attribute value limit 1024 characters - if len(attr_value) > 1024: - raise errors.InterfaceError( - "Attribute '{}' value: '{}' exceeds 1024 characters " - "limit size".format(attr_name, attr_value)) - - if self._client_flags & ClientFlag.CONNECT_ARGS: - self._add_default_conn_attrs() - - def _add_default_conn_attrs(self): - """Add the default connection attributes.""" - pass - - def _check_server_version(self, server_version): - """Check the MySQL version - - This method will check the MySQL version and raise an InterfaceError - when it is not supported or invalid. It will return the version - as a tuple with major, minor and patch. - - Raises InterfaceError if invalid server version. - - Returns tuple - """ - if isinstance(server_version, BYTE_TYPES): - server_version = server_version.decode() - - # pylint: disable=W1401 - regex_ver = re.compile(r"^(\d{1,2})\.(\d{1,2})\.(\d{1,3})(.*)") - # pylint: enable=W1401 - match = regex_ver.match(server_version) - if not match: - raise errors.InterfaceError("Failed parsing MySQL version") - - version = tuple([int(v) for v in match.groups()[0:3]]) - if version < (4, 1): - raise errors.InterfaceError( - "MySQL Version '{0}' is not supported.".format(server_version)) - - return version - - def get_server_version(self): - """Get the MySQL version - - This method returns the MySQL server version as a tuple. If not - previously connected, it will return None. - - Returns a tuple or None. - """ - return self._server_version - - def get_server_info(self): - """Get the original MySQL version information - - This method returns the original MySQL server as text. If not - previously connected, it will return None. - - Returns a string or None. - """ - try: - return self._handshake['server_version_original'] - except (TypeError, KeyError): - return None - - @abstractproperty - def in_transaction(self): - """MySQL session has started a transaction""" - pass - - def set_client_flags(self, flags): - """Set the client flags - - The flags-argument can be either an int or a list (or tuple) of - ClientFlag-values. If it is an integer, it will set client_flags - to flags as is. - If flags is a list (or tuple), each flag will be set or unset - when it's negative. - - set_client_flags([ClientFlag.FOUND_ROWS,-ClientFlag.LONG_FLAG]) - - Raises ProgrammingError when the flags argument is not a set or - an integer bigger than 0. - - Returns self.client_flags - """ - if isinstance(flags, int) and flags > 0: - self._client_flags = flags - elif isinstance(flags, (tuple, list)): - for flag in flags: - if flag < 0: - self._client_flags &= ~abs(flag) - else: - self._client_flags |= flag - else: - raise errors.ProgrammingError( - "set_client_flags expect integer (>0) or set") - return self._client_flags - - def isset_client_flag(self, flag): - """Check if a client flag is set""" - if (self._client_flags & flag) > 0: - return True - return False - - @property - def time_zone(self): - """Get the current time zone""" - return self.info_query("SELECT @@session.time_zone")[0] - - @time_zone.setter - def time_zone(self, value): - """Set the time zone""" - self.cmd_query("SET @@session.time_zone = '{0}'".format(value)) - self._time_zone = value - - @property - def sql_mode(self): - """Get the SQL mode""" - return self.info_query("SELECT @@session.sql_mode")[0] - - @sql_mode.setter - def sql_mode(self, value): - """Set the SQL mode - - This method sets the SQL Mode for the current connection. The value - argument can be either a string with comma separate mode names, or - a sequence of mode names. - - It is good practice to use the constants class SQLMode: - from mysql.connector.constants import SQLMode - cnx.sql_mode = [SQLMode.NO_ZERO_DATE, SQLMode.REAL_AS_FLOAT] - """ - if isinstance(value, (list, tuple)): - value = ','.join(value) - self.cmd_query("SET @@session.sql_mode = '{0}'".format(value)) - self._sql_mode = value - - @abstractmethod - def info_query(self, query): - """Send a query which only returns 1 row""" - pass - - def set_login(self, username=None, password=None): - """Set login information for MySQL - - Set the username and/or password for the user connecting to - the MySQL Server. - """ - if username is not None: - self._user = username.strip() - else: - self._user = '' - if password is not None: - self._password = password - else: - self._password = '' - - def set_unicode(self, value=True): - """Toggle unicode mode - - Set whether we return string fields as unicode or not. - Default is True. - """ - self._use_unicode = value - if self.converter: - self.converter.set_unicode(value) - - @property - def autocommit(self): - """Get whether autocommit is on or off""" - value = self.info_query("SELECT @@session.autocommit")[0] - return True if value == 1 else False - - @autocommit.setter - def autocommit(self, value): - """Toggle autocommit""" - switch = 'ON' if value else 'OFF' - self.cmd_query("SET @@session.autocommit = {0}".format(switch)) - self._autocommit = value - - @property - def get_warnings(self): - """Get whether this connection retrieves warnings automatically - - This method returns whether this connection retrieves warnings - automatically. - - Returns True, or False when warnings are not retrieved. - """ - return self._get_warnings - - @get_warnings.setter - def get_warnings(self, value): - """Set whether warnings should be automatically retrieved - - The toggle-argument must be a boolean. When True, cursors for this - connection will retrieve information about warnings (if any). - - Raises ValueError on error. - """ - if not isinstance(value, bool): - raise ValueError("Expected a boolean type") - self._get_warnings = value - - @property - def raise_on_warnings(self): - """Get whether this connection raises an error on warnings - - This method returns whether this connection will raise errors when - MySQL reports warnings. - - Returns True or False. - """ - return self._raise_on_warnings - - @raise_on_warnings.setter - def raise_on_warnings(self, value): - """Set whether warnings raise an error - - The toggle-argument must be a boolean. When True, cursors for this - connection will raise an error when MySQL reports warnings. - - Raising on warnings implies retrieving warnings automatically. In - other words: warnings will be set to True. If set to False, warnings - will be also set to False. - - Raises ValueError on error. - """ - if not isinstance(value, bool): - raise ValueError("Expected a boolean type") - self._raise_on_warnings = value - self._get_warnings = value - - - @property - def unread_result(self): - """Get whether there is an unread result - - This method is used by cursors to check whether another cursor still - needs to retrieve its result set. - - Returns True, or False when there is no unread result. - """ - return self._unread_result - - @unread_result.setter - def unread_result(self, value): - """Set whether there is an unread result - - This method is used by cursors to let other cursors know there is - still a result set that needs to be retrieved. - - Raises ValueError on errors. - """ - if not isinstance(value, bool): - raise ValueError("Expected a boolean type") - self._unread_result = value - - @property - def charset(self): - """Returns the character set for current connection - - This property returns the character set name of the current connection. - The server is queried when the connection is active. If not connected, - the configured character set name is returned. - - Returns a string. - """ - return CharacterSet.get_info(self._charset_id)[0] - - @property - def python_charset(self): - """Returns the Python character set for current connection - - This property returns the character set name of the current connection. - Note that, unlike property charset, this checks if the previously set - character set is supported by Python and if not, it returns the - equivalent character set that Python supports. - - Returns a string. - """ - encoding = CharacterSet.get_info(self._charset_id)[0] - if encoding in ('utf8mb4', 'binary'): - return 'utf8' - return encoding - - def set_charset_collation(self, charset=None, collation=None): - """Sets the character set and collation for the current connection - - This method sets the character set and collation to be used for - the current connection. The charset argument can be either the - name of a character set as a string, or the numerical equivalent - as defined in constants.CharacterSet. - - When the collation is not given, the default will be looked up and - used. - - For example, the following will set the collation for the latin1 - character set to latin1_general_ci: - - set_charset('latin1','latin1_general_ci') - - """ - if charset: - if isinstance(charset, int): - (self._charset_id, charset_name, collation_name) = \ - CharacterSet.get_charset_info(charset) - elif isinstance(charset, str): - (self._charset_id, charset_name, collation_name) = \ - CharacterSet.get_charset_info(charset, collation) - else: - raise ValueError( - "charset should be either integer, string or None") - elif collation: - (self._charset_id, charset_name, collation_name) = \ - CharacterSet.get_charset_info(collation=collation) - - self._execute_query("SET NAMES '{0}' COLLATE '{1}'".format( - charset_name, collation_name)) - - try: - # Required for C Extension - self.set_character_set_name(charset_name) # pylint: disable=E1101 - except AttributeError: - # Not required for pure Python connection - pass - - if self.converter: - self.converter.set_charset(charset_name) - - @property - def collation(self): - """Returns the collation for current connection - - This property returns the collation name of the current connection. - The server is queried when the connection is active. If not connected, - the configured collation name is returned. - - Returns a string. - """ - return CharacterSet.get_charset_info(self._charset_id)[2] - - @abstractmethod - def _do_handshake(self): - """Gather information of the MySQL server before authentication""" - pass - - @abstractmethod - def _open_connection(self): - """Open the connection to the MySQL server""" - pass - - def _post_connection(self): - """Executes commands after connection has been established - - This method executes commands after the connection has been - established. Some setting like autocommit, character set, and SQL mode - are set using this method. - """ - self.set_charset_collation(self._charset_id) - self.autocommit = self._autocommit - if self._time_zone: - self.time_zone = self._time_zone - if self._sql_mode: - self.sql_mode = self._sql_mode - - @abstractmethod - def disconnect(self): - """Disconnect from the MySQL server""" - pass - close = disconnect - - def connect(self, **kwargs): - """Connect to the MySQL server - - This method sets up the connection to the MySQL server. If no - arguments are given, it will use the already configured or default - values. - """ - if kwargs: - self.config(**kwargs) - - self.disconnect() - self._open_connection() - self._post_connection() - - def reconnect(self, attempts=1, delay=0): - """Attempt to reconnect to the MySQL server - - The argument attempts should be the number of times a reconnect - is tried. The delay argument is the number of seconds to wait between - each retry. - - You may want to set the number of attempts higher and use delay when - you expect the MySQL server to be down for maintenance or when you - expect the network to be temporary unavailable. - - Raises InterfaceError on errors. - """ - counter = 0 - while counter != attempts: - counter = counter + 1 - try: - self.disconnect() - self.connect() - if self.is_connected(): - break - except Exception as err: # pylint: disable=W0703 - if counter == attempts: - msg = "Can not reconnect to MySQL after {0} "\ - "attempt(s): {1}".format(attempts, str(err)) - raise errors.InterfaceError(msg) - if delay > 0: - time.sleep(delay) - - @abstractmethod - def is_connected(self): - """Reports whether the connection to MySQL Server is available""" - pass - - @abstractmethod - def ping(self, reconnect=False, attempts=1, delay=0): - """Check availability of the MySQL server""" - pass - - @abstractmethod - def commit(self): - """Commit current transaction""" - pass - - @abstractmethod - def cursor(self, buffered=None, raw=None, prepared=None, cursor_class=None, - dictionary=None, named_tuple=None): - """Instantiates and returns a cursor""" - pass - - @abstractmethod - def _execute_query(self, query): - """Execute a query""" - pass - - @abstractmethod - def rollback(self): - """Rollback current transaction""" - pass - - def start_transaction(self, consistent_snapshot=False, - isolation_level=None, readonly=None): - """Start a transaction - - This method explicitly starts a transaction sending the - START TRANSACTION statement to the MySQL server. You can optionally - set whether there should be a consistent snapshot, which - isolation level you need or which access mode i.e. READ ONLY or - READ WRITE. - - For example, to start a transaction with isolation level SERIALIZABLE, - you would do the following: - >>> cnx = mysql.connector.connect(..) - >>> cnx.start_transaction(isolation_level='SERIALIZABLE') - - Raises ProgrammingError when a transaction is already in progress - and when ValueError when isolation_level specifies an Unknown - level. - """ - if self.in_transaction: - raise errors.ProgrammingError("Transaction already in progress") - - if isolation_level: - level = isolation_level.strip().replace('-', ' ').upper() - levels = ['READ UNCOMMITTED', 'READ COMMITTED', 'REPEATABLE READ', - 'SERIALIZABLE'] - - if level not in levels: - raise ValueError( - 'Unknown isolation level "{0}"'.format(isolation_level)) - - self._execute_query( - "SET TRANSACTION ISOLATION LEVEL {0}".format(level)) - - if readonly is not None: - if self._server_version < (5, 6, 5): - raise ValueError( - "MySQL server version {0} does not support " - "this feature".format(self._server_version)) - - if readonly: - access_mode = 'READ ONLY' - else: - access_mode = 'READ WRITE' - self._execute_query( - "SET TRANSACTION {0}".format(access_mode)) - - query = "START TRANSACTION" - if consistent_snapshot: - query += " WITH CONSISTENT SNAPSHOT" - self.cmd_query(query) - - def reset_session(self, user_variables=None, session_variables=None): - """Clears the current active session - - This method resets the session state, if the MySQL server is 5.7.3 - or later active session will be reset without re-authenticating. - For other server versions session will be reset by re-authenticating. - - It is possible to provide a sequence of variables and their values to - be set after clearing the session. This is possible for both user - defined variables and session variables. - This method takes two arguments user_variables and session_variables - which are dictionaries. - - Raises OperationalError if not connected, InternalError if there are - unread results and InterfaceError on errors. - """ - if not self.is_connected(): - raise errors.OperationalError("MySQL Connection not available.") - - try: - self.cmd_reset_connection() - except (errors.NotSupportedError, NotImplementedError): - if self._compress: - raise errors.NotSupportedError( - "Reset session is not supported with compression for " - "MySQL server version 5.7.2 or earlier.") - else: - self.cmd_change_user(self._user, self._password, - self._database, self._charset_id) - - if user_variables or session_variables: - cur = self.cursor() - if user_variables: - for key, value in user_variables.items(): - cur.execute("SET @`{0}` = %s".format(key), (value,)) - if session_variables: - for key, value in session_variables.items(): - cur.execute("SET SESSION `{0}` = %s".format(key), (value,)) - cur.close() - - def set_converter_class(self, convclass): - """ - Set the converter class to be used. This should be a class overloading - methods and members of conversion.MySQLConverter. - """ - if convclass and issubclass(convclass, MySQLConverterBase): - charset_name = CharacterSet.get_info(self._charset_id)[0] - self._converter_class = convclass - self.converter = convclass(charset_name, self._use_unicode) - else: - raise TypeError("Converter class should be a subclass " - "of conversion.MySQLConverterBase.") - - @abstractmethod - def get_rows(self, count=None, binary=False, columns=None, raw=None, - prep_stmt=None): - """Get all rows returned by the MySQL server""" - pass - - def cmd_init_db(self, database): - """Change the current database""" - raise NotImplementedError - - def cmd_query(self, query, raw=False, buffered=False, raw_as_string=False): - """Send a query to the MySQL server""" - raise NotImplementedError - - def cmd_query_iter(self, statements): - """Send one or more statements to the MySQL server""" - raise NotImplementedError - - def cmd_refresh(self, options): - """Send the Refresh command to the MySQL server""" - raise NotImplementedError - - def cmd_quit(self): - """Close the current connection with the server""" - raise NotImplementedError - - def cmd_shutdown(self, shutdown_type=None): - """Shut down the MySQL Server""" - raise NotImplementedError - - def cmd_statistics(self): - """Send the statistics command to the MySQL Server""" - raise NotImplementedError - - def cmd_process_info(self): - """Get the process list of the MySQL Server - - This method is a placeholder to notify that the PROCESS_INFO command - is not supported by raising the NotSupportedError. The command - "SHOW PROCESSLIST" should be send using the cmd_query()-method or - using the INFORMATION_SCHEMA database. - - Raises NotSupportedError exception - """ - raise errors.NotSupportedError( - "Not implemented. Use SHOW PROCESSLIST or INFORMATION_SCHEMA") - - def cmd_process_kill(self, mysql_pid): - """Kill a MySQL process""" - raise NotImplementedError - - def cmd_debug(self): - """Send the DEBUG command""" - raise NotImplementedError - - def cmd_ping(self): - """Send the PING command""" - raise NotImplementedError - - def cmd_change_user(self, username='', password='', database='', - charset=45): - """Change the current logged in user""" - raise NotImplementedError - - def cmd_stmt_prepare(self, statement): - """Prepare a MySQL statement""" - raise NotImplementedError - - def cmd_stmt_execute(self, statement_id, data=(), parameters=(), flags=0): - """Execute a prepared MySQL statement""" - raise NotImplementedError - - def cmd_stmt_close(self, statement_id): - """Deallocate a prepared MySQL statement""" - raise NotImplementedError - - def cmd_stmt_send_long_data(self, statement_id, param_id, data): - """Send data for a column""" - raise NotImplementedError - - def cmd_stmt_reset(self, statement_id): - """Reset data for prepared statement sent as long data""" - raise NotImplementedError - - def cmd_reset_connection(self): - """Resets the session state without re-authenticating""" - raise NotImplementedError - - -@make_abc(ABCMeta) -class MySQLCursorAbstract(object): - """Abstract cursor class - - Abstract class defining cursor class with method and members - required by the Python Database API Specification v2.0. - """ - def __init__(self): - """Initialization""" - self._description = None - self._rowcount = -1 - self._last_insert_id = None - self._warnings = None - self.arraysize = 1 - - @abstractmethod - def callproc(self, procname, args=()): - """Calls a stored procedure with the given arguments - - The arguments will be set during this session, meaning - they will be called like ___arg where - is an enumeration (+1) of the arguments. - - Coding Example: - 1) Defining the Stored Routine in MySQL: - CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT) - BEGIN - SET pProd := pFac1 * pFac2; - END - - 2) Executing in Python: - args = (5,5,0) # 0 is to hold pprod - cursor.callproc('multiply', args) - print(cursor.fetchone()) - - Does not return a value, but a result set will be - available when the CALL-statement execute successfully. - Raises exceptions when something is wrong. - """ - pass - - @abstractmethod - def close(self): - """Close the cursor.""" - pass - - @abstractmethod - def execute(self, operation, params=(), multi=False): - """Executes the given operation - - Executes the given operation substituting any markers with - the given parameters. - - For example, getting all rows where id is 5: - cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,)) - - The multi argument should be set to True when executing multiple - statements in one operation. If not set and multiple results are - found, an InterfaceError will be raised. - - If warnings where generated, and connection.get_warnings is True, then - self._warnings will be a list containing these warnings. - - Returns an iterator when multi is True, otherwise None. - """ - pass - - @abstractmethod - def executemany(self, operation, seq_params): - """Execute the given operation multiple times - - The executemany() method will execute the operation iterating - over the list of parameters in seq_params. - - Example: Inserting 3 new employees and their phone number - - data = [ - ('Jane','555-001'), - ('Joe', '555-001'), - ('John', '555-003') - ] - stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s')" - cursor.executemany(stmt, data) - - INSERT statements are optimized by batching the data, that is - using the MySQL multiple rows syntax. - - Results are discarded. If they are needed, consider looping over - data using the execute() method. - """ - pass - - @abstractmethod - def fetchone(self): - """Returns next row of a query result set - - Returns a tuple or None. - """ - pass - - @abstractmethod - def fetchmany(self, size=1): - """Returns the next set of rows of a query result, returning a - list of tuples. When no more rows are available, it returns an - empty list. - - The number of rows returned can be specified using the size argument, - which defaults to one - """ - pass - - @abstractmethod - def fetchall(self): - """Returns all rows of a query result set - - Returns a list of tuples. - """ - pass - - def nextset(self): - """Not Implemented.""" - pass - - def setinputsizes(self, sizes): - """Not Implemented.""" - pass - - def setoutputsize(self, size, column=None): - """Not Implemented.""" - pass - - def reset(self, free=True): - """Reset the cursor to default""" - pass - - @abstractproperty - def description(self): - """Returns description of columns in a result - - This property returns a list of tuples describing the columns in - in a result set. A tuple is described as follows:: - - (column_name, - type, - None, - None, - None, - None, - null_ok, - column_flags) # Addition to PEP-249 specs - - Returns a list of tuples. - """ - return self._description - - @abstractproperty - def rowcount(self): - """Returns the number of rows produced or affected - - This property returns the number of rows produced by queries - such as a SELECT, or affected rows when executing DML statements - like INSERT or UPDATE. - - Note that for non-buffered cursors it is impossible to know the - number of rows produced before having fetched them all. For those, - the number of rows will be -1 right after execution, and - incremented when fetching rows. - - Returns an integer. - """ - return self._rowcount - - @abstractproperty - def lastrowid(self): - """Returns the value generated for an AUTO_INCREMENT column - - Returns the value generated for an AUTO_INCREMENT column by - the previous INSERT or UPDATE statement or None when there is - no such value available. - - Returns a long value or None. - """ - return self._last_insert_id - - def fetchwarnings(self): - """Returns Warnings.""" - return self._warnings diff --git a/modules/mysql-connector-python/mysql/connector/authentication.py b/modules/mysql-connector-python/mysql/connector/authentication.py deleted file mode 100644 index 28e6e16e5..000000000 --- a/modules/mysql-connector-python/mysql/connector/authentication.py +++ /dev/null @@ -1,272 +0,0 @@ -# Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Implementing support for MySQL Authentication Plugins""" - -from hashlib import sha1, sha256 -import struct - -from . import errors -from .catch23 import PY2, isstr, UNICODE_TYPES - - -class BaseAuthPlugin(object): - """Base class for authentication plugins - - - Classes inheriting from BaseAuthPlugin should implement the method - prepare_password(). When instantiating, auth_data argument is - required. The username, password and database are optional. The - ssl_enabled argument can be used to tell the plugin whether SSL is - active or not. - - The method auth_response() method is used to retrieve the password - which was prepared by prepare_password(). - """ - - requires_ssl = False - plugin_name = '' - - def __init__(self, auth_data, username=None, password=None, database=None, - ssl_enabled=False): - """Initialization""" - self._auth_data = auth_data - self._username = username - self._password = password - self._database = database - self._ssl_enabled = ssl_enabled - - def prepare_password(self): - """Prepares and returns password to be send to MySQL - - This method needs to be implemented by classes inheriting from - this class. It is used by the auth_response() method. - - Raises NotImplementedError. - """ - raise NotImplementedError - - def auth_response(self): - """Returns the prepared password to send to MySQL - - Raises InterfaceError on errors. For example, when SSL is required - by not enabled. - - Returns str - """ - if self.requires_ssl and not self._ssl_enabled: - raise errors.InterfaceError("{name} requires SSL".format( - name=self.plugin_name)) - return self.prepare_password() - - -class MySQLNativePasswordAuthPlugin(BaseAuthPlugin): - """Class implementing the MySQL Native Password authentication plugin""" - - requires_ssl = False - plugin_name = 'mysql_native_password' - - def prepare_password(self): - """Prepares and returns password as native MySQL 4.1+ password""" - if not self._auth_data: - raise errors.InterfaceError("Missing authentication data (seed)") - - if not self._password: - return b'' - password = self._password - - if isstr(self._password): - password = self._password.encode('utf-8') - else: - password = self._password - - if PY2: - password = buffer(password) # pylint: disable=E0602 - try: - auth_data = buffer(self._auth_data) # pylint: disable=E0602 - except TypeError: - raise errors.InterfaceError("Authentication data incorrect") - else: - password = password - auth_data = self._auth_data - - hash4 = None - try: - hash1 = sha1(password).digest() - hash2 = sha1(hash1).digest() - hash3 = sha1(auth_data + hash2).digest() - if PY2: - xored = [ord(h1) ^ ord(h3) for (h1, h3) in zip(hash1, hash3)] - else: - xored = [h1 ^ h3 for (h1, h3) in zip(hash1, hash3)] - hash4 = struct.pack('20B', *xored) - except Exception as exc: - raise errors.InterfaceError( - "Failed scrambling password; {0}".format(exc)) - - return hash4 - - -class MySQLClearPasswordAuthPlugin(BaseAuthPlugin): - """Class implementing the MySQL Clear Password authentication plugin""" - - requires_ssl = True - plugin_name = 'mysql_clear_password' - - def prepare_password(self): - """Returns password as as clear text""" - if not self._password: - return b'\x00' - password = self._password - - if PY2: - if isinstance(password, unicode): # pylint: disable=E0602 - password = password.encode('utf8') - elif isinstance(password, str): - password = password.encode('utf8') - - return password + b'\x00' - - -class MySQLSHA256PasswordAuthPlugin(BaseAuthPlugin): - """Class implementing the MySQL SHA256 authentication plugin - - Note that encrypting using RSA is not supported since the Python - Standard Library does not provide this OpenSSL functionality. - """ - - requires_ssl = True - plugin_name = 'sha256_password' - - def prepare_password(self): - """Returns password as as clear text""" - if not self._password: - return b'\x00' - password = self._password - - if PY2: - if isinstance(password, unicode): # pylint: disable=E0602 - password = password.encode('utf8') - elif isinstance(password, str): - password = password.encode('utf8') - - return password + b'\x00' - - -class MySQLCachingSHA2PasswordAuthPlugin(BaseAuthPlugin): - """Class implementing the MySQL caching_sha2_password authentication plugin - - Note that encrypting using RSA is not supported since the Python - Standard Library does not provide this OpenSSL functionality. - """ - requires_ssl = False - plugin_name = 'caching_sha2_password' - perform_full_authentication = 4 - fast_auth_success = 3 - - def _scramble(self): - """ Returns a scramble of the password using a Nonce sent by the - server. - - The scramble is of the form: - XOR(SHA2(password), SHA2(SHA2(SHA2(password)), Nonce)) - """ - if not self._auth_data: - raise errors.InterfaceError("Missing authentication data (seed)") - - if not self._password: - return b'' - - password = self._password.encode('utf-8') \ - if isinstance(self._password, UNICODE_TYPES) else self._password - - if PY2: - password = buffer(password) # pylint: disable=E0602 - try: - auth_data = buffer(self._auth_data) # pylint: disable=E0602 - except TypeError: - raise errors.InterfaceError("Authentication data incorrect") - else: - password = password - auth_data = self._auth_data - - hash1 = sha256(password).digest() - hash2 = sha256() - hash2.update(sha256(hash1).digest()) - hash2.update(auth_data) - hash2 = hash2.digest() - if PY2: - xored = [ord(h1) ^ ord(h2) for (h1, h2) in zip(hash1, hash2)] - else: - xored = [h1 ^ h2 for (h1, h2) in zip(hash1, hash2)] - hash3 = struct.pack('32B', *xored) - - return hash3 - - def prepare_password(self): - if len(self._auth_data) > 1: - return self._scramble() - elif self._auth_data[0] == self.perform_full_authentication: - return self._full_authentication() - return None - - def _full_authentication(self): - """Returns password as as clear text""" - if not self._ssl_enabled: - raise errors.InterfaceError("{name} requires SSL".format( - name=self.plugin_name)) - - if not self._password: - return b'\x00' - password = self._password - - if PY2: - if isinstance(password, unicode): # pylint: disable=E0602 - password = password.encode('utf8') - elif isinstance(password, str): - password = password.encode('utf8') - - return password + b'\x00' - - -def get_auth_plugin(plugin_name): - """Return authentication class based on plugin name - - This function returns the class for the authentication plugin plugin_name. - The returned class is a subclass of BaseAuthPlugin. - - Raises errors.NotSupportedError when plugin_name is not supported. - - Returns subclass of BaseAuthPlugin. - """ - for authclass in BaseAuthPlugin.__subclasses__(): # pylint: disable=E1101 - if authclass.plugin_name == plugin_name: - return authclass - - raise errors.NotSupportedError( - "Authentication plugin '{0}' is not supported".format(plugin_name)) diff --git a/modules/mysql-connector-python/mysql/connector/catch23.py b/modules/mysql-connector-python/mysql/connector/catch23.py deleted file mode 100644 index 315241460..000000000 --- a/modules/mysql-connector-python/mysql/connector/catch23.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Python v2 to v3 migration module""" - -from decimal import Decimal -import struct -import sys - -from .custom_types import HexLiteral - -# pylint: disable=E0602,E1103 - -PY2 = sys.version_info[0] == 2 - -if PY2: - NUMERIC_TYPES = (int, float, Decimal, HexLiteral, long) - INT_TYPES = (int, long) - UNICODE_TYPES = (unicode,) - STRING_TYPES = (str, unicode) - BYTE_TYPES = (bytearray,) -else: - NUMERIC_TYPES = (int, float, Decimal, HexLiteral) - INT_TYPES = (int,) - UNICODE_TYPES = (str,) - STRING_TYPES = (str,) - BYTE_TYPES = (bytearray, bytes) - - -def init_bytearray(payload=b'', encoding='utf-8'): - """Initializes a bytearray from the payload""" - if isinstance(payload, bytearray): - return payload - - if PY2: - return bytearray(payload) - - if isinstance(payload, int): - return bytearray(payload) - elif not isinstance(payload, bytes): - try: - return bytearray(payload.encode(encoding=encoding)) - except AttributeError: - raise ValueError("payload must be a str or bytes") - - return bytearray(payload) - - -def isstr(obj): - """Returns whether a variable is a string""" - if PY2: - return isinstance(obj, basestring) - return isinstance(obj, str) - -def isunicode(obj): - """Returns whether a variable is a of unicode type""" - if PY2: - return isinstance(obj, unicode) - return isinstance(obj, str) - - -if PY2: - def struct_unpack(fmt, buf): - """Wrapper around struct.unpack handling buffer as bytes and strings""" - if isinstance(buf, (bytearray, bytes)): - return struct.unpack_from(fmt, buffer(buf)) - return struct.unpack_from(fmt, buf) -else: - struct_unpack = struct.unpack # pylint: disable=C0103 - - -def make_abc(base_class): - """Decorator used to create a abstract base class - - We use this decorator to create abstract base classes instead of - using the abc-module. The decorator makes it possible to do the - same in both Python v2 and v3 code. - """ - def wrapper(class_): - """Wrapper""" - attrs = class_.__dict__.copy() - for attr in '__dict__', '__weakref__': - attrs.pop(attr, None) # ignore missing attributes - - bases = class_.__bases__ - if PY2: - attrs['__metaclass__'] = class_ - else: - bases = (class_,) + bases - return base_class(class_.__name__, bases, attrs) - return wrapper diff --git a/modules/mysql-connector-python/mysql/connector/charsets.py b/modules/mysql-connector-python/mysql/connector/charsets.py deleted file mode 100644 index 76ebaeb7e..000000000 --- a/modules/mysql-connector-python/mysql/connector/charsets.py +++ /dev/null @@ -1,350 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -# This file was auto-generated. -_GENERATED_ON = '2019-04-29' -_MYSQL_VERSION = (8, 0, 17) - -"""This module contains the MySQL Server Character Sets""" - -MYSQL_CHARACTER_SETS = [ - # (character set name, collation, default) - None, - ("big5", "big5_chinese_ci", True), # 1 - ("latin2", "latin2_czech_cs", False), # 2 - ("dec8", "dec8_swedish_ci", True), # 3 - ("cp850", "cp850_general_ci", True), # 4 - ("latin1", "latin1_german1_ci", False), # 5 - ("hp8", "hp8_english_ci", True), # 6 - ("koi8r", "koi8r_general_ci", True), # 7 - ("latin1", "latin1_swedish_ci", True), # 8 - ("latin2", "latin2_general_ci", True), # 9 - ("swe7", "swe7_swedish_ci", True), # 10 - ("ascii", "ascii_general_ci", True), # 11 - ("ujis", "ujis_japanese_ci", True), # 12 - ("sjis", "sjis_japanese_ci", True), # 13 - ("cp1251", "cp1251_bulgarian_ci", False), # 14 - ("latin1", "latin1_danish_ci", False), # 15 - ("hebrew", "hebrew_general_ci", True), # 16 - None, - ("tis620", "tis620_thai_ci", True), # 18 - ("euckr", "euckr_korean_ci", True), # 19 - ("latin7", "latin7_estonian_cs", False), # 20 - ("latin2", "latin2_hungarian_ci", False), # 21 - ("koi8u", "koi8u_general_ci", True), # 22 - ("cp1251", "cp1251_ukrainian_ci", False), # 23 - ("gb2312", "gb2312_chinese_ci", True), # 24 - ("greek", "greek_general_ci", True), # 25 - ("cp1250", "cp1250_general_ci", True), # 26 - ("latin2", "latin2_croatian_ci", False), # 27 - ("gbk", "gbk_chinese_ci", True), # 28 - ("cp1257", "cp1257_lithuanian_ci", False), # 29 - ("latin5", "latin5_turkish_ci", True), # 30 - ("latin1", "latin1_german2_ci", False), # 31 - ("armscii8", "armscii8_general_ci", True), # 32 - ("utf8", "utf8_general_ci", True), # 33 - ("cp1250", "cp1250_czech_cs", False), # 34 - ("ucs2", "ucs2_general_ci", True), # 35 - ("cp866", "cp866_general_ci", True), # 36 - ("keybcs2", "keybcs2_general_ci", True), # 37 - ("macce", "macce_general_ci", True), # 38 - ("macroman", "macroman_general_ci", True), # 39 - ("cp852", "cp852_general_ci", True), # 40 - ("latin7", "latin7_general_ci", True), # 41 - ("latin7", "latin7_general_cs", False), # 42 - ("macce", "macce_bin", False), # 43 - ("cp1250", "cp1250_croatian_ci", False), # 44 - ("utf8mb4", "utf8mb4_general_ci", False), # 45 - ("utf8mb4", "utf8mb4_bin", False), # 46 - ("latin1", "latin1_bin", False), # 47 - ("latin1", "latin1_general_ci", False), # 48 - ("latin1", "latin1_general_cs", False), # 49 - ("cp1251", "cp1251_bin", False), # 50 - ("cp1251", "cp1251_general_ci", True), # 51 - ("cp1251", "cp1251_general_cs", False), # 52 - ("macroman", "macroman_bin", False), # 53 - ("utf16", "utf16_general_ci", True), # 54 - ("utf16", "utf16_bin", False), # 55 - ("utf16le", "utf16le_general_ci", True), # 56 - ("cp1256", "cp1256_general_ci", True), # 57 - ("cp1257", "cp1257_bin", False), # 58 - ("cp1257", "cp1257_general_ci", True), # 59 - ("utf32", "utf32_general_ci", True), # 60 - ("utf32", "utf32_bin", False), # 61 - ("utf16le", "utf16le_bin", False), # 62 - ("binary", "binary", True), # 63 - ("armscii8", "armscii8_bin", False), # 64 - ("ascii", "ascii_bin", False), # 65 - ("cp1250", "cp1250_bin", False), # 66 - ("cp1256", "cp1256_bin", False), # 67 - ("cp866", "cp866_bin", False), # 68 - ("dec8", "dec8_bin", False), # 69 - ("greek", "greek_bin", False), # 70 - ("hebrew", "hebrew_bin", False), # 71 - ("hp8", "hp8_bin", False), # 72 - ("keybcs2", "keybcs2_bin", False), # 73 - ("koi8r", "koi8r_bin", False), # 74 - ("koi8u", "koi8u_bin", False), # 75 - ("utf8", "utf8_tolower_ci", False), # 76 - ("latin2", "latin2_bin", False), # 77 - ("latin5", "latin5_bin", False), # 78 - ("latin7", "latin7_bin", False), # 79 - ("cp850", "cp850_bin", False), # 80 - ("cp852", "cp852_bin", False), # 81 - ("swe7", "swe7_bin", False), # 82 - ("utf8", "utf8_bin", False), # 83 - ("big5", "big5_bin", False), # 84 - ("euckr", "euckr_bin", False), # 85 - ("gb2312", "gb2312_bin", False), # 86 - ("gbk", "gbk_bin", False), # 87 - ("sjis", "sjis_bin", False), # 88 - ("tis620", "tis620_bin", False), # 89 - ("ucs2", "ucs2_bin", False), # 90 - ("ujis", "ujis_bin", False), # 91 - ("geostd8", "geostd8_general_ci", True), # 92 - ("geostd8", "geostd8_bin", False), # 93 - ("latin1", "latin1_spanish_ci", False), # 94 - ("cp932", "cp932_japanese_ci", True), # 95 - ("cp932", "cp932_bin", False), # 96 - ("eucjpms", "eucjpms_japanese_ci", True), # 97 - ("eucjpms", "eucjpms_bin", False), # 98 - ("cp1250", "cp1250_polish_ci", False), # 99 - None, - ("utf16", "utf16_unicode_ci", False), # 101 - ("utf16", "utf16_icelandic_ci", False), # 102 - ("utf16", "utf16_latvian_ci", False), # 103 - ("utf16", "utf16_romanian_ci", False), # 104 - ("utf16", "utf16_slovenian_ci", False), # 105 - ("utf16", "utf16_polish_ci", False), # 106 - ("utf16", "utf16_estonian_ci", False), # 107 - ("utf16", "utf16_spanish_ci", False), # 108 - ("utf16", "utf16_swedish_ci", False), # 109 - ("utf16", "utf16_turkish_ci", False), # 110 - ("utf16", "utf16_czech_ci", False), # 111 - ("utf16", "utf16_danish_ci", False), # 112 - ("utf16", "utf16_lithuanian_ci", False), # 113 - ("utf16", "utf16_slovak_ci", False), # 114 - ("utf16", "utf16_spanish2_ci", False), # 115 - ("utf16", "utf16_roman_ci", False), # 116 - ("utf16", "utf16_persian_ci", False), # 117 - ("utf16", "utf16_esperanto_ci", False), # 118 - ("utf16", "utf16_hungarian_ci", False), # 119 - ("utf16", "utf16_sinhala_ci", False), # 120 - ("utf16", "utf16_german2_ci", False), # 121 - ("utf16", "utf16_croatian_ci", False), # 122 - ("utf16", "utf16_unicode_520_ci", False), # 123 - ("utf16", "utf16_vietnamese_ci", False), # 124 - None, - None, - None, - ("ucs2", "ucs2_unicode_ci", False), # 128 - ("ucs2", "ucs2_icelandic_ci", False), # 129 - ("ucs2", "ucs2_latvian_ci", False), # 130 - ("ucs2", "ucs2_romanian_ci", False), # 131 - ("ucs2", "ucs2_slovenian_ci", False), # 132 - ("ucs2", "ucs2_polish_ci", False), # 133 - ("ucs2", "ucs2_estonian_ci", False), # 134 - ("ucs2", "ucs2_spanish_ci", False), # 135 - ("ucs2", "ucs2_swedish_ci", False), # 136 - ("ucs2", "ucs2_turkish_ci", False), # 137 - ("ucs2", "ucs2_czech_ci", False), # 138 - ("ucs2", "ucs2_danish_ci", False), # 139 - ("ucs2", "ucs2_lithuanian_ci", False), # 140 - ("ucs2", "ucs2_slovak_ci", False), # 141 - ("ucs2", "ucs2_spanish2_ci", False), # 142 - ("ucs2", "ucs2_roman_ci", False), # 143 - ("ucs2", "ucs2_persian_ci", False), # 144 - ("ucs2", "ucs2_esperanto_ci", False), # 145 - ("ucs2", "ucs2_hungarian_ci", False), # 146 - ("ucs2", "ucs2_sinhala_ci", False), # 147 - ("ucs2", "ucs2_german2_ci", False), # 148 - ("ucs2", "ucs2_croatian_ci", False), # 149 - ("ucs2", "ucs2_unicode_520_ci", False), # 150 - ("ucs2", "ucs2_vietnamese_ci", False), # 151 - None, - None, - None, - None, - None, - None, - None, - ("ucs2", "ucs2_general_mysql500_ci", False), # 159 - ("utf32", "utf32_unicode_ci", False), # 160 - ("utf32", "utf32_icelandic_ci", False), # 161 - ("utf32", "utf32_latvian_ci", False), # 162 - ("utf32", "utf32_romanian_ci", False), # 163 - ("utf32", "utf32_slovenian_ci", False), # 164 - ("utf32", "utf32_polish_ci", False), # 165 - ("utf32", "utf32_estonian_ci", False), # 166 - ("utf32", "utf32_spanish_ci", False), # 167 - ("utf32", "utf32_swedish_ci", False), # 168 - ("utf32", "utf32_turkish_ci", False), # 169 - ("utf32", "utf32_czech_ci", False), # 170 - ("utf32", "utf32_danish_ci", False), # 171 - ("utf32", "utf32_lithuanian_ci", False), # 172 - ("utf32", "utf32_slovak_ci", False), # 173 - ("utf32", "utf32_spanish2_ci", False), # 174 - ("utf32", "utf32_roman_ci", False), # 175 - ("utf32", "utf32_persian_ci", False), # 176 - ("utf32", "utf32_esperanto_ci", False), # 177 - ("utf32", "utf32_hungarian_ci", False), # 178 - ("utf32", "utf32_sinhala_ci", False), # 179 - ("utf32", "utf32_german2_ci", False), # 180 - ("utf32", "utf32_croatian_ci", False), # 181 - ("utf32", "utf32_unicode_520_ci", False), # 182 - ("utf32", "utf32_vietnamese_ci", False), # 183 - None, - None, - None, - None, - None, - None, - None, - None, - ("utf8", "utf8_unicode_ci", False), # 192 - ("utf8", "utf8_icelandic_ci", False), # 193 - ("utf8", "utf8_latvian_ci", False), # 194 - ("utf8", "utf8_romanian_ci", False), # 195 - ("utf8", "utf8_slovenian_ci", False), # 196 - ("utf8", "utf8_polish_ci", False), # 197 - ("utf8", "utf8_estonian_ci", False), # 198 - ("utf8", "utf8_spanish_ci", False), # 199 - ("utf8", "utf8_swedish_ci", False), # 200 - ("utf8", "utf8_turkish_ci", False), # 201 - ("utf8", "utf8_czech_ci", False), # 202 - ("utf8", "utf8_danish_ci", False), # 203 - ("utf8", "utf8_lithuanian_ci", False), # 204 - ("utf8", "utf8_slovak_ci", False), # 205 - ("utf8", "utf8_spanish2_ci", False), # 206 - ("utf8", "utf8_roman_ci", False), # 207 - ("utf8", "utf8_persian_ci", False), # 208 - ("utf8", "utf8_esperanto_ci", False), # 209 - ("utf8", "utf8_hungarian_ci", False), # 210 - ("utf8", "utf8_sinhala_ci", False), # 211 - ("utf8", "utf8_german2_ci", False), # 212 - ("utf8", "utf8_croatian_ci", False), # 213 - ("utf8", "utf8_unicode_520_ci", False), # 214 - ("utf8", "utf8_vietnamese_ci", False), # 215 - None, - None, - None, - None, - None, - None, - None, - ("utf8", "utf8_general_mysql500_ci", False), # 223 - ("utf8mb4", "utf8mb4_unicode_ci", False), # 224 - ("utf8mb4", "utf8mb4_icelandic_ci", False), # 225 - ("utf8mb4", "utf8mb4_latvian_ci", False), # 226 - ("utf8mb4", "utf8mb4_romanian_ci", False), # 227 - ("utf8mb4", "utf8mb4_slovenian_ci", False), # 228 - ("utf8mb4", "utf8mb4_polish_ci", False), # 229 - ("utf8mb4", "utf8mb4_estonian_ci", False), # 230 - ("utf8mb4", "utf8mb4_spanish_ci", False), # 231 - ("utf8mb4", "utf8mb4_swedish_ci", False), # 232 - ("utf8mb4", "utf8mb4_turkish_ci", False), # 233 - ("utf8mb4", "utf8mb4_czech_ci", False), # 234 - ("utf8mb4", "utf8mb4_danish_ci", False), # 235 - ("utf8mb4", "utf8mb4_lithuanian_ci", False), # 236 - ("utf8mb4", "utf8mb4_slovak_ci", False), # 237 - ("utf8mb4", "utf8mb4_spanish2_ci", False), # 238 - ("utf8mb4", "utf8mb4_roman_ci", False), # 239 - ("utf8mb4", "utf8mb4_persian_ci", False), # 240 - ("utf8mb4", "utf8mb4_esperanto_ci", False), # 241 - ("utf8mb4", "utf8mb4_hungarian_ci", False), # 242 - ("utf8mb4", "utf8mb4_sinhala_ci", False), # 243 - ("utf8mb4", "utf8mb4_german2_ci", False), # 244 - ("utf8mb4", "utf8mb4_croatian_ci", False), # 245 - ("utf8mb4", "utf8mb4_unicode_520_ci", False), # 246 - ("utf8mb4", "utf8mb4_vietnamese_ci", False), # 247 - ("gb18030", "gb18030_chinese_ci", True), # 248 - ("gb18030", "gb18030_bin", False), # 249 - ("gb18030", "gb18030_unicode_520_ci", False), # 250 - None, - None, - None, - None, - ("utf8mb4", "utf8mb4_0900_ai_ci", True), # 255 - ("utf8mb4", "utf8mb4_de_pb_0900_ai_ci", False), # 256 - ("utf8mb4", "utf8mb4_is_0900_ai_ci", False), # 257 - ("utf8mb4", "utf8mb4_lv_0900_ai_ci", False), # 258 - ("utf8mb4", "utf8mb4_ro_0900_ai_ci", False), # 259 - ("utf8mb4", "utf8mb4_sl_0900_ai_ci", False), # 260 - ("utf8mb4", "utf8mb4_pl_0900_ai_ci", False), # 261 - ("utf8mb4", "utf8mb4_et_0900_ai_ci", False), # 262 - ("utf8mb4", "utf8mb4_es_0900_ai_ci", False), # 263 - ("utf8mb4", "utf8mb4_sv_0900_ai_ci", False), # 264 - ("utf8mb4", "utf8mb4_tr_0900_ai_ci", False), # 265 - ("utf8mb4", "utf8mb4_cs_0900_ai_ci", False), # 266 - ("utf8mb4", "utf8mb4_da_0900_ai_ci", False), # 267 - ("utf8mb4", "utf8mb4_lt_0900_ai_ci", False), # 268 - ("utf8mb4", "utf8mb4_sk_0900_ai_ci", False), # 269 - ("utf8mb4", "utf8mb4_es_trad_0900_ai_ci", False), # 270 - ("utf8mb4", "utf8mb4_la_0900_ai_ci", False), # 271 - None, - ("utf8mb4", "utf8mb4_eo_0900_ai_ci", False), # 273 - ("utf8mb4", "utf8mb4_hu_0900_ai_ci", False), # 274 - ("utf8mb4", "utf8mb4_hr_0900_ai_ci", False), # 275 - None, - ("utf8mb4", "utf8mb4_vi_0900_ai_ci", False), # 277 - ("utf8mb4", "utf8mb4_0900_as_cs", False), # 278 - ("utf8mb4", "utf8mb4_de_pb_0900_as_cs", False), # 279 - ("utf8mb4", "utf8mb4_is_0900_as_cs", False), # 280 - ("utf8mb4", "utf8mb4_lv_0900_as_cs", False), # 281 - ("utf8mb4", "utf8mb4_ro_0900_as_cs", False), # 282 - ("utf8mb4", "utf8mb4_sl_0900_as_cs", False), # 283 - ("utf8mb4", "utf8mb4_pl_0900_as_cs", False), # 284 - ("utf8mb4", "utf8mb4_et_0900_as_cs", False), # 285 - ("utf8mb4", "utf8mb4_es_0900_as_cs", False), # 286 - ("utf8mb4", "utf8mb4_sv_0900_as_cs", False), # 287 - ("utf8mb4", "utf8mb4_tr_0900_as_cs", False), # 288 - ("utf8mb4", "utf8mb4_cs_0900_as_cs", False), # 289 - ("utf8mb4", "utf8mb4_da_0900_as_cs", False), # 290 - ("utf8mb4", "utf8mb4_lt_0900_as_cs", False), # 291 - ("utf8mb4", "utf8mb4_sk_0900_as_cs", False), # 292 - ("utf8mb4", "utf8mb4_es_trad_0900_as_cs", False), # 293 - ("utf8mb4", "utf8mb4_la_0900_as_cs", False), # 294 - None, - ("utf8mb4", "utf8mb4_eo_0900_as_cs", False), # 296 - ("utf8mb4", "utf8mb4_hu_0900_as_cs", False), # 297 - ("utf8mb4", "utf8mb4_hr_0900_as_cs", False), # 298 - None, - ("utf8mb4", "utf8mb4_vi_0900_as_cs", False), # 300 - None, - None, - ("utf8mb4", "utf8mb4_ja_0900_as_cs", False), # 303 - ("utf8mb4", "utf8mb4_ja_0900_as_cs_ks", False), # 304 - ("utf8mb4", "utf8mb4_0900_as_ci", False), # 305 - ("utf8mb4", "utf8mb4_ru_0900_ai_ci", False), # 306 - ("utf8mb4", "utf8mb4_ru_0900_as_cs", False), # 307 - ("utf8mb4", "utf8mb4_zh_0900_as_cs", False), # 308 - ("utf8mb4", "utf8mb4_0900_bin", False), # 309 -] - diff --git a/modules/mysql-connector-python/mysql/connector/connection.py b/modules/mysql-connector-python/mysql/connector/connection.py deleted file mode 100644 index a7b9c12e1..000000000 --- a/modules/mysql-connector-python/mysql/connector/connection.py +++ /dev/null @@ -1,1169 +0,0 @@ -# Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Implementing communication with MySQL servers. -""" - -from io import IOBase -import os -import platform -import socket -import time - -from .authentication import get_auth_plugin -from .catch23 import PY2, isstr, UNICODE_TYPES -from .constants import ( - ClientFlag, ServerCmd, ServerFlag, - flag_is_set, ShutdownType, NET_BUFFER_LENGTH -) - -from . import errors, version -from .conversion import MySQLConverter -from .cursor import ( - CursorBase, MySQLCursor, MySQLCursorRaw, - MySQLCursorBuffered, MySQLCursorBufferedRaw, MySQLCursorPrepared, - MySQLCursorDict, MySQLCursorBufferedDict, MySQLCursorNamedTuple, - MySQLCursorBufferedNamedTuple) -from .network import MySQLUnixSocket, MySQLTCPSocket -from .protocol import MySQLProtocol -from .utils import int4store -from .abstracts import MySQLConnectionAbstract - - -class MySQLConnection(MySQLConnectionAbstract): - """Connection to a MySQL Server""" - def __init__(self, *args, **kwargs): - self._protocol = None - self._socket = None - self._handshake = None - super(MySQLConnection, self).__init__(*args, **kwargs) - - self._converter_class = MySQLConverter - - self._client_flags = ClientFlag.get_default() - self._charset_id = 45 - self._sql_mode = None - self._time_zone = None - self._autocommit = False - - self._user = '' - self._password = '' - self._database = '' - self._host = '127.0.0.1' - self._port = 3306 - self._unix_socket = None - self._client_host = '' - self._client_port = 0 - self._ssl = {} - self._force_ipv6 = False - - self._use_unicode = True - self._get_warnings = False - self._raise_on_warnings = False - self._buffered = False - self._unread_result = False - self._have_next_result = False - self._raw = False - self._in_transaction = False - - self._prepared_statements = None - - self._ssl_active = False - self._auth_plugin = None - self._pool_config_version = None - - self._columns_desc = [] - - if kwargs: - try: - self.connect(**kwargs) - except: - # Tidy-up underlying socket on failure - self.close() - self._socket = None - raise - - def _add_default_conn_attrs(self): - """Add the default connection attributes.""" - if os.name == "nt": - if "64" in platform.architecture()[0]: - platform_arch = "x86_64" - elif "32" in platform.architecture()[0]: - platform_arch = "i386" - else: - platform_arch = platform.architecture() - os_ver = "Windows-{}".format(platform.win32_ver()[1]) - else: - platform_arch = platform.machine() - if platform.system() == "Darwin": - os_ver = "{}-{}".format("macOS", platform.mac_ver()[0]) - else: - os_ver = "-".join(platform.linux_distribution()[0:2]) # pylint: disable=W1505 - - license_chunks = version.LICENSE.split(" ") - if license_chunks[0] == "GPLv2": - client_license = "GPL-2.0" - else: - client_license = "Commercial" - default_conn_attrs = { - "_pid": str(os.getpid()), - "_platform": platform_arch, - "_source_host": socket.gethostname(), - "_client_name": "mysql-connector-python", - "_client_license": client_license, - "_client_version": ".".join([str(x) for x in version.VERSION[0:3]]), - "_os": os_ver} - - self._conn_attrs.update((default_conn_attrs)) - - def _do_handshake(self): - """Get the handshake from the MySQL server""" - packet = self._socket.recv() - if packet[4] == 255: - raise errors.get_exception(packet) - - self._handshake = None - try: - handshake = self._protocol.parse_handshake(packet) - except Exception as err: - # pylint: disable=E1101 - raise errors.get_mysql_exception(msg=err.msg, errno=err.errno, - sqlstate=err.sqlstate) - - self._server_version = self._check_server_version( - handshake['server_version_original']) - - if not handshake['capabilities'] & ClientFlag.SSL: - self._client_flags &= ~ClientFlag.SSL - if self._ssl.get('verify_cert'): - raise errors.InterfaceError("SSL is required but the server " - "doesn't support it", errno=2026) - elif not self._ssl_disabled: - self._client_flags |= ClientFlag.SSL - - if handshake['capabilities'] & ClientFlag.PLUGIN_AUTH: - self.set_client_flags([ClientFlag.PLUGIN_AUTH]) - - self._handshake = handshake - - def _do_auth(self, username=None, password=None, database=None, - client_flags=0, charset=45, ssl_options=None, conn_attrs=None): - """Authenticate with the MySQL server - - Authentication happens in two parts. We first send a response to the - handshake. The MySQL server will then send either an AuthSwitchRequest - or an error packet. - - Raises NotSupportedError when we get the old, insecure password - reply back. Raises any error coming from MySQL. - """ - self._ssl_active = False - if client_flags & ClientFlag.SSL: - packet = self._protocol.make_auth_ssl(charset=charset, - client_flags=client_flags) - self._socket.send(packet) - self._socket.switch_to_ssl(ssl_options.get('ca'), - ssl_options.get('cert'), - ssl_options.get('key'), - ssl_options.get('verify_cert') or False, - ssl_options.get('verify_identity') or - False, - ssl_options.get('cipher'), - ssl_options.get('version', None)) - self._ssl_active = True - - packet = self._protocol.make_auth( - handshake=self._handshake, - username=username, password=password, database=database, - charset=charset, client_flags=client_flags, - ssl_enabled=self._ssl_active, - auth_plugin=self._auth_plugin, - conn_attrs=conn_attrs) - self._socket.send(packet) - self._auth_switch_request(username, password) - - if not (client_flags & ClientFlag.CONNECT_WITH_DB) and database: - self.cmd_init_db(database) - - return True - - def _auth_switch_request(self, username=None, password=None): - """Handle second part of authentication - - Raises NotSupportedError when we get the old, insecure password - reply back. Raises any error coming from MySQL. - """ - auth = None - new_auth_plugin = self._auth_plugin or self._handshake["auth_plugin"] - packet = self._socket.recv() - if packet[4] == 254 and len(packet) == 5: - raise errors.NotSupportedError( - "Authentication with old (insecure) passwords " - "is not supported. For more information, lookup " - "Password Hashing in the latest MySQL manual") - elif packet[4] == 254: - # AuthSwitchRequest - (new_auth_plugin, - auth_data) = self._protocol.parse_auth_switch_request(packet) - auth = get_auth_plugin(new_auth_plugin)( - auth_data, password=password, ssl_enabled=self._ssl_active) - response = auth.auth_response() - self._socket.send(response) - packet = self._socket.recv() - - if packet[4] == 1: - auth_data = self._protocol.parse_auth_more_data(packet) - auth = get_auth_plugin(new_auth_plugin)( - auth_data, password=password, ssl_enabled=self._ssl_active) - if new_auth_plugin == "caching_sha2_password": - response = auth.auth_response() - if response: - self._socket.send(response) - packet = self._socket.recv() - - if packet[4] == 0: - return self._handle_ok(packet) - elif packet[4] == 255: - raise errors.get_exception(packet) - return None - - def _get_connection(self, prtcls=None): - """Get connection based on configuration - - This method will return the appropriated connection object using - the connection parameters. - - Returns subclass of MySQLBaseSocket. - """ - conn = None - if self.unix_socket and os.name != 'nt': - conn = MySQLUnixSocket(unix_socket=self.unix_socket) - else: - conn = MySQLTCPSocket(host=self.server_host, - port=self.server_port, - force_ipv6=self._force_ipv6) - - conn.set_connection_timeout(self._connection_timeout) - return conn - - def _open_connection(self): - """Open the connection to the MySQL server - - This method sets up and opens the connection to the MySQL server. - - Raises on errors. - """ - self._protocol = MySQLProtocol() - self._socket = self._get_connection() - try: - self._socket.open_connection() - self._do_handshake() - self._do_auth(self._user, self._password, - self._database, self._client_flags, self._charset_id, - self._ssl, self._conn_attrs) - self.set_converter_class(self._converter_class) - if self._client_flags & ClientFlag.COMPRESS: - self._socket.recv = self._socket.recv_compressed - self._socket.send = self._socket.send_compressed - except: - # close socket - self.close() - raise - - def shutdown(self): - """Shut down connection to MySQL Server. - """ - if not self._socket: - return - - try: - self._socket.shutdown() - except (AttributeError, errors.Error): - pass # Getting an exception would mean we are disconnected. - - def close(self): - """Disconnect from the MySQL server""" - if not self._socket: - return - - try: - self.cmd_quit() - except (AttributeError, errors.Error): - pass # Getting an exception would mean we are disconnected. - self._socket.close_connection() - - disconnect = close - - def _send_cmd(self, command, argument=None, packet_number=0, packet=None, - expect_response=True, compressed_packet_number=0): - """Send a command to the MySQL server - - This method sends a command with an optional argument. - If packet is not None, it will be sent and the argument will be - ignored. - - The packet_number is optional and should usually not be used. - - Some commands might not result in the MySQL server returning - a response. If a command does not return anything, you should - set expect_response to False. The _send_cmd method will then - return None instead of a MySQL packet. - - Returns a MySQL packet or None. - """ - self.handle_unread_result() - - try: - self._socket.send( - self._protocol.make_command(command, packet or argument), - packet_number, compressed_packet_number) - except AttributeError: - raise errors.OperationalError("MySQL Connection not available.") - - if not expect_response: - return None - return self._socket.recv() - - def _send_data(self, data_file, send_empty_packet=False): - """Send data to the MySQL server - - This method accepts a file-like object and sends its data - as is to the MySQL server. If the send_empty_packet is - True, it will send an extra empty package (for example - when using LOAD LOCAL DATA INFILE). - - Returns a MySQL packet. - """ - self.handle_unread_result() - - if not hasattr(data_file, 'read'): - raise ValueError("expecting a file-like object") - - try: - buf = data_file.read(NET_BUFFER_LENGTH - 16) - while buf: - self._socket.send(buf) - buf = data_file.read(NET_BUFFER_LENGTH - 16) - except AttributeError: - raise errors.OperationalError("MySQL Connection not available.") - - if send_empty_packet: - try: - self._socket.send(b'') - except AttributeError: - raise errors.OperationalError( - "MySQL Connection not available.") - - return self._socket.recv() - - def _handle_server_status(self, flags): - """Handle the server flags found in MySQL packets - - This method handles the server flags send by MySQL OK and EOF - packets. It, for example, checks whether there exists more result - sets or whether there is an ongoing transaction. - """ - self._have_next_result = flag_is_set(ServerFlag.MORE_RESULTS_EXISTS, - flags) - self._in_transaction = flag_is_set(ServerFlag.STATUS_IN_TRANS, flags) - - @property - def in_transaction(self): - """MySQL session has started a transaction""" - return self._in_transaction - - def _handle_ok(self, packet): - """Handle a MySQL OK packet - - This method handles a MySQL OK packet. When the packet is found to - be an Error packet, an error will be raised. If the packet is neither - an OK or an Error packet, errors.InterfaceError will be raised. - - Returns a dict() - """ - if packet[4] == 0: - ok_pkt = self._protocol.parse_ok(packet) - self._handle_server_status(ok_pkt['status_flag']) - return ok_pkt - elif packet[4] == 255: - raise errors.get_exception(packet) - raise errors.InterfaceError('Expected OK packet') - - def _handle_eof(self, packet): - """Handle a MySQL EOF packet - - This method handles a MySQL EOF packet. When the packet is found to - be an Error packet, an error will be raised. If the packet is neither - and OK or an Error packet, errors.InterfaceError will be raised. - - Returns a dict() - """ - if packet[4] == 254: - eof = self._protocol.parse_eof(packet) - self._handle_server_status(eof['status_flag']) - return eof - elif packet[4] == 255: - raise errors.get_exception(packet) - raise errors.InterfaceError('Expected EOF packet') - - def _handle_load_data_infile(self, filename): - """Handle a LOAD DATA INFILE LOCAL request""" - try: - data_file = open(filename, 'rb') - except IOError: - # Send a empty packet to cancel the operation - try: - self._socket.send(b'') - except AttributeError: - raise errors.OperationalError( - "MySQL Connection not available.") - raise errors.InterfaceError( - "File '{0}' could not be read".format(filename)) - - return self._handle_ok(self._send_data(data_file, - send_empty_packet=True)) - - def _handle_result(self, packet): - """Handle a MySQL Result - - This method handles a MySQL result, for example, after sending the - query command. OK and EOF packets will be handled and returned. If - the packet is an Error packet, an errors.Error-exception will be - raised. - - The dictionary returned of: - - columns: column information - - eof: the EOF-packet information - - Returns a dict() - """ - if not packet or len(packet) < 4: - raise errors.InterfaceError('Empty response') - elif packet[4] == 0: - return self._handle_ok(packet) - elif packet[4] == 251: - if PY2: - filename = str(packet[5:]) - else: - filename = packet[5:].decode() - return self._handle_load_data_infile(filename) - elif packet[4] == 254: - return self._handle_eof(packet) - elif packet[4] == 255: - raise errors.get_exception(packet) - - # We have a text result set - column_count = self._protocol.parse_column_count(packet) - if not column_count or not isinstance(column_count, int): - raise errors.InterfaceError('Illegal result set.') - - self._columns_desc = [None,] * column_count - for i in range(0, column_count): - self._columns_desc[i] = self._protocol.parse_column( - self._socket.recv(), self.python_charset) - - eof = self._handle_eof(self._socket.recv()) - self.unread_result = True - return {'columns': self._columns_desc, 'eof': eof} - - def get_row(self, binary=False, columns=None, raw=None): - """Get the next rows returned by the MySQL server - - This method gets one row from the result set after sending, for - example, the query command. The result is a tuple consisting of the - row and the EOF packet. - If no row was available in the result set, the row data will be None. - - Returns a tuple. - """ - (rows, eof) = self.get_rows(count=1, binary=binary, columns=columns, - raw=raw) - if rows: - return (rows[0], eof) - return (None, eof) - - def get_rows(self, count=None, binary=False, columns=None, raw=None, - prep_stmt=None): - """Get all rows returned by the MySQL server - - This method gets all rows returned by the MySQL server after sending, - for example, the query command. The result is a tuple consisting of - a list of rows and the EOF packet. - - Returns a tuple() - """ - if raw is None: - raw = self._raw - - if not self.unread_result: - raise errors.InternalError("No result set available.") - - try: - if binary: - charset = self.charset - if charset == 'utf8mb4': - charset = 'utf8' - rows = self._protocol.read_binary_result( - self._socket, columns, count, charset) - else: - rows = self._protocol.read_text_result(self._socket, - self._server_version, - count=count) - except errors.Error as err: - self.unread_result = False - raise err - - rows, eof_p = rows - - if not (binary or raw) and self._columns_desc is not None and rows \ - and hasattr(self, 'converter'): - row_to_python = self.converter.row_to_python - rows = [row_to_python(row, self._columns_desc) for row in rows] - - if eof_p is not None: - self._handle_server_status(eof_p['status_flag'] if 'status_flag' in - eof_p else eof_p['server_status']) - self.unread_result = False - - return rows, eof_p - - def consume_results(self): - """Consume results - """ - if self.unread_result: - self.get_rows() - - def cmd_init_db(self, database): - """Change the current database - - This method changes the current (default) database by sending the - INIT_DB command. The result is a dictionary containing the OK packet - information. - - Returns a dict() - """ - return self._handle_ok( - self._send_cmd(ServerCmd.INIT_DB, database.encode('utf-8'))) - - def cmd_query(self, query, raw=False, buffered=False, raw_as_string=False): - """Send a query to the MySQL server - - This method send the query to the MySQL server and returns the result. - - If there was a text result, a tuple will be returned consisting of - the number of columns and a list containing information about these - columns. - - When the query doesn't return a text result, the OK or EOF packet - information as dictionary will be returned. In case the result was - an error, exception errors.Error will be raised. - - Returns a tuple() - """ - if not isinstance(query, bytes): - query = query.encode('utf-8') - result = self._handle_result(self._send_cmd(ServerCmd.QUERY, query)) - - if self._have_next_result: - raise errors.InterfaceError( - 'Use cmd_query_iter for statements with multiple queries.') - - return result - - def cmd_query_iter(self, statements): - """Send one or more statements to the MySQL server - - Similar to the cmd_query method, but instead returns a generator - object to iterate through results. It sends the statements to the - MySQL server and through the iterator you can get the results. - - statement = 'SELECT 1; INSERT INTO t1 VALUES (); SELECT 2' - for result in cnx.cmd_query(statement, iterate=True): - if 'columns' in result: - columns = result['columns'] - rows = cnx.get_rows() - else: - # do something useful with INSERT result - - Returns a generator. - """ - if not isinstance(statements, bytearray): - if isstr(statements) and isinstance(statements, UNICODE_TYPES): - statements = statements.encode('utf8') - statements = bytearray(statements) - - # Handle the first query result - yield self._handle_result(self._send_cmd(ServerCmd.QUERY, statements)) - - # Handle next results, if any - while self._have_next_result: - self.handle_unread_result() - yield self._handle_result(self._socket.recv()) - - def cmd_refresh(self, options): - """Send the Refresh command to the MySQL server - - This method sends the Refresh command to the MySQL server. The options - argument should be a bitwise value using constants.RefreshOption. - Usage example: - RefreshOption = mysql.connector.RefreshOption - refresh = RefreshOption.LOG | RefreshOption.THREADS - cnx.cmd_refresh(refresh) - - The result is a dictionary with the OK packet information. - - Returns a dict() - """ - return self._handle_ok( - self._send_cmd(ServerCmd.REFRESH, int4store(options))) - - def cmd_quit(self): - """Close the current connection with the server - - This method sends the QUIT command to the MySQL server, closing the - current connection. Since the no response can be returned to the - client, cmd_quit() will return the packet it send. - - Returns a str() - """ - self.handle_unread_result() - - packet = self._protocol.make_command(ServerCmd.QUIT) - self._socket.send(packet, 0, 0) - return packet - - def cmd_shutdown(self, shutdown_type=None): - """Shut down the MySQL Server - - This method sends the SHUTDOWN command to the MySQL server and is only - possible if the current user has SUPER privileges. The result is a - dictionary containing the OK packet information. - - Note: Most applications and scripts do not the SUPER privilege. - - Returns a dict() - """ - if shutdown_type: - if not ShutdownType.get_info(shutdown_type): - raise errors.InterfaceError("Invalid shutdown type") - atype = shutdown_type - else: - atype = ShutdownType.SHUTDOWN_DEFAULT - return self._handle_eof(self._send_cmd(ServerCmd.SHUTDOWN, - int4store(atype))) - - def cmd_statistics(self): - """Send the statistics command to the MySQL Server - - This method sends the STATISTICS command to the MySQL server. The - result is a dictionary with various statistical information. - - Returns a dict() - """ - self.handle_unread_result() - - packet = self._protocol.make_command(ServerCmd.STATISTICS) - self._socket.send(packet, 0, 0) - return self._protocol.parse_statistics(self._socket.recv()) - - def cmd_process_kill(self, mysql_pid): - """Kill a MySQL process - - This method send the PROCESS_KILL command to the server along with - the process ID. The result is a dictionary with the OK packet - information. - - Returns a dict() - """ - return self._handle_ok( - self._send_cmd(ServerCmd.PROCESS_KILL, int4store(mysql_pid))) - - def cmd_debug(self): - """Send the DEBUG command - - This method sends the DEBUG command to the MySQL server, which - requires the MySQL user to have SUPER privilege. The output will go - to the MySQL server error log and the result of this method is a - dictionary with EOF packet information. - - Returns a dict() - """ - return self._handle_eof(self._send_cmd(ServerCmd.DEBUG)) - - def cmd_ping(self): - """Send the PING command - - This method sends the PING command to the MySQL server. It is used to - check if the the connection is still valid. The result of this - method is dictionary with OK packet information. - - Returns a dict() - """ - return self._handle_ok(self._send_cmd(ServerCmd.PING)) - - def cmd_change_user(self, username='', password='', database='', - charset=45): - """Change the current logged in user - - This method allows to change the current logged in user information. - The result is a dictionary with OK packet information. - - Returns a dict() - """ - self.handle_unread_result() - - if self._compress: - raise errors.NotSupportedError("Change user is not supported with " - "compression.") - - packet = self._protocol.make_change_user( - handshake=self._handshake, - username=username, password=password, database=database, - charset=charset, client_flags=self._client_flags, - ssl_enabled=self._ssl_active, - auth_plugin=self._auth_plugin) - self._socket.send(packet, 0, 0) - - ok_packet = self._auth_switch_request(username, password) - - try: - if not (self._client_flags & ClientFlag.CONNECT_WITH_DB) \ - and database: - self.cmd_init_db(database) - except: - raise - - self._charset_id = charset - self._post_connection() - - return ok_packet - - @property - def database(self): - """Get the current database""" - return self.info_query("SELECT DATABASE()")[0] - - @database.setter - def database(self, value): # pylint: disable=W0221 - """Set the current database""" - self.cmd_query("USE %s" % value) - - def is_connected(self): - """Reports whether the connection to MySQL Server is available - - This method checks whether the connection to MySQL is available. - It is similar to ping(), but unlike the ping()-method, either True - or False is returned and no exception is raised. - - Returns True or False. - """ - try: - self.cmd_ping() - except: - return False # This method does not raise - return True - - def reset_session(self, user_variables=None, session_variables=None): - """Clears the current active session - - This method resets the session state, if the MySQL server is 5.7.3 - or later active session will be reset without re-authenticating. - For other server versions session will be reset by re-authenticating. - - It is possible to provide a sequence of variables and their values to - be set after clearing the session. This is possible for both user - defined variables and session variables. - This method takes two arguments user_variables and session_variables - which are dictionaries. - - Raises OperationalError if not connected, InternalError if there are - unread results and InterfaceError on errors. - """ - if not self.is_connected(): - raise errors.OperationalError("MySQL Connection not available.") - - try: - self.cmd_reset_connection() - except errors.NotSupportedError: - self.cmd_change_user(self._user, self._password, - self._database, self._charset_id) - - cur = self.cursor() - if user_variables: - for key, value in user_variables.items(): - cur.execute("SET @`{0}` = %s".format(key), (value,)) - if session_variables: - for key, value in session_variables.items(): - cur.execute("SET SESSION `{0}` = %s".format(key), (value,)) - - def reconnect(self, attempts=1, delay=0): - """Attempt to reconnect to the MySQL server - - The argument attempts should be the number of times a reconnect - is tried. The delay argument is the number of seconds to wait between - each retry. - - You may want to set the number of attempts higher and use delay when - you expect the MySQL server to be down for maintenance or when you - expect the network to be temporary unavailable. - - Raises InterfaceError on errors. - """ - counter = 0 - while counter != attempts: - counter = counter + 1 - try: - self.disconnect() - self.connect() - if self.is_connected(): - break - except Exception as err: # pylint: disable=W0703 - if counter == attempts: - msg = "Can not reconnect to MySQL after {0} "\ - "attempt(s): {1}".format(attempts, str(err)) - raise errors.InterfaceError(msg) - if delay > 0: - time.sleep(delay) - - def ping(self, reconnect=False, attempts=1, delay=0): - """Check availability of the MySQL server - - When reconnect is set to True, one or more attempts are made to try - to reconnect to the MySQL server using the reconnect()-method. - - delay is the number of seconds to wait between each retry. - - When the connection is not available, an InterfaceError is raised. Use - the is_connected()-method if you just want to check the connection - without raising an error. - - Raises InterfaceError on errors. - """ - try: - self.cmd_ping() - except: - if reconnect: - self.reconnect(attempts=attempts, delay=delay) - else: - raise errors.InterfaceError("Connection to MySQL is" - " not available.") - - @property - def connection_id(self): - """MySQL connection ID""" - try: - return self._handshake['server_threadid'] - except KeyError: - return None - - def cursor(self, buffered=None, raw=None, prepared=None, cursor_class=None, - dictionary=None, named_tuple=None): - """Instantiates and returns a cursor - - By default, MySQLCursor is returned. Depending on the options - while connecting, a buffered and/or raw cursor is instantiated - instead. Also depending upon the cursor options, rows can be - returned as dictionary or named tuple. - - Dictionary and namedtuple based cursors are available with buffered - output but not raw. - - It is possible to also give a custom cursor through the - cursor_class parameter, but it needs to be a subclass of - mysql.connector.cursor.CursorBase. - - Raises ProgrammingError when cursor_class is not a subclass of - CursorBase. Raises ValueError when cursor is not available. - - Returns a cursor-object - """ - self.handle_unread_result() - - if not self.is_connected(): - raise errors.OperationalError("MySQL Connection not available.") - if cursor_class is not None: - if not issubclass(cursor_class, CursorBase): - raise errors.ProgrammingError( - "Cursor class needs be to subclass of cursor.CursorBase") - return (cursor_class)(self) - - buffered = buffered if buffered is not None else self._buffered - raw = raw if raw is not None else self._raw - - cursor_type = 0 - if buffered is True: - cursor_type |= 1 - if raw is True: - cursor_type |= 2 - if dictionary is True: - cursor_type |= 4 - if named_tuple is True: - cursor_type |= 8 - if prepared is True: - cursor_type |= 16 - - types = { - 0: MySQLCursor, # 0 - 1: MySQLCursorBuffered, - 2: MySQLCursorRaw, - 3: MySQLCursorBufferedRaw, - 4: MySQLCursorDict, - 5: MySQLCursorBufferedDict, - 8: MySQLCursorNamedTuple, - 9: MySQLCursorBufferedNamedTuple, - 16: MySQLCursorPrepared - } - try: - return (types[cursor_type])(self) - except KeyError: - args = ('buffered', 'raw', 'dictionary', 'named_tuple', 'prepared') - raise ValueError('Cursor not available with given criteria: ' + - ', '.join([args[i] for i in range(5) - if cursor_type & (1 << i) != 0])) - - def commit(self): - """Commit current transaction""" - self._execute_query("COMMIT") - - def rollback(self): - """Rollback current transaction""" - if self.unread_result: - self.get_rows() - - self._execute_query("ROLLBACK") - - def _execute_query(self, query): - """Execute a query - - This method simply calls cmd_query() after checking for unread - result. If there are still unread result, an errors.InterfaceError - is raised. Otherwise whatever cmd_query() returns is returned. - - Returns a dict() - """ - self.handle_unread_result() - self.cmd_query(query) - - def info_query(self, query): - """Send a query which only returns 1 row""" - cursor = self.cursor(buffered=True) - cursor.execute(query) - return cursor.fetchone() - - def _handle_binary_ok(self, packet): - """Handle a MySQL Binary Protocol OK packet - - This method handles a MySQL Binary Protocol OK packet. When the - packet is found to be an Error packet, an error will be raised. If - the packet is neither an OK or an Error packet, errors.InterfaceError - will be raised. - - Returns a dict() - """ - if packet[4] == 0: - return self._protocol.parse_binary_prepare_ok(packet) - elif packet[4] == 255: - raise errors.get_exception(packet) - raise errors.InterfaceError('Expected Binary OK packet') - - def _handle_binary_result(self, packet): - """Handle a MySQL Result - - This method handles a MySQL result, for example, after sending the - query command. OK and EOF packets will be handled and returned. If - the packet is an Error packet, an errors.Error-exception will be - raised. - - The tuple returned by this method consist of: - - the number of columns in the result, - - a list of tuples with information about the columns, - - the EOF packet information as a dictionary. - - Returns tuple() or dict() - """ - if not packet or len(packet) < 4: - raise errors.InterfaceError('Empty response') - elif packet[4] == 0: - return self._handle_ok(packet) - elif packet[4] == 254: - return self._handle_eof(packet) - elif packet[4] == 255: - raise errors.get_exception(packet) - - # We have a binary result set - column_count = self._protocol.parse_column_count(packet) - if not column_count or not isinstance(column_count, int): - raise errors.InterfaceError('Illegal result set.') - - columns = [None] * column_count - for i in range(0, column_count): - columns[i] = self._protocol.parse_column( - self._socket.recv(), self.python_charset) - - eof = self._handle_eof(self._socket.recv()) - return (column_count, columns, eof) - - def cmd_stmt_fetch(self, statement_id, rows=1): - """Fetch a MySQL statement Result Set - - This method will send the FETCH command to MySQL together with the - given statement id and the number of rows to fetch. - """ - packet = self._protocol.make_stmt_fetch(statement_id, rows) - self.unread_result = False - self._send_cmd(ServerCmd.STMT_FETCH, packet, expect_response=False) - self.unread_result = True - - def cmd_stmt_prepare(self, statement): - """Prepare a MySQL statement - - This method will send the PREPARE command to MySQL together with the - given statement. - - Returns a dict() - """ - packet = self._send_cmd(ServerCmd.STMT_PREPARE, statement) - result = self._handle_binary_ok(packet) - - result['columns'] = [] - result['parameters'] = [] - if result['num_params'] > 0: - for _ in range(0, result['num_params']): - result['parameters'].append( - self._protocol.parse_column(self._socket.recv(), - self.python_charset)) - self._handle_eof(self._socket.recv()) - if result['num_columns'] > 0: - for _ in range(0, result['num_columns']): - result['columns'].append( - self._protocol.parse_column(self._socket.recv(), - self.python_charset)) - self._handle_eof(self._socket.recv()) - - return result - - def cmd_stmt_execute(self, statement_id, data=(), parameters=(), flags=0): - """Execute a prepared MySQL statement""" - parameters = list(parameters) - long_data_used = {} - - if data: - for param_id, _ in enumerate(parameters): - if isinstance(data[param_id], IOBase): - binary = True - try: - binary = 'b' not in data[param_id].mode - except AttributeError: - pass - self.cmd_stmt_send_long_data(statement_id, param_id, - data[param_id]) - long_data_used[param_id] = (binary,) - - execute_packet = self._protocol.make_stmt_execute( - statement_id, data, tuple(parameters), flags, - long_data_used, self.charset) - packet = self._send_cmd(ServerCmd.STMT_EXECUTE, packet=execute_packet) - result = self._handle_binary_result(packet) - return result - - def cmd_stmt_close(self, statement_id): - """Deallocate a prepared MySQL statement - - This method deallocates the prepared statement using the - statement_id. Note that the MySQL server does not return - anything. - """ - self._send_cmd(ServerCmd.STMT_CLOSE, int4store(statement_id), - expect_response=False) - - def cmd_stmt_send_long_data(self, statement_id, param_id, data): - """Send data for a column - - This methods send data for a column (for example BLOB) for statement - identified by statement_id. The param_id indicate which parameter - the data belongs too. - The data argument should be a file-like object. - - Since MySQL does not send anything back, no error is raised. When - the MySQL server is not reachable, an OperationalError is raised. - - cmd_stmt_send_long_data should be called before cmd_stmt_execute. - - The total bytes send is returned. - - Returns int. - """ - chunk_size = 8192 - total_sent = 0 - # pylint: disable=W0212 - prepare_packet = self._protocol._prepare_stmt_send_long_data - # pylint: enable=W0212 - try: - buf = data.read(chunk_size) - while buf: - packet = prepare_packet(statement_id, param_id, buf) - self._send_cmd(ServerCmd.STMT_SEND_LONG_DATA, packet=packet, - expect_response=False) - total_sent += len(buf) - buf = data.read(chunk_size) - except AttributeError: - raise errors.OperationalError("MySQL Connection not available.") - - return total_sent - - def cmd_stmt_reset(self, statement_id): - """Reset data for prepared statement sent as long data - - The result is a dictionary with OK packet information. - - Returns a dict() - """ - self._handle_ok(self._send_cmd(ServerCmd.STMT_RESET, - int4store(statement_id))) - - def cmd_reset_connection(self): - """Resets the session state without re-authenticating - - Works only for MySQL server 5.7.3 or later. - The result is a dictionary with OK packet information. - - Returns a dict() - """ - if self._server_version < (5, 7, 3): - raise errors.NotSupportedError("MySQL version 5.7.2 and " - "earlier does not support " - "COM_RESET_CONNECTION.") - self._handle_ok(self._send_cmd(ServerCmd.RESET_CONNECTION)) - self._post_connection() - - def handle_unread_result(self): - """Check whether there is an unread result""" - if self.can_consume_results: - self.consume_results() - elif self.unread_result: - raise errors.InternalError("Unread result found") diff --git a/modules/mysql-connector-python/mysql/connector/connection_cext.py b/modules/mysql-connector-python/mysql/connector/connection_cext.py deleted file mode 100644 index 6ffb0e43b..000000000 --- a/modules/mysql-connector-python/mysql/connector/connection_cext.py +++ /dev/null @@ -1,695 +0,0 @@ -# Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Connection class using the C Extension -""" - -# Detection of abstract methods in pylint is not working correctly -#pylint: disable=W0223 - -import socket - -from . import errors, version -from .catch23 import INT_TYPES -from .constants import ( - CharacterSet, FieldFlag, ServerFlag, ShutdownType, ClientFlag -) -from .abstracts import MySQLConnectionAbstract, MySQLCursorAbstract -from .protocol import MySQLProtocol - -HAVE_CMYSQL = False -# pylint: disable=F0401,C0413 -try: - import _mysql_connector - from .cursor_cext import ( - CMySQLCursor, CMySQLCursorRaw, - CMySQLCursorBuffered, CMySQLCursorBufferedRaw, CMySQLCursorPrepared, - CMySQLCursorDict, CMySQLCursorBufferedDict, CMySQLCursorNamedTuple, - CMySQLCursorBufferedNamedTuple) - from _mysql_connector import MySQLInterfaceError # pylint: disable=F0401 -except ImportError as exc: - raise ImportError( - "MySQL Connector/Python C Extension not available ({0})".format( - str(exc) - )) -else: - HAVE_CMYSQL = True -# pylint: enable=F0401,C0413 - - -class CMySQLConnection(MySQLConnectionAbstract): - - """Class initiating a MySQL Connection using Connector/C""" - - def __init__(self, **kwargs): - """Initialization""" - if not HAVE_CMYSQL: - raise RuntimeError( - "MySQL Connector/Python C Extension not available") - self._cmysql = None - self._columns = [] - self.converter = None - super(CMySQLConnection, self).__init__(**kwargs) - - if kwargs: - self.connect(**kwargs) - - def _add_default_conn_attrs(self): - """Add default connection attributes""" - license_chunks = version.LICENSE.split(" ") - if license_chunks[0] == "GPLv2": - client_license = "GPL-2.0" - else: - client_license = "Commercial" - - self._conn_attrs.update({ - "_connector_name": "mysql-connector-python", - "_connector_license": client_license, - "_connector_version": ".".join( - [str(x) for x in version.VERSION[0:3]]), - "_source_host": socket.gethostname() - }) - - def _do_handshake(self): - """Gather information of the MySQL server before authentication""" - self._handshake = { - 'protocol': self._cmysql.get_proto_info(), - 'server_version_original': self._cmysql.get_server_info(), - 'server_threadid': self._cmysql.thread_id(), - 'charset': None, - 'server_status': None, - 'auth_plugin': None, - 'auth_data': None, - 'capabilities': self._cmysql.st_server_capabilities(), - } - - self._server_version = self._check_server_version( - self._handshake['server_version_original'] - ) - - @property - def _server_status(self): - """Returns the server status attribute of MYSQL structure""" - return self._cmysql.st_server_status() - - def set_unicode(self, value=True): - """Toggle unicode mode - - Set whether we return string fields as unicode or not. - Default is True. - """ - self._use_unicode = value - if self._cmysql: - self._cmysql.use_unicode(value) - if self.converter: - self.converter.set_unicode(value) - - @property - def autocommit(self): - """Get whether autocommit is on or off""" - value = self.info_query("SELECT @@session.autocommit")[0] - return True if value == 1 else False - - @autocommit.setter - def autocommit(self, value): # pylint: disable=W0221 - """Toggle autocommit""" - try: - self._cmysql.autocommit(value) - self._autocommit = value - except MySQLInterfaceError as exc: - raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno, - sqlstate=exc.sqlstate) - - @property - def database(self): - """Get the current database""" - return self.info_query("SELECT DATABASE()")[0] - - @database.setter - def database(self, value): # pylint: disable=W0221 - """Set the current database""" - self._cmysql.select_db(value) - - @property - def in_transaction(self): - """MySQL session has started a transaction""" - return self._server_status & ServerFlag.STATUS_IN_TRANS - - def _open_connection(self): - charset_name = CharacterSet.get_info(self._charset_id)[0] - self._cmysql = _mysql_connector.MySQL( # pylint: disable=E1101,I1101 - buffered=self._buffered, - raw=self._raw, - charset_name=charset_name, - connection_timeout=(self._connection_timeout or 0), - use_unicode=self._use_unicode, - auth_plugin=self._auth_plugin) - - if not self.isset_client_flag(ClientFlag.CONNECT_ARGS): - self._conn_attrs = {} - cnx_kwargs = { - 'host': self._host, - 'user': self._user, - 'password': self._password, - 'database': self._database, - 'port': self._port, - 'client_flags': self._client_flags, - 'unix_socket': self._unix_socket, - 'compress': self.isset_client_flag(ClientFlag.COMPRESS), - 'ssl_disabled': True, - "conn_attrs": self._conn_attrs - } - - if not self._ssl_disabled: - cnx_kwargs.update({ - 'ssl_ca': self._ssl.get('ca'), - 'ssl_cert': self._ssl.get('cert'), - 'ssl_key': self._ssl.get('key'), - 'ssl_verify_cert': self._ssl.get('verify_cert') or False, - 'ssl_verify_identity': - self._ssl.get('verify_identity') or False, - 'ssl_disabled': self._ssl_disabled - }) - - try: - self._cmysql.connect(**cnx_kwargs) - except MySQLInterfaceError as exc: - raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno, - sqlstate=exc.sqlstate) - self._do_handshake() - - def close(self): - """Disconnect from the MySQL server""" - if self._cmysql: - try: - self.free_result() - self._cmysql.close() - except MySQLInterfaceError as exc: - raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno, - sqlstate=exc.sqlstate) - self._cmysql = None - disconnect = close - - def is_connected(self): - """Reports whether the connection to MySQL Server is available""" - if self._cmysql: - return self._cmysql.ping() - - return False - - def ping(self, reconnect=False, attempts=1, delay=0): - """Check availability of the MySQL server - - When reconnect is set to True, one or more attempts are made to try - to reconnect to the MySQL server using the reconnect()-method. - - delay is the number of seconds to wait between each retry. - - When the connection is not available, an InterfaceError is raised. Use - the is_connected()-method if you just want to check the connection - without raising an error. - - Raises InterfaceError on errors. - """ - errmsg = "Connection to MySQL is not available" - - try: - connected = self._cmysql.ping() - except AttributeError: - pass # Raise or reconnect later - else: - if connected: - return - - if reconnect: - self.reconnect(attempts=attempts, delay=delay) - else: - raise errors.InterfaceError(errmsg) - - def set_character_set_name(self, charset): - """Sets the default character set name for current connection. - """ - self._cmysql.set_character_set(charset) - - def info_query(self, query): - """Send a query which only returns 1 row""" - self._cmysql.query(query) - first_row = () - if self._cmysql.have_result_set: - first_row = self._cmysql.fetch_row() - if self._cmysql.fetch_row(): - self._cmysql.free_result() - raise errors.InterfaceError( - "Query should not return more than 1 row") - self._cmysql.free_result() - - return first_row - - @property - def connection_id(self): - """MySQL connection ID""" - try: - return self._cmysql.thread_id() - except MySQLInterfaceError: - pass # Just return None - - return None - - def get_rows(self, count=None, binary=False, columns=None, raw=None, - prep_stmt=None): - """Get all or a subset of rows returned by the MySQL server""" - unread_result = prep_stmt.have_result_set if prep_stmt \ - else self.unread_result - if not (self._cmysql and unread_result): - raise errors.InternalError("No result set available") - - if raw is None: - raw = self._raw - - rows = [] - if count is not None and count <= 0: - raise AttributeError("count should be 1 or higher, or None") - - counter = 0 - try: - row = prep_stmt.fetch_row() if prep_stmt \ - else self._cmysql.fetch_row() - while row: - if not self._raw and self.converter: - row = list(row) - for i, _ in enumerate(row): - if not raw: - row[i] = self.converter.to_python(self._columns[i], - row[i]) - row = tuple(row) - rows.append(row) - counter += 1 - if count and counter == count: - break - row = prep_stmt.fetch_row() if prep_stmt \ - else self._cmysql.fetch_row() - if not row: - _eof = self.fetch_eof_columns(prep_stmt)['eof'] - if prep_stmt: - prep_stmt.free_result() - self._unread_result = False - else: - self.free_result() - else: - _eof = None - except MySQLInterfaceError as exc: - if prep_stmt: - prep_stmt.free_result() - raise errors.InterfaceError(str(exc)) - else: - self.free_result() - raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno, - sqlstate=exc.sqlstate) - - return rows, _eof - - def get_row(self, binary=False, columns=None, raw=None, prep_stmt=None): - """Get the next rows returned by the MySQL server""" - try: - rows, eof = self.get_rows(count=1, binary=binary, columns=columns, - raw=raw, prep_stmt=prep_stmt) - if rows: - return (rows[0], eof) - return (None, eof) - except IndexError: - # No row available - return (None, None) - - def next_result(self): - """Reads the next result""" - if self._cmysql: - self._cmysql.consume_result() - return self._cmysql.next_result() - return None - - def free_result(self): - """Frees the result""" - if self._cmysql: - self._cmysql.free_result() - - def commit(self): - """Commit current transaction""" - if self._cmysql: - self._cmysql.commit() - - def rollback(self): - """Rollback current transaction""" - if self._cmysql: - self._cmysql.consume_result() - self._cmysql.rollback() - - def cmd_init_db(self, database): - """Change the current database""" - try: - self._cmysql.select_db(database) - except MySQLInterfaceError as exc: - raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno, - sqlstate=exc.sqlstate) - - def fetch_eof_columns(self, prep_stmt=None): - """Fetch EOF and column information""" - have_result_set = prep_stmt.have_result_set if prep_stmt \ - else self._cmysql.have_result_set - if not have_result_set: - raise errors.InterfaceError("No result set") - - fields = prep_stmt.fetch_fields() if prep_stmt \ - else self._cmysql.fetch_fields() - self._columns = [] - for col in fields: - self._columns.append(( - col[4], - int(col[8]), - None, - None, - None, - None, - ~int(col[9]) & FieldFlag.NOT_NULL, - int(col[9]) - )) - - return { - 'eof': { - 'status_flag': self._server_status, - 'warning_count': self._cmysql.st_warning_count(), - }, - 'columns': self._columns, - } - - def fetch_eof_status(self): - """Fetch EOF and status information""" - if self._cmysql: - return { - 'warning_count': self._cmysql.st_warning_count(), - 'field_count': self._cmysql.st_field_count(), - 'insert_id': self._cmysql.insert_id(), - 'affected_rows': self._cmysql.affected_rows(), - 'server_status': self._server_status, - } - - return None - - def cmd_stmt_prepare(self, statement): - """Prepares the SQL statement""" - if not self._cmysql: - raise errors.OperationalError("MySQL Connection not available") - - try: - return self._cmysql.stmt_prepare(statement) - except MySQLInterfaceError as err: - raise errors.InterfaceError(str(err)) - - # pylint: disable=W0221 - def cmd_stmt_execute(self, prep_stmt, *args): - """Executes the prepared statement""" - try: - prep_stmt.stmt_execute(*args) - except MySQLInterfaceError as err: - raise errors.InterfaceError(str(err)) - - self._columns = [] - if not prep_stmt.have_result_set: - # No result - self._unread_result = False - return self.fetch_eof_status() - - self._unread_result = True - return self.fetch_eof_columns(prep_stmt) - - def cmd_stmt_close(self, prep_stmt): - """Closes the prepared statement""" - if self._unread_result: - raise errors.InternalError("Unread result found") - prep_stmt.stmt_close() - - def cmd_stmt_reset(self, prep_stmt): - """Resets the prepared statement""" - if self._unread_result: - raise errors.InternalError("Unread result found") - prep_stmt.stmt_reset() - # pylint: enable=W0221 - - def cmd_query(self, query, raw=None, buffered=False, raw_as_string=False): - """Send a query to the MySQL server""" - self.handle_unread_result() - if raw is None: - raw = self._raw - try: - if not isinstance(query, bytes): - query = query.encode('utf-8') - self._cmysql.query(query, - raw=raw, buffered=buffered, - raw_as_string=raw_as_string) - except MySQLInterfaceError as exc: - raise errors.get_mysql_exception(exc.errno, msg=exc.msg, - sqlstate=exc.sqlstate) - except AttributeError: - if self._unix_socket: - addr = self._unix_socket - else: - addr = self._host + ':' + str(self._port) - raise errors.OperationalError( - errno=2055, values=(addr, 'Connection not available.')) - - self._columns = [] - if not self._cmysql.have_result_set: - # No result - return self.fetch_eof_status() - - return self.fetch_eof_columns() - _execute_query = cmd_query - - def cursor(self, buffered=None, raw=None, prepared=None, cursor_class=None, - dictionary=None, named_tuple=None): - """Instantiates and returns a cursor using C Extension - - By default, CMySQLCursor is returned. Depending on the options - while connecting, a buffered and/or raw cursor is instantiated - instead. Also depending upon the cursor options, rows can be - returned as dictionary or named tuple. - - Dictionary and namedtuple based cursors are available with buffered - output but not raw. - - It is possible to also give a custom cursor through the - cursor_class parameter, but it needs to be a subclass of - mysql.connector.cursor_cext.CMySQLCursor. - - Raises ProgrammingError when cursor_class is not a subclass of - CursorBase. Raises ValueError when cursor is not available. - - Returns instance of CMySQLCursor or subclass. - - :param buffered: Return a buffering cursor - :param raw: Return a raw cursor - :param prepared: Return a cursor which uses prepared statements - :param cursor_class: Use a custom cursor class - :param dictionary: Rows are returned as dictionary - :param named_tuple: Rows are returned as named tuple - :return: Subclass of CMySQLCursor - :rtype: CMySQLCursor or subclass - """ - self.handle_unread_result(prepared) - if not self.is_connected(): - raise errors.OperationalError("MySQL Connection not available.") - if cursor_class is not None: - if not issubclass(cursor_class, MySQLCursorAbstract): - raise errors.ProgrammingError( - "Cursor class needs be to subclass" - " of cursor_cext.CMySQLCursor") - return (cursor_class)(self) - - buffered = buffered or self._buffered - raw = raw or self._raw - - cursor_type = 0 - if buffered is True: - cursor_type |= 1 - if raw is True: - cursor_type |= 2 - if dictionary is True: - cursor_type |= 4 - if named_tuple is True: - cursor_type |= 8 - if prepared is True: - cursor_type |= 16 - - types = { - 0: CMySQLCursor, # 0 - 1: CMySQLCursorBuffered, - 2: CMySQLCursorRaw, - 3: CMySQLCursorBufferedRaw, - 4: CMySQLCursorDict, - 5: CMySQLCursorBufferedDict, - 8: CMySQLCursorNamedTuple, - 9: CMySQLCursorBufferedNamedTuple, - 16: CMySQLCursorPrepared - } - try: - return (types[cursor_type])(self) - except KeyError: - args = ('buffered', 'raw', 'dictionary', 'named_tuple', 'prepared') - raise ValueError('Cursor not available with given criteria: ' + - ', '.join([args[i] for i in range(5) - if cursor_type & (1 << i) != 0])) - - @property - def num_rows(self): - """Returns number of rows of current result set""" - if not self._cmysql.have_result_set: - raise errors.InterfaceError("No result set") - - return self._cmysql.num_rows() - - @property - def warning_count(self): - """Returns number of warnings""" - if not self._cmysql: - return 0 - - return self._cmysql.warning_count() - - @property - def result_set_available(self): - """Check if a result set is available""" - if not self._cmysql: - return False - - return self._cmysql.have_result_set - - @property - def unread_result(self): - """Check if there are unread results or rows""" - return self.result_set_available - - @property - def more_results(self): - """Check if there are more results""" - return self._cmysql.more_results() - - def prepare_for_mysql(self, params): - """Prepare parameters for statements - - This method is use by cursors to prepared parameters found in the - list (or tuple) params. - - Returns dict. - """ - if isinstance(params, (list, tuple)): - result = self._cmysql.convert_to_mysql(*params) - elif isinstance(params, dict): - result = {} - for key, value in params.items(): - result[key] = self._cmysql.convert_to_mysql(value)[0] - else: - raise ValueError("Could not process parameters") - - return result - - def consume_results(self): - """Consume the current result - - This method consume the result by reading (consuming) all rows. - """ - self._cmysql.consume_result() - - def cmd_change_user(self, username='', password='', database='', - charset=45): - """Change the current logged in user""" - try: - self._cmysql.change_user(username, password, database) - except MySQLInterfaceError as exc: - raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno, - sqlstate=exc.sqlstate) - - self._charset_id = charset - self._post_connection() - - def cmd_refresh(self, options): - """Send the Refresh command to the MySQL server""" - try: - self._cmysql.refresh(options) - except MySQLInterfaceError as exc: - raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno, - sqlstate=exc.sqlstate) - - return self.fetch_eof_status() - - def cmd_quit(self): - """Close the current connection with the server""" - self.close() - - def cmd_shutdown(self, shutdown_type=None): - """Shut down the MySQL Server""" - if not self._cmysql: - raise errors.OperationalError("MySQL Connection not available") - - if shutdown_type: - if not ShutdownType.get_info(shutdown_type): - raise errors.InterfaceError("Invalid shutdown type") - level = shutdown_type - else: - level = ShutdownType.SHUTDOWN_DEFAULT - - try: - self._cmysql.shutdown(level) - except MySQLInterfaceError as exc: - raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno, - sqlstate=exc.sqlstate) - self.close() - - def cmd_statistics(self): - """Return statistics from the MySQL server""" - self.handle_unread_result() - - try: - stat = self._cmysql.stat() - return MySQLProtocol().parse_statistics(stat, with_header=False) - except (MySQLInterfaceError, errors.InterfaceError) as exc: - raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno, - sqlstate=exc.sqlstate) - - def cmd_process_kill(self, mysql_pid): - """Kill a MySQL process""" - if not isinstance(mysql_pid, INT_TYPES): - raise ValueError("MySQL PID must be int") - self.info_query("KILL {0}".format(mysql_pid)) - - def handle_unread_result(self, prepared=False): - """Check whether there is an unread result""" - unread_result = self._unread_result if prepared is True \ - else self.unread_result - if self.can_consume_results: - self.consume_results() - elif unread_result: - raise errors.InternalError("Unread result found") diff --git a/modules/mysql-connector-python/mysql/connector/constants.py b/modules/mysql-connector-python/mysql/connector/constants.py deleted file mode 100644 index 6b8a13a02..000000000 --- a/modules/mysql-connector-python/mysql/connector/constants.py +++ /dev/null @@ -1,790 +0,0 @@ -# Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Various MySQL constants and character sets -""" - -from .errors import ProgrammingError -from .charsets import MYSQL_CHARACTER_SETS - -MAX_PACKET_LENGTH = 16777215 -NET_BUFFER_LENGTH = 8192 -MAX_MYSQL_TABLE_COLUMNS = 4096 - -DEFAULT_CONFIGURATION = { - 'database': None, - 'user': '', - 'password': '', - 'host': '127.0.0.1', - 'port': 3306, - 'unix_socket': None, - 'use_unicode': True, - 'charset': 'utf8mb4', - 'collation': None, - 'converter_class': None, - 'autocommit': False, - 'time_zone': None, - 'sql_mode': None, - 'get_warnings': False, - 'raise_on_warnings': False, - 'connection_timeout': None, - 'client_flags': 0, - 'compress': False, - 'buffered': False, - 'raw': False, - 'ssl_ca': None, - 'ssl_cert': None, - 'ssl_key': None, - 'ssl_verify_cert': False, - 'ssl_verify_identity': False, - 'ssl_cipher': None, - 'ssl_disabled': False, - 'ssl_version': None, - 'passwd': None, - 'db': None, - 'connect_timeout': None, - 'dsn': None, - 'force_ipv6': False, - 'auth_plugin': None, - 'allow_local_infile': False, - 'consume_results': False, - 'conn_attrs': None, -} - -CNX_POOL_ARGS = ('pool_name', 'pool_size', 'pool_reset_session') - -def flag_is_set(flag, flags): - """Checks if the flag is set - - Returns boolean""" - if (flags & flag) > 0: - return True - return False - - -class _Constants(object): - """ - Base class for constants - """ - prefix = '' - desc = {} - - def __new__(cls): - raise TypeError("Can not instanciate from %s" % cls.__name__) - - @classmethod - def get_desc(cls, name): - """Get description of given constant""" - try: - return cls.desc[name][1] - except: - return None - - @classmethod - def get_info(cls, setid): - """Get information about given constant""" - for name, info in cls.desc.items(): - if info[0] == setid: - return name - return None - - @classmethod - def get_full_info(cls): - """get full information about given constant""" - res = () - try: - res = ["%s : %s" % (k, v[1]) for k, v in cls.desc.items()] - except Exception as err: # pylint: disable=W0703 - res = ('No information found in constant class.%s' % err) - - return res - - -class _Flags(_Constants): - """Base class for classes describing flags - """ - - @classmethod - def get_bit_info(cls, value): - """Get the name of all bits set - - Returns a list of strings.""" - res = [] - for name, info in cls.desc.items(): - if value & info[0]: - res.append(name) - return res - - -class FieldType(_Constants): - """MySQL Field Types - """ - prefix = 'FIELD_TYPE_' - DECIMAL = 0x00 - TINY = 0x01 - SHORT = 0x02 - LONG = 0x03 - FLOAT = 0x04 - DOUBLE = 0x05 - NULL = 0x06 - TIMESTAMP = 0x07 - LONGLONG = 0x08 - INT24 = 0x09 - DATE = 0x0a - TIME = 0x0b - DATETIME = 0x0c - YEAR = 0x0d - NEWDATE = 0x0e - VARCHAR = 0x0f - BIT = 0x10 - JSON = 0xf5 - NEWDECIMAL = 0xf6 - ENUM = 0xf7 - SET = 0xf8 - TINY_BLOB = 0xf9 - MEDIUM_BLOB = 0xfa - LONG_BLOB = 0xfb - BLOB = 0xfc - VAR_STRING = 0xfd - STRING = 0xfe - GEOMETRY = 0xff - - desc = { - 'DECIMAL': (0x00, 'DECIMAL'), - 'TINY': (0x01, 'TINY'), - 'SHORT': (0x02, 'SHORT'), - 'LONG': (0x03, 'LONG'), - 'FLOAT': (0x04, 'FLOAT'), - 'DOUBLE': (0x05, 'DOUBLE'), - 'NULL': (0x06, 'NULL'), - 'TIMESTAMP': (0x07, 'TIMESTAMP'), - 'LONGLONG': (0x08, 'LONGLONG'), - 'INT24': (0x09, 'INT24'), - 'DATE': (0x0a, 'DATE'), - 'TIME': (0x0b, 'TIME'), - 'DATETIME': (0x0c, 'DATETIME'), - 'YEAR': (0x0d, 'YEAR'), - 'NEWDATE': (0x0e, 'NEWDATE'), - 'VARCHAR': (0x0f, 'VARCHAR'), - 'BIT': (0x10, 'BIT'), - 'JSON': (0xf5, 'JSON'), - 'NEWDECIMAL': (0xf6, 'NEWDECIMAL'), - 'ENUM': (0xf7, 'ENUM'), - 'SET': (0xf8, 'SET'), - 'TINY_BLOB': (0xf9, 'TINY_BLOB'), - 'MEDIUM_BLOB': (0xfa, 'MEDIUM_BLOB'), - 'LONG_BLOB': (0xfb, 'LONG_BLOB'), - 'BLOB': (0xfc, 'BLOB'), - 'VAR_STRING': (0xfd, 'VAR_STRING'), - 'STRING': (0xfe, 'STRING'), - 'GEOMETRY': (0xff, 'GEOMETRY'), - } - - @classmethod - def get_string_types(cls): - """Get the list of all string types""" - return [ - cls.VARCHAR, - cls.ENUM, - cls.VAR_STRING, cls.STRING, - ] - - @classmethod - def get_binary_types(cls): - """Get the list of all binary types""" - return [ - cls.TINY_BLOB, cls.MEDIUM_BLOB, - cls.LONG_BLOB, cls.BLOB, - ] - - @classmethod - def get_number_types(cls): - """Get the list of all number types""" - return [ - cls.DECIMAL, cls.NEWDECIMAL, - cls.TINY, cls.SHORT, cls.LONG, - cls.FLOAT, cls.DOUBLE, - cls.LONGLONG, cls.INT24, - cls.BIT, - cls.YEAR, - ] - - @classmethod - def get_timestamp_types(cls): - """Get the list of all timestamp types""" - return [ - cls.DATETIME, cls.TIMESTAMP, - ] - - -class FieldFlag(_Flags): - """MySQL Field Flags - - Field flags as found in MySQL sources mysql-src/include/mysql_com.h - """ - _prefix = '' - NOT_NULL = 1 << 0 - PRI_KEY = 1 << 1 - UNIQUE_KEY = 1 << 2 - MULTIPLE_KEY = 1 << 3 - BLOB = 1 << 4 - UNSIGNED = 1 << 5 - ZEROFILL = 1 << 6 - BINARY = 1 << 7 - - ENUM = 1 << 8 - AUTO_INCREMENT = 1 << 9 - TIMESTAMP = 1 << 10 - SET = 1 << 11 - - NO_DEFAULT_VALUE = 1 << 12 - ON_UPDATE_NOW = 1 << 13 - NUM = 1 << 14 - PART_KEY = 1 << 15 - GROUP = 1 << 14 # SAME AS NUM !!!!!!!???? - UNIQUE = 1 << 16 - BINCMP = 1 << 17 - - GET_FIXED_FIELDS = 1 << 18 - FIELD_IN_PART_FUNC = 1 << 19 - FIELD_IN_ADD_INDEX = 1 << 20 - FIELD_IS_RENAMED = 1 << 21 - - desc = { - 'NOT_NULL': (1 << 0, "Field can't be NULL"), - 'PRI_KEY': (1 << 1, "Field is part of a primary key"), - 'UNIQUE_KEY': (1 << 2, "Field is part of a unique key"), - 'MULTIPLE_KEY': (1 << 3, "Field is part of a key"), - 'BLOB': (1 << 4, "Field is a blob"), - 'UNSIGNED': (1 << 5, "Field is unsigned"), - 'ZEROFILL': (1 << 6, "Field is zerofill"), - 'BINARY': (1 << 7, "Field is binary "), - 'ENUM': (1 << 8, "field is an enum"), - 'AUTO_INCREMENT': (1 << 9, "field is a autoincrement field"), - 'TIMESTAMP': (1 << 10, "Field is a timestamp"), - 'SET': (1 << 11, "field is a set"), - 'NO_DEFAULT_VALUE': (1 << 12, "Field doesn't have default value"), - 'ON_UPDATE_NOW': (1 << 13, "Field is set to NOW on UPDATE"), - 'NUM': (1 << 14, "Field is num (for clients)"), - - 'PART_KEY': (1 << 15, "Intern; Part of some key"), - 'GROUP': (1 << 14, "Intern: Group field"), # Same as NUM - 'UNIQUE': (1 << 16, "Intern: Used by sql_yacc"), - 'BINCMP': (1 << 17, "Intern: Used by sql_yacc"), - 'GET_FIXED_FIELDS': (1 << 18, "Used to get fields in item tree"), - 'FIELD_IN_PART_FUNC': (1 << 19, "Field part of partition func"), - 'FIELD_IN_ADD_INDEX': (1 << 20, "Intern: Field used in ADD INDEX"), - 'FIELD_IS_RENAMED': (1 << 21, "Intern: Field is being renamed"), - } - - -class ServerCmd(_Constants): - """MySQL Server Commands - """ - _prefix = 'COM_' - SLEEP = 0 - QUIT = 1 - INIT_DB = 2 - QUERY = 3 - FIELD_LIST = 4 - CREATE_DB = 5 - DROP_DB = 6 - REFRESH = 7 - SHUTDOWN = 8 - STATISTICS = 9 - PROCESS_INFO = 10 - CONNECT = 11 - PROCESS_KILL = 12 - DEBUG = 13 - PING = 14 - TIME = 15 - DELAYED_INSERT = 16 - CHANGE_USER = 17 - BINLOG_DUMP = 18 - TABLE_DUMP = 19 - CONNECT_OUT = 20 - REGISTER_SLAVE = 21 - STMT_PREPARE = 22 - STMT_EXECUTE = 23 - STMT_SEND_LONG_DATA = 24 - STMT_CLOSE = 25 - STMT_RESET = 26 - SET_OPTION = 27 - STMT_FETCH = 28 - DAEMON = 29 - BINLOG_DUMP_GTID = 30 - RESET_CONNECTION = 31 - - desc = { - 'SLEEP': (0, 'SLEEP'), - 'QUIT': (1, 'QUIT'), - 'INIT_DB': (2, 'INIT_DB'), - 'QUERY': (3, 'QUERY'), - 'FIELD_LIST': (4, 'FIELD_LIST'), - 'CREATE_DB': (5, 'CREATE_DB'), - 'DROP_DB': (6, 'DROP_DB'), - 'REFRESH': (7, 'REFRESH'), - 'SHUTDOWN': (8, 'SHUTDOWN'), - 'STATISTICS': (9, 'STATISTICS'), - 'PROCESS_INFO': (10, 'PROCESS_INFO'), - 'CONNECT': (11, 'CONNECT'), - 'PROCESS_KILL': (12, 'PROCESS_KILL'), - 'DEBUG': (13, 'DEBUG'), - 'PING': (14, 'PING'), - 'TIME': (15, 'TIME'), - 'DELAYED_INSERT': (16, 'DELAYED_INSERT'), - 'CHANGE_USER': (17, 'CHANGE_USER'), - 'BINLOG_DUMP': (18, 'BINLOG_DUMP'), - 'TABLE_DUMP': (19, 'TABLE_DUMP'), - 'CONNECT_OUT': (20, 'CONNECT_OUT'), - 'REGISTER_SLAVE': (21, 'REGISTER_SLAVE'), - 'STMT_PREPARE': (22, 'STMT_PREPARE'), - 'STMT_EXECUTE': (23, 'STMT_EXECUTE'), - 'STMT_SEND_LONG_DATA': (24, 'STMT_SEND_LONG_DATA'), - 'STMT_CLOSE': (25, 'STMT_CLOSE'), - 'STMT_RESET': (26, 'STMT_RESET'), - 'SET_OPTION': (27, 'SET_OPTION'), - 'STMT_FETCH': (28, 'STMT_FETCH'), - 'DAEMON': (29, 'DAEMON'), - 'BINLOG_DUMP_GTID': (30, 'BINLOG_DUMP_GTID'), - 'RESET_CONNECTION': (31, 'RESET_CONNECTION'), - } - - -class ClientFlag(_Flags): - """MySQL Client Flags - - Client options as found in the MySQL sources mysql-src/include/mysql_com.h - """ - LONG_PASSWD = 1 << 0 - FOUND_ROWS = 1 << 1 - LONG_FLAG = 1 << 2 - CONNECT_WITH_DB = 1 << 3 - NO_SCHEMA = 1 << 4 - COMPRESS = 1 << 5 - ODBC = 1 << 6 - LOCAL_FILES = 1 << 7 - IGNORE_SPACE = 1 << 8 - PROTOCOL_41 = 1 << 9 - INTERACTIVE = 1 << 10 - SSL = 1 << 11 - IGNORE_SIGPIPE = 1 << 12 - TRANSACTIONS = 1 << 13 - RESERVED = 1 << 14 - SECURE_CONNECTION = 1 << 15 - MULTI_STATEMENTS = 1 << 16 - MULTI_RESULTS = 1 << 17 - PS_MULTI_RESULTS = 1 << 18 - PLUGIN_AUTH = 1 << 19 - CONNECT_ARGS = 1 << 20 - PLUGIN_AUTH_LENENC_CLIENT_DATA = 1 << 21 - CAN_HANDLE_EXPIRED_PASSWORDS = 1 << 22 - SESION_TRACK = 1 << 23 - DEPRECATE_EOF = 1 << 24 - SSL_VERIFY_SERVER_CERT = 1 << 30 - REMEMBER_OPTIONS = 1 << 31 - - desc = { - 'LONG_PASSWD': (1 << 0, 'New more secure passwords'), - 'FOUND_ROWS': (1 << 1, 'Found instead of affected rows'), - 'LONG_FLAG': (1 << 2, 'Get all column flags'), - 'CONNECT_WITH_DB': (1 << 3, 'One can specify db on connect'), - 'NO_SCHEMA': (1 << 4, "Don't allow database.table.column"), - 'COMPRESS': (1 << 5, 'Can use compression protocol'), - 'ODBC': (1 << 6, 'ODBC client'), - 'LOCAL_FILES': (1 << 7, 'Can use LOAD DATA LOCAL'), - 'IGNORE_SPACE': (1 << 8, "Ignore spaces before ''"), - 'PROTOCOL_41': (1 << 9, 'New 4.1 protocol'), - 'INTERACTIVE': (1 << 10, 'This is an interactive client'), - 'SSL': (1 << 11, 'Switch to SSL after handshake'), - 'IGNORE_SIGPIPE': (1 << 12, 'IGNORE sigpipes'), - 'TRANSACTIONS': (1 << 13, 'Client knows about transactions'), - 'RESERVED': (1 << 14, 'Old flag for 4.1 protocol'), - 'SECURE_CONNECTION': (1 << 15, 'New 4.1 authentication'), - 'MULTI_STATEMENTS': (1 << 16, 'Enable/disable multi-stmt support'), - 'MULTI_RESULTS': (1 << 17, 'Enable/disable multi-results'), - 'PS_MULTI_RESULTS': (1 << 18, 'Multi-results in PS-protocol'), - 'PLUGIN_AUTH': (1 << 19, 'Client supports plugin authentication'), - 'CONNECT_ARGS': (1 << 20, 'Client supports connection attributes'), - 'PLUGIN_AUTH_LENENC_CLIENT_DATA': (1 << 21, - 'Enable authentication response packet to be larger than 255 bytes'), - 'CAN_HANDLE_EXPIRED_PASSWORDS': (1 << 22, "Don't close the connection for a connection with expired password"), - 'SESION_TRACK': (1 << 23, 'Capable of handling server state change information'), - 'DEPRECATE_EOF': (1 << 24, 'Client no longer needs EOF packet'), - 'SSL_VERIFY_SERVER_CERT': (1 << 30, ''), - 'REMEMBER_OPTIONS': (1 << 31, ''), - } - - default = [ - LONG_PASSWD, - LONG_FLAG, - CONNECT_WITH_DB, - PROTOCOL_41, - TRANSACTIONS, - SECURE_CONNECTION, - MULTI_STATEMENTS, - MULTI_RESULTS, - CONNECT_ARGS - ] - - @classmethod - def get_default(cls): - """Get the default client options set - - Returns a flag with all the default client options set""" - flags = 0 - for option in cls.default: - flags |= option - return flags - - -class ServerFlag(_Flags): - """MySQL Server Flags - - Server flags as found in the MySQL sources mysql-src/include/mysql_com.h - """ - _prefix = 'SERVER_' - STATUS_IN_TRANS = 1 << 0 - STATUS_AUTOCOMMIT = 1 << 1 - MORE_RESULTS_EXISTS = 1 << 3 - QUERY_NO_GOOD_INDEX_USED = 1 << 4 - QUERY_NO_INDEX_USED = 1 << 5 - STATUS_CURSOR_EXISTS = 1 << 6 - STATUS_LAST_ROW_SENT = 1 << 7 - STATUS_DB_DROPPED = 1 << 8 - STATUS_NO_BACKSLASH_ESCAPES = 1 << 9 - SERVER_STATUS_METADATA_CHANGED = 1 << 10 - SERVER_QUERY_WAS_SLOW = 1 << 11 - SERVER_PS_OUT_PARAMS = 1 << 12 - SERVER_STATUS_IN_TRANS_READONLY = 1 << 13 - SERVER_SESSION_STATE_CHANGED = 1 << 14 - - desc = { - 'SERVER_STATUS_IN_TRANS': (1 << 0, - 'Transaction has started'), - 'SERVER_STATUS_AUTOCOMMIT': (1 << 1, - 'Server in auto_commit mode'), - 'SERVER_MORE_RESULTS_EXISTS': (1 << 3, - 'Multi query - ' - 'next query exists'), - 'SERVER_QUERY_NO_GOOD_INDEX_USED': (1 << 4, ''), - 'SERVER_QUERY_NO_INDEX_USED': (1 << 5, ''), - 'SERVER_STATUS_CURSOR_EXISTS': (1 << 6, - 'Set when server opened a read-only ' - 'non-scrollable cursor for a query.'), - 'SERVER_STATUS_LAST_ROW_SENT': (1 << 7, - 'Set when a read-only cursor is ' - 'exhausted'), - 'SERVER_STATUS_DB_DROPPED': (1 << 8, 'A database was dropped'), - 'SERVER_STATUS_NO_BACKSLASH_ESCAPES': (1 << 9, ''), - 'SERVER_STATUS_METADATA_CHANGED': (1024, - 'Set if after a prepared statement ' - 'reprepare we discovered that the ' - 'new statement returns a different ' - 'number of result set columns.'), - 'SERVER_QUERY_WAS_SLOW': (2048, ''), - 'SERVER_PS_OUT_PARAMS': (4096, - 'To mark ResultSet containing output ' - 'parameter values.'), - 'SERVER_STATUS_IN_TRANS_READONLY': (8192, - 'Set if multi-statement ' - 'transaction is a read-only ' - 'transaction.'), - 'SERVER_SESSION_STATE_CHANGED': (1 << 14, - 'Session state has changed on the ' - 'server because of the execution of ' - 'the last statement'), - } - - -class RefreshOption(_Constants): - """MySQL Refresh command options - - Options used when sending the COM_REFRESH server command. - """ - _prefix = 'REFRESH_' - GRANT = 1 << 0 - LOG = 1 << 1 - TABLES = 1 << 2 - HOST = 1 << 3 - STATUS = 1 << 4 - THREADS = 1 << 5 - SLAVE = 1 << 6 - - desc = { - 'GRANT': (1 << 0, 'Refresh grant tables'), - 'LOG': (1 << 1, 'Start on new log file'), - 'TABLES': (1 << 2, 'close all tables'), - 'HOSTS': (1 << 3, 'Flush host cache'), - 'STATUS': (1 << 4, 'Flush status variables'), - 'THREADS': (1 << 5, 'Flush thread cache'), - 'SLAVE': (1 << 6, 'Reset master info and restart slave thread'), - } - - -class ShutdownType(_Constants): - """MySQL Shutdown types - - Shutdown types used by the COM_SHUTDOWN server command. - """ - _prefix = '' - SHUTDOWN_DEFAULT = 0 - SHUTDOWN_WAIT_CONNECTIONS = 1 - SHUTDOWN_WAIT_TRANSACTIONS = 2 - SHUTDOWN_WAIT_UPDATES = 8 - SHUTDOWN_WAIT_ALL_BUFFERS = 16 - SHUTDOWN_WAIT_CRITICAL_BUFFERS = 17 - KILL_QUERY = 254 - KILL_CONNECTION = 255 - - desc = { - 'SHUTDOWN_DEFAULT': ( - SHUTDOWN_DEFAULT, - "defaults to SHUTDOWN_WAIT_ALL_BUFFERS"), - 'SHUTDOWN_WAIT_CONNECTIONS': ( - SHUTDOWN_WAIT_CONNECTIONS, - "wait for existing connections to finish"), - 'SHUTDOWN_WAIT_TRANSACTIONS': ( - SHUTDOWN_WAIT_TRANSACTIONS, - "wait for existing trans to finish"), - 'SHUTDOWN_WAIT_UPDATES': ( - SHUTDOWN_WAIT_UPDATES, - "wait for existing updates to finish"), - 'SHUTDOWN_WAIT_ALL_BUFFERS': ( - SHUTDOWN_WAIT_ALL_BUFFERS, - "flush InnoDB and other storage engine buffers"), - 'SHUTDOWN_WAIT_CRITICAL_BUFFERS': ( - SHUTDOWN_WAIT_CRITICAL_BUFFERS, - "don't flush InnoDB buffers, " - "flush other storage engines' buffers"), - 'KILL_QUERY': ( - KILL_QUERY, - "(no description)"), - 'KILL_CONNECTION': ( - KILL_CONNECTION, - "(no description)"), - } - - -class CharacterSet(_Constants): - """MySQL supported character sets and collations - - List of character sets with their collations supported by MySQL. This - maps to the character set we get from the server within the handshake - packet. - - The list is hardcode so we avoid a database query when getting the - name of the used character set or collation. - """ - desc = MYSQL_CHARACTER_SETS - - # Multi-byte character sets which use 5c (backslash) in characters - slash_charsets = (1, 13, 28, 84, 87, 88) - - @classmethod - def get_info(cls, setid): - """Retrieves character set information as tuple using an ID - - Retrieves character set and collation information based on the - given MySQL ID. - - Raises ProgrammingError when character set is not supported. - - Returns a tuple. - """ - try: - return cls.desc[setid][0:2] - except IndexError: - raise ProgrammingError( - "Character set '{0}' unsupported".format(setid)) - - @classmethod - def get_desc(cls, name): - """Retrieves character set information as string using an ID - - Retrieves character set and collation information based on the - given MySQL ID. - - Returns a tuple. - """ - try: - return "%s/%s" % cls.get_info(name) - except: - raise - - @classmethod - def get_default_collation(cls, charset): - """Retrieves the default collation for given character set - - Raises ProgrammingError when character set is not supported. - - Returns list (collation, charset, index) - """ - if isinstance(charset, int): - try: - info = cls.desc[charset] - return info[1], info[0], charset - except: - ProgrammingError("Character set ID '%s' unsupported." % ( - charset)) - - for cid, info in enumerate(cls.desc): - if info is None: - continue - if info[0] == charset and info[2] is True: - return info[1], info[0], cid - - raise ProgrammingError("Character set '%s' unsupported." % (charset)) - - @classmethod - def get_charset_info(cls, charset=None, collation=None): - """Get character set information using charset name and/or collation - - Retrieves character set and collation information given character - set name and/or a collation name. - If charset is an integer, it will look up the character set based - on the MySQL's ID. - For example: - get_charset_info('utf8',None) - get_charset_info(collation='utf8_general_ci') - get_charset_info(47) - - Raises ProgrammingError when character set is not supported. - - Returns a tuple with (id, characterset name, collation) - """ - if isinstance(charset, int): - try: - info = cls.desc[charset] - return (charset, info[0], info[1]) - except IndexError: - ProgrammingError("Character set ID {0} unknown.".format( - charset)) - - if charset is not None and collation is None: - info = cls.get_default_collation(charset) - return (info[2], info[1], info[0]) - elif charset is None and collation is not None: - for cid, info in enumerate(cls.desc): - if info is None: - continue - if collation == info[1]: - return (cid, info[0], info[1]) - raise ProgrammingError("Collation '{0}' unknown.".format(collation)) - else: - for cid, info in enumerate(cls.desc): - if info is None: - continue - if info[0] == charset and info[1] == collation: - return (cid, info[0], info[1]) - _ = cls.get_default_collation(charset) - raise ProgrammingError("Collation '{0}' unknown.".format(collation)) - - @classmethod - def get_supported(cls): - """Retrieves a list with names of all supproted character sets - - Returns a tuple. - """ - res = [] - for info in cls.desc: - if info and info[0] not in res: - res.append(info[0]) - return tuple(res) - - -class SQLMode(_Constants): - """MySQL SQL Modes - - The numeric values of SQL Modes are not interesting, only the names - are used when setting the SQL_MODE system variable using the MySQL - SET command. - - See http://dev.mysql.com/doc/refman/5.6/en/server-sql-mode.html - """ - _prefix = 'MODE_' - REAL_AS_FLOAT = 'REAL_AS_FLOAT' - PIPES_AS_CONCAT = 'PIPES_AS_CONCAT' - ANSI_QUOTES = 'ANSI_QUOTES' - IGNORE_SPACE = 'IGNORE_SPACE' - NOT_USED = 'NOT_USED' - ONLY_FULL_GROUP_BY = 'ONLY_FULL_GROUP_BY' - NO_UNSIGNED_SUBTRACTION = 'NO_UNSIGNED_SUBTRACTION' - NO_DIR_IN_CREATE = 'NO_DIR_IN_CREATE' - POSTGRESQL = 'POSTGRESQL' - ORACLE = 'ORACLE' - MSSQL = 'MSSQL' - DB2 = 'DB2' - MAXDB = 'MAXDB' - NO_KEY_OPTIONS = 'NO_KEY_OPTIONS' - NO_TABLE_OPTIONS = 'NO_TABLE_OPTIONS' - NO_FIELD_OPTIONS = 'NO_FIELD_OPTIONS' - MYSQL323 = 'MYSQL323' - MYSQL40 = 'MYSQL40' - ANSI = 'ANSI' - NO_AUTO_VALUE_ON_ZERO = 'NO_AUTO_VALUE_ON_ZERO' - NO_BACKSLASH_ESCAPES = 'NO_BACKSLASH_ESCAPES' - STRICT_TRANS_TABLES = 'STRICT_TRANS_TABLES' - STRICT_ALL_TABLES = 'STRICT_ALL_TABLES' - NO_ZERO_IN_DATE = 'NO_ZERO_IN_DATE' - NO_ZERO_DATE = 'NO_ZERO_DATE' - INVALID_DATES = 'INVALID_DATES' - ERROR_FOR_DIVISION_BY_ZERO = 'ERROR_FOR_DIVISION_BY_ZERO' - TRADITIONAL = 'TRADITIONAL' - NO_AUTO_CREATE_USER = 'NO_AUTO_CREATE_USER' - HIGH_NOT_PRECEDENCE = 'HIGH_NOT_PRECEDENCE' - NO_ENGINE_SUBSTITUTION = 'NO_ENGINE_SUBSTITUTION' - PAD_CHAR_TO_FULL_LENGTH = 'PAD_CHAR_TO_FULL_LENGTH' - - @classmethod - def get_desc(cls, name): - raise NotImplementedError - - @classmethod - def get_info(cls, setid): - raise NotImplementedError - - @classmethod - def get_full_info(cls): - """Returns a sequence of all available SQL Modes - - This class method returns a tuple containing all SQL Mode names. The - names will be alphabetically sorted. - - Returns a tuple. - """ - res = [] - for key in vars(cls).keys(): - if not key.startswith('_') \ - and not hasattr(getattr(cls, key), '__call__'): - res.append(key) - return tuple(sorted(res)) - -CONN_ATTRS_DN = ["_pid", "_platform", "_source_host", "_client_name", - "_client_license", "_client_version", "_os", "_connector_name", - "_connector_license", "_connector_version"] diff --git a/modules/mysql-connector-python/mysql/connector/conversion.py b/modules/mysql-connector-python/mysql/connector/conversion.py deleted file mode 100644 index 00bea4c90..000000000 --- a/modules/mysql-connector-python/mysql/connector/conversion.py +++ /dev/null @@ -1,685 +0,0 @@ -# Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Converting MySQL and Python types -""" - -import datetime -import time -from decimal import Decimal - -from .constants import FieldType, FieldFlag, CharacterSet -from .catch23 import PY2, NUMERIC_TYPES, struct_unpack -from .custom_types import HexLiteral - -CONVERT_ERROR = "Could not convert '{value}' to python {pytype}" - - -class MySQLConverterBase(object): - """Base class for conversion classes - - All class dealing with converting to and from MySQL data types must - be a subclass of this class. - """ - - def __init__(self, charset='utf8', use_unicode=True): - self.python_types = None - self.mysql_types = None - self.charset = None - self.charset_id = 0 - self.use_unicode = None - self.set_charset(charset) - self.set_unicode(use_unicode) - self._cache_field_types = {} - - def set_charset(self, charset): - """Set character set""" - if charset == 'utf8mb4': - charset = 'utf8' - if charset is not None: - self.charset = charset - else: - # default to utf8 - self.charset = 'utf8' - self.charset_id = CharacterSet.get_charset_info(self.charset)[0] - - def set_unicode(self, value=True): - """Set whether to use Unicode""" - self.use_unicode = value - - def to_mysql(self, value): - """Convert Python data type to MySQL""" - type_name = value.__class__.__name__.lower() - try: - return getattr(self, "_{0}_to_mysql".format(type_name))(value) - except AttributeError: - return value - - def to_python(self, vtype, value): - """Convert MySQL data type to Python""" - - if (value == b'\x00' or value is None) and vtype[1] != FieldType.BIT: - # Don't go further when we hit a NULL value - return None - - if not self._cache_field_types: - self._cache_field_types = {} - for name, info in FieldType.desc.items(): - try: - self._cache_field_types[info[0]] = getattr( - self, '_{0}_to_python'.format(name)) - except AttributeError: - # We ignore field types which has no method - pass - - try: - return self._cache_field_types[vtype[1]](value, vtype) - except KeyError: - return value - - def escape(self, value): - """Escape buffer for sending to MySQL""" - return value - - def quote(self, buf): - """Quote buffer for sending to MySQL""" - return str(buf) - - -class MySQLConverter(MySQLConverterBase): - """Default conversion class for MySQL Connector/Python. - - o escape method: for escaping values send to MySQL - o quoting method: for quoting values send to MySQL in statements - o conversion mapping: maps Python and MySQL data types to - function for converting them. - - Whenever one needs to convert values differently, a converter_class - argument can be given while instantiating a new connection like - cnx.connect(converter_class=CustomMySQLConverterClass). - - """ - - def __init__(self, charset=None, use_unicode=True): - MySQLConverterBase.__init__(self, charset, use_unicode) - self._cache_field_types = {} - - def escape(self, value): - """ - Escapes special characters as they are expected to by when MySQL - receives them. - As found in MySQL source mysys/charset.c - - Returns the value if not a string, or the escaped string. - """ - if value is None: - return value - elif isinstance(value, NUMERIC_TYPES): - return value - if isinstance(value, (bytes, bytearray)): - value = value.replace(b'\\', b'\\\\') - value = value.replace(b'\n', b'\\n') - value = value.replace(b'\r', b'\\r') - value = value.replace(b'\047', b'\134\047') # single quotes - value = value.replace(b'\042', b'\134\042') # double quotes - value = value.replace(b'\032', b'\134\032') # for Win32 - else: - value = value.replace('\\', '\\\\') - value = value.replace('\n', '\\n') - value = value.replace('\r', '\\r') - value = value.replace('\047', '\134\047') # single quotes - value = value.replace('\042', '\134\042') # double quotes - value = value.replace('\032', '\134\032') # for Win32 - return value - - def quote(self, buf): - """ - Quote the parameters for commands. General rules: - o numbers are returns as bytes using ascii codec - o None is returned as bytearray(b'NULL') - o Everything else is single quoted '' - - Returns a bytearray object. - """ - if isinstance(buf, NUMERIC_TYPES): - if PY2: - if isinstance(buf, float): - return repr(buf) - return str(buf) - return str(buf).encode('ascii') - elif isinstance(buf, type(None)): - return bytearray(b"NULL") - return bytearray(b"'" + buf + b"'") - - def to_mysql(self, value): - """Convert Python data type to MySQL""" - type_name = value.__class__.__name__.lower() - try: - return getattr(self, "_{0}_to_mysql".format(type_name))(value) - except AttributeError: - raise TypeError("Python '{0}' cannot be converted to a " - "MySQL type".format(type_name)) - - def to_python(self, vtype, value): - """Convert MySQL data type to Python""" - if value == 0 and vtype[1] != FieldType.BIT: # \x00 - # Don't go further when we hit a NULL value - return None - if value is None: - return None - - if not self._cache_field_types: - self._cache_field_types = {} - for name, info in FieldType.desc.items(): - try: - self._cache_field_types[info[0]] = getattr( - self, '_{0}_to_python'.format(name)) - except AttributeError: - # We ignore field types which has no method - pass - - try: - return self._cache_field_types[vtype[1]](value, vtype) - except KeyError: - # If one type is not defined, we just return the value as str - try: - return value.decode('utf-8') - except UnicodeDecodeError: - return value - except ValueError as err: - raise ValueError("%s (field %s)" % (err, vtype[0])) - except TypeError as err: - raise TypeError("%s (field %s)" % (err, vtype[0])) - except: - raise - - def _int_to_mysql(self, value): - """Convert value to int""" - return int(value) - - def _long_to_mysql(self, value): - """Convert value to int""" - return int(value) - - def _float_to_mysql(self, value): - """Convert value to float""" - return float(value) - - def _str_to_mysql(self, value): - """Convert value to string""" - if PY2: - return str(value) - return self._unicode_to_mysql(value) - - def _unicode_to_mysql(self, value): - """Convert unicode""" - charset = self.charset - charset_id = self.charset_id - if charset == 'binary': - charset = 'utf8' - charset_id = CharacterSet.get_charset_info(charset)[0] - encoded = value.encode(charset) - if charset_id in CharacterSet.slash_charsets: - if b'\x5c' in encoded: - return HexLiteral(value, charset) - return encoded - - def _bytes_to_mysql(self, value): - """Convert value to bytes""" - return value - - def _bytearray_to_mysql(self, value): - """Convert value to bytes""" - return bytes(value) - - def _bool_to_mysql(self, value): - """Convert value to boolean""" - if value: - return 1 - return 0 - - def _nonetype_to_mysql(self, value): - """ - This would return what None would be in MySQL, but instead we - leave it None and return it right away. The actual conversion - from None to NULL happens in the quoting functionality. - - Return None. - """ - return None - - def _datetime_to_mysql(self, value): - """ - Converts a datetime instance to a string suitable for MySQL. - The returned string has format: %Y-%m-%d %H:%M:%S[.%f] - - If the instance isn't a datetime.datetime type, it return None. - - Returns a bytes. - """ - if value.microsecond: - fmt = '{0:d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:06d}' - return fmt.format( - value.year, value.month, value.day, - value.hour, value.minute, value.second, - value.microsecond).encode('ascii') - - fmt = '{0:d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}' - return fmt.format( - value.year, value.month, value.day, - value.hour, value.minute, value.second).encode('ascii') - - def _date_to_mysql(self, value): - """ - Converts a date instance to a string suitable for MySQL. - The returned string has format: %Y-%m-%d - - If the instance isn't a datetime.date type, it return None. - - Returns a bytes. - """ - return '{0:d}-{1:02d}-{2:02d}'.format(value.year, value.month, - value.day).encode('ascii') - - def _time_to_mysql(self, value): - """ - Converts a time instance to a string suitable for MySQL. - The returned string has format: %H:%M:%S[.%f] - - If the instance isn't a datetime.time type, it return None. - - Returns a bytes. - """ - if value.microsecond: - return value.strftime('%H:%M:%S.%f').encode('ascii') - return value.strftime('%H:%M:%S').encode('ascii') - - def _struct_time_to_mysql(self, value): - """ - Converts a time.struct_time sequence to a string suitable - for MySQL. - The returned string has format: %Y-%m-%d %H:%M:%S - - Returns a bytes or None when not valid. - """ - return time.strftime('%Y-%m-%d %H:%M:%S', value).encode('ascii') - - def _timedelta_to_mysql(self, value): - """ - Converts a timedelta instance to a string suitable for MySQL. - The returned string has format: %H:%M:%S - - Returns a bytes. - """ - seconds = abs(value.days * 86400 + value.seconds) - - if value.microseconds: - fmt = '{0:02d}:{1:02d}:{2:02d}.{3:06d}' - if value.days < 0: - mcs = 1000000 - value.microseconds - seconds -= 1 - else: - mcs = value.microseconds - else: - fmt = '{0:02d}:{1:02d}:{2:02d}' - - if value.days < 0: - fmt = '-' + fmt - - (hours, remainder) = divmod(seconds, 3600) - (mins, secs) = divmod(remainder, 60) - - if value.microseconds: - result = fmt.format(hours, mins, secs, mcs) - else: - result = fmt.format(hours, mins, secs) - - if PY2: - return result - return result.encode('ascii') - - def _decimal_to_mysql(self, value): - """ - Converts a decimal.Decimal instance to a string suitable for - MySQL. - - Returns a bytes or None when not valid. - """ - if isinstance(value, Decimal): - return str(value).encode('ascii') - - return None - - def row_to_python(self, row, fields): - """Convert a MySQL text result row to Python types - - The row argument is a sequence containing text result returned - by a MySQL server. Each value of the row is converted to the - using the field type information in the fields argument. - - Returns a tuple. - """ - i = 0 - result = [None]*len(fields) - - if not self._cache_field_types: - self._cache_field_types = {} - for name, info in FieldType.desc.items(): - try: - self._cache_field_types[info[0]] = getattr( - self, '_{0}_to_python'.format(name)) - except AttributeError: - # We ignore field types which has no method - pass - - for field in fields: - field_type = field[1] - - if (row[i] == 0 and field_type != FieldType.BIT) or row[i] is None: - # Don't convert NULL value - i += 1 - continue - - try: - result[i] = self._cache_field_types[field_type](row[i], field) - except KeyError: - # If one type is not defined, we just return the value as str - try: - result[i] = row[i].decode('utf-8') - except UnicodeDecodeError: - result[i] = row[i] - except (ValueError, TypeError) as err: - err.message = "{0} (field {1})".format(str(err), field[0]) - raise - - i += 1 - - return tuple(result) - - def _FLOAT_to_python(self, value, desc=None): # pylint: disable=C0103 - """ - Returns value as float type. - """ - return float(value) - - _DOUBLE_to_python = _FLOAT_to_python - - def _INT_to_python(self, value, desc=None): # pylint: disable=C0103 - """ - Returns value as int type. - """ - return int(value) - - _TINY_to_python = _INT_to_python - _SHORT_to_python = _INT_to_python - _INT24_to_python = _INT_to_python - _LONG_to_python = _INT_to_python - _LONGLONG_to_python = _INT_to_python - - def _DECIMAL_to_python(self, value, desc=None): # pylint: disable=C0103 - """ - Returns value as a decimal.Decimal. - """ - val = value.decode(self.charset) - return Decimal(val) - - _NEWDECIMAL_to_python = _DECIMAL_to_python - - def _str(self, value, desc=None): - """ - Returns value as str type. - """ - return str(value) - - def _BIT_to_python(self, value, dsc=None): # pylint: disable=C0103 - """Returns BIT columntype as integer""" - int_val = value - if len(int_val) < 8: - int_val = b'\x00' * (8 - len(int_val)) + int_val - return struct_unpack('>Q', int_val)[0] - - def _DATE_to_python(self, value, dsc=None): # pylint: disable=C0103 - """Converts TIME column MySQL to a python datetime.datetime type. - - Raises ValueError if the value can not be converted. - - Returns DATE column type as datetime.date type. - """ - if isinstance(value, datetime.date): - return value - try: - parts = value.split(b'-') - if len(parts) != 3: - raise ValueError("invalid datetime format: {} len: {}" - "".format(parts, len(parts))) - try: - return datetime.date(int(parts[0]), int(parts[1]), int(parts[2])) - except ValueError: - return None - except (IndexError, ValueError): - raise ValueError( - "Could not convert {0} to python datetime.timedelta".format( - value)) - - _NEWDATE_to_python = _DATE_to_python - - def _TIME_to_python(self, value, dsc=None): # pylint: disable=C0103 - """Converts TIME column value to python datetime.time value type. - - Converts the TIME column MySQL type passed as bytes to a python - datetime.datetime type. - - Raises ValueError if the value can not be converted. - - Returns datetime.time type. - """ - try: - (hms, mcs) = value.split(b'.') - mcs = int(mcs.ljust(6, b'0')) - except (TypeError, ValueError): - hms = value - mcs = 0 - try: - (hours, mins, secs) = [int(d) for d in hms.split(b':')] - if value[0] == 45 or value[0] == '-': # if PY3 or PY2 - mins, secs, mcs = -mins, -secs, -mcs - return datetime.timedelta(hours=hours, minutes=mins, - seconds=secs, microseconds=mcs) - except (IndexError, TypeError, ValueError): - raise ValueError(CONVERT_ERROR.format(value=value, - pytype="datetime.timedelta")) - - def _DATETIME_to_python(self, value, dsc=None): # pylint: disable=C0103 - """"Converts DATETIME column value to python datetime.time value type. - - Converts the DATETIME column MySQL type passed as bytes to a python - datetime.datetime type. - - Returns: datetime.datetime type. - """ - if isinstance(value, datetime.datetime): - return value - datetime_val = None - try: - (date_, time_) = value.split(b' ') - if len(time_) > 8: - (hms, mcs) = time_.split(b'.') - mcs = int(mcs.ljust(6, b'0')) - else: - hms = time_ - mcs = 0 - dtval = [int(i) for i in date_.split(b'-')] + \ - [int(i) for i in hms.split(b':')] + [mcs, ] - if len(dtval) < 6: - raise ValueError("invalid datetime format: {} len: {}" - "".format(dtval, len(dtval))) - else: - # Note that by default MySQL accepts invalid timestamps - # (this is also backward compatibility). - # Traditionaly C/py returns None for this well formed but - # invalid datetime for python like '0000-00-00 HH:MM:SS'. - try: - datetime_val = datetime.datetime(*dtval) - except ValueError: - return None - except (IndexError, TypeError): - raise ValueError(CONVERT_ERROR.format(value=value, - pytype="datetime.timedelta")) - - return datetime_val - - _TIMESTAMP_to_python = _DATETIME_to_python - - def _YEAR_to_python(self, value, desc=None): # pylint: disable=C0103 - """Returns YEAR column type as integer""" - try: - year = int(value) - except ValueError: - raise ValueError("Failed converting YEAR to int (%s)" % value) - - return year - - def _SET_to_python(self, value, dsc=None): # pylint: disable=C0103 - """Returns SET column type as set - - Actually, MySQL protocol sees a SET as a string type field. So this - code isn't called directly, but used by STRING_to_python() method. - - Returns SET column type as a set. - """ - set_type = None - val = value.decode(self.charset) - if not val: - return set() - try: - set_type = set(val.split(',')) - except ValueError: - raise ValueError("Could not convert set %s to a sequence." % value) - return set_type - - def _JSON_to_python(self, value, dsc=None): # pylint: disable=C0103 - """Returns JSON column type as python type - - Returns JSON column type as python type. - """ - try: - num = float(value) - if num.is_integer(): - return int(value) - return num - except ValueError: - pass - - if value == b'true': - return True - elif value == b'false': - return False - - # The following types are returned between double quotes or - # bytearray(b'"')[0] or int 34 for shortness. - if value[0] == 34 and value[-1] == 34: - value_nq = value[1:-1] - - try: - value_datetime = self._DATETIME_to_python(value_nq) - if value_datetime is not None: - return value_datetime - except ValueError: - pass - try: - value_date = self._DATE_to_python(value_nq) - if value_date is not None: - return value_date - except ValueError: - pass - try: - value_time = self._TIME_to_python(value_nq) - if value_time is not None: - return value_time - except ValueError: - pass - - if isinstance(value, (bytes, bytearray)): - return value.decode(self.charset) - - if dsc is not None: - # Check if we deal with a SET - if dsc[7] & FieldFlag.SET: - return self._SET_to_python(value, dsc) - if dsc[7] & FieldFlag.BINARY: - if self.charset != 'binary' and not isinstance(value, str): - try: - return value.decode(self.charset) - except (LookupError, UnicodeDecodeError): - return value - else: - return value - - return self._STRING_to_python(value, dsc) - - def _STRING_to_python(self, value, dsc=None): # pylint: disable=C0103 - """ - Note that a SET is a string too, but using the FieldFlag we can see - whether we have to split it. - - Returns string typed columns as string type. - """ - if dsc is not None: - # Check if we deal with a SET - if dsc[7] & FieldFlag.SET: - return self._SET_to_python(value, dsc) - if dsc[7] & FieldFlag.BINARY: - if self.charset != 'binary' and not isinstance(value, str): - try: - return value.decode(self.charset) - except (LookupError, UnicodeDecodeError): - return value - else: - return value - - if self.charset == 'binary': - return value - if isinstance(value, (bytes, bytearray)) and self.use_unicode: - return value.decode(self.charset) - - return value - - _VAR_STRING_to_python = _STRING_to_python - - def _BLOB_to_python(self, value, dsc=None): # pylint: disable=C0103 - """Convert BLOB data type to Python""" - if not value: - # This is an empty BLOB - return "" - # JSON Values are stored in LONG BLOB, but the blob values recived - # from the server are not dilutable, check for JSON values. - return self._JSON_to_python(value, dsc) - - _LONG_BLOB_to_python = _BLOB_to_python - _MEDIUM_BLOB_to_python = _BLOB_to_python - _TINY_BLOB_to_python = _BLOB_to_python diff --git a/modules/mysql-connector-python/mysql/connector/cursor.py b/modules/mysql-connector-python/mysql/connector/cursor.py deleted file mode 100644 index 1a2ab5c6a..000000000 --- a/modules/mysql-connector-python/mysql/connector/cursor.py +++ /dev/null @@ -1,1435 +0,0 @@ -# Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Cursor classes -""" - -from collections import namedtuple -import re -import weakref - -from . import errors -from .abstracts import MySQLCursorAbstract, NAMED_TUPLE_CACHE -from .catch23 import PY2 -from .constants import ServerFlag - -SQL_COMMENT = r"\/\*.*?\*\/" -RE_SQL_COMMENT = re.compile( - r'''({0})|(["'`][^"'`]*?({0})[^"'`]*?["'`])'''.format(SQL_COMMENT), - re.I | re.M | re.S) -RE_SQL_ON_DUPLICATE = re.compile( - r'''\s*ON\s+DUPLICATE\s+KEY(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$''', - re.I | re.M | re.S) -RE_SQL_INSERT_STMT = re.compile( - r"({0}|\s)*INSERT({0}|\s)*INTO\s+[`'\"]?.+[`'\"]?(?:\.[`'\"]?.+[`'\"]?)" - r"{{0,2}}\s+VALUES\s*\(.+(?:\s*,.+)*\)".format(SQL_COMMENT), - re.I | re.M | re.S) -RE_SQL_INSERT_VALUES = re.compile(r'.*VALUES\s*(\(.*\)).*', re.I | re.M | re.S) -RE_PY_PARAM = re.compile(b'(%s)') -RE_PY_MAPPING_PARAM = re.compile( - br''' - % - \((?P[^)]+)\) - (?P[diouxXeEfFgGcrs%]) - ''', - re.X -) -RE_SQL_SPLIT_STMTS = re.compile( - b''';(?=(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$)''') -RE_SQL_FIND_PARAM = re.compile( - b'''%s(?=(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$)''') - -ERR_NO_RESULT_TO_FETCH = "No result set to fetch from" - -MAX_RESULTS = 4294967295 - -class _ParamSubstitutor(object): - """ - Substitutes parameters into SQL statement. - """ - def __init__(self, params): - self.params = params - self.index = 0 - - def __call__(self, matchobj): - index = self.index - self.index += 1 - try: - return bytes(self.params[index]) - except IndexError: - raise errors.ProgrammingError( - "Not enough parameters for the SQL statement") - - @property - def remaining(self): - """Returns number of parameters remaining to be substituted""" - return len(self.params) - self.index - - -def _bytestr_format_dict(bytestr, value_dict): - """ - >>> _bytestr_format_dict(b'%(a)s', {b'a': b'foobar'}) - b'foobar - >>> _bytestr_format_dict(b'%%(a)s', {b'a': b'foobar'}) - b'%%(a)s' - >>> _bytestr_format_dict(b'%%%(a)s', {b'a': b'foobar'}) - b'%%foobar' - >>> _bytestr_format_dict(b'%(x)s %(y)s', - ... {b'x': b'x=%(y)s', b'y': b'y=%(x)s'}) - b'x=%(y)s y=%(x)s' - """ - def replace(matchobj): - """Replace pattern.""" - value = None - groups = matchobj.groupdict() - if groups["conversion_type"] == b"%": - value = b"%" - if groups["conversion_type"] == b"s": - key = groups["mapping_key"] - value = value_dict[key] - if value is None: - raise ValueError("Unsupported conversion_type: {0}" - "".format(groups["conversion_type"])) - return bytes(value) if PY2 else value - - stmt = RE_PY_MAPPING_PARAM.sub(replace, bytestr) - if PY2: - try: - return stmt.decode("utf-8") - except UnicodeDecodeError: - pass - return stmt - - -class CursorBase(MySQLCursorAbstract): - """ - Base for defining MySQLCursor. This class is a skeleton and defines - methods and members as required for the Python Database API - Specification v2.0. - - It's better to inherite from MySQLCursor. - """ - - _raw = False - - def __init__(self): - self._description = None - self._rowcount = -1 - self._last_insert_id = None - self.arraysize = 1 - super(CursorBase, self).__init__() - - def callproc(self, procname, args=()): - """Calls a stored procedue with the given arguments - - The arguments will be set during this session, meaning - they will be called like ___arg where - is an enumeration (+1) of the arguments. - - Coding Example: - 1) Definining the Stored Routine in MySQL: - CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT) - BEGIN - SET pProd := pFac1 * pFac2; - END - - 2) Executing in Python: - args = (5,5,0) # 0 is to hold pprod - cursor.callproc('multiply', args) - print(cursor.fetchone()) - - Does not return a value, but a result set will be - available when the CALL-statement execute successfully. - Raises exceptions when something is wrong. - """ - pass - - def close(self): - """Close the cursor.""" - pass - - def execute(self, operation, params=(), multi=False): - """Executes the given operation - - Executes the given operation substituting any markers with - the given parameters. - - For example, getting all rows where id is 5: - cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,)) - - The multi argument should be set to True when executing multiple - statements in one operation. If not set and multiple results are - found, an InterfaceError will be raised. - - If warnings where generated, and connection.get_warnings is True, then - self._warnings will be a list containing these warnings. - - Returns an iterator when multi is True, otherwise None. - """ - pass - - def executemany(self, operation, seq_params): - """Execute the given operation multiple times - - The executemany() method will execute the operation iterating - over the list of parameters in seq_params. - - Example: Inserting 3 new employees and their phone number - - data = [ - ('Jane','555-001'), - ('Joe', '555-001'), - ('John', '555-003') - ] - stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s')" - cursor.executemany(stmt, data) - - INSERT statements are optimized by batching the data, that is - using the MySQL multiple rows syntax. - - Results are discarded. If they are needed, consider looping over - data using the execute() method. - """ - pass - - def fetchone(self): - """Returns next row of a query result set - - Returns a tuple or None. - """ - pass - - def fetchmany(self, size=1): - """Returns the next set of rows of a query result, returning a - list of tuples. When no more rows are available, it returns an - empty list. - - The number of rows returned can be specified using the size argument, - which defaults to one - """ - pass - - def fetchall(self): - """Returns all rows of a query result set - - Returns a list of tuples. - """ - pass - - def nextset(self): - """Not Implemented.""" - pass - - def setinputsizes(self, sizes): - """Not Implemented.""" - pass - - def setoutputsize(self, size, column=None): - """Not Implemented.""" - pass - - def reset(self, free=True): - """Reset the cursor to default""" - pass - - @property - def description(self): - """Returns description of columns in a result - - This property returns a list of tuples describing the columns in - in a result set. A tuple is described as follows:: - - (column_name, - type, - None, - None, - None, - None, - null_ok, - column_flags) # Addition to PEP-249 specs - - Returns a list of tuples. - """ - return self._description - - @property - def rowcount(self): - """Returns the number of rows produced or affected - - This property returns the number of rows produced by queries - such as a SELECT, or affected rows when executing DML statements - like INSERT or UPDATE. - - Note that for non-buffered cursors it is impossible to know the - number of rows produced before having fetched them all. For those, - the number of rows will be -1 right after execution, and - incremented when fetching rows. - - Returns an integer. - """ - return self._rowcount - - @property - def lastrowid(self): - """Returns the value generated for an AUTO_INCREMENT column - - Returns the value generated for an AUTO_INCREMENT column by - the previous INSERT or UPDATE statement or None when there is - no such value available. - - Returns a long value or None. - """ - return self._last_insert_id - - -class MySQLCursor(CursorBase): - """Default cursor for interacting with MySQL - - This cursor will execute statements and handle the result. It will - not automatically fetch all rows. - - MySQLCursor should be inherited whenever other functionallity is - required. An example would to change the fetch* member functions - to return dictionaries instead of lists of values. - - Implements the Python Database API Specification v2.0 (PEP-249) - """ - def __init__(self, connection=None): - CursorBase.__init__(self) - self._connection = None - self._stored_results = [] - self._nextrow = (None, None) - self._warnings = None - self._warning_count = 0 - self._executed = None - self._executed_list = [] - self._binary = False - - if connection is not None: - self._set_connection(connection) - - def __iter__(self): - """ - Iteration over the result set which calls self.fetchone() - and returns the next row. - """ - return iter(self.fetchone, None) - - def _set_connection(self, connection): - """Set the connection""" - try: - self._connection = weakref.proxy(connection) - self._connection.is_connected() - except (AttributeError, TypeError): - raise errors.InterfaceError(errno=2048) - - def _reset_result(self): - """Reset the cursor to default""" - self._rowcount = -1 - self._nextrow = (None, None) - self._stored_results = [] - self._warnings = None - self._warning_count = 0 - self._description = None - self._executed = None - self._executed_list = [] - self.reset() - - def _have_unread_result(self): - """Check whether there is an unread result""" - try: - return self._connection.unread_result - except AttributeError: - return False - - def next(self): - """Used for iterating over the result set.""" - return self.__next__() - - def __next__(self): - """ - Used for iterating over the result set. Calles self.fetchone() - to get the next row. - """ - try: - row = self.fetchone() - except errors.InterfaceError: - raise StopIteration - if not row: - raise StopIteration - return row - - def close(self): - """Close the cursor - - Returns True when successful, otherwise False. - """ - if self._connection is None: - return False - - self._connection.handle_unread_result() - self._reset_result() - self._connection = None - - return True - - def _process_params_dict(self, params): - """Process query parameters given as dictionary""" - try: - to_mysql = self._connection.converter.to_mysql - escape = self._connection.converter.escape - quote = self._connection.converter.quote - res = {} - for key, value in list(params.items()): - conv = value - conv = to_mysql(conv) - conv = escape(conv) - conv = quote(conv) - if PY2: - res[key] = conv - else: - res[key.encode()] = conv - except Exception as err: - raise errors.ProgrammingError( - "Failed processing pyformat-parameters; %s" % err) - else: - return res - - def _process_params(self, params): - """Process query parameters.""" - try: - res = params - - to_mysql = self._connection.converter.to_mysql - escape = self._connection.converter.escape - quote = self._connection.converter.quote - - res = [to_mysql(i) for i in res] - res = [escape(i) for i in res] - res = [quote(i) for i in res] - except Exception as err: - raise errors.ProgrammingError( - "Failed processing format-parameters; %s" % err) - else: - return tuple(res) - - def _handle_noresultset(self, res): - """Handles result of execute() when there is no result set - """ - try: - self._rowcount = res['affected_rows'] - self._last_insert_id = res['insert_id'] - self._warning_count = res['warning_count'] - except (KeyError, TypeError) as err: - raise errors.ProgrammingError( - "Failed handling non-resultset; {0}".format(err)) - - self._handle_warnings() - if self._connection.raise_on_warnings is True and self._warnings: - raise errors.get_mysql_exception( - self._warnings[0][1], self._warnings[0][2]) - - def _handle_resultset(self): - """Handles result set - - This method handles the result set and is called after reading - and storing column information in _handle_result(). For non-buffering - cursors, this method is usually doing nothing. - """ - pass - - def _handle_result(self, result): - """ - Handle the result after a command was send. The result can be either - an OK-packet or a dictionary containing column/eof information. - - Raises InterfaceError when result is not a dict() or result is - invalid. - """ - if not isinstance(result, dict): - raise errors.InterfaceError('Result was not a dict()') - - if 'columns' in result: - # Weak test, must be column/eof information - self._description = result['columns'] - self._connection.unread_result = True - self._handle_resultset() - elif 'affected_rows' in result: - # Weak test, must be an OK-packet - self._connection.unread_result = False - self._handle_noresultset(result) - else: - raise errors.InterfaceError('Invalid result') - - def _execute_iter(self, query_iter): - """Generator returns MySQLCursor objects for multiple statements - - This method is only used when multiple statements are executed - by the execute() method. It uses zip() to make an iterator from the - given query_iter (result of MySQLConnection.cmd_query_iter()) and - the list of statements that were executed. - """ - executed_list = RE_SQL_SPLIT_STMTS.split(self._executed) - - i = 0 - while True: - try: - result = next(query_iter) - self._reset_result() - self._handle_result(result) - try: - self._executed = executed_list[i].strip() - i += 1 - except IndexError: - self._executed = executed_list[0] - - yield self - except StopIteration: - return - - def execute(self, operation, params=None, multi=False): - """Executes the given operation - - Executes the given operation substituting any markers with - the given parameters. - - For example, getting all rows where id is 5: - cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,)) - - The multi argument should be set to True when executing multiple - statements in one operation. If not set and multiple results are - found, an InterfaceError will be raised. - - If warnings where generated, and connection.get_warnings is True, then - self._warnings will be a list containing these warnings. - - Returns an iterator when multi is True, otherwise None. - """ - if not operation: - return None - - if not self._connection: - raise errors.ProgrammingError("Cursor is not connected") - - self._connection.handle_unread_result() - - self._reset_result() - stmt = '' - - try: - if not isinstance(operation, (bytes, bytearray)): - stmt = operation.encode(self._connection.python_charset) - else: - stmt = operation - except (UnicodeDecodeError, UnicodeEncodeError) as err: - raise errors.ProgrammingError(str(err)) - - if params is not None: - if isinstance(params, dict): - stmt = _bytestr_format_dict( - stmt, self._process_params_dict(params)) - elif isinstance(params, (list, tuple)): - psub = _ParamSubstitutor(self._process_params(params)) - stmt = RE_PY_PARAM.sub(psub, stmt) - if psub.remaining != 0: - raise errors.ProgrammingError( - "Not all parameters were used in the SQL statement") - - self._executed = stmt - if multi: - self._executed_list = [] - return self._execute_iter(self._connection.cmd_query_iter(stmt)) - - try: - self._handle_result(self._connection.cmd_query(stmt)) - except errors.InterfaceError: - if self._connection._have_next_result: # pylint: disable=W0212 - raise errors.InterfaceError( - "Use multi=True when executing multiple statements") - raise - return None - - def _batch_insert(self, operation, seq_params): - """Implements multi row insert""" - def remove_comments(match): - """Remove comments from INSERT statements. - - This function is used while removing comments from INSERT - statements. If the matched string is a comment not enclosed - by quotes, it returns an empty string, else the string itself. - """ - if match.group(1): - return "" - return match.group(2) - - tmp = re.sub(RE_SQL_ON_DUPLICATE, '', - re.sub(RE_SQL_COMMENT, remove_comments, operation)) - - matches = re.search(RE_SQL_INSERT_VALUES, tmp) - if not matches: - raise errors.InterfaceError( - "Failed rewriting statement for multi-row INSERT. " - "Check SQL syntax." - ) - fmt = matches.group(1).encode(self._connection.python_charset) - values = [] - - try: - stmt = operation.encode(self._connection.python_charset) - for params in seq_params: - tmp = fmt - if isinstance(params, dict): - tmp = _bytestr_format_dict( - tmp, self._process_params_dict(params)) - else: - psub = _ParamSubstitutor(self._process_params(params)) - tmp = RE_PY_PARAM.sub(psub, tmp) - if psub.remaining != 0: - raise errors.ProgrammingError( - "Not all parameters were used in the SQL statement") - #for p in self._process_params(params): - # tmp = tmp.replace(b'%s',p,1) - values.append(tmp) - if fmt in stmt: - stmt = stmt.replace(fmt, b','.join(values), 1) - self._executed = stmt - return stmt - return None - except (UnicodeDecodeError, UnicodeEncodeError) as err: - raise errors.ProgrammingError(str(err)) - except errors.Error: - raise - except Exception as err: - raise errors.InterfaceError( - "Failed executing the operation; %s" % err) - - def executemany(self, operation, seq_params): - """Execute the given operation multiple times - - The executemany() method will execute the operation iterating - over the list of parameters in seq_params. - - Example: Inserting 3 new employees and their phone number - - data = [ - ('Jane','555-001'), - ('Joe', '555-001'), - ('John', '555-003') - ] - stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s)" - cursor.executemany(stmt, data) - - INSERT statements are optimized by batching the data, that is - using the MySQL multiple rows syntax. - - Results are discarded. If they are needed, consider looping over - data using the execute() method. - """ - if not operation or not seq_params: - return None - self._connection.handle_unread_result() - - try: - _ = iter(seq_params) - except TypeError: - raise errors.ProgrammingError( - "Parameters for query must be an Iterable.") - - # Optimize INSERTs by batching them - if re.match(RE_SQL_INSERT_STMT, operation): - if not seq_params: - self._rowcount = 0 - return None - stmt = self._batch_insert(operation, seq_params) - if stmt is not None: - return self.execute(stmt) - - rowcnt = 0 - try: - for params in seq_params: - self.execute(operation, params) - if self.with_rows and self._have_unread_result(): - self.fetchall() - rowcnt += self._rowcount - except (ValueError, TypeError) as err: - raise errors.InterfaceError( - "Failed executing the operation; {0}".format(err)) - except: - # Raise whatever execute() raises - raise - self._rowcount = rowcnt - return None - - def stored_results(self): - """Returns an iterator for stored results - - This method returns an iterator over results which are stored when - callproc() is called. The iterator will provide MySQLCursorBuffered - instances. - - Returns a iterator. - """ - return iter(self._stored_results) - - def callproc(self, procname, args=()): - """Calls a stored procedure with the given arguments - - The arguments will be set during this session, meaning - they will be called like ___arg where - is an enumeration (+1) of the arguments. - - Coding Example: - 1) Defining the Stored Routine in MySQL: - CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT) - BEGIN - SET pProd := pFac1 * pFac2; - END - - 2) Executing in Python: - args = (5, 5, 0) # 0 is to hold pprod - cursor.callproc('multiply', args) - print(cursor.fetchone()) - - For OUT and INOUT parameters the user should provide the - type of the parameter as well. The argument should be a - tuple with first item as the value of the parameter to pass - and second argument the type of the argument. - - In the above example, one can call callproc method like: - args = (5, 5, (0, 'INT')) - cursor.callproc('multiply', args) - - The type of the argument given in the tuple will be used by - the MySQL CAST function to convert the values in the corresponding - MySQL type (See CAST in MySQL Reference for more information) - - Does not return a value, but a result set will be - available when the CALL-statement execute successfully. - Raises exceptions when something is wrong. - """ - if not procname or not isinstance(procname, str): - raise ValueError("procname must be a string") - - if not isinstance(args, (tuple, list)): - raise ValueError("args must be a sequence") - - argfmt = "@_{name}_arg{index}" - self._stored_results = [] - - results = [] - try: - argnames = [] - argtypes = [] - if args: - for idx, arg in enumerate(args): - argname = argfmt.format(name=procname, index=idx + 1) - argnames.append(argname) - if isinstance(arg, tuple): - argtypes.append(" CAST({0} AS {1})".format(argname, - arg[1])) - self.execute("SET {0}=%s".format(argname), (arg[0],)) - else: - argtypes.append(argname) - self.execute("SET {0}=%s".format(argname), (arg,)) - - call = "CALL {0}({1})".format(procname, ','.join(argnames)) - - # pylint: disable=W0212 - # We disable consuming results temporary to make sure we - # getting all results - can_consume_results = self._connection._consume_results - for result in self._connection.cmd_query_iter(call): - self._connection._consume_results = False - if self._raw: - tmp = MySQLCursorBufferedRaw(self._connection._get_self()) - else: - tmp = MySQLCursorBuffered(self._connection._get_self()) - tmp._executed = "(a result of {0})".format(call) - tmp._handle_result(result) - if tmp._warnings is not None: - self._warnings = tmp._warnings - if 'columns' in result: - results.append(tmp) - self._connection._consume_results = can_consume_results - # pylint: enable=W0212 - - if argnames: - select = "SELECT {0}".format(','.join(argtypes)) - self.execute(select) - self._stored_results = results - return self.fetchone() - - self._stored_results = results - return () - - except errors.Error: - raise - except Exception as err: - raise errors.InterfaceError( - "Failed calling stored routine; {0}".format(err)) - - def getlastrowid(self): - """Returns the value generated for an AUTO_INCREMENT column - - Returns the value generated for an AUTO_INCREMENT column by - the previous INSERT or UPDATE statement. - - Returns a long value or None. - """ - return self._last_insert_id - - def _fetch_warnings(self): - """ - Fetch warnings doing a SHOW WARNINGS. Can be called after getting - the result. - - Returns a result set or None when there were no warnings. - """ - res = [] - try: - cur = self._connection.cursor(raw=False) - cur.execute("SHOW WARNINGS") - res = cur.fetchall() - cur.close() - except Exception as err: - raise errors.InterfaceError( - "Failed getting warnings; %s" % err) - - if res: - return res - - return None - - def _handle_warnings(self): - """Handle possible warnings after all results are consumed""" - if self._connection.get_warnings is True and self._warning_count: - self._warnings = self._fetch_warnings() - - def _handle_eof(self, eof): - """Handle EOF packet""" - self._connection.unread_result = False - self._nextrow = (None, None) - self._warning_count = eof['warning_count'] - self._handle_warnings() - if self._connection.raise_on_warnings is True and self._warnings: - raise errors.get_mysql_exception( - self._warnings[0][1], self._warnings[0][2]) - - def _fetch_row(self, raw=False): - """Returns the next row in the result set - - Returns a tuple or None. - """ - if not self._have_unread_result(): - return None - row = None - - if self._nextrow == (None, None): - (row, eof) = self._connection.get_row( - binary=self._binary, columns=self.description, raw=raw) - else: - (row, eof) = self._nextrow - - if row: - self._nextrow = self._connection.get_row( - binary=self._binary, columns=self.description, raw=raw) - eof = self._nextrow[1] - if eof is not None: - self._handle_eof(eof) - if self._rowcount == -1: - self._rowcount = 1 - else: - self._rowcount += 1 - if eof: - self._handle_eof(eof) - - return row - - def fetchone(self): - """Returns next row of a query result set - - Returns a tuple or None. - """ - row = self._fetch_row() - if row: - return row - return None - - def fetchmany(self, size=None): - res = [] - cnt = (size or self.arraysize) - while cnt > 0 and self._have_unread_result(): - cnt -= 1 - row = self.fetchone() - if row: - res.append(row) - return res - - def fetchall(self): - if not self._have_unread_result(): - raise errors.InterfaceError("No result set to fetch from.") - (rows, eof) = self._connection.get_rows() - if self._nextrow[0]: - rows.insert(0, self._nextrow[0]) - - self._handle_eof(eof) - rowcount = len(rows) - if rowcount >= 0 and self._rowcount == -1: - self._rowcount = 0 - self._rowcount += rowcount - return rows - - @property - def column_names(self): - """Returns column names - - This property returns the columns names as a tuple. - - Returns a tuple. - """ - if not self.description: - return () - return tuple([d[0] for d in self.description]) - - @property - def statement(self): - """Returns the executed statement - - This property returns the executed statement. When multiple - statements were executed, the current statement in the iterator - will be returned. - """ - if self._executed is None: - return None - try: - return self._executed.strip().decode('utf-8') - except (AttributeError, UnicodeDecodeError): - return self._executed.strip() - - @property - def with_rows(self): - """Returns whether the cursor could have rows returned - - This property returns True when column descriptions are available - and possibly also rows, which will need to be fetched. - - Returns True or False. - """ - if not self.description: - return False - return True - - def __str__(self): - fmt = "{class_name}: {stmt}" - if self._executed: - try: - executed = self._executed.decode('utf-8') - except AttributeError: - executed = self._executed - if len(executed) > 40: - executed = executed[:40] + '..' - else: - executed = '(Nothing executed yet)' - return fmt.format(class_name=self.__class__.__name__, stmt=executed) - - -class MySQLCursorBuffered(MySQLCursor): - """Cursor which fetches rows within execute()""" - - def __init__(self, connection=None): - MySQLCursor.__init__(self, connection) - self._rows = None - self._next_row = 0 - - def _handle_resultset(self): - (self._rows, eof) = self._connection.get_rows() - self._rowcount = len(self._rows) - self._handle_eof(eof) - self._next_row = 0 - try: - self._connection.unread_result = False - except: - pass - - def reset(self, free=True): - self._rows = None - - def _fetch_row(self, raw=False): - row = None - try: - row = self._rows[self._next_row] - except: - return None - else: - self._next_row += 1 - return row - return None - - def fetchone(self): - """Returns next row of a query result set - - Returns a tuple or None. - """ - row = self._fetch_row() - if row: - return row - return None - - def fetchall(self): - if self._rows is None: - raise errors.InterfaceError("No result set to fetch from.") - res = [] - res = self._rows[self._next_row:] - self._next_row = len(self._rows) - return res - - def fetchmany(self, size=None): - res = [] - cnt = (size or self.arraysize) - while cnt > 0: - cnt -= 1 - row = self.fetchone() - if row: - res.append(row) - - return res - - @property - def with_rows(self): - return self._rows is not None - - -class MySQLCursorRaw(MySQLCursor): - """ - Skips conversion from MySQL datatypes to Python types when fetching rows. - """ - - _raw = True - - def fetchone(self): - row = self._fetch_row(raw=True) - - if row: - return row - return None - - def fetchall(self): - if not self._have_unread_result(): - raise errors.InterfaceError("No result set to fetch from.") - (rows, eof) = self._connection.get_rows(raw=True) - if self._nextrow[0]: - rows.insert(0, self._nextrow[0]) - self._handle_eof(eof) - rowcount = len(rows) - if rowcount >= 0 and self._rowcount == -1: - self._rowcount = 0 - self._rowcount += rowcount - return rows - - -class MySQLCursorBufferedRaw(MySQLCursorBuffered): - """ - Cursor which skips conversion from MySQL datatypes to Python types when - fetching rows and fetches rows within execute(). - """ - - _raw = True - - def _handle_resultset(self): - (self._rows, eof) = self._connection.get_rows(raw=self._raw) - self._rowcount = len(self._rows) - self._handle_eof(eof) - self._next_row = 0 - try: - self._connection.unread_result = False - except: - pass - - def fetchone(self): - row = self._fetch_row() - if row: - return row - return None - - def fetchall(self): - if self._rows is None: - raise errors.InterfaceError("No result set to fetch from.") - return [r for r in self._rows[self._next_row:]] - - @property - def with_rows(self): - return self._rows is not None - - -class MySQLCursorPrepared(MySQLCursor): - """Cursor using MySQL Prepared Statements - """ - def __init__(self, connection=None): - super(MySQLCursorPrepared, self).__init__(connection) - self._rows = None - self._next_row = 0 - self._prepared = None - self._binary = True - self._have_result = None - self._last_row_sent = False - self._cursor_exists = False - - def reset(self, free=True): - if self._prepared: - try: - self._connection.cmd_stmt_close(self._prepared['statement_id']) - except errors.Error: - # We tried to deallocate, but it's OK when we fail. - pass - self._prepared = None - self._last_row_sent = False - self._cursor_exists = False - - def _handle_noresultset(self, res): - self._handle_server_status(res.get('status_flag', - res.get('server_status', 0))) - super(MySQLCursorPrepared, self)._handle_noresultset(res) - - def _handle_server_status(self, flags): - """Check for SERVER_STATUS_CURSOR_EXISTS and - SERVER_STATUS_LAST_ROW_SENT flags set by the server. - """ - self._cursor_exists = flags & ServerFlag.STATUS_CURSOR_EXISTS != 0 - self._last_row_sent = flags & ServerFlag.STATUS_LAST_ROW_SENT != 0 - - def _handle_eof(self, eof): - self._handle_server_status(eof.get('status_flag', - eof.get('server_status', 0))) - super(MySQLCursorPrepared, self)._handle_eof(eof) - - def callproc(self, procname, args=()): - """Calls a stored procedue - - Not supported with MySQLCursorPrepared. - """ - raise errors.NotSupportedError() - - def close(self): - """Close the cursor - - This method will try to deallocate the prepared statement and close - the cursor. - """ - self.reset() - super(MySQLCursorPrepared, self).close() - - def _row_to_python(self, rowdata, desc=None): - """Convert row data from MySQL to Python types - - The conversion is done while reading binary data in the - protocol module. - """ - pass - - def _handle_result(self, result): - """Handle result after execution""" - if isinstance(result, dict): - self._connection.unread_result = False - self._have_result = False - self._handle_noresultset(result) - else: - self._description = result[1] - self._connection.unread_result = True - self._have_result = True - - if 'status_flag' in result[2]: - self._handle_server_status(result[2]['status_flag']) - elif 'server_status' in result[2]: - self._handle_server_status(result[2]['server_status']) - - def execute(self, operation, params=(), multi=False): # multi is unused - """Prepare and execute a MySQL Prepared Statement - - This method will preare the given operation and execute it using - the optionally given parameters. - - If the cursor instance already had a prepared statement, it is - first closed. - """ - if operation is not self._executed: - if self._prepared: - self._connection.cmd_stmt_close(self._prepared['statement_id']) - - self._executed = operation - try: - if not isinstance(operation, bytes): - charset = self._connection.charset - if charset == 'utf8mb4': - charset = 'utf8' - operation = operation.encode(charset) - except (UnicodeDecodeError, UnicodeEncodeError) as err: - raise errors.ProgrammingError(str(err)) - - # need to convert %s to ? before sending it to MySQL - if b'%s' in operation: - operation = re.sub(RE_SQL_FIND_PARAM, b'?', operation) - - try: - self._prepared = self._connection.cmd_stmt_prepare(operation) - except errors.Error: - self._executed = None - raise - - self._connection.cmd_stmt_reset(self._prepared['statement_id']) - - if self._prepared['parameters'] and not params: - return - elif len(self._prepared['parameters']) != len(params): - raise errors.ProgrammingError( - errno=1210, - msg="Incorrect number of arguments " \ - "executing prepared statement") - - res = self._connection.cmd_stmt_execute( - self._prepared['statement_id'], - data=params, - parameters=self._prepared['parameters']) - self._handle_result(res) - - def executemany(self, operation, seq_params): - """Prepare and execute a MySQL Prepared Statement many times - - This method will prepare the given operation and execute with each - tuple found the list seq_params. - - If the cursor instance already had a prepared statement, it is - first closed. - - executemany() simply calls execute(). - """ - rowcnt = 0 - try: - for params in seq_params: - self.execute(operation, params) - if self.with_rows and self._have_unread_result(): - self.fetchall() - rowcnt += self._rowcount - except (ValueError, TypeError) as err: - raise errors.InterfaceError( - "Failed executing the operation; {error}".format(error=err)) - except: - # Raise whatever execute() raises - raise - self._rowcount = rowcnt - - def fetchone(self): - """Returns next row of a query result set - - Returns a tuple or None. - """ - if self._cursor_exists: - self._connection.cmd_stmt_fetch(self._prepared['statement_id']) - return self._fetch_row() or None - - def fetchmany(self, size=None): - res = [] - cnt = (size or self.arraysize) - while cnt > 0 and self._have_unread_result(): - cnt -= 1 - row = self._fetch_row() - if row: - res.append(row) - return res - - def fetchall(self): - if not self._have_unread_result(): - raise errors.InterfaceError("No result set to fetch from.") - rows = [] - if self._nextrow[0]: - rows.append(self._nextrow[0]) - while self._have_unread_result(): - if self._cursor_exists: - self._connection.cmd_stmt_fetch( - self._prepared['statement_id'], MAX_RESULTS) - (tmp, eof) = self._connection.get_rows( - binary=self._binary, columns=self.description) - rows.extend(tmp) - self._handle_eof(eof) - self._rowcount = len(rows) - return rows - - -class MySQLCursorDict(MySQLCursor): - """ - Cursor fetching rows as dictionaries. - - The fetch methods of this class will return dictionaries instead of tuples. - Each row is a dictionary that looks like: - row = { - "col1": value1, - "col2": value2 - } - """ - def _row_to_python(self, rowdata, desc=None): - """Convert a MySQL text result row to Python types - - Returns a dictionary. - """ - row = rowdata - - if row: - return dict(zip(self.column_names, row)) - - return None - - def fetchone(self): - """Returns next row of a query result set - """ - row = self._fetch_row() - if row: - return self._row_to_python(row, self.description) - return None - - def fetchall(self): - """Returns all rows of a query result set - """ - if not self._have_unread_result(): - raise errors.InterfaceError(ERR_NO_RESULT_TO_FETCH) - (rows, eof) = self._connection.get_rows() - if self._nextrow[0]: - rows.insert(0, self._nextrow[0]) - res = [] - for row in rows: - res.append(self._row_to_python(row, self.description)) - self._handle_eof(eof) - rowcount = len(rows) - if rowcount >= 0 and self._rowcount == -1: - self._rowcount = 0 - self._rowcount += rowcount - return res - - -class MySQLCursorNamedTuple(MySQLCursor): - """ - Cursor fetching rows as named tuple. - - The fetch methods of this class will return namedtuples instead of tuples. - Each row is returned as a namedtuple and the values can be accessed as: - row.col1, row.col2 - """ - def _row_to_python(self, rowdata, desc=None): - """Convert a MySQL text result row to Python types - - Returns a named tuple. - """ - row = rowdata - - if row: - # pylint: disable=W0201 - columns = tuple(self.column_names) - try: - named_tuple = NAMED_TUPLE_CACHE[columns] - except KeyError: - named_tuple = namedtuple('Row', columns) - NAMED_TUPLE_CACHE[columns] = named_tuple - # pylint: enable=W0201 - return named_tuple(*row) - return None - - def fetchone(self): - """Returns next row of a query result set - """ - row = self._fetch_row() - if row: - if hasattr(self._connection, 'converter'): - return self._row_to_python(row, self.description) - return row - return None - - def fetchall(self): - """Returns all rows of a query result set - """ - if not self._have_unread_result(): - raise errors.InterfaceError(ERR_NO_RESULT_TO_FETCH) - (rows, eof) = self._connection.get_rows() - if self._nextrow[0]: - rows.insert(0, self._nextrow[0]) - res = [self._row_to_python(row, self.description) - for row in rows] - - self._handle_eof(eof) - rowcount = len(rows) - if rowcount >= 0 and self._rowcount == -1: - self._rowcount = 0 - self._rowcount += rowcount - return res - - -class MySQLCursorBufferedDict(MySQLCursorDict, MySQLCursorBuffered): - """ - Buffered Cursor fetching rows as dictionaries. - """ - def fetchone(self): - """Returns next row of a query result set - """ - row = self._fetch_row() - if row: - return self._row_to_python(row, self.description) - return None - - def fetchall(self): - """Returns all rows of a query result set - """ - if self._rows is None: - raise errors.InterfaceError(ERR_NO_RESULT_TO_FETCH) - res = [] - for row in self._rows[self._next_row:]: - res.append(self._row_to_python( - row, self.description)) - self._next_row = len(self._rows) - return res - - -class MySQLCursorBufferedNamedTuple(MySQLCursorNamedTuple, MySQLCursorBuffered): - """ - Buffered Cursor fetching rows as named tuple. - """ - def fetchone(self): - """Returns next row of a query result set - """ - row = self._fetch_row() - if row: - return self._row_to_python(row, self.description) - return None - - def fetchall(self): - """Returns all rows of a query result set - """ - if self._rows is None: - raise errors.InterfaceError(ERR_NO_RESULT_TO_FETCH) - res = [] - for row in self._rows[self._next_row:]: - res.append(self._row_to_python( - row, self.description)) - self._next_row = len(self._rows) - return res diff --git a/modules/mysql-connector-python/mysql/connector/cursor_cext.py b/modules/mysql-connector-python/mysql/connector/cursor_cext.py deleted file mode 100644 index 64f8c28a7..000000000 --- a/modules/mysql-connector-python/mysql/connector/cursor_cext.py +++ /dev/null @@ -1,1007 +0,0 @@ -# Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Cursor classes using the C Extension -""" - -from collections import namedtuple -import re -import weakref - -from _mysql_connector import MySQLInterfaceError # pylint: disable=F0401,E0611 - -from .abstracts import (MySQLConnectionAbstract, MySQLCursorAbstract, - NAMED_TUPLE_CACHE) -from .catch23 import PY2, isunicode -from . import errors -from .errorcode import CR_NO_RESULT_SET - -from .cursor import ( - RE_PY_PARAM, RE_SQL_INSERT_STMT, - RE_SQL_ON_DUPLICATE, RE_SQL_COMMENT, RE_SQL_INSERT_VALUES, - RE_SQL_SPLIT_STMTS, RE_SQL_FIND_PARAM -) - - -class _ParamSubstitutor(object): - - """ - Substitutes parameters into SQL statement. - """ - - def __init__(self, params): - self.params = params - self.index = 0 - - def __call__(self, matchobj): - index = self.index - self.index += 1 - try: - return self.params[index] - except IndexError: - raise errors.ProgrammingError( - "Not enough parameters for the SQL statement") - - @property - def remaining(self): - """Returns number of parameters remaining to be substituted""" - return len(self.params) - self.index - - -class CMySQLCursor(MySQLCursorAbstract): - - """Default cursor for interacting with MySQL using C Extension""" - - _raw = False - _buffered = False - _raw_as_string = False - - def __init__(self, connection): - """Initialize""" - MySQLCursorAbstract.__init__(self) - - self._insert_id = 0 - self._warning_count = 0 - self._warnings = None - self._affected_rows = -1 - self._rowcount = -1 - self._nextrow = (None, None) - self._executed = None - self._executed_list = [] - self._stored_results = [] - - if not isinstance(connection, MySQLConnectionAbstract): - raise errors.InterfaceError(errno=2048) - self._cnx = weakref.proxy(connection) - - def reset(self, free=True): - """Reset the cursor - - When free is True (default) the result will be freed. - """ - self._rowcount = -1 - self._nextrow = None - self._affected_rows = -1 - self._insert_id = 0 - self._warning_count = 0 - self._warnings = None - self._warnings = None - self._warning_count = 0 - self._description = None - self._executed = None - self._executed_list = [] - if free and self._cnx: - self._cnx.free_result() - super(CMySQLCursor, self).reset() - - def _fetch_warnings(self): - """Fetch warnings - - Fetch warnings doing a SHOW WARNINGS. Can be called after getting - the result. - - Returns a result set or None when there were no warnings. - - Raises errors.Error (or subclass) on errors. - - Returns list of tuples or None. - """ - warnings = [] - try: - # force freeing result - self._cnx.consume_results() - _ = self._cnx.cmd_query("SHOW WARNINGS") - warnings = self._cnx.get_rows()[0] - self._cnx.consume_results() - except MySQLInterfaceError as exc: - raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno, - sqlstate=exc.sqlstate) - except Exception as err: - raise errors.InterfaceError( - "Failed getting warnings; {0}".format(str(err))) - - if warnings: - return warnings - - return None - - def _handle_warnings(self): - """Handle possible warnings after all results are consumed""" - if self._cnx.get_warnings is True and self._warning_count: - self._warnings = self._fetch_warnings() - - def _handle_result(self, result): - """Handles the result after statement execution""" - if 'columns' in result: - self._description = result['columns'] - self._rowcount = 0 - self._handle_resultset() - else: - self._insert_id = result['insert_id'] - self._warning_count = result['warning_count'] - self._affected_rows = result['affected_rows'] - self._rowcount = -1 - self._handle_warnings() - if self._cnx.raise_on_warnings is True and self._warnings: - raise errors.get_mysql_exception(*self._warnings[0][1:3]) - - def _handle_resultset(self): - """Handle a result set""" - pass - - def _handle_eof(self): - """Handle end of reading the result - - Raises an errors.Error on errors. - """ - self._warning_count = self._cnx.warning_count - self._handle_warnings() - if self._cnx.raise_on_warnings is True and self._warnings: - raise errors.get_mysql_exception(*self._warnings[0][1:3]) - - if not self._cnx.more_results: - self._cnx.free_result() - - def _execute_iter(self): - """Generator returns MySQLCursor objects for multiple statements - - Deprecated: use nextset() method directly. - - This method is only used when multiple statements are executed - by the execute() method. It uses zip() to make an iterator from the - given query_iter (result of MySQLConnection.cmd_query_iter()) and - the list of statements that were executed. - """ - executed_list = RE_SQL_SPLIT_STMTS.split(self._executed) - i = 0 - self._executed = executed_list[i] - yield self - - while True: - try: - if not self.nextset(): - raise StopIteration - except errors.InterfaceError as exc: - # Result without result set - if exc.errno != CR_NO_RESULT_SET: - raise - except StopIteration: - return - i += 1 - try: - self._executed = executed_list[i].strip() - except IndexError: - self._executed = executed_list[0] - yield self - return - - def execute(self, operation, params=(), multi=False): - """Execute given statement using given parameters - - Deprecated: The multi argument is not needed and nextset() should - be used to handle multiple result sets. - """ - if not operation: - return None - - if not self._cnx: - raise errors.ProgrammingError("Cursor is not connected") - self._cnx.handle_unread_result() - - stmt = '' - self.reset() - - try: - if isunicode(operation): - stmt = operation.encode(self._cnx.python_charset) - else: - stmt = operation - except (UnicodeDecodeError, UnicodeEncodeError) as err: - raise errors.ProgrammingError(str(err)) - - if params: - prepared = self._cnx.prepare_for_mysql(params) - if isinstance(prepared, dict): - for key, value in prepared.items(): - if PY2: - stmt = stmt.replace("%({0})s".format(key), value) - else: - stmt = stmt.replace("%({0})s".format(key).encode(), - value) - elif isinstance(prepared, (list, tuple)): - psub = _ParamSubstitutor(prepared) - stmt = RE_PY_PARAM.sub(psub, stmt) - if psub.remaining != 0: - raise errors.ProgrammingError( - "Not all parameters were used in the SQL statement") - - try: - result = self._cnx.cmd_query(stmt, raw=self._raw, - buffered=self._buffered, - raw_as_string=self._raw_as_string) - except MySQLInterfaceError as exc: - raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno, - sqlstate=exc.sqlstate) - - self._executed = stmt - self._handle_result(result) - - if multi: - return self._execute_iter() - - return None - - def _batch_insert(self, operation, seq_params): - """Implements multi row insert""" - def remove_comments(match): - """Remove comments from INSERT statements. - - This function is used while removing comments from INSERT - statements. If the matched string is a comment not enclosed - by quotes, it returns an empty string, else the string itself. - """ - if match.group(1): - return "" - return match.group(2) - - tmp = re.sub(RE_SQL_ON_DUPLICATE, '', - re.sub(RE_SQL_COMMENT, remove_comments, operation)) - - matches = re.search(RE_SQL_INSERT_VALUES, tmp) - if not matches: - raise errors.InterfaceError( - "Failed rewriting statement for multi-row INSERT. " - "Check SQL syntax." - ) - fmt = matches.group(1).encode(self._cnx.python_charset) - values = [] - - try: - stmt = operation.encode(self._cnx.python_charset) - for params in seq_params: - tmp = fmt - prepared = self._cnx.prepare_for_mysql(params) - if isinstance(prepared, dict): - for key, value in prepared.items(): - tmp = tmp.replace("%({0})s".format(key).encode(), value) - elif isinstance(prepared, (list, tuple)): - psub = _ParamSubstitutor(prepared) - tmp = RE_PY_PARAM.sub(psub, tmp) - if psub.remaining != 0: - raise errors.ProgrammingError( - "Not all parameters were used in the SQL statement") - values.append(tmp) - - if fmt in stmt: - stmt = stmt.replace(fmt, b','.join(values), 1) - self._executed = stmt - return stmt - return None - except (UnicodeDecodeError, UnicodeEncodeError) as err: - raise errors.ProgrammingError(str(err)) - except Exception as err: - raise errors.InterfaceError( - "Failed executing the operation; %s" % err) - - - def executemany(self, operation, seq_params): - """Execute the given operation multiple times""" - if not operation or not seq_params: - return None - - if not self._cnx: - raise errors.ProgrammingError("Cursor is not connected") - self._cnx.handle_unread_result() - - if not isinstance(seq_params, (list, tuple)): - raise errors.ProgrammingError( - "Parameters for query must be list or tuple.") - - # Optimize INSERTs by batching them - if re.match(RE_SQL_INSERT_STMT, operation): - if not seq_params: - self._rowcount = 0 - return None - stmt = self._batch_insert(operation, seq_params) - if stmt is not None: - return self.execute(stmt) - - rowcnt = 0 - try: - for params in seq_params: - self.execute(operation, params) - try: - while True: - if self._description: - rowcnt += len(self._cnx.get_rows()[0]) - else: - rowcnt += self._affected_rows - if not self.nextset(): - break - except StopIteration: - # No more results - pass - - except (ValueError, TypeError) as err: - raise errors.ProgrammingError( - "Failed executing the operation; {0}".format(err)) - - self._rowcount = rowcnt - return None - - @property - def description(self): - """Returns description of columns in a result""" - return self._description - - @property - def rowcount(self): - """Returns the number of rows produced or affected""" - if self._rowcount == -1: - return self._affected_rows - return self._rowcount - - @property - def lastrowid(self): - """Returns the value generated for an AUTO_INCREMENT column""" - return self._insert_id - - def close(self): - """Close the cursor - - The result will be freed. - """ - if not self._cnx: - return False - - self._cnx.handle_unread_result() - self._warnings = None - self._cnx = None - return True - - def callproc(self, procname, args=()): - """Calls a stored procedure with the given arguments""" - if not procname or not isinstance(procname, str): - raise ValueError("procname must be a string") - - if not isinstance(args, (tuple, list)): - raise ValueError("args must be a sequence") - - argfmt = "@_{name}_arg{index}" - self._stored_results = [] - - try: - argnames = [] - argtypes = [] - if args: - for idx, arg in enumerate(args): - argname = argfmt.format(name=procname, index=idx + 1) - argnames.append(argname) - if isinstance(arg, tuple): - argtypes.append(" CAST({0} AS {1})".format(argname, - arg[1])) - self.execute("SET {0}=%s".format(argname), (arg[0],)) - else: - argtypes.append(argname) - self.execute("SET {0}=%s".format(argname), (arg,)) - - call = "CALL {0}({1})".format(procname, ','.join(argnames)) - - result = self._cnx.cmd_query(call, raw=self._raw, - raw_as_string=self._raw_as_string) - - results = [] - while self._cnx.result_set_available: - result = self._cnx.fetch_eof_columns() - # pylint: disable=W0212 - if self._raw: - cur = CMySQLCursorBufferedRaw(self._cnx._get_self()) - else: - cur = CMySQLCursorBuffered(self._cnx._get_self()) - cur._executed = "(a result of {0})".format(call) - cur._handle_result(result) - # pylint: enable=W0212 - results.append(cur) - self._cnx.next_result() - self._stored_results = results - self._handle_eof() - - if argnames: - self.reset() - select = "SELECT {0}".format(','.join(argtypes)) - self.execute(select) - - return self.fetchone() - return tuple() - - except errors.Error: - raise - except Exception as err: - raise errors.InterfaceError( - "Failed calling stored routine; {0}".format(err)) - - def nextset(self): - """Skip to the next available result set""" - if not self._cnx.next_result(): - self.reset(free=True) - return None - self.reset(free=False) - - if not self._cnx.result_set_available: - eof = self._cnx.fetch_eof_status() - self._handle_result(eof) - raise errors.InterfaceError(errno=CR_NO_RESULT_SET) - - self._handle_result(self._cnx.fetch_eof_columns()) - return True - - def fetchall(self): - """Returns all rows of a query result set - - Returns a list of tuples. - """ - if not self._cnx.unread_result: - raise errors.InterfaceError("No result set to fetch from.") - rows = self._cnx.get_rows() - if self._nextrow and self._nextrow[0]: - rows[0].insert(0, self._nextrow[0]) - - if not rows[0]: - self._handle_eof() - return [] - - self._rowcount += len(rows[0]) - self._handle_eof() - #self._cnx.handle_unread_result() - return rows[0] - - def fetchmany(self, size=1): - """Returns the next set of rows of a result set""" - if self._nextrow and self._nextrow[0]: - rows = [self._nextrow[0]] - size -= 1 - else: - rows = [] - - if size and self._cnx.unread_result: - rows.extend(self._cnx.get_rows(size)[0]) - - if size and self._cnx.unread_result: - self._nextrow = self._cnx.get_row() - if self._nextrow and not self._nextrow[0] and \ - not self._cnx.more_results: - self._cnx.free_result() - - if not rows: - self._handle_eof() - return [] - - self._rowcount += len(rows) - return rows - - def fetchone(self): - """Returns next row of a query result set""" - row = self._nextrow - if not row and self._cnx.unread_result: - row = self._cnx.get_row() - - if row and row[0]: - self._nextrow = self._cnx.get_row() - if not self._nextrow[0] and not self._cnx.more_results: - self._cnx.free_result() - else: - self._handle_eof() - return None - self._rowcount += 1 - return row[0] - - def __iter__(self): - """Iteration over the result set - - Iteration over the result set which calls self.fetchone() - and returns the next row. - """ - return iter(self.fetchone, None) - - def stored_results(self): - """Returns an iterator for stored results - - This method returns an iterator over results which are stored when - callproc() is called. The iterator will provide MySQLCursorBuffered - instances. - - Returns a iterator. - """ - for i in range(len(self._stored_results)): - yield self._stored_results[i] - - self._stored_results = [] - - if PY2: - def next(self): - """Used for iterating over the result set.""" - return self.__next__() - - def __next__(self): - """Iteration over the result set - Used for iterating over the result set. Calls self.fetchone() - to get the next row. - - Raises StopIteration when no more rows are available. - """ - try: - row = self.fetchone() - except errors.InterfaceError: - raise StopIteration - if not row: - raise StopIteration - return row - - @property - def column_names(self): - """Returns column names - - This property returns the columns names as a tuple. - - Returns a tuple. - """ - if not self.description: - return () - return tuple([d[0] for d in self.description]) - - @property - def statement(self): - """Returns the executed statement - - This property returns the executed statement. When multiple - statements were executed, the current statement in the iterator - will be returned. - """ - try: - return self._executed.strip().decode('utf8') - except AttributeError: - return self._executed.strip() - - @property - def with_rows(self): - """Returns whether the cursor could have rows returned - - This property returns True when column descriptions are available - and possibly also rows, which will need to be fetched. - - Returns True or False. - """ - if self.description: - return True - return False - - def __str__(self): - fmt = "{class_name}: {stmt}" - if self._executed: - try: - executed = self._executed.decode('utf-8') - except AttributeError: - executed = self._executed - if len(executed) > 40: - executed = executed[:40] + '..' - else: - executed = '(Nothing executed yet)' - - return fmt.format(class_name=self.__class__.__name__, stmt=executed) - - -class CMySQLCursorBuffered(CMySQLCursor): - - """Cursor using C Extension buffering results""" - - def __init__(self, connection): - """Initialize""" - super(CMySQLCursorBuffered, self).__init__(connection) - - self._rows = None - self._next_row = 0 - - def _handle_resultset(self): - """Handle a result set""" - self._rows = self._cnx.get_rows()[0] - self._next_row = 0 - self._rowcount = len(self._rows) - self._handle_eof() - - def reset(self, free=True): - """Reset the cursor to default""" - self._rows = None - self._next_row = 0 - super(CMySQLCursorBuffered, self).reset(free=free) - - def _fetch_row(self): - """Returns the next row in the result set - - Returns a tuple or None. - """ - row = None - try: - row = self._rows[self._next_row] - except IndexError: - return None - else: - self._next_row += 1 - - return row - - def fetchall(self): - if self._rows is None: - raise errors.InterfaceError("No result set to fetch from.") - res = self._rows[self._next_row:] - self._next_row = len(self._rows) - return res - - def fetchmany(self, size=1): - res = [] - cnt = size or self.arraysize - while cnt > 0: - cnt -= 1 - row = self._fetch_row() - if row: - res.append(row) - else: - break - return res - - def fetchone(self): - return self._fetch_row() - - -class CMySQLCursorRaw(CMySQLCursor): - - """Cursor using C Extension return raw results""" - - _raw = True - - -class CMySQLCursorBufferedRaw(CMySQLCursorBuffered): - - """Cursor using C Extension buffering raw results""" - - _raw = True - - -class CMySQLCursorDict(CMySQLCursor): - - """Cursor using C Extension returning rows as dictionaries""" - - _raw = False - - def fetchone(self): - """Returns all rows of a query result set - """ - row = super(CMySQLCursorDict, self).fetchone() - if row: - return dict(zip(self.column_names, row)) - return None - - def fetchmany(self, size=1): - """Returns next set of rows as list of dictionaries""" - res = super(CMySQLCursorDict, self).fetchmany(size=size) - return [dict(zip(self.column_names, row)) for row in res] - - def fetchall(self): - """Returns all rows of a query result set as list of dictionaries""" - res = super(CMySQLCursorDict, self).fetchall() - return [dict(zip(self.column_names, row)) for row in res] - - -class CMySQLCursorBufferedDict(CMySQLCursorBuffered): - - """Cursor using C Extension buffering and returning rows as dictionaries""" - - _raw = False - - def _fetch_row(self): - row = super(CMySQLCursorBufferedDict, self)._fetch_row() - if row: - return dict(zip(self.column_names, row)) - return None - - def fetchall(self): - res = super(CMySQLCursorBufferedDict, self).fetchall() - return [dict(zip(self.column_names, row)) for row in res] - - -class CMySQLCursorNamedTuple(CMySQLCursor): - - """Cursor using C Extension returning rows as named tuples""" - - def _handle_resultset(self): - """Handle a result set""" - super(CMySQLCursorNamedTuple, self)._handle_resultset() - # pylint: disable=W0201 - columns = tuple(self.column_names) - try: - self.named_tuple = NAMED_TUPLE_CACHE[columns] - except KeyError: - self.named_tuple = namedtuple('Row', columns) - NAMED_TUPLE_CACHE[columns] = self.named_tuple - # pylint: enable=W0201 - - def fetchone(self): - """Returns all rows of a query result set - """ - row = super(CMySQLCursorNamedTuple, self).fetchone() - if row: - return self.named_tuple(*row) - return None - - def fetchmany(self, size=1): - """Returns next set of rows as list of named tuples""" - res = super(CMySQLCursorNamedTuple, self).fetchmany(size=size) - return [self.named_tuple(*res[0])] - - def fetchall(self): - """Returns all rows of a query result set as list of named tuples""" - res = super(CMySQLCursorNamedTuple, self).fetchall() - return [self.named_tuple(*row) for row in res] - - -class CMySQLCursorBufferedNamedTuple(CMySQLCursorBuffered): - - """Cursor using C Extension buffering and returning rows as named tuples""" - - def _handle_resultset(self): - super(CMySQLCursorBufferedNamedTuple, self)._handle_resultset() - # pylint: disable=W0201 - self.named_tuple = namedtuple('Row', self.column_names) - # pylint: enable=W0201 - - def _fetch_row(self): - row = super(CMySQLCursorBufferedNamedTuple, self)._fetch_row() - if row: - return self.named_tuple(*row) - return None - - def fetchall(self): - res = super(CMySQLCursorBufferedNamedTuple, self).fetchall() - return [self.named_tuple(*row) for row in res] - - -class CMySQLCursorPrepared(CMySQLCursor): - - """Cursor using MySQL Prepared Statements""" - - def __init__(self, connection): - super(CMySQLCursorPrepared, self).__init__(connection) - self._rows = None - self._rowcount = 0 - self._next_row = 0 - self._binary = True - self._stmt = None - - def _handle_eof(self): - """Handle EOF packet""" - self._nextrow = (None, None) - self._handle_warnings() - if self._cnx.raise_on_warnings is True and self._warnings: - raise errors.get_mysql_exception( - self._warnings[0][1], self._warnings[0][2]) - - def _fetch_row(self, raw=False): - """Returns the next row in the result set - - Returns a tuple or None. - """ - if not self._stmt or not self._stmt.have_result_set: - return None - row = None - - if self._nextrow == (None, None): - (row, eof) = self._cnx.get_row( - binary=self._binary, columns=self.description, raw=raw, - prep_stmt=self._stmt) - else: - (row, eof) = self._nextrow - - if row: - self._nextrow = self._cnx.get_row( - binary=self._binary, columns=self.description, raw=raw, - prep_stmt=self._stmt) - eof = self._nextrow[1] - if eof is not None: - self._warning_count = eof["warning_count"] - self._handle_eof() - if self._rowcount == -1: - self._rowcount = 1 - else: - self._rowcount += 1 - if eof: - self._warning_count = eof["warning_count"] - self._handle_eof() - - return row - - def callproc(self, procname, args=None): - """Calls a stored procedue - - Not supported with CMySQLCursorPrepared. - """ - raise errors.NotSupportedError() - - def close(self): - """Close the cursor - - This method will try to deallocate the prepared statement and close - the cursor. - """ - if self._stmt: - self.reset() - self._cnx.cmd_stmt_close(self._stmt) - self._stmt = None - super(CMySQLCursorPrepared, self).close() - - def reset(self, free=True): - """Resets the prepared statement.""" - if self._stmt: - self._cnx.cmd_stmt_reset(self._stmt) - super(CMySQLCursorPrepared, self).reset(free=free) - - def execute(self, operation, params=(), multi=False): # multi is unused - """Prepare and execute a MySQL Prepared Statement - - This method will preare the given operation and execute it using - the given parameters. - - If the cursor instance already had a prepared statement, it is - first closed. - """ - if not operation: - return - - if not self._cnx: - raise errors.ProgrammingError("Cursor is not connected") - - self._cnx.handle_unread_result(prepared=True) - - if operation is not self._executed: - if self._stmt: - self._cnx.cmd_stmt_close(self._stmt) - - self._executed = operation - - try: - if not isinstance(operation, bytes): - charset = self._cnx.charset - if charset == "utf8mb4": - charset = "utf8" - operation = operation.encode(charset) - except (UnicodeDecodeError, UnicodeEncodeError) as err: - raise errors.ProgrammingError(str(err)) - - # need to convert %s to ? before sending it to MySQL - if b"%s" in operation: - operation = re.sub(RE_SQL_FIND_PARAM, b"?", operation) - - try: - self._stmt = self._cnx.cmd_stmt_prepare(operation) - except (errors.Error, errors.ProgrammingError): - self._stmt = None - raise - - self._cnx.cmd_stmt_reset(self._stmt) - - if params and self._stmt.param_count != len(params): - raise errors.ProgrammingError( - errno=1210, - msg="Incorrect number of arguments executing prepared " - "statement") - - res = self._cnx.cmd_stmt_execute(self._stmt, *params) - if res: - self._handle_result(res) - - def executemany(self, operation, seq_params): - """Prepare and execute a MySQL Prepared Statement many times - - This method will prepare the given operation and execute with each - tuple found the list seq_params. - - If the cursor instance already had a prepared statement, it is - first closed. - """ - rowcnt = 0 - try: - for params in seq_params: - self.execute(operation, params) - if self.with_rows: - self.fetchall() - rowcnt += self._rowcount - except (ValueError, TypeError) as err: - raise errors.InterfaceError( - "Failed executing the operation; {error}".format(error=err)) - except: - # Raise whatever execute() raises - raise - self._rowcount = rowcnt - - def fetchone(self): - """Returns next row of a query result set - - Returns a tuple or None. - """ - return self._fetch_row() or None - - def fetchmany(self, size=None): - """Returns the next set of rows of a result set - - Returns a list of tuples. - """ - res = [] - cnt = size or self.arraysize - while cnt > 0 and self._stmt.have_result_set: - cnt -= 1 - row = self._fetch_row() - if row: - res.append(row) - return res - - def fetchall(self): - """Returns all rows of a query result set - - Returns a list of tuples. - """ - if not self._stmt.have_result_set: - raise errors.InterfaceError("No result set to fetch from.") - rows = self._cnx.get_rows(prep_stmt=self._stmt) - if self._nextrow and self._nextrow[0]: - rows[0].insert(0, self._nextrow[0]) - - if not rows[0]: - self._handle_eof() - return [] - - self._rowcount += len(rows[0]) - self._handle_eof() - return rows[0] diff --git a/modules/mysql-connector-python/mysql/connector/custom_types.py b/modules/mysql-connector-python/mysql/connector/custom_types.py deleted file mode 100644 index 3613af693..000000000 --- a/modules/mysql-connector-python/mysql/connector/custom_types.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Custom Python types used by MySQL Connector/Python""" - - -import sys - - -class HexLiteral(str): - - """Class holding MySQL hex literals""" - - def __new__(cls, str_, charset='utf8'): - if sys.version_info[0] == 2: - hexed = ["%02x" % ord(i) for i in str_.encode(charset)] - else: - hexed = ["%02x" % i for i in str_.encode(charset)] - obj = str.__new__(cls, ''.join(hexed)) - obj.charset = charset - obj.original = str_ - return obj - - def __str__(self): - return '0x' + self diff --git a/modules/mysql-connector-python/mysql/connector/dbapi.py b/modules/mysql-connector-python/mysql/connector/dbapi.py deleted file mode 100644 index 873cfbb8c..000000000 --- a/modules/mysql-connector-python/mysql/connector/dbapi.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (c) 2009, 2017, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -""" -This module implements some constructors and singletons as required by the -DB API v2.0 (PEP-249). -""" - -# Python Db API v2 -apilevel = '2.0' -threadsafety = 1 -paramstyle = 'pyformat' - -import time -import datetime - -from . import constants - -class _DBAPITypeObject(object): - - def __init__(self, *values): - self.values = values - - def __eq__(self, other): - if other in self.values: - return True - else: - return False - - def __ne__(self, other): - if other in self.values: - return False - else: - return True - -Date = datetime.date -Time = datetime.time -Timestamp = datetime.datetime - -def DateFromTicks(ticks): - return Date(*time.localtime(ticks)[:3]) - -def TimeFromTicks(ticks): - return Time(*time.localtime(ticks)[3:6]) - -def TimestampFromTicks(ticks): - return Timestamp(*time.localtime(ticks)[:6]) - -Binary = bytes - -STRING = _DBAPITypeObject(*constants.FieldType.get_string_types()) -BINARY = _DBAPITypeObject(*constants.FieldType.get_binary_types()) -NUMBER = _DBAPITypeObject(*constants.FieldType.get_number_types()) -DATETIME = _DBAPITypeObject(*constants.FieldType.get_timestamp_types()) -ROWID = _DBAPITypeObject() diff --git a/modules/mysql-connector-python/mysql/connector/django/__init__.py b/modules/mysql-connector-python/mysql/connector/django/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/modules/mysql-connector-python/mysql/connector/django/base.py b/modules/mysql-connector-python/mysql/connector/django/base.py deleted file mode 100644 index c1377462a..000000000 --- a/modules/mysql-connector-python/mysql/connector/django/base.py +++ /dev/null @@ -1,546 +0,0 @@ -# MySQL Connector/Python - MySQL driver written in Python. - -"""Django database Backend using MySQL Connector/Python - -This Django database backend is heavily based on the MySQL backend coming -with Django. - -Changes include: -* Support for microseconds (MySQL 5.6.3 and later) -* Using INFORMATION_SCHEMA where possible -* Using new defaults for, for example SQL_AUTO_IS_NULL - -Requires and comes with MySQL Connector/Python v1.1 and later: - http://dev.mysql.com/downloads/connector/python/ -""" - - -from __future__ import unicode_literals - -from datetime import datetime -import sys -import warnings - -import django -from django.core.exceptions import ImproperlyConfigured -from django.utils.functional import cached_property - -try: - import mysql.connector - from mysql.connector.conversion import MySQLConverter, MySQLConverterBase - from mysql.connector.catch23 import PY2 -except ImportError as err: - raise ImproperlyConfigured( - "Error loading mysql.connector module: {0}".format(err)) - -try: - version = mysql.connector.__version_info__[0:3] -except AttributeError: - from mysql.connector.version import VERSION - version = VERSION[0:3] - -try: - from _mysql_connector import datetime_to_mysql, time_to_mysql -except ImportError: - HAVE_CEXT = False -else: - HAVE_CEXT = True - -if version < (1, 11): - raise ImproperlyConfigured( - "MySQL Connector/Python v1.11.0 or newer " - "is required; you have %s" % mysql.connector.__version__) - -from django.db import utils -from django.db.backends import utils as backend_utils -from django.db.backends.base.base import BaseDatabaseWrapper -from django.db.backends.signals import connection_created -from django.utils import (six, timezone, dateparse) -from django.conf import settings - -from mysql.connector.django.client import DatabaseClient -from mysql.connector.django.creation import DatabaseCreation -from mysql.connector.django.introspection import DatabaseIntrospection -from mysql.connector.django.validation import DatabaseValidation -from mysql.connector.django.features import DatabaseFeatures -from mysql.connector.django.operations import DatabaseOperations -from mysql.connector.django.schema import DatabaseSchemaEditor - - -DatabaseError = mysql.connector.DatabaseError -IntegrityError = mysql.connector.IntegrityError -NotSupportedError = mysql.connector.NotSupportedError - - -def adapt_datetime_with_timezone_support(value): - # Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL. - if settings.USE_TZ: - if timezone.is_naive(value): - warnings.warn("MySQL received a naive datetime (%s)" - " while time zone support is active." % value, - RuntimeWarning) - default_timezone = timezone.get_default_timezone() - value = timezone.make_aware(value, default_timezone) - value = value.astimezone(timezone.utc).replace(tzinfo=None) - if HAVE_CEXT: - return datetime_to_mysql(value) - else: - return value.strftime("%Y-%m-%d %H:%M:%S.%f") - - -class DjangoMySQLConverter(MySQLConverter): - """Custom converter for Django for MySQLConnection""" - def _TIME_to_python(self, value, dsc=None): - """Return MySQL TIME data type as datetime.time() - - Returns datetime.time() - """ - return dateparse.parse_time(value.decode('utf-8')) - - def _DATETIME_to_python(self, value, dsc=None): - """Connector/Python always returns naive datetime.datetime - - Connector/Python always returns naive timestamps since MySQL has - no time zone support. Since Django needs non-naive, we need to add - the UTC time zone. - - Returns datetime.datetime() - """ - if not value: - return None - dt = MySQLConverter._DATETIME_to_python(self, value) - if dt is None: - return None - if settings.USE_TZ and timezone.is_naive(dt): - dt = dt.replace(tzinfo=timezone.utc) - return dt - - def _safetext_to_mysql(self, value): - if PY2: - return self._unicode_to_mysql(value) - else: - return self._str_to_mysql(value) - - def _safebytes_to_mysql(self, value): - return self._bytes_to_mysql(value) - - -class DjangoCMySQLConverter(MySQLConverterBase): - """Custom converter for Django for CMySQLConnection""" - def _TIME_to_python(self, value, dsc=None): - """Return MySQL TIME data type as datetime.time() - - Returns datetime.time() - """ - return dateparse.parse_time(str(value)) - - def _DATETIME_to_python(self, value, dsc=None): - """Connector/Python always returns naive datetime.datetime - - Connector/Python always returns naive timestamps since MySQL has - no time zone support. Since Django needs non-naive, we need to add - the UTC time zone. - - Returns datetime.datetime() - """ - if not value: - return None - if settings.USE_TZ and timezone.is_naive(value): - value = value.replace(tzinfo=timezone.utc) - return value - - -class CursorWrapper(object): - """Wrapper around MySQL Connector/Python's cursor class. - - The cursor class is defined by the options passed to MySQL - Connector/Python. If buffered option is True in those options, - MySQLCursorBuffered will be used. - """ - codes_for_integrityerror = (1048,) - - def __init__(self, cursor): - self.cursor = cursor - - def _execute_wrapper(self, method, query, args): - """Wrapper around execute() and executemany()""" - try: - return method(query, args) - except (mysql.connector.ProgrammingError) as err: - six.reraise(utils.ProgrammingError, - utils.ProgrammingError(err.msg), sys.exc_info()[2]) - except (mysql.connector.IntegrityError) as err: - six.reraise(utils.IntegrityError, - utils.IntegrityError(err.msg), sys.exc_info()[2]) - except mysql.connector.OperationalError as err: - # Map some error codes to IntegrityError, since they seem to be - # misclassified and Django would prefer the more logical place. - if err.args[0] in self.codes_for_integrityerror: - six.reraise(utils.IntegrityError, - utils.IntegrityError(err.msg), sys.exc_info()[2]) - else: - six.reraise(utils.DatabaseError, - utils.DatabaseError(err.msg), sys.exc_info()[2]) - except mysql.connector.DatabaseError as err: - six.reraise(utils.DatabaseError, - utils.DatabaseError(err.msg), sys.exc_info()[2]) - - def _adapt_execute_args_dict(self, args): - if not args: - return args - new_args = dict(args) - for key, value in args.items(): - if isinstance(value, datetime): - new_args[key] = adapt_datetime_with_timezone_support(value) - - return new_args - - def _adapt_execute_args(self, args): - if not args: - return args - new_args = list(args) - for i, arg in enumerate(args): - if isinstance(arg, datetime): - new_args[i] = adapt_datetime_with_timezone_support(arg) - - return tuple(new_args) - - def execute(self, query, args=None): - """Executes the given operation - - This wrapper method around the execute()-method of the cursor is - mainly needed to re-raise using different exceptions. - """ - if isinstance(args, dict): - new_args = self._adapt_execute_args_dict(args) - else: - new_args = self._adapt_execute_args(args) - return self._execute_wrapper(self.cursor.execute, query, new_args) - - def executemany(self, query, args): - """Executes the given operation - - This wrapper method around the executemany()-method of the cursor is - mainly needed to re-raise using different exceptions. - """ - return self._execute_wrapper(self.cursor.executemany, query, args) - - def __getattr__(self, attr): - """Return attribute of wrapped cursor""" - return getattr(self.cursor, attr) - - def __iter__(self): - """Returns iterator over wrapped cursor""" - return iter(self.cursor) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, exc_traceback): - self.close() - - -class DatabaseWrapper(BaseDatabaseWrapper): - vendor = 'mysql' - # This dictionary maps Field objects to their associated MySQL column - # types, as strings. Column-type strings can contain format strings; they'll - # be interpolated against the values of Field.__dict__ before being output. - # If a column type is set to None, it won't be included in the output. - - _data_types = { - 'AutoField': 'integer AUTO_INCREMENT', - 'BinaryField': 'longblob', - 'BooleanField': 'bool', - 'CharField': 'varchar(%(max_length)s)', - 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', - 'DateField': 'date', - 'DateTimeField': 'datetime', - 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', - 'DurationField': 'bigint', - 'FileField': 'varchar(%(max_length)s)', - 'FilePathField': 'varchar(%(max_length)s)', - 'FloatField': 'double precision', - 'IntegerField': 'integer', - 'BigIntegerField': 'bigint', - 'IPAddressField': 'char(15)', - 'GenericIPAddressField': 'char(39)', - 'NullBooleanField': 'bool', - 'OneToOneField': 'integer', - 'PositiveIntegerField': 'integer UNSIGNED', - 'PositiveSmallIntegerField': 'smallint UNSIGNED', - 'SlugField': 'varchar(%(max_length)s)', - 'SmallIntegerField': 'smallint', - 'TextField': 'longtext', - 'TimeField': 'time', - 'UUIDField': 'char(32)', - } - - @cached_property - def data_types(self): - if self.features.supports_microsecond_precision: - return dict(self._data_types, DateTimeField='datetime(6)', - TimeField='time(6)') - else: - return self._data_types - - operators = { - 'exact': '= %s', - 'iexact': 'LIKE %s', - 'contains': 'LIKE BINARY %s', - 'icontains': 'LIKE %s', - 'regex': 'REGEXP BINARY %s', - 'iregex': 'REGEXP %s', - 'gt': '> %s', - 'gte': '>= %s', - 'lt': '< %s', - 'lte': '<= %s', - 'startswith': 'LIKE BINARY %s', - 'endswith': 'LIKE BINARY %s', - 'istartswith': 'LIKE %s', - 'iendswith': 'LIKE %s', - } - - # The patterns below are used to generate SQL pattern lookup clauses when - # the right-hand side of the lookup isn't a raw string (it might be an - # expression or the result of a bilateral transformation). - # In those cases, special characters for LIKE operators (e.g. \, *, _) - # should be escaped on database side. - # - # Note: we use str.format() here for readability as '%' is used as a - # wildcard for the LIKE operator. - pattern_esc = (r"REPLACE(REPLACE(REPLACE({}, '\\', '\\\\')," - r" '%%', '\%%'), '_', '\_')") - pattern_ops = { - 'contains': "LIKE BINARY CONCAT('%%', {}, '%%')", - 'icontains': "LIKE CONCAT('%%', {}, '%%')", - 'startswith': "LIKE BINARY CONCAT({}, '%%')", - 'istartswith': "LIKE CONCAT({}, '%%')", - 'endswith': "LIKE BINARY CONCAT('%%', {})", - 'iendswith': "LIKE CONCAT('%%', {})", - } - - SchemaEditorClass = DatabaseSchemaEditor - Database = mysql.connector - - client_class = DatabaseClient - creation_class = DatabaseCreation - features_class = DatabaseFeatures - introspection_class = DatabaseIntrospection - ops_class = DatabaseOperations - validation_class = DatabaseValidation - - def __init__(self, *args, **kwargs): - super(DatabaseWrapper, self).__init__(*args, **kwargs) - - try: - self._use_pure = self.settings_dict['OPTIONS']['use_pure'] - except KeyError: - self._use_pure = True - - if not self.use_pure: - self.converter = DjangoCMySQLConverter() - else: - self.converter = DjangoMySQLConverter() - - def _valid_connection(self): - if self.connection: - return self.connection.is_connected() - return False - - def get_connection_params(self): - kwargs = { - 'charset': 'utf8', - 'use_unicode': True, - 'buffered': False, - 'consume_results': True, - } - - settings_dict = self.settings_dict - - if settings_dict['USER']: - kwargs['user'] = settings_dict['USER'] - if settings_dict['NAME']: - kwargs['database'] = settings_dict['NAME'] - if settings_dict['PASSWORD']: - kwargs['passwd'] = settings_dict['PASSWORD'] - if settings_dict['HOST'].startswith('/'): - kwargs['unix_socket'] = settings_dict['HOST'] - elif settings_dict['HOST']: - kwargs['host'] = settings_dict['HOST'] - if settings_dict['PORT']: - kwargs['port'] = int(settings_dict['PORT']) - - # Raise exceptions for database warnings if DEBUG is on - kwargs['raise_on_warnings'] = settings.DEBUG - - kwargs['client_flags'] = [ - # Need potentially affected rows on UPDATE - mysql.connector.constants.ClientFlag.FOUND_ROWS, - ] - try: - kwargs.update(settings_dict['OPTIONS']) - except KeyError: - # OPTIONS missing is OK - pass - - return kwargs - - def get_new_connection(self, conn_params): - if not self.use_pure: - conn_params['converter_class'] = DjangoCMySQLConverter - else: - conn_params['converter_class'] = DjangoMySQLConverter - cnx = mysql.connector.connect(**conn_params) - - return cnx - - def init_connection_state(self): - if self.mysql_version < (5, 5, 3): - # See sysvar_sql_auto_is_null in MySQL Reference manual - self.connection.cmd_query("SET SQL_AUTO_IS_NULL = 0") - - if 'AUTOCOMMIT' in self.settings_dict: - try: - self.set_autocommit(self.settings_dict['AUTOCOMMIT']) - except AttributeError: - self._set_autocommit(self.settings_dict['AUTOCOMMIT']) - - def create_cursor(self, name=None): - cursor = self.connection.cursor() - return CursorWrapper(cursor) - - def _connect(self): - """Setup the connection with MySQL""" - self.connection = self.get_new_connection(self.get_connection_params()) - connection_created.send(sender=self.__class__, connection=self) - self.init_connection_state() - - def _cursor(self): - """Return a CursorWrapper object - - Returns a CursorWrapper - """ - try: - return super(DatabaseWrapper, self)._cursor() - except AttributeError: - if not self.connection: - self._connect() - return self.create_cursor() - - def get_server_version(self): - """Returns the MySQL server version of current connection - - Returns a tuple - """ - try: - self.ensure_connection() - except AttributeError: - if not self.connection: - self._connect() - - return self.connection.get_server_version() - - def disable_constraint_checking(self): - """Disables foreign key checks - - Disables foreign key checks, primarily for use in adding rows with - forward references. Always returns True, - to indicate constraint checks need to be re-enabled. - - Returns True - """ - self.cursor().execute('SET @@session.foreign_key_checks = 0') - return True - - def enable_constraint_checking(self): - """Re-enable foreign key checks - - Re-enable foreign key checks after they have been disabled. - """ - # Override needs_rollback in case constraint_checks_disabled is - # nested inside transaction.atomic. - self.needs_rollback, needs_rollback = False, self.needs_rollback - try: - self.cursor().execute('SET @@session.foreign_key_checks = 1') - finally: - self.needs_rollback = needs_rollback - - def check_constraints(self, table_names=None): - """Check rows in tables for invalid foreign key references - - Checks each table name in `table_names` for rows with invalid foreign - key references. This method is intended to be used in conjunction with - `disable_constraint_checking()` and `enable_constraint_checking()`, to - determine if rows with invalid references were entered while - constraint checks were off. - - Raises an IntegrityError on the first invalid foreign key reference - encountered (if any) and provides detailed information about the - invalid reference in the error message. - - Backends can override this method if they can more directly apply - constraint checking (e.g. via "SET CONSTRAINTS ALL IMMEDIATE") - """ - ref_query = """ - SELECT REFERRING.`{0}`, REFERRING.`{1}` FROM `{2}` as REFERRING - LEFT JOIN `{3}` as REFERRED - ON (REFERRING.`{4}` = REFERRED.`{5}`) - WHERE REFERRING.`{6}` IS NOT NULL AND REFERRED.`{7}` IS NULL""" - cursor = self.cursor() - if table_names is None: - table_names = self.introspection.table_names(cursor) - for table_name in table_names: - primary_key_column_name = \ - self.introspection.get_primary_key_column(cursor, table_name) - if not primary_key_column_name: - continue - key_columns = self.introspection.get_key_columns(cursor, - table_name) - for column_name, referenced_table_name, referenced_column_name \ - in key_columns: - cursor.execute(ref_query.format(primary_key_column_name, - column_name, table_name, - referenced_table_name, - column_name, - referenced_column_name, - column_name, - referenced_column_name)) - for bad_row in cursor.fetchall(): - msg = ("The row in table '{0}' with primary key '{1}' has " - "an invalid foreign key: {2}.{3} contains a value " - "'{4}' that does not have a corresponding value in " - "{5}.{6}.".format(table_name, bad_row[0], - table_name, column_name, - bad_row[1], referenced_table_name, - referenced_column_name)) - raise utils.IntegrityError(msg) - - def _rollback(self): - try: - BaseDatabaseWrapper._rollback(self) - except NotSupportedError: - pass - - def _set_autocommit(self, autocommit): - with self.wrap_database_errors: - self.connection.autocommit = autocommit - - def schema_editor(self, *args, **kwargs): - """Returns a new instance of this backend's SchemaEditor""" - return DatabaseSchemaEditor(self, *args, **kwargs) - - def is_usable(self): - return self.connection.is_connected() - - @cached_property - def mysql_version(self): - config = self.get_connection_params() - temp_conn = mysql.connector.connect(**config) - server_version = temp_conn.get_server_version() - temp_conn.close() - - return server_version - - @property - def use_pure(self): - return not HAVE_CEXT or self._use_pure diff --git a/modules/mysql-connector-python/mysql/connector/django/client.py b/modules/mysql-connector-python/mysql/connector/django/client.py deleted file mode 100644 index 07b3144e4..000000000 --- a/modules/mysql-connector-python/mysql/connector/django/client.py +++ /dev/null @@ -1,54 +0,0 @@ -# MySQL Connector/Python - MySQL driver written in Python. - -import django -import subprocess - -from django.db.backends.base.client import BaseDatabaseClient - - -class DatabaseClient(BaseDatabaseClient): - executable_name = 'mysql' - - @classmethod - def settings_to_cmd_args(cls, settings_dict): - args = [cls.executable_name] - - db = settings_dict['OPTIONS'].get('database', settings_dict['NAME']) - user = settings_dict['OPTIONS'].get('user', - settings_dict['USER']) - passwd = settings_dict['OPTIONS'].get('password', - settings_dict['PASSWORD']) - host = settings_dict['OPTIONS'].get('host', settings_dict['HOST']) - port = settings_dict['OPTIONS'].get('port', settings_dict['PORT']) - defaults_file = settings_dict['OPTIONS'].get('read_default_file') - - # --defaults-file should always be the first option - if defaults_file: - args.append("--defaults-file={0}".format(defaults_file)) - - # We force SQL_MODE to TRADITIONAL - args.append("--init-command=SET @@session.SQL_MODE=TRADITIONAL") - - if user: - args.append("--user={0}".format(user)) - if passwd: - args.append("--password={0}".format(passwd)) - - if host: - if '/' in host: - args.append("--socket={0}".format(host)) - else: - args.append("--host={0}".format(host)) - - if port: - args.append("--port={0}".format(port)) - - if db: - args.append("--database={0}".format(db)) - - return args - - def runshell(self): - args = DatabaseClient.settings_to_cmd_args( - self.connection.settings_dict) - subprocess.call(args) diff --git a/modules/mysql-connector-python/mysql/connector/django/compiler.py b/modules/mysql-connector-python/mysql/connector/django/compiler.py deleted file mode 100644 index 076f70387..000000000 --- a/modules/mysql-connector-python/mysql/connector/django/compiler.py +++ /dev/null @@ -1,41 +0,0 @@ -# MySQL Connector/Python - MySQL driver written in Python. - - -import django -from django.db.models.sql import compiler -from django.utils.six.moves import zip_longest - - -class SQLCompiler(compiler.SQLCompiler): - def resolve_columns(self, row, fields=()): - values = [] - index_extra_select = len(self.query.extra_select) - bool_fields = ("BooleanField", "NullBooleanField") - for value, field in zip_longest(row[index_extra_select:], fields): - if (field and field.get_internal_type() in bool_fields and - value in (0, 1)): - value = bool(value) - values.append(value) - return row[:index_extra_select] + tuple(values) - - def as_subquery_condition(self, alias, columns, compiler): - qn = compiler.quote_name_unless_alias - qn2 = self.connection.ops.quote_name - sql, params = self.as_sql() - return '(%s) IN (%s)' % (', '.join('%s.%s' % (qn(alias), qn2(column)) for column in columns), sql), params - - -class SQLInsertCompiler(compiler.SQLInsertCompiler, SQLCompiler): - pass - - -class SQLDeleteCompiler(compiler.SQLDeleteCompiler, SQLCompiler): - pass - - -class SQLUpdateCompiler(compiler.SQLUpdateCompiler, SQLCompiler): - pass - - -class SQLAggregateCompiler(compiler.SQLAggregateCompiler, SQLCompiler): - pass diff --git a/modules/mysql-connector-python/mysql/connector/django/creation.py b/modules/mysql-connector-python/mysql/connector/django/creation.py deleted file mode 100644 index 558030803..000000000 --- a/modules/mysql-connector-python/mysql/connector/django/creation.py +++ /dev/null @@ -1,84 +0,0 @@ -# MySQL Connector/Python - MySQL driver written in Python. - -import django -from django.db import models - -from django.db.backends.base.creation import BaseDatabaseCreation -from django.db.backends.utils import truncate_name - -class DatabaseCreation(BaseDatabaseCreation): - """Maps Django Field object with MySQL data types - """ - def __init__(self, connection): - super(DatabaseCreation, self).__init__(connection) - - def sql_table_creation_suffix(self): - suffix = [] - test_settings = self.connection.settings_dict['TEST'] - if test_settings['CHARSET']: - suffix.append('CHARACTER SET %s' % test_settings['CHARSET']) - if test_settings['COLLATION']: - suffix.append('COLLATE %s' % test_settings['COLLATION']) - - return ' '.join(suffix) - - def sql_for_inline_foreign_key_references(self, model, field, - known_models, style): - "All inline references are pending under MySQL" - return [], True - - def sql_for_inline_many_to_many_references(self, model, field, style): - opts = model._meta - qn = self.connection.ops.quote_name - - columndef = ' {column} {type} {options},' - table_output = [ - columndef.format( - column=style.SQL_FIELD(qn(field.m2m_column_name())), - type=style.SQL_COLTYPE(models.ForeignKey(model).db_type( - connection=self.connection)), - options=style.SQL_KEYWORD('NOT NULL') - ), - columndef.format( - column=style.SQL_FIELD(qn(field.m2m_reverse_name())), - type=style.SQL_COLTYPE(models.ForeignKey(field.rel.to).db_type( - connection=self.connection)), - options=style.SQL_KEYWORD('NOT NULL') - ), - ] - - deferred = [ - (field.m2m_db_table(), field.m2m_column_name(), opts.db_table, - opts.pk.column), - (field.m2m_db_table(), field.m2m_reverse_name(), - field.rel.to._meta.db_table, field.rel.to._meta.pk.column) - ] - return table_output, deferred - - def sql_destroy_indexes_for_fields(self, model, fields, style): - if len(fields) == 1 and fields[0].db_tablespace: - tablespace_sql = self.connection.ops.tablespace_sql( - fields[0].db_tablespace) - elif model._meta.db_tablespace: - tablespace_sql = self.connection.ops.tablespace_sql( - model._meta.db_tablespace) - else: - tablespace_sql = "" - if tablespace_sql: - tablespace_sql = " " + tablespace_sql - - field_names = [] - qn = self.connection.ops.quote_name - for f in fields: - field_names.append(style.SQL_FIELD(qn(f.column))) - - index_name = "{0}_{1}".format(model._meta.db_table, - self._digest([f.name for f in fields])) - - return [ - style.SQL_KEYWORD("DROP INDEX") + " " + - style.SQL_TABLE(qn(truncate_name(index_name, - self.connection.ops.max_name_length()))) + " " + - style.SQL_KEYWORD("ON") + " " + - style.SQL_TABLE(qn(model._meta.db_table)) + ";", - ] diff --git a/modules/mysql-connector-python/mysql/connector/django/features.py b/modules/mysql-connector-python/mysql/connector/django/features.py deleted file mode 100644 index 47d53da5f..000000000 --- a/modules/mysql-connector-python/mysql/connector/django/features.py +++ /dev/null @@ -1,115 +0,0 @@ -# MySQL Connector/Python - MySQL driver written in Python. - -import django -from django.db.backends.base.features import BaseDatabaseFeatures -from django.utils.functional import cached_property -from django.utils import six - -try: - import pytz - HAVE_PYTZ = True -except ImportError: - HAVE_PYTZ = False - - -class DatabaseFeatures(BaseDatabaseFeatures): - """Features specific to MySQL - - Microsecond precision is supported since MySQL 5.6.3 and turned on - by default if this MySQL version is used. - """ - empty_fetchmany_value = [] - update_can_self_select = False - allows_group_by_pk = True - related_fields_match_type = True - allow_sliced_subqueries = False - has_bulk_insert = True - has_select_for_update = True - has_select_for_update_nowait = False - supports_forward_references = False - supports_regex_backreferencing = False - supports_date_lookup_using_string = False - can_introspect_autofield = True - can_introspect_binary_field = False - can_introspect_small_integer_field = True - supports_timezones = False - requires_explicit_null_ordering_when_grouping = True - allows_auto_pk_0 = False - allows_primary_key_0 = False - uses_savepoints = True - atomic_transactions = False - supports_column_check_constraints = False - - def __init__(self, connection): - super(DatabaseFeatures, self).__init__(connection) - - @cached_property - def supports_microsecond_precision(self): - if self.connection.mysql_version >= (5, 6, 3): - return True - return False - - @cached_property - def mysql_storage_engine(self): - """Get default storage engine of MySQL - - This method creates a table without ENGINE table option and inspects - which engine was used. - - Used by Django tests. - """ - tblname = 'INTROSPECT_TEST' - - droptable = 'DROP TABLE IF EXISTS {table}'.format(table=tblname) - with self.connection.cursor() as cursor: - cursor.execute(droptable) - cursor.execute('CREATE TABLE {table} (X INT)'.format(table=tblname)) - - if self.connection.mysql_version >= (5, 0, 0): - cursor.execute( - "SELECT ENGINE FROM INFORMATION_SCHEMA.TABLES " - "WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s", - (self.connection.settings_dict['NAME'], tblname)) - engine = cursor.fetchone()[0] - else: - # Very old MySQL servers.. - cursor.execute("SHOW TABLE STATUS WHERE Name='{table}'".format( - table=tblname)) - engine = cursor.fetchone()[1] - cursor.execute(droptable) - - self._cached_storage_engine = engine - return engine - - @cached_property - def _disabled_supports_transactions(self): - return self.mysql_storage_engine == 'InnoDB' - - @cached_property - def can_introspect_foreign_keys(self): - """Confirm support for introspected foreign keys - - Only the InnoDB storage engine supports Foreigen Key (not taking - into account MySQL Cluster here). - """ - return self.mysql_storage_engine == 'InnoDB' - - @cached_property - def has_zoneinfo_database(self): - """Tests if the time zone definitions are installed - - MySQL accepts full time zones names (eg. Africa/Nairobi) but rejects - abbreviations (eg. EAT). When pytz isn't installed and the current - time zone is LocalTimezone (the only sensible value in this context), - the current time zone name will be an abbreviation. As a consequence, - MySQL cannot perform time zone conversions reliably. - """ - if not HAVE_PYTZ: - return False - - with self.connection.cursor() as cursor: - cursor.execute("SELECT 1 FROM mysql.time_zone LIMIT 1") - return cursor.fetchall() != [] - - def introspected_boolean_field_type(self, *args, **kwargs): - return 'IntegerField' diff --git a/modules/mysql-connector-python/mysql/connector/django/introspection.py b/modules/mysql-connector-python/mysql/connector/django/introspection.py deleted file mode 100644 index 4de604338..000000000 --- a/modules/mysql-connector-python/mysql/connector/django/introspection.py +++ /dev/null @@ -1,255 +0,0 @@ -# MySQL Connector/Python - MySQL driver written in Python. - - -import re -from collections import namedtuple - -import django -from django.db.backends.base.introspection import ( - BaseDatabaseIntrospection, FieldInfo, TableInfo -) -from django.utils.encoding import force_text -from django.utils.datastructures import OrderedSet - -from mysql.connector.constants import FieldType - -foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) " - r"REFERENCES `([^`]*)` \(`([^`]*)`\)") - -FieldInfo = namedtuple('FieldInfo', FieldInfo._fields + ('extra',)) - - -class DatabaseIntrospection(BaseDatabaseIntrospection): - data_types_reverse = { - FieldType.BLOB: 'TextField', - FieldType.DECIMAL: 'DecimalField', - FieldType.NEWDECIMAL: 'DecimalField', - FieldType.DATE: 'DateField', - FieldType.DATETIME: 'DateTimeField', - FieldType.DOUBLE: 'FloatField', - FieldType.FLOAT: 'FloatField', - FieldType.INT24: 'IntegerField', - FieldType.LONG: 'IntegerField', - FieldType.LONGLONG: 'BigIntegerField', - FieldType.SHORT: 'SmallIntegerField', - FieldType.STRING: 'CharField', - FieldType.TIME: 'TimeField', - FieldType.TIMESTAMP: 'DateTimeField', - FieldType.TINY: 'IntegerField', - FieldType.TINY_BLOB: 'TextField', - FieldType.MEDIUM_BLOB: 'TextField', - FieldType.LONG_BLOB: 'TextField', - FieldType.VAR_STRING: 'CharField', - } - - def get_field_type(self, data_type, description): - field_type = super(DatabaseIntrospection, self).get_field_type( - data_type, description) - if (field_type == 'IntegerField' - and 'auto_increment' in description.extra): - return 'AutoField' - return field_type - - def get_table_list(self, cursor): - """Returns a list of table names in the current database.""" - cursor.execute("SHOW FULL TABLES") - return [ - TableInfo(row[0], {'BASE TABLE': 't', 'VIEW': 'v'}.get(row[1])) - for row in cursor.fetchall() - ] - - def get_table_description(self, cursor, table_name): - """ - Returns a description of the table, with the DB-API - cursor.description interface." - """ - # - information_schema database gives more accurate results for - # some figures: - # - varchar length returned by cursor.description is an internal - # length, not visible length (#5725) - # - precision and scale (for decimal fields) (#5014) - # - auto_increment is not available in cursor.description - - InfoLine = namedtuple('InfoLine', 'col_name data_type max_len ' - 'num_prec num_scale extra column_default') - cursor.execute(""" - SELECT column_name, data_type, character_maximum_length, - numeric_precision, numeric_scale, extra, column_default - FROM information_schema.columns - WHERE table_name = %s AND table_schema = DATABASE()""", - [table_name]) - field_info = dict( - (line[0], InfoLine(*line)) for line in cursor.fetchall() - ) - - cursor.execute("SELECT * FROM %s LIMIT 1" - % self.connection.ops.quote_name(table_name)) - to_int = lambda i: int(i) if i is not None else i - fields = [] - for line in cursor.description: - col_name = force_text(line[0]) - fields.append( - FieldInfo(*( - (col_name,) + - line[1:3] + - ( - to_int(field_info[col_name].max_len) or line[3], - to_int(field_info[col_name].num_prec) or line[4], - to_int(field_info[col_name].num_scale) or line[5], - line[6], - field_info[col_name].column_default, - field_info[col_name].extra, - ) - )) - ) - return fields - - def _name_to_index(self, cursor, table_name): - """ - Returns a dictionary of {field_name: field_index} for the given table. - Indexes are 0-based. - """ - return dict((d[0], i) for i, d in enumerate( - self.get_table_description(cursor, table_name))) - - def get_relations(self, cursor, table_name): - """ - Returns a dictionary of {field_index: (field_index_other_table, - other_table)} - representing all relationships to the given table. Indexes are 0-based. - """ - constraints = self.get_key_columns(cursor, table_name) - relations = {} - for my_fieldname, other_table, other_field in constraints: - relations[my_fieldname] = (other_field, other_table) - return relations - - def get_key_columns(self, cursor, table_name): - """ - Returns a list of (column_name, referenced_table_name, - referenced_column_name) for all key columns in given table. - """ - key_columns = [] - cursor.execute( - "SELECT column_name, referenced_table_name, referenced_column_name " - "FROM information_schema.key_column_usage " - "WHERE table_name = %s " - "AND table_schema = DATABASE() " - "AND referenced_table_name IS NOT NULL " - "AND referenced_column_name IS NOT NULL", [table_name]) - key_columns.extend(cursor.fetchall()) - return key_columns - - def get_indexes(self, cursor, table_name): - cursor.execute("SHOW INDEX FROM {0}" - "".format(self.connection.ops.quote_name(table_name))) - # Do a two-pass search for indexes: on first pass check which indexes - # are multicolumn, on second pass check which single-column indexes - # are present. - rows = list(cursor.fetchall()) - multicol_indexes = set() - for row in rows: - if row[3] > 1: - multicol_indexes.add(row[2]) - indexes = {} - for row in rows: - if row[2] in multicol_indexes: - continue - if row[4] not in indexes: - indexes[row[4]] = {'primary_key': False, 'unique': False} - # It's possible to have the unique and PK constraints in - # separate indexes. - if row[2] == 'PRIMARY': - indexes[row[4]]['primary_key'] = True - if not row[1]: - indexes[row[4]]['unique'] = True - return indexes - - def get_primary_key_column(self, cursor, table_name): - """ - Returns the name of the primary key column for the given table - """ - for column in self.get_indexes(cursor, table_name).items(): - if column[1]['primary_key']: - return column[0] - return None - - def get_storage_engine(self, cursor, table_name): - """ - Retrieves the storage engine for a given table. Returns the default - storage engine if the table doesn't exist. - """ - cursor.execute( - "SELECT engine " - "FROM information_schema.tables " - "WHERE table_name = %s", [table_name]) - result = cursor.fetchone() - if not result: - return self.connection.features.mysql_storage_engine - return result[0] - - def get_constraints(self, cursor, table_name): - """ - Retrieves any constraints or keys (unique, pk, fk, check, index) across - one or more columns. - """ - constraints = {} - # Get the actual constraint names and columns - name_query = ( - "SELECT kc.`constraint_name`, kc.`column_name`, " - "kc.`referenced_table_name`, kc.`referenced_column_name` " - "FROM information_schema.key_column_usage AS kc " - "WHERE " - "kc.table_schema = %s AND " - "kc.table_name = %s" - ) - cursor.execute(name_query, [self.connection.settings_dict['NAME'], - table_name]) - for constraint, column, ref_table, ref_column in cursor.fetchall(): - if constraint not in constraints: - constraints[constraint] = { - 'columns': OrderedSet(), - 'primary_key': False, - 'unique': False, - 'index': False, - 'check': False, - 'foreign_key': \ - (ref_table, ref_column) if ref_column else None - } - constraints[constraint]['columns'].add(column) - # Now get the constraint types - type_query = """ - SELECT c.constraint_name, c.constraint_type - FROM information_schema.table_constraints AS c - WHERE - c.table_schema = %s AND - c.table_name = %s - """ - cursor.execute(type_query, [self.connection.settings_dict['NAME'], - table_name]) - for constraint, kind in cursor.fetchall(): - if kind.lower() == "primary key": - constraints[constraint]['primary_key'] = True - constraints[constraint]['unique'] = True - elif kind.lower() == "unique": - constraints[constraint]['unique'] = True - # Now add in the indexes - cursor.execute("SHOW INDEX FROM %s" % self.connection.ops.quote_name( - table_name)) - for table, non_unique, index, colseq, column in [x[:5] for x in - cursor.fetchall()]: - if index not in constraints: - constraints[index] = { - 'columns': OrderedSet(), - 'primary_key': False, - 'unique': False, - 'index': True, - 'check': False, - 'foreign_key': None, - } - constraints[index]['index'] = True - constraints[index]['columns'].add(column) - # Convert the sorted sets to lists - for constraint in constraints.values(): - constraint['columns'] = list(constraint['columns']) - return constraints diff --git a/modules/mysql-connector-python/mysql/connector/django/operations.py b/modules/mysql-connector-python/mysql/connector/django/operations.py deleted file mode 100644 index 798acd3d5..000000000 --- a/modules/mysql-connector-python/mysql/connector/django/operations.py +++ /dev/null @@ -1,240 +0,0 @@ -# MySQL Connector/Python - MySQL driver written in Python. - -from __future__ import unicode_literals - -import uuid - -import django -from django.conf import settings -from django.db.backends.base.operations import BaseDatabaseOperations -from django.utils import six, timezone -from django.utils.encoding import force_text - -try: - from _mysql_connector import datetime_to_mysql, time_to_mysql -except ImportError: - HAVE_CEXT = False -else: - HAVE_CEXT = True - - -class DatabaseOperations(BaseDatabaseOperations): - compiler_module = "mysql.connector.django.compiler" - - # MySQL stores positive fields as UNSIGNED ints. - integer_field_ranges = dict(BaseDatabaseOperations.integer_field_ranges, - PositiveSmallIntegerField=(0, 4294967295), - PositiveIntegerField=( - 0, 18446744073709551615),) - - def date_extract_sql(self, lookup_type, field_name): - # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html - if lookup_type == 'week_day': - # DAYOFWEEK() returns an integer, 1-7, Sunday=1. - # Note: WEEKDAY() returns 0-6, Monday=0. - return "DAYOFWEEK({0})".format(field_name) - else: - return "EXTRACT({0} FROM {1})".format( - lookup_type.upper(), field_name) - - def date_trunc_sql(self, lookup_type, field_name): - """Returns SQL simulating DATE_TRUNC - - This function uses MySQL functions DATE_FORMAT and CAST to - simulate DATE_TRUNC. - - The field_name is returned when lookup_type is not supported. - """ - fields = ['year', 'month', 'day', 'hour', 'minute', 'second'] - format = ('%Y-', '%m', '-%d', ' %H:', '%i', ':%S') - format_def = ('0000-', '01', '-01', ' 00:', '00', ':00') - try: - i = fields.index(lookup_type) + 1 - except ValueError: - # Wrong lookup type, just return the value from MySQL as-is - sql = field_name - else: - format_str = ''.join([f for f in format[:i]] + - [f for f in format_def[i:]]) - sql = "CAST(DATE_FORMAT({0}, '{1}') AS DATETIME)".format( - field_name, format_str) - return sql - - def datetime_extract_sql(self, lookup_type, field_name, tzname): - if settings.USE_TZ: - field_name = "CONVERT_TZ({0}, 'UTC', %s)".format(field_name) - params = [tzname] - else: - params = [] - - # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html - if lookup_type == 'week_day': - # DAYOFWEEK() returns an integer, 1-7, Sunday=1. - # Note: WEEKDAY() returns 0-6, Monday=0. - sql = "DAYOFWEEK({0})".format(field_name) - else: - sql = "EXTRACT({0} FROM {1})".format(lookup_type.upper(), - field_name) - return sql, params - - def datetime_trunc_sql(self, lookup_type, field_name, tzname): - if settings.USE_TZ: - field_name = "CONVERT_TZ({0}, 'UTC', %s)".format(field_name) - params = [tzname] - else: - params = [] - fields = ['year', 'month', 'day', 'hour', 'minute', 'second'] - format_ = ('%Y-', '%m', '-%d', ' %H:', '%i', ':%S') - format_def = ('0000-', '01', '-01', ' 00:', '00', ':00') - try: - i = fields.index(lookup_type) + 1 - except ValueError: - sql = field_name - else: - format_str = ''.join([f for f in format_[:i]] + - [f for f in format_def[i:]]) - sql = "CAST(DATE_FORMAT({0}, '{1}') AS DATETIME)".format( - field_name, format_str) - return sql, params - - def date_interval_sql(self, timedelta): - """Returns SQL for calculating date/time intervals - """ - return "INTERVAL '%d 0:0:%d:%d' DAY_MICROSECOND" % ( - timedelta.days, timedelta.seconds, timedelta.microseconds), [] - - def format_for_duration_arithmetic(self, sql): - if self.connection.features.supports_microsecond_precision: - return 'INTERVAL %s MICROSECOND' % sql - else: - return 'INTERVAL FLOOR(%s / 1000000) SECOND' % sql - - def drop_foreignkey_sql(self): - return "DROP FOREIGN KEY" - - def force_no_ordering(self): - """ - "ORDER BY NULL" prevents MySQL from implicitly ordering by grouped - columns. If no ordering would otherwise be applied, we don't want any - implicit sorting going on. - """ - return [(None, ("NULL", [], False))] - - def fulltext_search_sql(self, field_name): - return 'MATCH ({0}) AGAINST (%s IN BOOLEAN MODE)'.format(field_name) - - def last_executed_query(self, cursor, sql, params): - return force_text(cursor.statement, errors='replace') - - def no_limit_value(self): - # 2**64 - 1, as recommended by the MySQL documentation - return 18446744073709551615 - - def quote_name(self, name): - if name.startswith("`") and name.endswith("`"): - return name # Quoting once is enough. - return "`{0}`".format(name) - - def random_function_sql(self): - return 'RAND()' - - def sql_flush(self, style, tables, sequences, allow_cascade=False): - if tables: - sql = ['SET FOREIGN_KEY_CHECKS = 0;'] - for table in tables: - sql.append('{keyword} {table};'.format( - keyword=style.SQL_KEYWORD('TRUNCATE'), - table=style.SQL_FIELD(self.quote_name(table)))) - sql.append('SET FOREIGN_KEY_CHECKS = 1;') - sql.extend(self.sequence_reset_by_name_sql(style, sequences)) - return sql - else: - return [] - - def validate_autopk_value(self, value): - # MySQLism: zero in AUTO_INCREMENT field does not work. Refs #17653. - if value == 0: - raise ValueError('The database backend does not accept 0 as a ' - 'value for AutoField.') - return value - - def adapt_datetimefield_value(self, value): - return self.value_to_db_datetime(value) - - def value_to_db_datetime(self, value): - if value is None: - return None - # MySQL doesn't support tz-aware times - if timezone.is_aware(value): - if settings.USE_TZ: - value = value.astimezone(timezone.utc).replace(tzinfo=None) - else: - raise ValueError( - "MySQL backend does not support timezone-aware times." - ) - if not self.connection.features.supports_microsecond_precision: - value = value.replace(microsecond=0) - if not self.connection.use_pure: - return datetime_to_mysql(value) - return self.connection.converter.to_mysql(value) - - def adapt_timefield_value(self, value): - return self.value_to_db_time(value) - - def value_to_db_time(self, value): - if value is None: - return None - - # MySQL doesn't support tz-aware times - if timezone.is_aware(value): - raise ValueError("MySQL backend does not support timezone-aware " - "times.") - - if not self.connection.use_pure: - return time_to_mysql(value) - return self.connection.converter.to_mysql(value) - - def max_name_length(self): - return 64 - - def bulk_insert_sql(self, fields, placeholder_rows): - placeholder_rows_sql = (", ".join(row) for row in placeholder_rows) - values_sql = ", ".join("({0})".format(sql) for sql in placeholder_rows_sql) - return "VALUES " + values_sql - - def combine_expression(self, connector, sub_expressions): - """ - MySQL requires special cases for ^ operators in query expressions - """ - if connector == '^': - return 'POW(%s)' % ','.join(sub_expressions) - return super(DatabaseOperations, self).combine_expression( - connector, sub_expressions) - - def get_db_converters(self, expression): - converters = super(DatabaseOperations, self).get_db_converters( - expression) - internal_type = expression.output_field.get_internal_type() - if internal_type in ['BooleanField', 'NullBooleanField']: - converters.append(self.convert_booleanfield_value) - if internal_type == 'UUIDField': - converters.append(self.convert_uuidfield_value) - if internal_type == 'TextField': - converters.append(self.convert_textfield_value) - return converters - - def convert_booleanfield_value(self, value, - expression, connection, context): - if value in (0, 1): - value = bool(value) - return value - - def convert_uuidfield_value(self, value, expression, connection, context): - if value is not None: - value = uuid.UUID(value) - return value - - def convert_textfield_value(self, value, expression, connection, context): - if value is not None: - value = force_text(value) - return value diff --git a/modules/mysql-connector-python/mysql/connector/django/schema.py b/modules/mysql-connector-python/mysql/connector/django/schema.py deleted file mode 100644 index 90cb10ac0..000000000 --- a/modules/mysql-connector-python/mysql/connector/django/schema.py +++ /dev/null @@ -1,79 +0,0 @@ -# MySQL Connector/Python - MySQL driver written in Python. - -import django -from django.db.backends.base.schema import BaseDatabaseSchemaEditor -from django.db.models import NOT_PROVIDED - - -class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): - - sql_rename_table = "RENAME TABLE %(old_table)s TO %(new_table)s" - - sql_alter_column_null = "MODIFY %(column)s %(type)s NULL" - sql_alter_column_not_null = "MODIFY %(column)s %(type)s NOT NULL" - sql_alter_column_type = "MODIFY %(column)s %(type)s" - sql_rename_column = "ALTER TABLE %(table)s CHANGE %(old_column)s " \ - "%(new_column)s %(type)s" - - sql_delete_unique = "ALTER TABLE %(table)s DROP INDEX %(name)s" - - sql_create_fk = "ALTER TABLE %(table)s ADD CONSTRAINT %(name)s FOREIGN " \ - "KEY (%(column)s) REFERENCES %(to_table)s (%(to_column)s)" - sql_delete_fk = "ALTER TABLE %(table)s DROP FOREIGN KEY %(name)s" - - sql_delete_index = "DROP INDEX %(name)s ON %(table)s" - - alter_string_set_null = 'MODIFY %(column)s %(type)s NULL;' - alter_string_drop_null = 'MODIFY %(column)s %(type)s NOT NULL;' - - sql_create_pk = "ALTER TABLE %(table)s ADD CONSTRAINT %(name)s " \ - "PRIMARY KEY (%(columns)s)" - sql_delete_pk = "ALTER TABLE %(table)s DROP PRIMARY KEY" - - def quote_value(self, value): - # Inner import to allow module to fail to load gracefully - from mysql.connector.conversion import MySQLConverter - return MySQLConverter.quote(MySQLConverter.escape(value)) - - def skip_default(self, field): - """ - MySQL doesn't accept default values for longtext and longblob - and implicitly treats these columns as nullable. - """ - return field.db_type(self.connection) in ('longtext', 'longblob') - - def add_field(self, model, field): - super(DatabaseSchemaEditor, self).add_field(model, field) - - # Simulate the effect of a one-off default. - if (self.skip_default(field) - and field.default not in (None, NOT_PROVIDED)): - effective_default = self.effective_default(field) - self.execute('UPDATE %(table)s SET %(column)s = %%s' % { - 'table': self.quote_name(model._meta.db_table), - 'column': self.quote_name(field.column), - }, [effective_default]) - - def _model_indexes_sql(self, model): - storage = self.connection.introspection.get_storage_engine( - self.connection.cursor(), model._meta.db_table - ) - if storage == "InnoDB": - for field in model._meta.local_fields: - if (field.db_index and not field.unique - and field.get_internal_type() == "ForeignKey"): - # Temporary setting db_index to False (in memory) to - # disable index creation for FKs (index automatically - # created by MySQL) - field.db_index = False - return super(DatabaseSchemaEditor, self)._model_indexes_sql(model) - - def _alter_column_type_sql(self, table, old_field, new_field, new_type): - # Keep null property of old field, if it has changed, it will be - # handled separately - if old_field.null: - new_type += " NULL" - else: - new_type += " NOT NULL" - return super(DatabaseSchemaEditor, self)._alter_column_type_sql( - table, old_field, new_field, new_type) diff --git a/modules/mysql-connector-python/mysql/connector/django/validation.py b/modules/mysql-connector-python/mysql/connector/django/validation.py deleted file mode 100644 index 24ca6ea83..000000000 --- a/modules/mysql-connector-python/mysql/connector/django/validation.py +++ /dev/null @@ -1,40 +0,0 @@ -# MySQL Connector/Python - MySQL driver written in Python. - -import django - -from django.db.backends.base.validation import BaseDatabaseValidation -from django.core import checks -from django.db import connection - - -class DatabaseValidation(BaseDatabaseValidation): - def check_field(self, field, **kwargs): - """ - MySQL has the following field length restriction: - No character (varchar) fields can have a length exceeding 255 - characters if they have a unique index on them. - """ - errors = super(DatabaseValidation, self).check_field(field, - **kwargs) - - # Ignore any related fields. - if getattr(field, 'rel', None) is None: - field_type = field.db_type(connection) - - if field_type is None: - return errors - - if (field_type.startswith('varchar') # Look for CharFields... - and field.unique # ... that are unique - and (field.max_length is None or - int(field.max_length) > 255)): - errors.append( - checks.Error( - ('MySQL does not allow unique CharFields to have a ' - 'max_length > 255.'), - hint=None, - obj=field, - id='mysql.E001', - ) - ) - return errors diff --git a/modules/mysql-connector-python/mysql/connector/errorcode.py b/modules/mysql-connector-python/mysql/connector/errorcode.py deleted file mode 100644 index ecc14701e..000000000 --- a/modules/mysql-connector-python/mysql/connector/errorcode.py +++ /dev/null @@ -1,4611 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -# This file was auto-generated. -_GENERATED_ON = '2018-03-16' -_MYSQL_VERSION = (8, 0, 11) - -"""This module contains the MySQL Server and Client error codes""" - -# Start MySQL Errors -OBSOLETE_ER_HASHCHK = 1000 -OBSOLETE_ER_NISAMCHK = 1001 -ER_NO = 1002 -ER_YES = 1003 -ER_CANT_CREATE_FILE = 1004 -ER_CANT_CREATE_TABLE = 1005 -ER_CANT_CREATE_DB = 1006 -ER_DB_CREATE_EXISTS = 1007 -ER_DB_DROP_EXISTS = 1008 -OBSOLETE_ER_DB_DROP_DELETE = 1009 -ER_DB_DROP_RMDIR = 1010 -OBSOLETE_ER_CANT_DELETE_FILE = 1011 -ER_CANT_FIND_SYSTEM_REC = 1012 -ER_CANT_GET_STAT = 1013 -OBSOLETE_ER_CANT_GET_WD = 1014 -ER_CANT_LOCK = 1015 -ER_CANT_OPEN_FILE = 1016 -ER_FILE_NOT_FOUND = 1017 -ER_CANT_READ_DIR = 1018 -OBSOLETE_ER_CANT_SET_WD = 1019 -ER_CHECKREAD = 1020 -OBSOLETE_ER_DISK_FULL = 1021 -ER_DUP_KEY = 1022 -OBSOLETE_ER_ERROR_ON_CLOSE = 1023 -ER_ERROR_ON_READ = 1024 -ER_ERROR_ON_RENAME = 1025 -ER_ERROR_ON_WRITE = 1026 -ER_FILE_USED = 1027 -ER_FILSORT_ABORT = 1028 -OBSOLETE_ER_FORM_NOT_FOUND = 1029 -ER_GET_ERRNO = 1030 -ER_ILLEGAL_HA = 1031 -ER_KEY_NOT_FOUND = 1032 -ER_NOT_FORM_FILE = 1033 -ER_NOT_KEYFILE = 1034 -ER_OLD_KEYFILE = 1035 -ER_OPEN_AS_READONLY = 1036 -ER_OUTOFMEMORY = 1037 -ER_OUT_OF_SORTMEMORY = 1038 -OBSOLETE_ER_UNEXPECTED_EOF = 1039 -ER_CON_COUNT_ERROR = 1040 -ER_OUT_OF_RESOURCES = 1041 -ER_BAD_HOST_ERROR = 1042 -ER_HANDSHAKE_ERROR = 1043 -ER_DBACCESS_DENIED_ERROR = 1044 -ER_ACCESS_DENIED_ERROR = 1045 -ER_NO_DB_ERROR = 1046 -ER_UNKNOWN_COM_ERROR = 1047 -ER_BAD_NULL_ERROR = 1048 -ER_BAD_DB_ERROR = 1049 -ER_TABLE_EXISTS_ERROR = 1050 -ER_BAD_TABLE_ERROR = 1051 -ER_NON_UNIQ_ERROR = 1052 -ER_SERVER_SHUTDOWN = 1053 -ER_BAD_FIELD_ERROR = 1054 -ER_WRONG_FIELD_WITH_GROUP = 1055 -ER_WRONG_GROUP_FIELD = 1056 -ER_WRONG_SUM_SELECT = 1057 -ER_WRONG_VALUE_COUNT = 1058 -ER_TOO_LONG_IDENT = 1059 -ER_DUP_FIELDNAME = 1060 -ER_DUP_KEYNAME = 1061 -ER_DUP_ENTRY = 1062 -ER_WRONG_FIELD_SPEC = 1063 -ER_PARSE_ERROR = 1064 -ER_EMPTY_QUERY = 1065 -ER_NONUNIQ_TABLE = 1066 -ER_INVALID_DEFAULT = 1067 -ER_MULTIPLE_PRI_KEY = 1068 -ER_TOO_MANY_KEYS = 1069 -ER_TOO_MANY_KEY_PARTS = 1070 -ER_TOO_LONG_KEY = 1071 -ER_KEY_COLUMN_DOES_NOT_EXITS = 1072 -ER_BLOB_USED_AS_KEY = 1073 -ER_TOO_BIG_FIELDLENGTH = 1074 -ER_WRONG_AUTO_KEY = 1075 -ER_READY = 1076 -OBSOLETE_ER_NORMAL_SHUTDOWN = 1077 -OBSOLETE_ER_GOT_SIGNAL = 1078 -ER_SHUTDOWN_COMPLETE = 1079 -ER_FORCING_CLOSE = 1080 -ER_IPSOCK_ERROR = 1081 -ER_NO_SUCH_INDEX = 1082 -ER_WRONG_FIELD_TERMINATORS = 1083 -ER_BLOBS_AND_NO_TERMINATED = 1084 -ER_TEXTFILE_NOT_READABLE = 1085 -ER_FILE_EXISTS_ERROR = 1086 -ER_LOAD_INFO = 1087 -ER_ALTER_INFO = 1088 -ER_WRONG_SUB_KEY = 1089 -ER_CANT_REMOVE_ALL_FIELDS = 1090 -ER_CANT_DROP_FIELD_OR_KEY = 1091 -ER_INSERT_INFO = 1092 -ER_UPDATE_TABLE_USED = 1093 -ER_NO_SUCH_THREAD = 1094 -ER_KILL_DENIED_ERROR = 1095 -ER_NO_TABLES_USED = 1096 -ER_TOO_BIG_SET = 1097 -ER_NO_UNIQUE_LOGFILE = 1098 -ER_TABLE_NOT_LOCKED_FOR_WRITE = 1099 -ER_TABLE_NOT_LOCKED = 1100 -ER_BLOB_CANT_HAVE_DEFAULT = 1101 -ER_WRONG_DB_NAME = 1102 -ER_WRONG_TABLE_NAME = 1103 -ER_TOO_BIG_SELECT = 1104 -ER_UNKNOWN_ERROR = 1105 -ER_UNKNOWN_PROCEDURE = 1106 -ER_WRONG_PARAMCOUNT_TO_PROCEDURE = 1107 -ER_WRONG_PARAMETERS_TO_PROCEDURE = 1108 -ER_UNKNOWN_TABLE = 1109 -ER_FIELD_SPECIFIED_TWICE = 1110 -ER_INVALID_GROUP_FUNC_USE = 1111 -ER_UNSUPPORTED_EXTENSION = 1112 -ER_TABLE_MUST_HAVE_COLUMNS = 1113 -ER_RECORD_FILE_FULL = 1114 -ER_UNKNOWN_CHARACTER_SET = 1115 -ER_TOO_MANY_TABLES = 1116 -ER_TOO_MANY_FIELDS = 1117 -ER_TOO_BIG_ROWSIZE = 1118 -ER_STACK_OVERRUN = 1119 -ER_WRONG_OUTER_JOIN_UNUSED = 1120 -ER_NULL_COLUMN_IN_INDEX = 1121 -ER_CANT_FIND_UDF = 1122 -ER_CANT_INITIALIZE_UDF = 1123 -ER_UDF_NO_PATHS = 1124 -ER_UDF_EXISTS = 1125 -ER_CANT_OPEN_LIBRARY = 1126 -ER_CANT_FIND_DL_ENTRY = 1127 -ER_FUNCTION_NOT_DEFINED = 1128 -ER_HOST_IS_BLOCKED = 1129 -ER_HOST_NOT_PRIVILEGED = 1130 -ER_PASSWORD_ANONYMOUS_USER = 1131 -ER_PASSWORD_NOT_ALLOWED = 1132 -ER_PASSWORD_NO_MATCH = 1133 -ER_UPDATE_INFO = 1134 -ER_CANT_CREATE_THREAD = 1135 -ER_WRONG_VALUE_COUNT_ON_ROW = 1136 -ER_CANT_REOPEN_TABLE = 1137 -ER_INVALID_USE_OF_NULL = 1138 -ER_REGEXP_ERROR = 1139 -ER_MIX_OF_GROUP_FUNC_AND_FIELDS = 1140 -ER_NONEXISTING_GRANT = 1141 -ER_TABLEACCESS_DENIED_ERROR = 1142 -ER_COLUMNACCESS_DENIED_ERROR = 1143 -ER_ILLEGAL_GRANT_FOR_TABLE = 1144 -ER_GRANT_WRONG_HOST_OR_USER = 1145 -ER_NO_SUCH_TABLE = 1146 -ER_NONEXISTING_TABLE_GRANT = 1147 -ER_NOT_ALLOWED_COMMAND = 1148 -ER_SYNTAX_ERROR = 1149 -OBSOLETE_ER_UNUSED1 = 1150 -OBSOLETE_ER_UNUSED2 = 1151 -ER_ABORTING_CONNECTION = 1152 -ER_NET_PACKET_TOO_LARGE = 1153 -ER_NET_READ_ERROR_FROM_PIPE = 1154 -ER_NET_FCNTL_ERROR = 1155 -ER_NET_PACKETS_OUT_OF_ORDER = 1156 -ER_NET_UNCOMPRESS_ERROR = 1157 -ER_NET_READ_ERROR = 1158 -ER_NET_READ_INTERRUPTED = 1159 -ER_NET_ERROR_ON_WRITE = 1160 -ER_NET_WRITE_INTERRUPTED = 1161 -ER_TOO_LONG_STRING = 1162 -ER_TABLE_CANT_HANDLE_BLOB = 1163 -ER_TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164 -OBSOLETE_ER_UNUSED3 = 1165 -ER_WRONG_COLUMN_NAME = 1166 -ER_WRONG_KEY_COLUMN = 1167 -ER_WRONG_MRG_TABLE = 1168 -ER_DUP_UNIQUE = 1169 -ER_BLOB_KEY_WITHOUT_LENGTH = 1170 -ER_PRIMARY_CANT_HAVE_NULL = 1171 -ER_TOO_MANY_ROWS = 1172 -ER_REQUIRES_PRIMARY_KEY = 1173 -OBSOLETE_ER_NO_RAID_COMPILED = 1174 -ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175 -ER_KEY_DOES_NOT_EXITS = 1176 -ER_CHECK_NO_SUCH_TABLE = 1177 -ER_CHECK_NOT_IMPLEMENTED = 1178 -ER_CANT_DO_THIS_DURING_AN_TRANSACTION = 1179 -ER_ERROR_DURING_COMMIT = 1180 -ER_ERROR_DURING_ROLLBACK = 1181 -ER_ERROR_DURING_FLUSH_LOGS = 1182 -OBSOLETE_ER_ERROR_DURING_CHECKPOINT = 1183 -ER_NEW_ABORTING_CONNECTION = 1184 -OBSOLETE_ER_DUMP_NOT_IMPLEMENTED = 1185 -OBSOLETE_ER_FLUSH_MASTER_BINLOG_CLOSED = 1186 -OBSOLETE_ER_INDEX_REBUILD = 1187 -ER_MASTER = 1188 -ER_MASTER_NET_READ = 1189 -ER_MASTER_NET_WRITE = 1190 -ER_FT_MATCHING_KEY_NOT_FOUND = 1191 -ER_LOCK_OR_ACTIVE_TRANSACTION = 1192 -ER_UNKNOWN_SYSTEM_VARIABLE = 1193 -ER_CRASHED_ON_USAGE = 1194 -ER_CRASHED_ON_REPAIR = 1195 -ER_WARNING_NOT_COMPLETE_ROLLBACK = 1196 -ER_TRANS_CACHE_FULL = 1197 -OBSOLETE_ER_SLAVE_MUST_STOP = 1198 -ER_SLAVE_NOT_RUNNING = 1199 -ER_BAD_SLAVE = 1200 -ER_MASTER_INFO = 1201 -ER_SLAVE_THREAD = 1202 -ER_TOO_MANY_USER_CONNECTIONS = 1203 -ER_SET_CONSTANTS_ONLY = 1204 -ER_LOCK_WAIT_TIMEOUT = 1205 -ER_LOCK_TABLE_FULL = 1206 -ER_READ_ONLY_TRANSACTION = 1207 -OBSOLETE_ER_DROP_DB_WITH_READ_LOCK = 1208 -OBSOLETE_ER_CREATE_DB_WITH_READ_LOCK = 1209 -ER_WRONG_ARGUMENTS = 1210 -ER_NO_PERMISSION_TO_CREATE_USER = 1211 -OBSOLETE_ER_UNION_TABLES_IN_DIFFERENT_DIR = 1212 -ER_LOCK_DEADLOCK = 1213 -ER_TABLE_CANT_HANDLE_FT = 1214 -ER_CANNOT_ADD_FOREIGN = 1215 -ER_NO_REFERENCED_ROW = 1216 -ER_ROW_IS_REFERENCED = 1217 -ER_CONNECT_TO_MASTER = 1218 -OBSOLETE_ER_QUERY_ON_MASTER = 1219 -ER_ERROR_WHEN_EXECUTING_COMMAND = 1220 -ER_WRONG_USAGE = 1221 -ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222 -ER_CANT_UPDATE_WITH_READLOCK = 1223 -ER_MIXING_NOT_ALLOWED = 1224 -ER_DUP_ARGUMENT = 1225 -ER_USER_LIMIT_REACHED = 1226 -ER_SPECIFIC_ACCESS_DENIED_ERROR = 1227 -ER_LOCAL_VARIABLE = 1228 -ER_GLOBAL_VARIABLE = 1229 -ER_NO_DEFAULT = 1230 -ER_WRONG_VALUE_FOR_VAR = 1231 -ER_WRONG_TYPE_FOR_VAR = 1232 -ER_VAR_CANT_BE_READ = 1233 -ER_CANT_USE_OPTION_HERE = 1234 -ER_NOT_SUPPORTED_YET = 1235 -ER_MASTER_FATAL_ERROR_READING_BINLOG = 1236 -ER_SLAVE_IGNORED_TABLE = 1237 -ER_INCORRECT_GLOBAL_LOCAL_VAR = 1238 -ER_WRONG_FK_DEF = 1239 -ER_KEY_REF_DO_NOT_MATCH_TABLE_REF = 1240 -ER_OPERAND_COLUMNS = 1241 -ER_SUBQUERY_NO_1_ROW = 1242 -ER_UNKNOWN_STMT_HANDLER = 1243 -ER_CORRUPT_HELP_DB = 1244 -OBSOLETE_ER_CYCLIC_REFERENCE = 1245 -ER_AUTO_CONVERT = 1246 -ER_ILLEGAL_REFERENCE = 1247 -ER_DERIVED_MUST_HAVE_ALIAS = 1248 -ER_SELECT_REDUCED = 1249 -ER_TABLENAME_NOT_ALLOWED_HERE = 1250 -ER_NOT_SUPPORTED_AUTH_MODE = 1251 -ER_SPATIAL_CANT_HAVE_NULL = 1252 -ER_COLLATION_CHARSET_MISMATCH = 1253 -OBSOLETE_ER_SLAVE_WAS_RUNNING = 1254 -OBSOLETE_ER_SLAVE_WAS_NOT_RUNNING = 1255 -ER_TOO_BIG_FOR_UNCOMPRESS = 1256 -ER_ZLIB_Z_MEM_ERROR = 1257 -ER_ZLIB_Z_BUF_ERROR = 1258 -ER_ZLIB_Z_DATA_ERROR = 1259 -ER_CUT_VALUE_GROUP_CONCAT = 1260 -ER_WARN_TOO_FEW_RECORDS = 1261 -ER_WARN_TOO_MANY_RECORDS = 1262 -ER_WARN_NULL_TO_NOTNULL = 1263 -ER_WARN_DATA_OUT_OF_RANGE = 1264 -WARN_DATA_TRUNCATED = 1265 -ER_WARN_USING_OTHER_HANDLER = 1266 -ER_CANT_AGGREGATE_2COLLATIONS = 1267 -OBSOLETE_ER_DROP_USER = 1268 -ER_REVOKE_GRANTS = 1269 -ER_CANT_AGGREGATE_3COLLATIONS = 1270 -ER_CANT_AGGREGATE_NCOLLATIONS = 1271 -ER_VARIABLE_IS_NOT_STRUCT = 1272 -ER_UNKNOWN_COLLATION = 1273 -ER_SLAVE_IGNORED_SSL_PARAMS = 1274 -ER_SERVER_IS_IN_SECURE_AUTH_MODE = 1275 -ER_WARN_FIELD_RESOLVED = 1276 -ER_BAD_SLAVE_UNTIL_COND = 1277 -ER_MISSING_SKIP_SLAVE = 1278 -ER_UNTIL_COND_IGNORED = 1279 -ER_WRONG_NAME_FOR_INDEX = 1280 -ER_WRONG_NAME_FOR_CATALOG = 1281 -OBSOLETE_ER_WARN_QC_RESIZE = 1282 -ER_BAD_FT_COLUMN = 1283 -ER_UNKNOWN_KEY_CACHE = 1284 -ER_WARN_HOSTNAME_WONT_WORK = 1285 -ER_UNKNOWN_STORAGE_ENGINE = 1286 -ER_WARN_DEPRECATED_SYNTAX = 1287 -ER_NON_UPDATABLE_TABLE = 1288 -ER_FEATURE_DISABLED = 1289 -ER_OPTION_PREVENTS_STATEMENT = 1290 -ER_DUPLICATED_VALUE_IN_TYPE = 1291 -ER_TRUNCATED_WRONG_VALUE = 1292 -OBSOLETE_ER_TOO_MUCH_AUTO_TIMESTAMP_COLS = 1293 -ER_INVALID_ON_UPDATE = 1294 -ER_UNSUPPORTED_PS = 1295 -ER_GET_ERRMSG = 1296 -ER_GET_TEMPORARY_ERRMSG = 1297 -ER_UNKNOWN_TIME_ZONE = 1298 -ER_WARN_INVALID_TIMESTAMP = 1299 -ER_INVALID_CHARACTER_STRING = 1300 -ER_WARN_ALLOWED_PACKET_OVERFLOWED = 1301 -ER_CONFLICTING_DECLARATIONS = 1302 -ER_SP_NO_RECURSIVE_CREATE = 1303 -ER_SP_ALREADY_EXISTS = 1304 -ER_SP_DOES_NOT_EXIST = 1305 -ER_SP_DROP_FAILED = 1306 -ER_SP_STORE_FAILED = 1307 -ER_SP_LILABEL_MISMATCH = 1308 -ER_SP_LABEL_REDEFINE = 1309 -ER_SP_LABEL_MISMATCH = 1310 -ER_SP_UNINIT_VAR = 1311 -ER_SP_BADSELECT = 1312 -ER_SP_BADRETURN = 1313 -ER_SP_BADSTATEMENT = 1314 -ER_UPDATE_LOG_DEPRECATED_IGNORED = 1315 -ER_UPDATE_LOG_DEPRECATED_TRANSLATED = 1316 -ER_QUERY_INTERRUPTED = 1317 -ER_SP_WRONG_NO_OF_ARGS = 1318 -ER_SP_COND_MISMATCH = 1319 -ER_SP_NORETURN = 1320 -ER_SP_NORETURNEND = 1321 -ER_SP_BAD_CURSOR_QUERY = 1322 -ER_SP_BAD_CURSOR_SELECT = 1323 -ER_SP_CURSOR_MISMATCH = 1324 -ER_SP_CURSOR_ALREADY_OPEN = 1325 -ER_SP_CURSOR_NOT_OPEN = 1326 -ER_SP_UNDECLARED_VAR = 1327 -ER_SP_WRONG_NO_OF_FETCH_ARGS = 1328 -ER_SP_FETCH_NO_DATA = 1329 -ER_SP_DUP_PARAM = 1330 -ER_SP_DUP_VAR = 1331 -ER_SP_DUP_COND = 1332 -ER_SP_DUP_CURS = 1333 -ER_SP_CANT_ALTER = 1334 -ER_SP_SUBSELECT_NYI = 1335 -ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG = 1336 -ER_SP_VARCOND_AFTER_CURSHNDLR = 1337 -ER_SP_CURSOR_AFTER_HANDLER = 1338 -ER_SP_CASE_NOT_FOUND = 1339 -ER_FPARSER_TOO_BIG_FILE = 1340 -ER_FPARSER_BAD_HEADER = 1341 -ER_FPARSER_EOF_IN_COMMENT = 1342 -ER_FPARSER_ERROR_IN_PARAMETER = 1343 -ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344 -ER_VIEW_NO_EXPLAIN = 1345 -OBSOLETE_ER_FRM_UNKNOWN_TYPE = 1346 -ER_WRONG_OBJECT = 1347 -ER_NONUPDATEABLE_COLUMN = 1348 -OBSOLETE_ER_VIEW_SELECT_DERIVED_UNUSED = 1349 -ER_VIEW_SELECT_CLAUSE = 1350 -ER_VIEW_SELECT_VARIABLE = 1351 -ER_VIEW_SELECT_TMPTABLE = 1352 -ER_VIEW_WRONG_LIST = 1353 -ER_WARN_VIEW_MERGE = 1354 -ER_WARN_VIEW_WITHOUT_KEY = 1355 -ER_VIEW_INVALID = 1356 -ER_SP_NO_DROP_SP = 1357 -OBSOLETE_ER_SP_GOTO_IN_HNDLR = 1358 -ER_TRG_ALREADY_EXISTS = 1359 -ER_TRG_DOES_NOT_EXIST = 1360 -ER_TRG_ON_VIEW_OR_TEMP_TABLE = 1361 -ER_TRG_CANT_CHANGE_ROW = 1362 -ER_TRG_NO_SUCH_ROW_IN_TRG = 1363 -ER_NO_DEFAULT_FOR_FIELD = 1364 -ER_DIVISION_BY_ZERO = 1365 -ER_TRUNCATED_WRONG_VALUE_FOR_FIELD = 1366 -ER_ILLEGAL_VALUE_FOR_TYPE = 1367 -ER_VIEW_NONUPD_CHECK = 1368 -ER_VIEW_CHECK_FAILED = 1369 -ER_PROCACCESS_DENIED_ERROR = 1370 -ER_RELAY_LOG_FAIL = 1371 -OBSOLETE_ER_PASSWD_LENGTH = 1372 -ER_UNKNOWN_TARGET_BINLOG = 1373 -ER_IO_ERR_LOG_INDEX_READ = 1374 -ER_BINLOG_PURGE_PROHIBITED = 1375 -ER_FSEEK_FAIL = 1376 -ER_BINLOG_PURGE_FATAL_ERR = 1377 -ER_LOG_IN_USE = 1378 -ER_LOG_PURGE_UNKNOWN_ERR = 1379 -ER_RELAY_LOG_INIT = 1380 -ER_NO_BINARY_LOGGING = 1381 -ER_RESERVED_SYNTAX = 1382 -OBSOLETE_ER_WSAS_FAILED = 1383 -OBSOLETE_ER_DIFF_GROUPS_PROC = 1384 -OBSOLETE_ER_NO_GROUP_FOR_PROC = 1385 -OBSOLETE_ER_ORDER_WITH_PROC = 1386 -OBSOLETE_ER_LOGGING_PROHIBIT_CHANGING_OF = 1387 -OBSOLETE_ER_NO_FILE_MAPPING = 1388 -OBSOLETE_ER_WRONG_MAGIC = 1389 -ER_PS_MANY_PARAM = 1390 -ER_KEY_PART_0 = 1391 -ER_VIEW_CHECKSUM = 1392 -ER_VIEW_MULTIUPDATE = 1393 -ER_VIEW_NO_INSERT_FIELD_LIST = 1394 -ER_VIEW_DELETE_MERGE_VIEW = 1395 -ER_CANNOT_USER = 1396 -ER_XAER_NOTA = 1397 -ER_XAER_INVAL = 1398 -ER_XAER_RMFAIL = 1399 -ER_XAER_OUTSIDE = 1400 -ER_XAER_RMERR = 1401 -ER_XA_RBROLLBACK = 1402 -ER_NONEXISTING_PROC_GRANT = 1403 -ER_PROC_AUTO_GRANT_FAIL = 1404 -ER_PROC_AUTO_REVOKE_FAIL = 1405 -ER_DATA_TOO_LONG = 1406 -ER_SP_BAD_SQLSTATE = 1407 -ER_STARTUP = 1408 -ER_LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR = 1409 -ER_CANT_CREATE_USER_WITH_GRANT = 1410 -ER_WRONG_VALUE_FOR_TYPE = 1411 -ER_TABLE_DEF_CHANGED = 1412 -ER_SP_DUP_HANDLER = 1413 -ER_SP_NOT_VAR_ARG = 1414 -ER_SP_NO_RETSET = 1415 -ER_CANT_CREATE_GEOMETRY_OBJECT = 1416 -OBSOLETE_ER_FAILED_ROUTINE_BREAK_BINLOG = 1417 -ER_BINLOG_UNSAFE_ROUTINE = 1418 -ER_BINLOG_CREATE_ROUTINE_NEED_SUPER = 1419 -OBSOLETE_ER_EXEC_STMT_WITH_OPEN_CURSOR = 1420 -ER_STMT_HAS_NO_OPEN_CURSOR = 1421 -ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG = 1422 -ER_NO_DEFAULT_FOR_VIEW_FIELD = 1423 -ER_SP_NO_RECURSION = 1424 -ER_TOO_BIG_SCALE = 1425 -ER_TOO_BIG_PRECISION = 1426 -ER_M_BIGGER_THAN_D = 1427 -ER_WRONG_LOCK_OF_SYSTEM_TABLE = 1428 -ER_CONNECT_TO_FOREIGN_DATA_SOURCE = 1429 -ER_QUERY_ON_FOREIGN_DATA_SOURCE = 1430 -ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST = 1431 -ER_FOREIGN_DATA_STRING_INVALID_CANT_CREATE = 1432 -ER_FOREIGN_DATA_STRING_INVALID = 1433 -OBSOLETE_ER_CANT_CREATE_FEDERATED_TABLE = 1434 -ER_TRG_IN_WRONG_SCHEMA = 1435 -ER_STACK_OVERRUN_NEED_MORE = 1436 -ER_TOO_LONG_BODY = 1437 -ER_WARN_CANT_DROP_DEFAULT_KEYCACHE = 1438 -ER_TOO_BIG_DISPLAYWIDTH = 1439 -ER_XAER_DUPID = 1440 -ER_DATETIME_FUNCTION_OVERFLOW = 1441 -ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG = 1442 -ER_VIEW_PREVENT_UPDATE = 1443 -ER_PS_NO_RECURSION = 1444 -ER_SP_CANT_SET_AUTOCOMMIT = 1445 -OBSOLETE_ER_MALFORMED_DEFINER = 1446 -ER_VIEW_FRM_NO_USER = 1447 -ER_VIEW_OTHER_USER = 1448 -ER_NO_SUCH_USER = 1449 -ER_FORBID_SCHEMA_CHANGE = 1450 -ER_ROW_IS_REFERENCED_2 = 1451 -ER_NO_REFERENCED_ROW_2 = 1452 -ER_SP_BAD_VAR_SHADOW = 1453 -ER_TRG_NO_DEFINER = 1454 -ER_OLD_FILE_FORMAT = 1455 -ER_SP_RECURSION_LIMIT = 1456 -OBSOLETE_ER_SP_PROC_TABLE_CORRUPT = 1457 -ER_SP_WRONG_NAME = 1458 -ER_TABLE_NEEDS_UPGRADE = 1459 -ER_SP_NO_AGGREGATE = 1460 -ER_MAX_PREPARED_STMT_COUNT_REACHED = 1461 -ER_VIEW_RECURSIVE = 1462 -ER_NON_GROUPING_FIELD_USED = 1463 -ER_TABLE_CANT_HANDLE_SPKEYS = 1464 -ER_NO_TRIGGERS_ON_SYSTEM_SCHEMA = 1465 -ER_REMOVED_SPACES = 1466 -ER_AUTOINC_READ_FAILED = 1467 -ER_USERNAME = 1468 -ER_HOSTNAME = 1469 -ER_WRONG_STRING_LENGTH = 1470 -ER_NON_INSERTABLE_TABLE = 1471 -ER_ADMIN_WRONG_MRG_TABLE = 1472 -ER_TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT = 1473 -ER_NAME_BECOMES_EMPTY = 1474 -ER_AMBIGUOUS_FIELD_TERM = 1475 -ER_FOREIGN_SERVER_EXISTS = 1476 -ER_FOREIGN_SERVER_DOESNT_EXIST = 1477 -ER_ILLEGAL_HA_CREATE_OPTION = 1478 -ER_PARTITION_REQUIRES_VALUES_ERROR = 1479 -ER_PARTITION_WRONG_VALUES_ERROR = 1480 -ER_PARTITION_MAXVALUE_ERROR = 1481 -OBSOLETE_ER_PARTITION_SUBPARTITION_ERROR = 1482 -OBSOLETE_ER_PARTITION_SUBPART_MIX_ERROR = 1483 -ER_PARTITION_WRONG_NO_PART_ERROR = 1484 -ER_PARTITION_WRONG_NO_SUBPART_ERROR = 1485 -ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR = 1486 -OBSOLETE_ER_NO_CONST_EXPR_IN_RANGE_OR_LIST_ERROR = 1487 -ER_FIELD_NOT_FOUND_PART_ERROR = 1488 -OBSOLETE_ER_LIST_OF_FIELDS_ONLY_IN_HASH_ERROR = 1489 -ER_INCONSISTENT_PARTITION_INFO_ERROR = 1490 -ER_PARTITION_FUNC_NOT_ALLOWED_ERROR = 1491 -ER_PARTITIONS_MUST_BE_DEFINED_ERROR = 1492 -ER_RANGE_NOT_INCREASING_ERROR = 1493 -ER_INCONSISTENT_TYPE_OF_FUNCTIONS_ERROR = 1494 -ER_MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR = 1495 -ER_PARTITION_ENTRY_ERROR = 1496 -ER_MIX_HANDLER_ERROR = 1497 -ER_PARTITION_NOT_DEFINED_ERROR = 1498 -ER_TOO_MANY_PARTITIONS_ERROR = 1499 -ER_SUBPARTITION_ERROR = 1500 -ER_CANT_CREATE_HANDLER_FILE = 1501 -ER_BLOB_FIELD_IN_PART_FUNC_ERROR = 1502 -ER_UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF = 1503 -ER_NO_PARTS_ERROR = 1504 -ER_PARTITION_MGMT_ON_NONPARTITIONED = 1505 -ER_FOREIGN_KEY_ON_PARTITIONED = 1506 -ER_DROP_PARTITION_NON_EXISTENT = 1507 -ER_DROP_LAST_PARTITION = 1508 -ER_COALESCE_ONLY_ON_HASH_PARTITION = 1509 -ER_REORG_HASH_ONLY_ON_SAME_NO = 1510 -ER_REORG_NO_PARAM_ERROR = 1511 -ER_ONLY_ON_RANGE_LIST_PARTITION = 1512 -ER_ADD_PARTITION_SUBPART_ERROR = 1513 -ER_ADD_PARTITION_NO_NEW_PARTITION = 1514 -ER_COALESCE_PARTITION_NO_PARTITION = 1515 -ER_REORG_PARTITION_NOT_EXIST = 1516 -ER_SAME_NAME_PARTITION = 1517 -ER_NO_BINLOG_ERROR = 1518 -ER_CONSECUTIVE_REORG_PARTITIONS = 1519 -ER_REORG_OUTSIDE_RANGE = 1520 -ER_PARTITION_FUNCTION_FAILURE = 1521 -OBSOLETE_ER_PART_STATE_ERROR = 1522 -ER_LIMITED_PART_RANGE = 1523 -ER_PLUGIN_IS_NOT_LOADED = 1524 -ER_WRONG_VALUE = 1525 -ER_NO_PARTITION_FOR_GIVEN_VALUE = 1526 -ER_FILEGROUP_OPTION_ONLY_ONCE = 1527 -ER_CREATE_FILEGROUP_FAILED = 1528 -ER_DROP_FILEGROUP_FAILED = 1529 -ER_TABLESPACE_AUTO_EXTEND_ERROR = 1530 -ER_WRONG_SIZE_NUMBER = 1531 -ER_SIZE_OVERFLOW_ERROR = 1532 -ER_ALTER_FILEGROUP_FAILED = 1533 -ER_BINLOG_ROW_LOGGING_FAILED = 1534 -OBSOLETE_ER_BINLOG_ROW_WRONG_TABLE_DEF = 1535 -OBSOLETE_ER_BINLOG_ROW_RBR_TO_SBR = 1536 -ER_EVENT_ALREADY_EXISTS = 1537 -OBSOLETE_ER_EVENT_STORE_FAILED = 1538 -ER_EVENT_DOES_NOT_EXIST = 1539 -OBSOLETE_ER_EVENT_CANT_ALTER = 1540 -OBSOLETE_ER_EVENT_DROP_FAILED = 1541 -ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG = 1542 -ER_EVENT_ENDS_BEFORE_STARTS = 1543 -ER_EVENT_EXEC_TIME_IN_THE_PAST = 1544 -OBSOLETE_ER_EVENT_OPEN_TABLE_FAILED = 1545 -OBSOLETE_ER_EVENT_NEITHER_M_EXPR_NOR_M_AT = 1546 -OBSOLETE_ER_COL_COUNT_DOESNT_MATCH_CORRUPTED = 1547 -OBSOLETE_ER_CANNOT_LOAD_FROM_TABLE = 1548 -OBSOLETE_ER_EVENT_CANNOT_DELETE = 1549 -OBSOLETE_ER_EVENT_COMPILE_ERROR = 1550 -ER_EVENT_SAME_NAME = 1551 -OBSOLETE_ER_EVENT_DATA_TOO_LONG = 1552 -ER_DROP_INDEX_FK = 1553 -ER_WARN_DEPRECATED_SYNTAX_WITH_VER = 1554 -OBSOLETE_ER_CANT_WRITE_LOCK_LOG_TABLE = 1555 -ER_CANT_LOCK_LOG_TABLE = 1556 -ER_FOREIGN_DUPLICATE_KEY_OLD_UNUSED = 1557 -ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE = 1558 -ER_TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR = 1559 -ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1560 -OBSOLETE_ER_NDB_CANT_SWITCH_BINLOG_FORMAT = 1561 -ER_PARTITION_NO_TEMPORARY = 1562 -ER_PARTITION_CONST_DOMAIN_ERROR = 1563 -ER_PARTITION_FUNCTION_IS_NOT_ALLOWED = 1564 -OBSOLETE_ER_DDL_LOG_ERROR_UNUSED = 1565 -ER_NULL_IN_VALUES_LESS_THAN = 1566 -ER_WRONG_PARTITION_NAME = 1567 -ER_CANT_CHANGE_TX_CHARACTERISTICS = 1568 -ER_DUP_ENTRY_AUTOINCREMENT_CASE = 1569 -OBSOLETE_ER_EVENT_MODIFY_QUEUE_ERROR = 1570 -ER_EVENT_SET_VAR_ERROR = 1571 -ER_PARTITION_MERGE_ERROR = 1572 -OBSOLETE_ER_CANT_ACTIVATE_LOG = 1573 -OBSOLETE_ER_RBR_NOT_AVAILABLE = 1574 -ER_BASE64_DECODE_ERROR = 1575 -ER_EVENT_RECURSION_FORBIDDEN = 1576 -OBSOLETE_ER_EVENTS_DB_ERROR = 1577 -ER_ONLY_INTEGERS_ALLOWED = 1578 -ER_UNSUPORTED_LOG_ENGINE = 1579 -ER_BAD_LOG_STATEMENT = 1580 -ER_CANT_RENAME_LOG_TABLE = 1581 -ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT = 1582 -ER_WRONG_PARAMETERS_TO_NATIVE_FCT = 1583 -ER_WRONG_PARAMETERS_TO_STORED_FCT = 1584 -ER_NATIVE_FCT_NAME_COLLISION = 1585 -ER_DUP_ENTRY_WITH_KEY_NAME = 1586 -ER_BINLOG_PURGE_EMFILE = 1587 -ER_EVENT_CANNOT_CREATE_IN_THE_PAST = 1588 -ER_EVENT_CANNOT_ALTER_IN_THE_PAST = 1589 -OBSOLETE_ER_SLAVE_INCIDENT = 1590 -ER_NO_PARTITION_FOR_GIVEN_VALUE_SILENT = 1591 -ER_BINLOG_UNSAFE_STATEMENT = 1592 -ER_BINLOG_FATAL_ERROR = 1593 -OBSOLETE_ER_SLAVE_RELAY_LOG_READ_FAILURE = 1594 -OBSOLETE_ER_SLAVE_RELAY_LOG_WRITE_FAILURE = 1595 -OBSOLETE_ER_SLAVE_CREATE_EVENT_FAILURE = 1596 -OBSOLETE_ER_SLAVE_MASTER_COM_FAILURE = 1597 -ER_BINLOG_LOGGING_IMPOSSIBLE = 1598 -ER_VIEW_NO_CREATION_CTX = 1599 -ER_VIEW_INVALID_CREATION_CTX = 1600 -OBSOLETE_ER_SR_INVALID_CREATION_CTX = 1601 -ER_TRG_CORRUPTED_FILE = 1602 -ER_TRG_NO_CREATION_CTX = 1603 -ER_TRG_INVALID_CREATION_CTX = 1604 -ER_EVENT_INVALID_CREATION_CTX = 1605 -ER_TRG_CANT_OPEN_TABLE = 1606 -OBSOLETE_ER_CANT_CREATE_SROUTINE = 1607 -OBSOLETE_ER_NEVER_USED = 1608 -ER_NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT = 1609 -ER_SLAVE_CORRUPT_EVENT = 1610 -OBSOLETE_ER_LOAD_DATA_INVALID_COLUMN_UNUSED = 1611 -ER_LOG_PURGE_NO_FILE = 1612 -ER_XA_RBTIMEOUT = 1613 -ER_XA_RBDEADLOCK = 1614 -ER_NEED_REPREPARE = 1615 -OBSOLETE_ER_DELAYED_NOT_SUPPORTED = 1616 -WARN_NO_MASTER_INFO = 1617 -WARN_OPTION_IGNORED = 1618 -ER_PLUGIN_DELETE_BUILTIN = 1619 -WARN_PLUGIN_BUSY = 1620 -ER_VARIABLE_IS_READONLY = 1621 -ER_WARN_ENGINE_TRANSACTION_ROLLBACK = 1622 -OBSOLETE_ER_SLAVE_HEARTBEAT_FAILURE = 1623 -ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE = 1624 -ER_NDB_REPLICATION_SCHEMA_ERROR = 1625 -ER_CONFLICT_FN_PARSE_ERROR = 1626 -ER_EXCEPTIONS_WRITE_ERROR = 1627 -ER_TOO_LONG_TABLE_COMMENT = 1628 -ER_TOO_LONG_FIELD_COMMENT = 1629 -ER_FUNC_INEXISTENT_NAME_COLLISION = 1630 -ER_DATABASE_NAME = 1631 -ER_TABLE_NAME = 1632 -ER_PARTITION_NAME = 1633 -ER_SUBPARTITION_NAME = 1634 -ER_TEMPORARY_NAME = 1635 -ER_RENAMED_NAME = 1636 -ER_TOO_MANY_CONCURRENT_TRXS = 1637 -WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED = 1638 -ER_DEBUG_SYNC_TIMEOUT = 1639 -ER_DEBUG_SYNC_HIT_LIMIT = 1640 -ER_DUP_SIGNAL_SET = 1641 -ER_SIGNAL_WARN = 1642 -ER_SIGNAL_NOT_FOUND = 1643 -ER_SIGNAL_EXCEPTION = 1644 -ER_RESIGNAL_WITHOUT_ACTIVE_HANDLER = 1645 -ER_SIGNAL_BAD_CONDITION_TYPE = 1646 -WARN_COND_ITEM_TRUNCATED = 1647 -ER_COND_ITEM_TOO_LONG = 1648 -ER_UNKNOWN_LOCALE = 1649 -ER_SLAVE_IGNORE_SERVER_IDS = 1650 -OBSOLETE_ER_QUERY_CACHE_DISABLED = 1651 -ER_SAME_NAME_PARTITION_FIELD = 1652 -ER_PARTITION_COLUMN_LIST_ERROR = 1653 -ER_WRONG_TYPE_COLUMN_VALUE_ERROR = 1654 -ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR = 1655 -ER_MAXVALUE_IN_VALUES_IN = 1656 -ER_TOO_MANY_VALUES_ERROR = 1657 -ER_ROW_SINGLE_PARTITION_FIELD_ERROR = 1658 -ER_FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD = 1659 -ER_PARTITION_FIELDS_TOO_LONG = 1660 -ER_BINLOG_ROW_ENGINE_AND_STMT_ENGINE = 1661 -ER_BINLOG_ROW_MODE_AND_STMT_ENGINE = 1662 -ER_BINLOG_UNSAFE_AND_STMT_ENGINE = 1663 -ER_BINLOG_ROW_INJECTION_AND_STMT_ENGINE = 1664 -ER_BINLOG_STMT_MODE_AND_ROW_ENGINE = 1665 -ER_BINLOG_ROW_INJECTION_AND_STMT_MODE = 1666 -ER_BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1667 -ER_BINLOG_UNSAFE_LIMIT = 1668 -OBSOLETE_ER_UNUSED4 = 1669 -ER_BINLOG_UNSAFE_SYSTEM_TABLE = 1670 -ER_BINLOG_UNSAFE_AUTOINC_COLUMNS = 1671 -ER_BINLOG_UNSAFE_UDF = 1672 -ER_BINLOG_UNSAFE_SYSTEM_VARIABLE = 1673 -ER_BINLOG_UNSAFE_SYSTEM_FUNCTION = 1674 -ER_BINLOG_UNSAFE_NONTRANS_AFTER_TRANS = 1675 -ER_MESSAGE_AND_STATEMENT = 1676 -OBSOLETE_ER_SLAVE_CONVERSION_FAILED = 1677 -ER_SLAVE_CANT_CREATE_CONVERSION = 1678 -ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1679 -ER_PATH_LENGTH = 1680 -ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT = 1681 -ER_WRONG_NATIVE_TABLE_STRUCTURE = 1682 -ER_WRONG_PERFSCHEMA_USAGE = 1683 -ER_WARN_I_S_SKIPPED_TABLE = 1684 -ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1685 -ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1686 -ER_SPATIAL_MUST_HAVE_GEOM_COL = 1687 -ER_TOO_LONG_INDEX_COMMENT = 1688 -ER_LOCK_ABORTED = 1689 -ER_DATA_OUT_OF_RANGE = 1690 -ER_WRONG_SPVAR_TYPE_IN_LIMIT = 1691 -ER_BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1692 -ER_BINLOG_UNSAFE_MIXED_STATEMENT = 1693 -ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1694 -ER_STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1695 -ER_FAILED_READ_FROM_PAR_FILE = 1696 -ER_VALUES_IS_NOT_INT_TYPE_ERROR = 1697 -ER_ACCESS_DENIED_NO_PASSWORD_ERROR = 1698 -ER_SET_PASSWORD_AUTH_PLUGIN = 1699 -OBSOLETE_ER_GRANT_PLUGIN_USER_EXISTS = 1700 -ER_TRUNCATE_ILLEGAL_FK = 1701 -ER_PLUGIN_IS_PERMANENT = 1702 -ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN = 1703 -ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX = 1704 -ER_STMT_CACHE_FULL = 1705 -ER_MULTI_UPDATE_KEY_CONFLICT = 1706 -ER_TABLE_NEEDS_REBUILD = 1707 -WARN_OPTION_BELOW_LIMIT = 1708 -ER_INDEX_COLUMN_TOO_LONG = 1709 -ER_ERROR_IN_TRIGGER_BODY = 1710 -ER_ERROR_IN_UNKNOWN_TRIGGER_BODY = 1711 -ER_INDEX_CORRUPT = 1712 -ER_UNDO_RECORD_TOO_BIG = 1713 -ER_BINLOG_UNSAFE_INSERT_IGNORE_SELECT = 1714 -ER_BINLOG_UNSAFE_INSERT_SELECT_UPDATE = 1715 -ER_BINLOG_UNSAFE_REPLACE_SELECT = 1716 -ER_BINLOG_UNSAFE_CREATE_IGNORE_SELECT = 1717 -ER_BINLOG_UNSAFE_CREATE_REPLACE_SELECT = 1718 -ER_BINLOG_UNSAFE_UPDATE_IGNORE = 1719 -ER_PLUGIN_NO_UNINSTALL = 1720 -ER_PLUGIN_NO_INSTALL = 1721 -ER_BINLOG_UNSAFE_WRITE_AUTOINC_SELECT = 1722 -ER_BINLOG_UNSAFE_CREATE_SELECT_AUTOINC = 1723 -ER_BINLOG_UNSAFE_INSERT_TWO_KEYS = 1724 -ER_TABLE_IN_FK_CHECK = 1725 -ER_UNSUPPORTED_ENGINE = 1726 -ER_BINLOG_UNSAFE_AUTOINC_NOT_FIRST = 1727 -ER_CANNOT_LOAD_FROM_TABLE_V2 = 1728 -ER_MASTER_DELAY_VALUE_OUT_OF_RANGE = 1729 -ER_ONLY_FD_AND_RBR_EVENTS_ALLOWED_IN_BINLOG_STATEMENT = 1730 -ER_PARTITION_EXCHANGE_DIFFERENT_OPTION = 1731 -ER_PARTITION_EXCHANGE_PART_TABLE = 1732 -ER_PARTITION_EXCHANGE_TEMP_TABLE = 1733 -ER_PARTITION_INSTEAD_OF_SUBPARTITION = 1734 -ER_UNKNOWN_PARTITION = 1735 -ER_TABLES_DIFFERENT_METADATA = 1736 -ER_ROW_DOES_NOT_MATCH_PARTITION = 1737 -ER_BINLOG_CACHE_SIZE_GREATER_THAN_MAX = 1738 -ER_WARN_INDEX_NOT_APPLICABLE = 1739 -ER_PARTITION_EXCHANGE_FOREIGN_KEY = 1740 -OBSOLETE_ER_NO_SUCH_KEY_VALUE = 1741 -ER_RPL_INFO_DATA_TOO_LONG = 1742 -OBSOLETE_ER_NETWORK_READ_EVENT_CHECKSUM_FAILURE = 1743 -OBSOLETE_ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE = 1744 -ER_BINLOG_STMT_CACHE_SIZE_GREATER_THAN_MAX = 1745 -ER_CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT = 1746 -ER_PARTITION_CLAUSE_ON_NONPARTITIONED = 1747 -ER_ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET = 1748 -OBSOLETE_ER_NO_SUCH_PARTITION__UNUSED = 1749 -ER_CHANGE_RPL_INFO_REPOSITORY_FAILURE = 1750 -ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_CREATED_TEMP_TABLE = 1751 -ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_DROPPED_TEMP_TABLE = 1752 -ER_MTS_FEATURE_IS_NOT_SUPPORTED = 1753 -ER_MTS_UPDATED_DBS_GREATER_MAX = 1754 -ER_MTS_CANT_PARALLEL = 1755 -ER_MTS_INCONSISTENT_DATA = 1756 -ER_FULLTEXT_NOT_SUPPORTED_WITH_PARTITIONING = 1757 -ER_DA_INVALID_CONDITION_NUMBER = 1758 -ER_INSECURE_PLAIN_TEXT = 1759 -ER_INSECURE_CHANGE_MASTER = 1760 -ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO = 1761 -ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO = 1762 -ER_SQLTHREAD_WITH_SECURE_SLAVE = 1763 -ER_TABLE_HAS_NO_FT = 1764 -ER_VARIABLE_NOT_SETTABLE_IN_SF_OR_TRIGGER = 1765 -ER_VARIABLE_NOT_SETTABLE_IN_TRANSACTION = 1766 -OBSOLETE_ER_GTID_NEXT_IS_NOT_IN_GTID_NEXT_LIST = 1767 -OBSOLETE_ER_CANT_CHANGE_GTID_NEXT_IN_TRANSACTION = 1768 -ER_SET_STATEMENT_CANNOT_INVOKE_FUNCTION = 1769 -ER_GTID_NEXT_CANT_BE_AUTOMATIC_IF_GTID_NEXT_LIST_IS_NON_NULL = 1770 -OBSOLETE_ER_SKIPPING_LOGGED_TRANSACTION = 1771 -ER_MALFORMED_GTID_SET_SPECIFICATION = 1772 -ER_MALFORMED_GTID_SET_ENCODING = 1773 -ER_MALFORMED_GTID_SPECIFICATION = 1774 -ER_GNO_EXHAUSTED = 1775 -ER_BAD_SLAVE_AUTO_POSITION = 1776 -ER_AUTO_POSITION_REQUIRES_GTID_MODE_NOT_OFF = 1777 -ER_CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET = 1778 -ER_GTID_MODE_ON_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON = 1779 -OBSOLETE_ER_GTID_MODE_REQUIRES_BINLOG = 1780 -ER_CANT_SET_GTID_NEXT_TO_GTID_WHEN_GTID_MODE_IS_OFF = 1781 -ER_CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON = 1782 -ER_CANT_SET_GTID_NEXT_LIST_TO_NON_NULL_WHEN_GTID_MODE_IS_OFF = 1783 -OBSOLETE_ER_FOUND_GTID_EVENT_WHEN_GTID_MODE_IS_OFF__UNUSED = 1784 -ER_GTID_UNSAFE_NON_TRANSACTIONAL_TABLE = 1785 -ER_GTID_UNSAFE_CREATE_SELECT = 1786 -ER_GTID_UNSAFE_CREATE_DROP_TEMPORARY_TABLE_IN_TRANSACTION = 1787 -ER_GTID_MODE_CAN_ONLY_CHANGE_ONE_STEP_AT_A_TIME = 1788 -ER_MASTER_HAS_PURGED_REQUIRED_GTIDS = 1789 -ER_CANT_SET_GTID_NEXT_WHEN_OWNING_GTID = 1790 -ER_UNKNOWN_EXPLAIN_FORMAT = 1791 -ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION = 1792 -ER_TOO_LONG_TABLE_PARTITION_COMMENT = 1793 -ER_SLAVE_CONFIGURATION = 1794 -ER_INNODB_FT_LIMIT = 1795 -ER_INNODB_NO_FT_TEMP_TABLE = 1796 -ER_INNODB_FT_WRONG_DOCID_COLUMN = 1797 -ER_INNODB_FT_WRONG_DOCID_INDEX = 1798 -ER_INNODB_ONLINE_LOG_TOO_BIG = 1799 -ER_UNKNOWN_ALTER_ALGORITHM = 1800 -ER_UNKNOWN_ALTER_LOCK = 1801 -ER_MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS = 1802 -ER_MTS_RECOVERY_FAILURE = 1803 -ER_MTS_RESET_WORKERS = 1804 -ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2 = 1805 -ER_SLAVE_SILENT_RETRY_TRANSACTION = 1806 -ER_DISCARD_FK_CHECKS_RUNNING = 1807 -ER_TABLE_SCHEMA_MISMATCH = 1808 -ER_TABLE_IN_SYSTEM_TABLESPACE = 1809 -ER_IO_READ_ERROR = 1810 -ER_IO_WRITE_ERROR = 1811 -ER_TABLESPACE_MISSING = 1812 -ER_TABLESPACE_EXISTS = 1813 -ER_TABLESPACE_DISCARDED = 1814 -ER_INTERNAL_ERROR = 1815 -ER_INNODB_IMPORT_ERROR = 1816 -ER_INNODB_INDEX_CORRUPT = 1817 -ER_INVALID_YEAR_COLUMN_LENGTH = 1818 -ER_NOT_VALID_PASSWORD = 1819 -ER_MUST_CHANGE_PASSWORD = 1820 -ER_FK_NO_INDEX_CHILD = 1821 -ER_FK_NO_INDEX_PARENT = 1822 -ER_FK_FAIL_ADD_SYSTEM = 1823 -ER_FK_CANNOT_OPEN_PARENT = 1824 -ER_FK_INCORRECT_OPTION = 1825 -ER_FK_DUP_NAME = 1826 -ER_PASSWORD_FORMAT = 1827 -ER_FK_COLUMN_CANNOT_DROP = 1828 -ER_FK_COLUMN_CANNOT_DROP_CHILD = 1829 -ER_FK_COLUMN_NOT_NULL = 1830 -ER_DUP_INDEX = 1831 -ER_FK_COLUMN_CANNOT_CHANGE = 1832 -ER_FK_COLUMN_CANNOT_CHANGE_CHILD = 1833 -OBSOLETE_ER_UNUSED5 = 1834 -ER_MALFORMED_PACKET = 1835 -ER_READ_ONLY_MODE = 1836 -ER_GTID_NEXT_TYPE_UNDEFINED_GTID = 1837 -ER_VARIABLE_NOT_SETTABLE_IN_SP = 1838 -OBSOLETE_ER_CANT_SET_GTID_PURGED_WHEN_GTID_MODE_IS_OFF = 1839 -ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY = 1840 -ER_CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY = 1841 -ER_GTID_PURGED_WAS_CHANGED = 1842 -ER_GTID_EXECUTED_WAS_CHANGED = 1843 -ER_BINLOG_STMT_MODE_AND_NO_REPL_TABLES = 1844 -ER_ALTER_OPERATION_NOT_SUPPORTED = 1845 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON = 1846 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY = 1847 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION = 1848 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME = 1849 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE = 1850 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK = 1851 -OBSOLETE_ER_UNUSED6 = 1852 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK = 1853 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC = 1854 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS = 1855 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS = 1856 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS = 1857 -ER_SQL_SLAVE_SKIP_COUNTER_NOT_SETTABLE_IN_GTID_MODE = 1858 -ER_DUP_UNKNOWN_IN_INDEX = 1859 -ER_IDENT_CAUSES_TOO_LONG_PATH = 1860 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL = 1861 -ER_MUST_CHANGE_PASSWORD_LOGIN = 1862 -ER_ROW_IN_WRONG_PARTITION = 1863 -ER_MTS_EVENT_BIGGER_PENDING_JOBS_SIZE_MAX = 1864 -OBSOLETE_ER_INNODB_NO_FT_USES_PARSER = 1865 -ER_BINLOG_LOGICAL_CORRUPTION = 1866 -ER_WARN_PURGE_LOG_IN_USE = 1867 -ER_WARN_PURGE_LOG_IS_ACTIVE = 1868 -ER_AUTO_INCREMENT_CONFLICT = 1869 -WARN_ON_BLOCKHOLE_IN_RBR = 1870 -ER_SLAVE_MI_INIT_REPOSITORY = 1871 -ER_SLAVE_RLI_INIT_REPOSITORY = 1872 -ER_ACCESS_DENIED_CHANGE_USER_ERROR = 1873 -ER_INNODB_READ_ONLY = 1874 -ER_STOP_SLAVE_SQL_THREAD_TIMEOUT = 1875 -ER_STOP_SLAVE_IO_THREAD_TIMEOUT = 1876 -ER_TABLE_CORRUPT = 1877 -ER_TEMP_FILE_WRITE_FAILURE = 1878 -ER_INNODB_FT_AUX_NOT_HEX_ID = 1879 -ER_OLD_TEMPORALS_UPGRADED = 1880 -ER_INNODB_FORCED_RECOVERY = 1881 -ER_AES_INVALID_IV = 1882 -ER_PLUGIN_CANNOT_BE_UNINSTALLED = 1883 -ER_GTID_UNSAFE_BINLOG_SPLITTABLE_STATEMENT_AND_ASSIGNED_GTID = 1884 -ER_SLAVE_HAS_MORE_GTIDS_THAN_MASTER = 1885 -ER_MISSING_KEY = 1886 -ER_FILE_CORRUPT = 3000 -ER_ERROR_ON_MASTER = 3001 -OBSOLETE_ER_INCONSISTENT_ERROR = 3002 -ER_STORAGE_ENGINE_NOT_LOADED = 3003 -ER_GET_STACKED_DA_WITHOUT_ACTIVE_HANDLER = 3004 -ER_WARN_LEGACY_SYNTAX_CONVERTED = 3005 -ER_BINLOG_UNSAFE_FULLTEXT_PLUGIN = 3006 -ER_CANNOT_DISCARD_TEMPORARY_TABLE = 3007 -ER_FK_DEPTH_EXCEEDED = 3008 -ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE_V2 = 3009 -ER_WARN_TRIGGER_DOESNT_HAVE_CREATED = 3010 -ER_REFERENCED_TRG_DOES_NOT_EXIST = 3011 -ER_EXPLAIN_NOT_SUPPORTED = 3012 -ER_INVALID_FIELD_SIZE = 3013 -ER_MISSING_HA_CREATE_OPTION = 3014 -ER_ENGINE_OUT_OF_MEMORY = 3015 -ER_PASSWORD_EXPIRE_ANONYMOUS_USER = 3016 -ER_SLAVE_SQL_THREAD_MUST_STOP = 3017 -ER_NO_FT_MATERIALIZED_SUBQUERY = 3018 -ER_INNODB_UNDO_LOG_FULL = 3019 -ER_INVALID_ARGUMENT_FOR_LOGARITHM = 3020 -ER_SLAVE_CHANNEL_IO_THREAD_MUST_STOP = 3021 -ER_WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO = 3022 -ER_WARN_ONLY_MASTER_LOG_FILE_NO_POS = 3023 -ER_QUERY_TIMEOUT = 3024 -ER_NON_RO_SELECT_DISABLE_TIMER = 3025 -ER_DUP_LIST_ENTRY = 3026 -OBSOLETE_ER_SQL_MODE_NO_EFFECT = 3027 -ER_AGGREGATE_ORDER_FOR_UNION = 3028 -ER_AGGREGATE_ORDER_NON_AGG_QUERY = 3029 -ER_SLAVE_WORKER_STOPPED_PREVIOUS_THD_ERROR = 3030 -ER_DONT_SUPPORT_SLAVE_PRESERVE_COMMIT_ORDER = 3031 -ER_SERVER_OFFLINE_MODE = 3032 -ER_GIS_DIFFERENT_SRIDS = 3033 -ER_GIS_UNSUPPORTED_ARGUMENT = 3034 -ER_GIS_UNKNOWN_ERROR = 3035 -ER_GIS_UNKNOWN_EXCEPTION = 3036 -ER_GIS_INVALID_DATA = 3037 -ER_BOOST_GEOMETRY_EMPTY_INPUT_EXCEPTION = 3038 -ER_BOOST_GEOMETRY_CENTROID_EXCEPTION = 3039 -ER_BOOST_GEOMETRY_OVERLAY_INVALID_INPUT_EXCEPTION = 3040 -ER_BOOST_GEOMETRY_TURN_INFO_EXCEPTION = 3041 -ER_BOOST_GEOMETRY_SELF_INTERSECTION_POINT_EXCEPTION = 3042 -ER_BOOST_GEOMETRY_UNKNOWN_EXCEPTION = 3043 -ER_STD_BAD_ALLOC_ERROR = 3044 -ER_STD_DOMAIN_ERROR = 3045 -ER_STD_LENGTH_ERROR = 3046 -ER_STD_INVALID_ARGUMENT = 3047 -ER_STD_OUT_OF_RANGE_ERROR = 3048 -ER_STD_OVERFLOW_ERROR = 3049 -ER_STD_RANGE_ERROR = 3050 -ER_STD_UNDERFLOW_ERROR = 3051 -ER_STD_LOGIC_ERROR = 3052 -ER_STD_RUNTIME_ERROR = 3053 -ER_STD_UNKNOWN_EXCEPTION = 3054 -ER_GIS_DATA_WRONG_ENDIANESS = 3055 -ER_CHANGE_MASTER_PASSWORD_LENGTH = 3056 -ER_USER_LOCK_WRONG_NAME = 3057 -ER_USER_LOCK_DEADLOCK = 3058 -ER_REPLACE_INACCESSIBLE_ROWS = 3059 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS = 3060 -ER_ILLEGAL_USER_VAR = 3061 -ER_GTID_MODE_OFF = 3062 -OBSOLETE_ER_UNSUPPORTED_BY_REPLICATION_THREAD = 3063 -ER_INCORRECT_TYPE = 3064 -ER_FIELD_IN_ORDER_NOT_SELECT = 3065 -ER_AGGREGATE_IN_ORDER_NOT_SELECT = 3066 -ER_INVALID_RPL_WILD_TABLE_FILTER_PATTERN = 3067 -ER_NET_OK_PACKET_TOO_LARGE = 3068 -ER_INVALID_JSON_DATA = 3069 -ER_INVALID_GEOJSON_MISSING_MEMBER = 3070 -ER_INVALID_GEOJSON_WRONG_TYPE = 3071 -ER_INVALID_GEOJSON_UNSPECIFIED = 3072 -ER_DIMENSION_UNSUPPORTED = 3073 -ER_SLAVE_CHANNEL_DOES_NOT_EXIST = 3074 -OBSOLETE_ER_SLAVE_MULTIPLE_CHANNELS_HOST_PORT = 3075 -ER_SLAVE_CHANNEL_NAME_INVALID_OR_TOO_LONG = 3076 -ER_SLAVE_NEW_CHANNEL_WRONG_REPOSITORY = 3077 -OBSOLETE_ER_SLAVE_CHANNEL_DELETE = 3078 -ER_SLAVE_MULTIPLE_CHANNELS_CMD = 3079 -ER_SLAVE_MAX_CHANNELS_EXCEEDED = 3080 -ER_SLAVE_CHANNEL_MUST_STOP = 3081 -ER_SLAVE_CHANNEL_NOT_RUNNING = 3082 -ER_SLAVE_CHANNEL_WAS_RUNNING = 3083 -ER_SLAVE_CHANNEL_WAS_NOT_RUNNING = 3084 -ER_SLAVE_CHANNEL_SQL_THREAD_MUST_STOP = 3085 -ER_SLAVE_CHANNEL_SQL_SKIP_COUNTER = 3086 -ER_WRONG_FIELD_WITH_GROUP_V2 = 3087 -ER_MIX_OF_GROUP_FUNC_AND_FIELDS_V2 = 3088 -ER_WARN_DEPRECATED_SYSVAR_UPDATE = 3089 -ER_WARN_DEPRECATED_SQLMODE = 3090 -ER_CANNOT_LOG_PARTIAL_DROP_DATABASE_WITH_GTID = 3091 -ER_GROUP_REPLICATION_CONFIGURATION = 3092 -ER_GROUP_REPLICATION_RUNNING = 3093 -ER_GROUP_REPLICATION_APPLIER_INIT_ERROR = 3094 -ER_GROUP_REPLICATION_STOP_APPLIER_THREAD_TIMEOUT = 3095 -ER_GROUP_REPLICATION_COMMUNICATION_LAYER_SESSION_ERROR = 3096 -ER_GROUP_REPLICATION_COMMUNICATION_LAYER_JOIN_ERROR = 3097 -ER_BEFORE_DML_VALIDATION_ERROR = 3098 -ER_PREVENTS_VARIABLE_WITHOUT_RBR = 3099 -ER_RUN_HOOK_ERROR = 3100 -ER_TRANSACTION_ROLLBACK_DURING_COMMIT = 3101 -ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED = 3102 -ER_UNSUPPORTED_ALTER_INPLACE_ON_VIRTUAL_COLUMN = 3103 -ER_WRONG_FK_OPTION_FOR_GENERATED_COLUMN = 3104 -ER_NON_DEFAULT_VALUE_FOR_GENERATED_COLUMN = 3105 -ER_UNSUPPORTED_ACTION_ON_GENERATED_COLUMN = 3106 -ER_GENERATED_COLUMN_NON_PRIOR = 3107 -ER_DEPENDENT_BY_GENERATED_COLUMN = 3108 -ER_GENERATED_COLUMN_REF_AUTO_INC = 3109 -ER_FEATURE_NOT_AVAILABLE = 3110 -ER_CANT_SET_GTID_MODE = 3111 -ER_CANT_USE_AUTO_POSITION_WITH_GTID_MODE_OFF = 3112 -OBSOLETE_ER_CANT_REPLICATE_ANONYMOUS_WITH_AUTO_POSITION = 3113 -OBSOLETE_ER_CANT_REPLICATE_ANONYMOUS_WITH_GTID_MODE_ON = 3114 -OBSOLETE_ER_CANT_REPLICATE_GTID_WITH_GTID_MODE_OFF = 3115 -ER_CANT_ENFORCE_GTID_CONSISTENCY_WITH_ONGOING_GTID_VIOLATING_TX = 3116 -ER_ENFORCE_GTID_CONSISTENCY_WARN_WITH_ONGOING_GTID_VIOLATING_TX = 3117 -ER_ACCOUNT_HAS_BEEN_LOCKED = 3118 -ER_WRONG_TABLESPACE_NAME = 3119 -ER_TABLESPACE_IS_NOT_EMPTY = 3120 -ER_WRONG_FILE_NAME = 3121 -ER_BOOST_GEOMETRY_INCONSISTENT_TURNS_EXCEPTION = 3122 -ER_WARN_OPTIMIZER_HINT_SYNTAX_ERROR = 3123 -ER_WARN_BAD_MAX_EXECUTION_TIME = 3124 -ER_WARN_UNSUPPORTED_MAX_EXECUTION_TIME = 3125 -ER_WARN_CONFLICTING_HINT = 3126 -ER_WARN_UNKNOWN_QB_NAME = 3127 -ER_UNRESOLVED_HINT_NAME = 3128 -ER_WARN_ON_MODIFYING_GTID_EXECUTED_TABLE = 3129 -ER_PLUGGABLE_PROTOCOL_COMMAND_NOT_SUPPORTED = 3130 -ER_LOCKING_SERVICE_WRONG_NAME = 3131 -ER_LOCKING_SERVICE_DEADLOCK = 3132 -ER_LOCKING_SERVICE_TIMEOUT = 3133 -ER_GIS_MAX_POINTS_IN_GEOMETRY_OVERFLOWED = 3134 -ER_SQL_MODE_MERGED = 3135 -ER_VTOKEN_PLUGIN_TOKEN_MISMATCH = 3136 -ER_VTOKEN_PLUGIN_TOKEN_NOT_FOUND = 3137 -ER_CANT_SET_VARIABLE_WHEN_OWNING_GTID = 3138 -ER_SLAVE_CHANNEL_OPERATION_NOT_ALLOWED = 3139 -ER_INVALID_JSON_TEXT = 3140 -ER_INVALID_JSON_TEXT_IN_PARAM = 3141 -ER_INVALID_JSON_BINARY_DATA = 3142 -ER_INVALID_JSON_PATH = 3143 -ER_INVALID_JSON_CHARSET = 3144 -ER_INVALID_JSON_CHARSET_IN_FUNCTION = 3145 -ER_INVALID_TYPE_FOR_JSON = 3146 -ER_INVALID_CAST_TO_JSON = 3147 -ER_INVALID_JSON_PATH_CHARSET = 3148 -ER_INVALID_JSON_PATH_WILDCARD = 3149 -ER_JSON_VALUE_TOO_BIG = 3150 -ER_JSON_KEY_TOO_BIG = 3151 -ER_JSON_USED_AS_KEY = 3152 -ER_JSON_VACUOUS_PATH = 3153 -ER_JSON_BAD_ONE_OR_ALL_ARG = 3154 -ER_NUMERIC_JSON_VALUE_OUT_OF_RANGE = 3155 -ER_INVALID_JSON_VALUE_FOR_CAST = 3156 -ER_JSON_DOCUMENT_TOO_DEEP = 3157 -ER_JSON_DOCUMENT_NULL_KEY = 3158 -ER_SECURE_TRANSPORT_REQUIRED = 3159 -ER_NO_SECURE_TRANSPORTS_CONFIGURED = 3160 -ER_DISABLED_STORAGE_ENGINE = 3161 -ER_USER_DOES_NOT_EXIST = 3162 -ER_USER_ALREADY_EXISTS = 3163 -ER_AUDIT_API_ABORT = 3164 -ER_INVALID_JSON_PATH_ARRAY_CELL = 3165 -ER_BUFPOOL_RESIZE_INPROGRESS = 3166 -ER_FEATURE_DISABLED_SEE_DOC = 3167 -ER_SERVER_ISNT_AVAILABLE = 3168 -ER_SESSION_WAS_KILLED = 3169 -ER_CAPACITY_EXCEEDED = 3170 -ER_CAPACITY_EXCEEDED_IN_RANGE_OPTIMIZER = 3171 -OBSOLETE_ER_TABLE_NEEDS_UPG_PART = 3172 -ER_CANT_WAIT_FOR_EXECUTED_GTID_SET_WHILE_OWNING_A_GTID = 3173 -ER_CANNOT_ADD_FOREIGN_BASE_COL_VIRTUAL = 3174 -ER_CANNOT_CREATE_VIRTUAL_INDEX_CONSTRAINT = 3175 -ER_ERROR_ON_MODIFYING_GTID_EXECUTED_TABLE = 3176 -ER_LOCK_REFUSED_BY_ENGINE = 3177 -ER_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN = 3178 -ER_MASTER_KEY_ROTATION_NOT_SUPPORTED_BY_SE = 3179 -OBSOLETE_ER_MASTER_KEY_ROTATION_ERROR_BY_SE = 3180 -ER_MASTER_KEY_ROTATION_BINLOG_FAILED = 3181 -ER_MASTER_KEY_ROTATION_SE_UNAVAILABLE = 3182 -ER_TABLESPACE_CANNOT_ENCRYPT = 3183 -ER_INVALID_ENCRYPTION_OPTION = 3184 -ER_CANNOT_FIND_KEY_IN_KEYRING = 3185 -ER_CAPACITY_EXCEEDED_IN_PARSER = 3186 -ER_UNSUPPORTED_ALTER_ENCRYPTION_INPLACE = 3187 -ER_KEYRING_UDF_KEYRING_SERVICE_ERROR = 3188 -ER_USER_COLUMN_OLD_LENGTH = 3189 -ER_CANT_RESET_MASTER = 3190 -ER_GROUP_REPLICATION_MAX_GROUP_SIZE = 3191 -ER_CANNOT_ADD_FOREIGN_BASE_COL_STORED = 3192 -ER_TABLE_REFERENCED = 3193 -OBSOLETE_ER_PARTITION_ENGINE_DEPRECATED_FOR_TABLE = 3194 -OBSOLETE_ER_WARN_USING_GEOMFROMWKB_TO_SET_SRID_ZERO = 3195 -OBSOLETE_ER_WARN_USING_GEOMFROMWKB_TO_SET_SRID = 3196 -ER_XA_RETRY = 3197 -ER_KEYRING_AWS_UDF_AWS_KMS_ERROR = 3198 -ER_BINLOG_UNSAFE_XA = 3199 -ER_UDF_ERROR = 3200 -ER_KEYRING_MIGRATION_FAILURE = 3201 -ER_KEYRING_ACCESS_DENIED_ERROR = 3202 -ER_KEYRING_MIGRATION_STATUS = 3203 -OBSOLETE_ER_PLUGIN_FAILED_TO_OPEN_TABLES = 3204 -OBSOLETE_ER_PLUGIN_FAILED_TO_OPEN_TABLE = 3205 -OBSOLETE_ER_AUDIT_LOG_NO_KEYRING_PLUGIN_INSTALLED = 3206 -OBSOLETE_ER_AUDIT_LOG_ENCRYPTION_PASSWORD_HAS_NOT_BEEN_SET = 3207 -OBSOLETE_ER_AUDIT_LOG_COULD_NOT_CREATE_AES_KEY = 3208 -OBSOLETE_ER_AUDIT_LOG_ENCRYPTION_PASSWORD_CANNOT_BE_FETCHED = 3209 -OBSOLETE_ER_AUDIT_LOG_JSON_FILTERING_NOT_ENABLED = 3210 -OBSOLETE_ER_AUDIT_LOG_UDF_INSUFFICIENT_PRIVILEGE = 3211 -ER_AUDIT_LOG_SUPER_PRIVILEGE_REQUIRED = 3212 -OBSOLETE_ER_COULD_NOT_REINITIALIZE_AUDIT_LOG_FILTERS = 3213 -ER_AUDIT_LOG_UDF_INVALID_ARGUMENT_TYPE = 3214 -ER_AUDIT_LOG_UDF_INVALID_ARGUMENT_COUNT = 3215 -ER_AUDIT_LOG_HAS_NOT_BEEN_INSTALLED = 3216 -ER_AUDIT_LOG_UDF_READ_INVALID_MAX_ARRAY_LENGTH_ARG_TYPE = 3217 -ER_AUDIT_LOG_UDF_READ_INVALID_MAX_ARRAY_LENGTH_ARG_VALUE = 3218 -OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_PARSING_ERROR = 3219 -OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_NAME_CANNOT_BE_EMPTY = 3220 -OBSOLETE_ER_AUDIT_LOG_JSON_USER_NAME_CANNOT_BE_EMPTY = 3221 -OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_DOES_NOT_EXISTS = 3222 -OBSOLETE_ER_AUDIT_LOG_USER_FIRST_CHARACTER_MUST_BE_ALPHANUMERIC = 3223 -OBSOLETE_ER_AUDIT_LOG_USER_NAME_INVALID_CHARACTER = 3224 -OBSOLETE_ER_AUDIT_LOG_HOST_NAME_INVALID_CHARACTER = 3225 -ER_UNSUPPORT_COMPRESSED_TEMPORARY_TABLE = 3500 -ER_ACL_OPERATION_FAILED = 3501 -ER_UNSUPPORTED_INDEX_ALGORITHM = 3502 -ER_NO_SUCH_DB = 3503 -ER_TOO_BIG_ENUM = 3504 -ER_TOO_LONG_SET_ENUM_VALUE = 3505 -ER_INVALID_DD_OBJECT = 3506 -ER_UPDATING_DD_TABLE = 3507 -ER_INVALID_DD_OBJECT_ID = 3508 -ER_INVALID_DD_OBJECT_NAME = 3509 -ER_TABLESPACE_MISSING_WITH_NAME = 3510 -ER_TOO_LONG_ROUTINE_COMMENT = 3511 -ER_SP_LOAD_FAILED = 3512 -ER_INVALID_BITWISE_OPERANDS_SIZE = 3513 -ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE = 3514 -ER_WARN_UNSUPPORTED_HINT = 3515 -ER_UNEXPECTED_GEOMETRY_TYPE = 3516 -ER_SRS_PARSE_ERROR = 3517 -ER_SRS_PROJ_PARAMETER_MISSING = 3518 -ER_WARN_SRS_NOT_FOUND = 3519 -ER_SRS_NOT_CARTESIAN = 3520 -ER_SRS_NOT_CARTESIAN_UNDEFINED = 3521 -ER_PK_INDEX_CANT_BE_INVISIBLE = 3522 -ER_UNKNOWN_AUTHID = 3523 -ER_FAILED_ROLE_GRANT = 3524 -ER_OPEN_ROLE_TABLES = 3525 -ER_FAILED_DEFAULT_ROLES = 3526 -ER_COMPONENTS_NO_SCHEME = 3527 -ER_COMPONENTS_NO_SCHEME_SERVICE = 3528 -ER_COMPONENTS_CANT_LOAD = 3529 -ER_ROLE_NOT_GRANTED = 3530 -ER_FAILED_REVOKE_ROLE = 3531 -ER_RENAME_ROLE = 3532 -ER_COMPONENTS_CANT_ACQUIRE_SERVICE_IMPLEMENTATION = 3533 -ER_COMPONENTS_CANT_SATISFY_DEPENDENCY = 3534 -ER_COMPONENTS_LOAD_CANT_REGISTER_SERVICE_IMPLEMENTATION = 3535 -ER_COMPONENTS_LOAD_CANT_INITIALIZE = 3536 -ER_COMPONENTS_UNLOAD_NOT_LOADED = 3537 -ER_COMPONENTS_UNLOAD_CANT_DEINITIALIZE = 3538 -ER_COMPONENTS_CANT_RELEASE_SERVICE = 3539 -ER_COMPONENTS_UNLOAD_CANT_UNREGISTER_SERVICE = 3540 -ER_COMPONENTS_CANT_UNLOAD = 3541 -ER_WARN_UNLOAD_THE_NOT_PERSISTED = 3542 -ER_COMPONENT_TABLE_INCORRECT = 3543 -ER_COMPONENT_MANIPULATE_ROW_FAILED = 3544 -ER_COMPONENTS_UNLOAD_DUPLICATE_IN_GROUP = 3545 -ER_CANT_SET_GTID_PURGED_DUE_SETS_CONSTRAINTS = 3546 -ER_CANNOT_LOCK_USER_MANAGEMENT_CACHES = 3547 -ER_SRS_NOT_FOUND = 3548 -ER_VARIABLE_NOT_PERSISTED = 3549 -ER_IS_QUERY_INVALID_CLAUSE = 3550 -ER_UNABLE_TO_STORE_STATISTICS = 3551 -ER_NO_SYSTEM_SCHEMA_ACCESS = 3552 -ER_NO_SYSTEM_TABLESPACE_ACCESS = 3553 -ER_NO_SYSTEM_TABLE_ACCESS = 3554 -ER_NO_SYSTEM_TABLE_ACCESS_FOR_DICTIONARY_TABLE = 3555 -ER_NO_SYSTEM_TABLE_ACCESS_FOR_SYSTEM_TABLE = 3556 -ER_NO_SYSTEM_TABLE_ACCESS_FOR_TABLE = 3557 -ER_INVALID_OPTION_KEY = 3558 -ER_INVALID_OPTION_VALUE = 3559 -ER_INVALID_OPTION_KEY_VALUE_PAIR = 3560 -ER_INVALID_OPTION_START_CHARACTER = 3561 -ER_INVALID_OPTION_END_CHARACTER = 3562 -ER_INVALID_OPTION_CHARACTERS = 3563 -ER_DUPLICATE_OPTION_KEY = 3564 -ER_WARN_SRS_NOT_FOUND_AXIS_ORDER = 3565 -ER_NO_ACCESS_TO_NATIVE_FCT = 3566 -ER_RESET_MASTER_TO_VALUE_OUT_OF_RANGE = 3567 -ER_UNRESOLVED_TABLE_LOCK = 3568 -ER_DUPLICATE_TABLE_LOCK = 3569 -ER_BINLOG_UNSAFE_SKIP_LOCKED = 3570 -ER_BINLOG_UNSAFE_NOWAIT = 3571 -ER_LOCK_NOWAIT = 3572 -ER_CTE_RECURSIVE_REQUIRES_UNION = 3573 -ER_CTE_RECURSIVE_REQUIRES_NONRECURSIVE_FIRST = 3574 -ER_CTE_RECURSIVE_FORBIDS_AGGREGATION = 3575 -ER_CTE_RECURSIVE_FORBIDDEN_JOIN_ORDER = 3576 -ER_CTE_RECURSIVE_REQUIRES_SINGLE_REFERENCE = 3577 -ER_SWITCH_TMP_ENGINE = 3578 -ER_WINDOW_NO_SUCH_WINDOW = 3579 -ER_WINDOW_CIRCULARITY_IN_WINDOW_GRAPH = 3580 -ER_WINDOW_NO_CHILD_PARTITIONING = 3581 -ER_WINDOW_NO_INHERIT_FRAME = 3582 -ER_WINDOW_NO_REDEFINE_ORDER_BY = 3583 -ER_WINDOW_FRAME_START_ILLEGAL = 3584 -ER_WINDOW_FRAME_END_ILLEGAL = 3585 -ER_WINDOW_FRAME_ILLEGAL = 3586 -ER_WINDOW_RANGE_FRAME_ORDER_TYPE = 3587 -ER_WINDOW_RANGE_FRAME_TEMPORAL_TYPE = 3588 -ER_WINDOW_RANGE_FRAME_NUMERIC_TYPE = 3589 -ER_WINDOW_RANGE_BOUND_NOT_CONSTANT = 3590 -ER_WINDOW_DUPLICATE_NAME = 3591 -ER_WINDOW_ILLEGAL_ORDER_BY = 3592 -ER_WINDOW_INVALID_WINDOW_FUNC_USE = 3593 -ER_WINDOW_INVALID_WINDOW_FUNC_ALIAS_USE = 3594 -ER_WINDOW_NESTED_WINDOW_FUNC_USE_IN_WINDOW_SPEC = 3595 -ER_WINDOW_ROWS_INTERVAL_USE = 3596 -ER_WINDOW_NO_GROUP_ORDER = 3597 -ER_WINDOW_EXPLAIN_JSON = 3598 -ER_WINDOW_FUNCTION_IGNORES_FRAME = 3599 -ER_WL9236_NOW_UNUSED = 3600 -ER_INVALID_NO_OF_ARGS = 3601 -ER_FIELD_IN_GROUPING_NOT_GROUP_BY = 3602 -ER_TOO_LONG_TABLESPACE_COMMENT = 3603 -ER_ENGINE_CANT_DROP_TABLE = 3604 -ER_ENGINE_CANT_DROP_MISSING_TABLE = 3605 -ER_TABLESPACE_DUP_FILENAME = 3606 -ER_DB_DROP_RMDIR2 = 3607 -ER_IMP_NO_FILES_MATCHED = 3608 -ER_IMP_SCHEMA_DOES_NOT_EXIST = 3609 -ER_IMP_TABLE_ALREADY_EXISTS = 3610 -ER_IMP_INCOMPATIBLE_MYSQLD_VERSION = 3611 -ER_IMP_INCOMPATIBLE_DD_VERSION = 3612 -ER_IMP_INCOMPATIBLE_SDI_VERSION = 3613 -ER_WARN_INVALID_HINT = 3614 -ER_VAR_DOES_NOT_EXIST = 3615 -ER_LONGITUDE_OUT_OF_RANGE = 3616 -ER_LATITUDE_OUT_OF_RANGE = 3617 -ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS = 3618 -ER_ILLEGAL_PRIVILEGE_LEVEL = 3619 -ER_NO_SYSTEM_VIEW_ACCESS = 3620 -ER_COMPONENT_FILTER_FLABBERGASTED = 3621 -ER_PART_EXPR_TOO_LONG = 3622 -ER_UDF_DROP_DYNAMICALLY_REGISTERED = 3623 -ER_UNABLE_TO_STORE_COLUMN_STATISTICS = 3624 -ER_UNABLE_TO_UPDATE_COLUMN_STATISTICS = 3625 -ER_UNABLE_TO_DROP_COLUMN_STATISTICS = 3626 -ER_UNABLE_TO_BUILD_HISTOGRAM = 3627 -ER_MANDATORY_ROLE = 3628 -ER_MISSING_TABLESPACE_FILE = 3629 -ER_PERSIST_ONLY_ACCESS_DENIED_ERROR = 3630 -ER_CMD_NEED_SUPER = 3631 -ER_PATH_IN_DATADIR = 3632 -ER_DDL_IN_PROGRESS = 3633 -ER_TOO_MANY_CONCURRENT_CLONES = 3634 -ER_APPLIER_LOG_EVENT_VALIDATION_ERROR = 3635 -ER_CTE_MAX_RECURSION_DEPTH = 3636 -ER_NOT_HINT_UPDATABLE_VARIABLE = 3637 -ER_CREDENTIALS_CONTRADICT_TO_HISTORY = 3638 -ER_WARNING_PASSWORD_HISTORY_CLAUSES_VOID = 3639 -ER_CLIENT_DOES_NOT_SUPPORT = 3640 -ER_I_S_SKIPPED_TABLESPACE = 3641 -ER_TABLESPACE_ENGINE_MISMATCH = 3642 -ER_WRONG_SRID_FOR_COLUMN = 3643 -ER_CANNOT_ALTER_SRID_DUE_TO_INDEX = 3644 -ER_WARN_BINLOG_PARTIAL_UPDATES_DISABLED = 3645 -ER_WARN_BINLOG_V1_ROW_EVENTS_DISABLED = 3646 -ER_WARN_BINLOG_PARTIAL_UPDATES_SUGGESTS_PARTIAL_IMAGES = 3647 -ER_COULD_NOT_APPLY_JSON_DIFF = 3648 -ER_CORRUPTED_JSON_DIFF = 3649 -ER_RESOURCE_GROUP_EXISTS = 3650 -ER_RESOURCE_GROUP_NOT_EXISTS = 3651 -ER_INVALID_VCPU_ID = 3652 -ER_INVALID_VCPU_RANGE = 3653 -ER_INVALID_THREAD_PRIORITY = 3654 -ER_DISALLOWED_OPERATION = 3655 -ER_RESOURCE_GROUP_BUSY = 3656 -ER_RESOURCE_GROUP_DISABLED = 3657 -ER_FEATURE_UNSUPPORTED = 3658 -ER_ATTRIBUTE_IGNORED = 3659 -ER_INVALID_THREAD_ID = 3660 -ER_RESOURCE_GROUP_BIND_FAILED = 3661 -ER_INVALID_USE_OF_FORCE_OPTION = 3662 -ER_GROUP_REPLICATION_COMMAND_FAILURE = 3663 -ER_SDI_OPERATION_FAILED = 3664 -ER_MISSING_JSON_TABLE_VALUE = 3665 -ER_WRONG_JSON_TABLE_VALUE = 3666 -ER_TF_MUST_HAVE_ALIAS = 3667 -ER_TF_FORBIDDEN_JOIN_TYPE = 3668 -ER_JT_VALUE_OUT_OF_RANGE = 3669 -ER_JT_MAX_NESTED_PATH = 3670 -ER_PASSWORD_EXPIRATION_NOT_SUPPORTED_BY_AUTH_METHOD = 3671 -ER_INVALID_GEOJSON_CRS_NOT_TOP_LEVEL = 3672 -ER_BAD_NULL_ERROR_NOT_IGNORED = 3673 -WARN_USELESS_SPATIAL_INDEX = 3674 -ER_DISK_FULL_NOWAIT = 3675 -ER_PARSE_ERROR_IN_DIGEST_FN = 3676 -ER_UNDISCLOSED_PARSE_ERROR_IN_DIGEST_FN = 3677 -ER_SCHEMA_DIR_EXISTS = 3678 -ER_SCHEMA_DIR_MISSING = 3679 -ER_SCHEMA_DIR_CREATE_FAILED = 3680 -ER_SCHEMA_DIR_UNKNOWN = 3681 -ER_ONLY_IMPLEMENTED_FOR_SRID_0_AND_4326 = 3682 -ER_BINLOG_EXPIRE_LOG_DAYS_AND_SECS_USED_TOGETHER = 3683 -ER_REGEXP_BUFFER_OVERFLOW = 3684 -ER_REGEXP_ILLEGAL_ARGUMENT = 3685 -ER_REGEXP_INDEX_OUTOFBOUNDS_ERROR = 3686 -ER_REGEXP_INTERNAL_ERROR = 3687 -ER_REGEXP_RULE_SYNTAX = 3688 -ER_REGEXP_BAD_ESCAPE_SEQUENCE = 3689 -ER_REGEXP_UNIMPLEMENTED = 3690 -ER_REGEXP_MISMATCHED_PAREN = 3691 -ER_REGEXP_BAD_INTERVAL = 3692 -ER_REGEXP_MAX_LT_MIN = 3693 -ER_REGEXP_INVALID_BACK_REF = 3694 -ER_REGEXP_LOOK_BEHIND_LIMIT = 3695 -ER_REGEXP_MISSING_CLOSE_BRACKET = 3696 -ER_REGEXP_INVALID_RANGE = 3697 -ER_REGEXP_STACK_OVERFLOW = 3698 -ER_REGEXP_TIME_OUT = 3699 -ER_REGEXP_PATTERN_TOO_BIG = 3700 -ER_CANT_SET_ERROR_LOG_SERVICE = 3701 -ER_EMPTY_PIPELINE_FOR_ERROR_LOG_SERVICE = 3702 -ER_COMPONENT_FILTER_DIAGNOSTICS = 3703 -ER_NOT_IMPLEMENTED_FOR_CARTESIAN_SRS = 3704 -ER_NOT_IMPLEMENTED_FOR_PROJECTED_SRS = 3705 -ER_NONPOSITIVE_RADIUS = 3706 -ER_RESTART_SERVER_FAILED = 3707 -ER_SRS_MISSING_MANDATORY_ATTRIBUTE = 3708 -ER_SRS_MULTIPLE_ATTRIBUTE_DEFINITIONS = 3709 -ER_SRS_NAME_CANT_BE_EMPTY_OR_WHITESPACE = 3710 -ER_SRS_ORGANIZATION_CANT_BE_EMPTY_OR_WHITESPACE = 3711 -ER_SRS_ID_ALREADY_EXISTS = 3712 -ER_WARN_SRS_ID_ALREADY_EXISTS = 3713 -ER_CANT_MODIFY_SRID_0 = 3714 -ER_WARN_RESERVED_SRID_RANGE = 3715 -ER_CANT_MODIFY_SRS_USED_BY_COLUMN = 3716 -ER_SRS_INVALID_CHARACTER_IN_ATTRIBUTE = 3717 -ER_SRS_ATTRIBUTE_STRING_TOO_LONG = 3718 -ER_DEPRECATED_UTF8_ALIAS = 3719 -ER_DEPRECATED_NATIONAL = 3720 -ER_INVALID_DEFAULT_UTF8MB4_COLLATION = 3721 -ER_UNABLE_TO_COLLECT_INSTANCE_LOG_STATUS = 3722 -ER_RESERVED_TABLESPACE_NAME = 3723 -ER_UNABLE_TO_SET_OPTION = 3724 -ER_SLAVE_POSSIBLY_DIVERGED_AFTER_DDL = 3725 -ER_PARSER_TRACE = 10000 -ER_BOOTSTRAP_CANT_THREAD = 10001 -ER_TRIGGER_INVALID_VALUE = 10002 -ER_OPT_WRONG_TREE = 10003 -ER_DD_FAILSAFE = 10004 -ER_DD_NO_WRITES_NO_REPOPULATION = 10005 -ER_DD_VERSION_FOUND = 10006 -ER_DD_VERSION_INSTALLED = 10007 -ER_DD_VERSION_UNSUPPORTED = 10008 -ER_LOG_SYSLOG_FACILITY_FAIL = 10009 -ER_LOG_SYSLOG_CANNOT_OPEN = 10010 -ER_LOG_SLOW_CANNOT_OPEN = 10011 -ER_LOG_GENERAL_CANNOT_OPEN = 10012 -ER_LOG_CANNOT_WRITE = 10013 -ER_RPL_ZOMBIE_ENCOUNTERED = 10014 -ER_RPL_GTID_TABLE_CANNOT_OPEN = 10015 -ER_SYSTEM_SCHEMA_NOT_FOUND = 10016 -ER_DD_INIT_UPGRADE_FAILED = 10017 -ER_VIEW_UNKNOWN_CHARSET_OR_COLLATION = 10018 -ER_DD_VIEW_CANT_ALLOC_CHARSET = 10019 -ER_DD_INIT_FAILED = 10020 -ER_DD_UPDATING_PLUGIN_MD_FAILED = 10021 -ER_DD_POPULATING_TABLES_FAILED = 10022 -ER_DD_VIEW_CANT_CREATE = 10023 -ER_DD_METADATA_NOT_FOUND = 10024 -ER_DD_CACHE_NOT_EMPTY_AT_SHUTDOWN = 10025 -ER_DD_OBJECT_REMAINS = 10026 -ER_DD_OBJECT_REMAINS_IN_RELEASER = 10027 -ER_DD_OBJECT_RELEASER_REMAINS = 10028 -ER_DD_CANT_GET_OBJECT_KEY = 10029 -ER_DD_CANT_CREATE_OBJECT_KEY = 10030 -ER_CANT_CREATE_HANDLE_MGR_THREAD = 10031 -ER_RPL_REPO_HAS_GAPS = 10032 -ER_INVALID_VALUE_FOR_ENFORCE_GTID_CONSISTENCY = 10033 -ER_CHANGED_ENFORCE_GTID_CONSISTENCY = 10034 -ER_CHANGED_GTID_MODE = 10035 -ER_DISABLED_STORAGE_ENGINE_AS_DEFAULT = 10036 -ER_DEBUG_SYNC_HIT = 10037 -ER_DEBUG_SYNC_EXECUTED = 10038 -ER_DEBUG_SYNC_THREAD_MAX = 10039 -ER_DEBUG_SYNC_OOM = 10040 -ER_CANT_INIT_TC_LOG = 10041 -ER_EVENT_CANT_INIT_QUEUE = 10042 -ER_EVENT_PURGING_QUEUE = 10043 -ER_EVENT_LAST_EXECUTION = 10044 -ER_EVENT_MESSAGE_STACK = 10045 -ER_EVENT_EXECUTION_FAILED = 10046 -ER_CANT_INIT_SCHEDULER_THREAD = 10047 -ER_SCHEDULER_STOPPED = 10048 -ER_CANT_CREATE_SCHEDULER_THREAD = 10049 -ER_SCHEDULER_WAITING = 10050 -ER_SCHEDULER_STARTED = 10051 -ER_SCHEDULER_STOPPING_FAILED_TO_GET_EVENT = 10052 -ER_SCHEDULER_STOPPING_FAILED_TO_CREATE_WORKER = 10053 -ER_SCHEDULER_KILLING = 10054 -ER_UNABLE_TO_RESOLVE_IP = 10055 -ER_UNABLE_TO_RESOLVE_HOSTNAME = 10056 -ER_HOSTNAME_RESEMBLES_IPV4 = 10057 -ER_HOSTNAME_DOESNT_RESOLVE_TO = 10058 -ER_ADDRESSES_FOR_HOSTNAME_HEADER = 10059 -ER_ADDRESSES_FOR_HOSTNAME_LIST_ITEM = 10060 -ER_TRG_WITHOUT_DEFINER = 10061 -ER_TRG_NO_CLIENT_CHARSET = 10062 -ER_PARSING_VIEW = 10063 -ER_COMPONENTS_INFRASTRUCTURE_BOOTSTRAP = 10064 -ER_COMPONENTS_INFRASTRUCTURE_SHUTDOWN = 10065 -ER_COMPONENTS_PERSIST_LOADER_BOOTSTRAP = 10066 -ER_DEPART_WITH_GRACE = 10067 -ER_CA_SELF_SIGNED = 10068 -ER_SSL_LIBRARY_ERROR = 10069 -ER_NO_THD_NO_UUID = 10070 -ER_UUID_SALT = 10071 -ER_UUID_IS = 10072 -ER_UUID_INVALID = 10073 -ER_UUID_SCRUB = 10074 -ER_CREATING_NEW_UUID = 10075 -ER_CANT_CREATE_UUID = 10076 -ER_UNKNOWN_UNSUPPORTED_STORAGE_ENGINE = 10077 -ER_SECURE_AUTH_VALUE_UNSUPPORTED = 10078 -ER_INVALID_INSTRUMENT = 10079 -ER_INNODB_MANDATORY = 10080 -OBSOLETE_ER_INNODB_CANNOT_BE_IGNORED = 10081 -ER_OLD_PASSWORDS_NO_MIDDLE_GROUND = 10082 -ER_VERBOSE_REQUIRES_HELP = 10083 -ER_POINTLESS_WITHOUT_SLOWLOG = 10084 -ER_WASTEFUL_NET_BUFFER_SIZE = 10085 -ER_DEPRECATED_TIMESTAMP_IMPLICIT_DEFAULTS = 10086 -ER_FT_BOOL_SYNTAX_INVALID = 10087 -ER_CREDENTIALLESS_AUTO_USER_BAD = 10088 -ER_CONNECTION_HANDLING_OOM = 10089 -ER_THREAD_HANDLING_OOM = 10090 -ER_CANT_CREATE_TEST_FILE = 10091 -ER_CANT_CREATE_PID_FILE = 10092 -ER_CANT_REMOVE_PID_FILE = 10093 -ER_CANT_CREATE_SHUTDOWN_THREAD = 10094 -ER_SEC_FILE_PRIV_CANT_ACCESS_DIR = 10095 -ER_SEC_FILE_PRIV_IGNORED = 10096 -ER_SEC_FILE_PRIV_EMPTY = 10097 -ER_SEC_FILE_PRIV_NULL = 10098 -ER_SEC_FILE_PRIV_DIRECTORY_INSECURE = 10099 -ER_SEC_FILE_PRIV_CANT_STAT = 10100 -ER_SEC_FILE_PRIV_DIRECTORY_PERMISSIONS = 10101 -ER_SEC_FILE_PRIV_ARGUMENT_TOO_LONG = 10102 -ER_CANT_CREATE_NAMED_PIPES_THREAD = 10103 -ER_CANT_CREATE_TCPIP_THREAD = 10104 -ER_CANT_CREATE_SHM_THREAD = 10105 -ER_CANT_CREATE_INTERRUPT_THREAD = 10106 -ER_WRITABLE_CONFIG_REMOVED = 10107 -ER_CORE_VALUES = 10108 -ER_WRONG_DATETIME_SPEC = 10109 -ER_RPL_BINLOG_FILTERS_OOM = 10110 -ER_KEYCACHE_OOM = 10111 -ER_CONFIRMING_THE_FUTURE = 10112 -ER_BACK_IN_TIME = 10113 -ER_FUTURE_DATE = 10114 -ER_UNSUPPORTED_DATE = 10115 -ER_STARTING_AS = 10116 -ER_SHUTTING_DOWN_SLAVE_THREADS = 10117 -ER_DISCONNECTING_REMAINING_CLIENTS = 10118 -ER_ABORTING = 10119 -ER_BINLOG_END = 10120 -ER_CALL_ME_LOCALHOST = 10121 -ER_USER_REQUIRES_ROOT = 10122 -ER_REALLY_RUN_AS_ROOT = 10123 -ER_USER_WHAT_USER = 10124 -ER_TRANSPORTS_WHAT_TRANSPORTS = 10125 -ER_FAIL_SETGID = 10126 -ER_FAIL_SETUID = 10127 -ER_FAIL_SETREGID = 10128 -ER_FAIL_SETREUID = 10129 -ER_FAIL_CHROOT = 10130 -ER_WIN_LISTEN_BUT_HOW = 10131 -ER_NOT_RIGHT_NOW = 10132 -ER_FIXING_CLIENT_CHARSET = 10133 -ER_OOM = 10134 -ER_FAILED_TO_LOCK_MEM = 10135 -ER_MYINIT_FAILED = 10136 -ER_BEG_INITFILE = 10137 -ER_END_INITFILE = 10138 -ER_CHANGED_MAX_OPEN_FILES = 10139 -ER_CANT_INCREASE_MAX_OPEN_FILES = 10140 -ER_CHANGED_MAX_CONNECTIONS = 10141 -ER_CHANGED_TABLE_OPEN_CACHE = 10142 -ER_THE_USER_ABIDES = 10143 -ER_RPL_CANT_ADD_DO_TABLE = 10144 -ER_RPL_CANT_ADD_IGNORE_TABLE = 10145 -ER_TRACK_VARIABLES_BOGUS = 10146 -ER_EXCESS_ARGUMENTS = 10147 -ER_VERBOSE_HINT = 10148 -ER_CANT_READ_ERRMSGS = 10149 -ER_CANT_INIT_DBS = 10150 -ER_LOG_OUTPUT_CONTRADICTORY = 10151 -ER_NO_CSV_NO_LOG_TABLES = 10152 -ER_RPL_REWRITEDB_MISSING_ARROW = 10153 -ER_RPL_REWRITEDB_EMPTY_FROM = 10154 -ER_RPL_REWRITEDB_EMPTY_TO = 10155 -ER_LOG_FILES_GIVEN_LOG_OUTPUT_IS_TABLE = 10156 -ER_LOG_FILE_INVALID = 10157 -ER_LOWER_CASE_TABLE_NAMES_CS_DD_ON_CI_FS_UNSUPPORTED = 10158 -ER_LOWER_CASE_TABLE_NAMES_USING_2 = 10159 -ER_LOWER_CASE_TABLE_NAMES_USING_0 = 10160 -ER_NEED_LOG_BIN = 10161 -ER_NEED_FILE_INSTEAD_OF_DIR = 10162 -ER_LOG_BIN_BETTER_WITH_NAME = 10163 -ER_BINLOG_NEEDS_SERVERID = 10164 -ER_RPL_CANT_MAKE_PATHS = 10165 -ER_CANT_INITIALIZE_GTID = 10166 -ER_CANT_INITIALIZE_EARLY_PLUGINS = 10167 -ER_CANT_INITIALIZE_BUILTIN_PLUGINS = 10168 -ER_CANT_INITIALIZE_DYNAMIC_PLUGINS = 10169 -ER_PERFSCHEMA_INIT_FAILED = 10170 -ER_STACKSIZE_UNEXPECTED = 10171 -ER_CANT_SET_DATADIR = 10172 -ER_CANT_STAT_DATADIR = 10173 -ER_CANT_CHOWN_DATADIR = 10174 -ER_CANT_SET_UP_PERSISTED_VALUES = 10175 -ER_CANT_SAVE_GTIDS = 10176 -ER_AUTH_CANT_SET_DEFAULT_PLUGIN = 10177 -ER_CANT_JOIN_SHUTDOWN_THREAD = 10178 -ER_CANT_HASH_DO_AND_IGNORE_RULES = 10179 -ER_CANT_OPEN_CA = 10180 -ER_CANT_ACCESS_CAPATH = 10181 -ER_SSL_TRYING_DATADIR_DEFAULTS = 10182 -ER_AUTO_OPTIONS_FAILED = 10183 -ER_CANT_INIT_TIMER = 10184 -ER_SERVERID_TOO_LARGE = 10185 -ER_DEFAULT_SE_UNAVAILABLE = 10186 -ER_CANT_OPEN_ERROR_LOG = 10187 -ER_INVALID_ERROR_LOG_NAME = 10188 -ER_RPL_INFINITY_DENIED = 10189 -ER_RPL_INFINITY_IGNORED = 10190 -ER_NDB_TABLES_NOT_READY = 10191 -ER_TABLE_CHECK_INTACT = 10192 -ER_DD_TABLESPACE_NOT_FOUND = 10193 -ER_DD_TRG_CONNECTION_COLLATION_MISSING = 10194 -ER_DD_TRG_DB_COLLATION_MISSING = 10195 -ER_DD_TRG_DEFINER_OOM = 10196 -ER_DD_TRG_FILE_UNREADABLE = 10197 -ER_TRG_CANT_PARSE = 10198 -ER_DD_TRG_CANT_ADD = 10199 -ER_DD_CANT_RESOLVE_VIEW = 10200 -ER_DD_VIEW_WITHOUT_DEFINER = 10201 -ER_PLUGIN_INIT_FAILED = 10202 -ER_RPL_TRX_DELEGATES_INIT_FAILED = 10203 -ER_RPL_BINLOG_STORAGE_DELEGATES_INIT_FAILED = 10204 -ER_RPL_BINLOG_TRANSMIT_DELEGATES_INIT_FAILED = 10205 -ER_RPL_BINLOG_RELAY_DELEGATES_INIT_FAILED = 10206 -ER_RPL_PLUGIN_FUNCTION_FAILED = 10207 -ER_SQL_HA_READ_FAILED = 10208 -ER_SR_BOGUS_VALUE = 10209 -ER_SR_INVALID_CONTEXT = 10210 -ER_READING_TABLE_FAILED = 10211 -ER_DES_FILE_WRONG_KEY = 10212 -ER_CANT_SET_PERSISTED = 10213 -ER_JSON_PARSE_ERROR = 10214 -ER_CONFIG_OPTION_WITHOUT_GROUP = 10215 -ER_VALGRIND_DO_QUICK_LEAK_CHECK = 10216 -ER_VALGRIND_COUNT_LEAKS = 10217 -ER_LOAD_DATA_INFILE_FAILED_IN_UNEXPECTED_WAY = 10218 -ER_UNKNOWN_ERROR_NUMBER = 10219 -ER_UDF_CANT_ALLOC_FOR_STRUCTURES = 10220 -ER_UDF_CANT_ALLOC_FOR_FUNCTION = 10221 -ER_UDF_INVALID_ROW_IN_FUNCTION_TABLE = 10222 -ER_UDF_CANT_OPEN_FUNCTION_TABLE = 10223 -ER_XA_RECOVER_FOUND_TRX_IN_SE = 10224 -ER_XA_RECOVER_FOUND_XA_TRX = 10225 -ER_XA_IGNORING_XID = 10226 -ER_XA_COMMITTING_XID = 10227 -ER_XA_ROLLING_BACK_XID = 10228 -ER_XA_STARTING_RECOVERY = 10229 -ER_XA_NO_MULTI_2PC_HEURISTIC_RECOVER = 10230 -ER_XA_RECOVER_EXPLANATION = 10231 -ER_XA_RECOVERY_DONE = 10232 -ER_TRX_GTID_COLLECT_REJECT = 10233 -ER_SQL_AUTHOR_DEFAULT_ROLES_FAIL = 10234 -ER_SQL_USER_TABLE_CREATE_WARNING = 10235 -ER_SQL_USER_TABLE_ALTER_WARNING = 10236 -ER_ROW_IN_WRONG_PARTITION_PLEASE_REPAIR = 10237 -ER_MYISAM_CRASHED_ERROR_IN_THREAD = 10238 -ER_MYISAM_CRASHED_ERROR_IN = 10239 -ER_TOO_MANY_STORAGE_ENGINES = 10240 -ER_SE_TYPECODE_CONFLICT = 10241 -ER_TRX_WRITE_SET_OOM = 10242 -ER_HANDLERTON_OOM = 10243 -ER_CONN_SHM_LISTENER = 10244 -ER_CONN_SHM_CANT_CREATE_SERVICE = 10245 -ER_CONN_SHM_CANT_CREATE_CONNECTION = 10246 -ER_CONN_PIP_CANT_CREATE_EVENT = 10247 -ER_CONN_PIP_CANT_CREATE_PIPE = 10248 -ER_CONN_PER_THREAD_NO_THREAD = 10249 -ER_CONN_TCP_NO_SOCKET = 10250 -ER_CONN_TCP_CREATED = 10251 -ER_CONN_TCP_ADDRESS = 10252 -ER_CONN_TCP_IPV6_AVAILABLE = 10253 -ER_CONN_TCP_IPV6_UNAVAILABLE = 10254 -ER_CONN_TCP_ERROR_WITH_STRERROR = 10255 -ER_CONN_TCP_CANT_RESOLVE_HOSTNAME = 10256 -ER_CONN_TCP_IS_THERE_ANOTHER_USING_PORT = 10257 -ER_CONN_UNIX_IS_THERE_ANOTHER_USING_SOCKET = 10258 -ER_CONN_UNIX_PID_CLAIMED_SOCKET_FILE = 10259 -ER_CONN_TCP_CANT_RESET_V6ONLY = 10260 -ER_CONN_TCP_BIND_RETRY = 10261 -ER_CONN_TPC_BIND_FAIL = 10262 -ER_CONN_TCP_IP_NOT_LOGGED = 10263 -ER_CONN_TCP_RESOLVE_INFO = 10264 -ER_CONN_TCP_START_FAIL = 10265 -ER_CONN_TCP_LISTEN_FAIL = 10266 -ER_CONN_UNIX_PATH_TOO_LONG = 10267 -ER_CONN_UNIX_LOCK_FILE_FAIL = 10268 -ER_CONN_UNIX_NO_FD = 10269 -ER_CONN_UNIX_NO_BIND_NO_START = 10270 -ER_CONN_UNIX_LISTEN_FAILED = 10271 -ER_CONN_UNIX_LOCK_FILE_GIVING_UP = 10272 -ER_CONN_UNIX_LOCK_FILE_CANT_CREATE = 10273 -ER_CONN_UNIX_LOCK_FILE_CANT_OPEN = 10274 -ER_CONN_UNIX_LOCK_FILE_CANT_READ = 10275 -ER_CONN_UNIX_LOCK_FILE_EMPTY = 10276 -ER_CONN_UNIX_LOCK_FILE_PIDLESS = 10277 -ER_CONN_UNIX_LOCK_FILE_CANT_WRITE = 10278 -ER_CONN_UNIX_LOCK_FILE_CANT_DELETE = 10279 -ER_CONN_UNIX_LOCK_FILE_CANT_SYNC = 10280 -ER_CONN_UNIX_LOCK_FILE_CANT_CLOSE = 10281 -ER_CONN_SOCKET_SELECT_FAILED = 10282 -ER_CONN_SOCKET_ACCEPT_FAILED = 10283 -ER_AUTH_RSA_CANT_FIND = 10284 -ER_AUTH_RSA_CANT_PARSE = 10285 -ER_AUTH_RSA_CANT_READ = 10286 -ER_AUTH_RSA_FILES_NOT_FOUND = 10287 -ER_CONN_ATTR_TRUNCATED = 10288 -ER_X509_CIPHERS_MISMATCH = 10289 -ER_X509_ISSUER_MISMATCH = 10290 -ER_X509_SUBJECT_MISMATCH = 10291 -ER_AUTH_CANT_ACTIVATE_ROLE = 10292 -ER_X509_NEEDS_RSA_PRIVKEY = 10293 -ER_X509_CANT_WRITE_KEY = 10294 -ER_X509_CANT_CHMOD_KEY = 10295 -ER_X509_CANT_READ_CA_KEY = 10296 -ER_X509_CANT_READ_CA_CERT = 10297 -ER_X509_CANT_CREATE_CERT = 10298 -ER_X509_CANT_WRITE_CERT = 10299 -ER_AUTH_CANT_CREATE_RSA_PAIR = 10300 -ER_AUTH_CANT_WRITE_PRIVKEY = 10301 -ER_AUTH_CANT_WRITE_PUBKEY = 10302 -ER_AUTH_SSL_CONF_PREVENTS_CERT_GENERATION = 10303 -ER_AUTH_USING_EXISTING_CERTS = 10304 -ER_AUTH_CERTS_SAVED_TO_DATADIR = 10305 -ER_AUTH_CERT_GENERATION_DISABLED = 10306 -ER_AUTH_RSA_CONF_PREVENTS_KEY_GENERATION = 10307 -ER_AUTH_KEY_GENERATION_SKIPPED_PAIR_PRESENT = 10308 -ER_AUTH_KEYS_SAVED_TO_DATADIR = 10309 -ER_AUTH_KEY_GENERATION_DISABLED = 10310 -ER_AUTHCACHE_PROXIES_PRIV_SKIPPED_NEEDS_RESOLVE = 10311 -ER_AUTHCACHE_PLUGIN_MISSING = 10312 -ER_AUTHCACHE_PLUGIN_CONFIG = 10313 -OBSOLETE_ER_AUTHCACHE_ROLE_TABLES_DODGY = 10314 -ER_AUTHCACHE_USER_SKIPPED_NEEDS_RESOLVE = 10315 -ER_AUTHCACHE_USER_TABLE_DODGY = 10316 -ER_AUTHCACHE_USER_IGNORED_DEPRECATED_PASSWORD = 10317 -ER_AUTHCACHE_USER_IGNORED_NEEDS_PLUGIN = 10318 -ER_AUTHCACHE_USER_IGNORED_INVALID_PASSWORD = 10319 -ER_AUTHCACHE_EXPIRED_PASSWORD_UNSUPPORTED = 10320 -ER_NO_SUPER_WITHOUT_USER_PLUGIN = 10321 -ER_AUTHCACHE_DB_IGNORED_EMPTY_NAME = 10322 -ER_AUTHCACHE_DB_SKIPPED_NEEDS_RESOLVE = 10323 -ER_AUTHCACHE_DB_ENTRY_LOWERCASED_REVOKE_WILL_FAIL = 10324 -ER_AUTHCACHE_TABLE_PROXIES_PRIV_MISSING = 10325 -ER_AUTHCACHE_CANT_OPEN_AND_LOCK_PRIVILEGE_TABLES = 10326 -ER_AUTHCACHE_CANT_INIT_GRANT_SUBSYSTEM = 10327 -ER_AUTHCACHE_PROCS_PRIV_SKIPPED_NEEDS_RESOLVE = 10328 -ER_AUTHCACHE_PROCS_PRIV_ENTRY_IGNORED_BAD_ROUTINE_TYPE = 10329 -ER_AUTHCACHE_TABLES_PRIV_SKIPPED_NEEDS_RESOLVE = 10330 -ER_USER_NOT_IN_EXTRA_USERS_BINLOG_POSSIBLY_INCOMPLETE = 10331 -ER_DD_SCHEMA_NOT_FOUND = 10332 -ER_DD_TABLE_NOT_FOUND = 10333 -ER_DD_SE_INIT_FAILED = 10334 -ER_DD_ABORTING_PARTIAL_UPGRADE = 10335 -ER_DD_FRM_EXISTS_FOR_TABLE = 10336 -ER_DD_CREATED_FOR_UPGRADE = 10337 -ER_ERRMSG_CANT_FIND_FILE = 10338 -ER_ERRMSG_LOADING_55_STYLE = 10339 -ER_ERRMSG_MISSING_IN_FILE = 10340 -ER_ERRMSG_OOM = 10341 -ER_ERRMSG_CANT_READ = 10342 -ER_TABLE_INCOMPATIBLE_DECIMAL_FIELD = 10343 -ER_TABLE_INCOMPATIBLE_YEAR_FIELD = 10344 -ER_INVALID_CHARSET_AND_DEFAULT_IS_MB = 10345 -ER_TABLE_WRONG_KEY_DEFINITION = 10346 -ER_CANT_OPEN_FRM_FILE = 10347 -ER_CANT_READ_FRM_FILE = 10348 -ER_TABLE_CREATED_WITH_DIFFERENT_VERSION = 10349 -ER_VIEW_UNPARSABLE = 10350 -ER_FILE_TYPE_UNKNOWN = 10351 -ER_INVALID_INFO_IN_FRM = 10352 -ER_CANT_OPEN_AND_LOCK_PRIVILEGE_TABLES = 10353 -ER_AUDIT_PLUGIN_DOES_NOT_SUPPORT_AUDIT_AUTH_EVENTS = 10354 -ER_AUDIT_PLUGIN_HAS_INVALID_DATA = 10355 -ER_TZ_OOM_INITIALIZING_TIME_ZONES = 10356 -ER_TZ_CANT_OPEN_AND_LOCK_TIME_ZONE_TABLE = 10357 -ER_TZ_OOM_LOADING_LEAP_SECOND_TABLE = 10358 -ER_TZ_TOO_MANY_LEAPS_IN_LEAP_SECOND_TABLE = 10359 -ER_TZ_ERROR_LOADING_LEAP_SECOND_TABLE = 10360 -ER_TZ_UNKNOWN_OR_ILLEGAL_DEFAULT_TIME_ZONE = 10361 -ER_TZ_CANT_FIND_DESCRIPTION_FOR_TIME_ZONE = 10362 -ER_TZ_CANT_FIND_DESCRIPTION_FOR_TIME_ZONE_ID = 10363 -ER_TZ_TRANSITION_TYPE_TABLE_TYPE_TOO_LARGE = 10364 -ER_TZ_TRANSITION_TYPE_TABLE_ABBREVIATIONS_EXCEED_SPACE = 10365 -ER_TZ_TRANSITION_TYPE_TABLE_LOAD_ERROR = 10366 -ER_TZ_TRANSITION_TABLE_TOO_MANY_TRANSITIONS = 10367 -ER_TZ_TRANSITION_TABLE_BAD_TRANSITION_TYPE = 10368 -ER_TZ_TRANSITION_TABLE_LOAD_ERROR = 10369 -ER_TZ_NO_TRANSITION_TYPES_IN_TIME_ZONE = 10370 -ER_TZ_OOM_LOADING_TIME_ZONE_DESCRIPTION = 10371 -ER_TZ_CANT_BUILD_MKTIME_MAP = 10372 -ER_TZ_OOM_WHILE_LOADING_TIME_ZONE = 10373 -ER_TZ_OOM_WHILE_SETTING_TIME_ZONE = 10374 -ER_SLAVE_SQL_THREAD_STOPPED_UNTIL_CONDITION_BAD = 10375 -ER_SLAVE_SQL_THREAD_STOPPED_UNTIL_POSITION_REACHED = 10376 -ER_SLAVE_SQL_THREAD_STOPPED_BEFORE_GTIDS_ALREADY_APPLIED = 10377 -ER_SLAVE_SQL_THREAD_STOPPED_BEFORE_GTIDS_REACHED = 10378 -ER_SLAVE_SQL_THREAD_STOPPED_AFTER_GTIDS_REACHED = 10379 -ER_SLAVE_SQL_THREAD_STOPPED_GAP_TRX_PROCESSED = 10380 -ER_GROUP_REPLICATION_PLUGIN_NOT_INSTALLED = 10381 -ER_GTID_ALREADY_ADDED_BY_USER = 10382 -ER_FAILED_TO_DELETE_FROM_GTID_EXECUTED_TABLE = 10383 -ER_FAILED_TO_COMPRESS_GTID_EXECUTED_TABLE = 10384 -ER_FAILED_TO_COMPRESS_GTID_EXECUTED_TABLE_OOM = 10385 -ER_FAILED_TO_INIT_THREAD_ATTR_FOR_GTID_TABLE_COMPRESSION = 10386 -ER_FAILED_TO_CREATE_GTID_TABLE_COMPRESSION_THREAD = 10387 -ER_FAILED_TO_JOIN_GTID_TABLE_COMPRESSION_THREAD = 10388 -ER_NPIPE_FAILED_TO_INIT_SECURITY_DESCRIPTOR = 10389 -ER_NPIPE_FAILED_TO_SET_SECURITY_DESCRIPTOR = 10390 -ER_NPIPE_PIPE_ALREADY_IN_USE = 10391 -ER_NDB_SLAVE_SAW_EPOCH_LOWER_THAN_PREVIOUS_ON_START = 10392 -ER_NDB_SLAVE_SAW_EPOCH_LOWER_THAN_PREVIOUS = 10393 -ER_NDB_SLAVE_SAW_ALREADY_COMMITTED_EPOCH = 10394 -ER_NDB_SLAVE_PREVIOUS_EPOCH_NOT_COMMITTED = 10395 -ER_NDB_SLAVE_MISSING_DATA_FOR_TIMESTAMP_COLUMN = 10396 -ER_NDB_SLAVE_LOGGING_EXCEPTIONS_TO = 10397 -ER_NDB_SLAVE_LOW_EPOCH_RESOLUTION = 10398 -ER_NDB_INFO_FOUND_UNEXPECTED_FIELD_TYPE = 10399 -ER_NDB_INFO_FAILED_TO_CREATE_NDBINFO = 10400 -ER_NDB_INFO_FAILED_TO_INIT_NDBINFO = 10401 -ER_NDB_CLUSTER_WRONG_NUMBER_OF_FUNCTION_ARGUMENTS = 10402 -ER_NDB_CLUSTER_SCHEMA_INFO = 10403 -ER_NDB_CLUSTER_GENERIC_MESSAGE = 10404 -ER_RPL_CANT_OPEN_INFO_TABLE = 10405 -ER_RPL_CANT_SCAN_INFO_TABLE = 10406 -ER_RPL_CORRUPTED_INFO_TABLE = 10407 -ER_RPL_CORRUPTED_KEYS_IN_INFO_TABLE = 10408 -ER_RPL_WORKER_ID_IS = 10409 -ER_RPL_INCONSISTENT_TIMESTAMPS_IN_TRX = 10410 -ER_RPL_INCONSISTENT_SEQUENCE_NO_IN_TRX = 10411 -ER_RPL_CHANNELS_REQUIRE_TABLES_AS_INFO_REPOSITORIES = 10412 -ER_RPL_CHANNELS_REQUIRE_NON_ZERO_SERVER_ID = 10413 -ER_RPL_REPO_SHOULD_BE_TABLE = 10414 -ER_RPL_ERROR_CREATING_MASTER_INFO = 10415 -ER_RPL_ERROR_CHANGING_MASTER_INFO_REPO_TYPE = 10416 -ER_RPL_CHANGING_RELAY_LOG_INFO_REPO_TYPE_FAILED_DUE_TO_GAPS = 10417 -ER_RPL_ERROR_CREATING_RELAY_LOG_INFO = 10418 -ER_RPL_ERROR_CHANGING_RELAY_LOG_INFO_REPO_TYPE = 10419 -ER_RPL_FAILED_TO_DELETE_FROM_SLAVE_WORKERS_INFO_REPOSITORY = 10420 -ER_RPL_FAILED_TO_RESET_STATE_IN_SLAVE_INFO_REPOSITORY = 10421 -ER_RPL_ERROR_CHECKING_REPOSITORY = 10422 -ER_RPL_SLAVE_GENERIC_MESSAGE = 10423 -ER_RPL_SLAVE_COULD_NOT_CREATE_CHANNEL_LIST = 10424 -ER_RPL_MULTISOURCE_REQUIRES_TABLE_TYPE_REPOSITORIES = 10425 -ER_RPL_SLAVE_FAILED_TO_INIT_A_MASTER_INFO_STRUCTURE = 10426 -ER_RPL_SLAVE_FAILED_TO_INIT_MASTER_INFO_STRUCTURE = 10427 -ER_RPL_SLAVE_FAILED_TO_CREATE_CHANNEL_FROM_MASTER_INFO = 10428 -ER_RPL_FAILED_TO_CREATE_NEW_INFO_FILE = 10429 -ER_RPL_FAILED_TO_CREATE_CACHE_FOR_INFO_FILE = 10430 -ER_RPL_FAILED_TO_OPEN_INFO_FILE = 10431 -ER_RPL_GTID_MEMORY_FINALLY_AVAILABLE = 10432 -ER_SERVER_COST_UNKNOWN_COST_CONSTANT = 10433 -ER_SERVER_COST_INVALID_COST_CONSTANT = 10434 -ER_ENGINE_COST_UNKNOWN_COST_CONSTANT = 10435 -ER_ENGINE_COST_UNKNOWN_STORAGE_ENGINE = 10436 -ER_ENGINE_COST_INVALID_DEVICE_TYPE_FOR_SE = 10437 -ER_ENGINE_COST_INVALID_CONST_CONSTANT_FOR_SE_AND_DEVICE = 10438 -ER_SERVER_COST_FAILED_TO_READ = 10439 -ER_ENGINE_COST_FAILED_TO_READ = 10440 -ER_FAILED_TO_OPEN_COST_CONSTANT_TABLES = 10441 -ER_RPL_UNSUPPORTED_UNIGNORABLE_EVENT_IN_STREAM = 10442 -ER_RPL_GTID_LOG_EVENT_IN_STREAM = 10443 -ER_RPL_UNEXPECTED_BEGIN_IN_STREAM = 10444 -ER_RPL_UNEXPECTED_COMMIT_ROLLBACK_OR_XID_LOG_EVENT_IN_STREAM = 10445 -ER_RPL_UNEXPECTED_XA_ROLLBACK_IN_STREAM = 10446 -ER_EVENT_EXECUTION_FAILED_CANT_AUTHENTICATE_USER = 10447 -ER_EVENT_EXECUTION_FAILED_USER_LOST_EVEN_PRIVILEGE = 10448 -ER_EVENT_ERROR_DURING_COMPILATION = 10449 -ER_EVENT_DROPPING = 10450 -ER_NDB_SCHEMA_GENERIC_MESSAGE = 10451 -ER_RPL_INCOMPATIBLE_DECIMAL_IN_RBR = 10452 -ER_INIT_ROOT_WITHOUT_PASSWORD = 10453 -ER_INIT_GENERATING_TEMP_PASSWORD_FOR_ROOT = 10454 -ER_INIT_CANT_OPEN_BOOTSTRAP_FILE = 10455 -ER_INIT_BOOTSTRAP_COMPLETE = 10456 -ER_INIT_DATADIR_NOT_EMPTY_WONT_INITIALIZE = 10457 -ER_INIT_DATADIR_EXISTS_WONT_INITIALIZE = 10458 -ER_INIT_DATADIR_EXISTS_AND_PATH_TOO_LONG_WONT_INITIALIZE = 10459 -ER_INIT_DATADIR_EXISTS_AND_NOT_WRITABLE_WONT_INITIALIZE = 10460 -ER_INIT_CREATING_DD = 10461 -ER_RPL_BINLOG_STARTING_DUMP = 10462 -ER_RPL_BINLOG_MASTER_SENDS_HEARTBEAT = 10463 -ER_RPL_BINLOG_SKIPPING_REMAINING_HEARTBEAT_INFO = 10464 -ER_RPL_BINLOG_MASTER_USES_CHECKSUM_AND_SLAVE_CANT = 10465 -ER_NDB_QUERY_FAILED = 10466 -ER_KILLING_THREAD = 10467 -ER_DETACHING_SESSION_LEFT_BY_PLUGIN = 10468 -ER_CANT_DETACH_SESSION_LEFT_BY_PLUGIN = 10469 -ER_DETACHED_SESSIONS_LEFT_BY_PLUGIN = 10470 -ER_FAILED_TO_DECREMENT_NUMBER_OF_THREADS = 10471 -ER_PLUGIN_DID_NOT_DEINITIALIZE_THREADS = 10472 -ER_KILLED_THREADS_OF_PLUGIN = 10473 -ER_NDB_SLAVE_MAX_REPLICATED_EPOCH_UNKNOWN = 10474 -ER_NDB_SLAVE_MAX_REPLICATED_EPOCH_SET_TO = 10475 -ER_NDB_NODE_ID_AND_MANAGEMENT_SERVER_INFO = 10476 -ER_NDB_DISCONNECT_INFO = 10477 -ER_NDB_COLUMN_DEFAULTS_DIFFER = 10478 -ER_NDB_COLUMN_SHOULD_NOT_HAVE_NATIVE_DEFAULT = 10479 -ER_NDB_FIELD_INFO = 10480 -ER_NDB_COLUMN_INFO = 10481 -ER_NDB_OOM_IN_FIX_UNIQUE_INDEX_ATTR_ORDER = 10482 -ER_NDB_SLAVE_MALFORMED_EVENT_RECEIVED_ON_TABLE = 10483 -ER_NDB_SLAVE_CONFLICT_FUNCTION_REQUIRES_ROLE = 10484 -ER_NDB_SLAVE_CONFLICT_DETECTION_REQUIRES_TRANSACTION_IDS = 10485 -ER_NDB_SLAVE_BINLOG_MISSING_INFO_FOR_CONFLICT_DETECTION = 10486 -ER_NDB_ERROR_IN_READAUTOINCREMENTVALUE = 10487 -ER_NDB_FOUND_UNCOMMITTED_AUTOCOMMIT = 10488 -ER_NDB_SLAVE_TOO_MANY_RETRIES = 10489 -ER_NDB_SLAVE_ERROR_IN_UPDATE_CREATE_INFO = 10490 -ER_NDB_SLAVE_CANT_ALLOCATE_TABLE_SHARE = 10491 -ER_NDB_BINLOG_ERROR_INFO_FROM_DA = 10492 -ER_NDB_BINLOG_CREATE_TABLE_EVENT = 10493 -ER_NDB_BINLOG_FAILED_CREATE_TABLE_EVENT_OPERATIONS = 10494 -ER_NDB_BINLOG_RENAME_EVENT = 10495 -ER_NDB_BINLOG_FAILED_CREATE_EVENT_OPERATIONS_DURING_RENAME = 10496 -ER_NDB_UNEXPECTED_RENAME_TYPE = 10497 -ER_NDB_ERROR_IN_GET_AUTO_INCREMENT = 10498 -ER_NDB_CREATING_SHARE_IN_OPEN = 10499 -ER_NDB_TABLE_OPENED_READ_ONLY = 10500 -ER_NDB_INITIALIZE_GIVEN_CLUSTER_PLUGIN_DISABLED = 10501 -ER_NDB_BINLOG_FORMAT_CHANGED_FROM_STMT_TO_MIXED = 10502 -ER_NDB_TRAILING_SHARE_RELEASED_BY_CLOSE_CACHED_TABLES = 10503 -ER_NDB_SHARE_ALREADY_EXISTS = 10504 -ER_NDB_HANDLE_TRAILING_SHARE_INFO = 10505 -ER_NDB_CLUSTER_GET_SHARE_INFO = 10506 -ER_NDB_CLUSTER_REAL_FREE_SHARE_INFO = 10507 -ER_NDB_CLUSTER_REAL_FREE_SHARE_DROP_FAILED = 10508 -ER_NDB_CLUSTER_FREE_SHARE_INFO = 10509 -ER_NDB_CLUSTER_MARK_SHARE_DROPPED_INFO = 10510 -ER_NDB_CLUSTER_MARK_SHARE_DROPPED_DESTROYING_SHARE = 10511 -ER_NDB_CLUSTER_OOM_THD_NDB = 10512 -ER_NDB_BINLOG_NDB_TABLES_INITIALLY_READ_ONLY = 10513 -ER_NDB_UTIL_THREAD_OOM = 10514 -ER_NDB_ILLEGAL_VALUE_FOR_NDB_RECV_THREAD_CPU_MASK = 10515 -ER_NDB_TOO_MANY_CPUS_IN_NDB_RECV_THREAD_CPU_MASK = 10516 -ER_DBUG_CHECK_SHARES_OPEN = 10517 -ER_DBUG_CHECK_SHARES_INFO = 10518 -ER_DBUG_CHECK_SHARES_DROPPED = 10519 -ER_INVALID_OR_OLD_TABLE_OR_DB_NAME = 10520 -ER_TC_RECOVERING_AFTER_CRASH_USING = 10521 -ER_TC_CANT_AUTO_RECOVER_WITH_TC_HEURISTIC_RECOVER = 10522 -ER_TC_BAD_MAGIC_IN_TC_LOG = 10523 -ER_TC_NEED_N_SE_SUPPORTING_2PC_FOR_RECOVERY = 10524 -ER_TC_RECOVERY_FAILED_THESE_ARE_YOUR_OPTIONS = 10525 -ER_TC_HEURISTIC_RECOVERY_MODE = 10526 -ER_TC_HEURISTIC_RECOVERY_FAILED = 10527 -ER_TC_RESTART_WITHOUT_TC_HEURISTIC_RECOVER = 10528 -ER_RPL_SLAVE_FAILED_TO_CREATE_OR_RECOVER_INFO_REPOSITORIES = 10529 -ER_RPL_SLAVE_AUTO_POSITION_IS_1_AND_GTID_MODE_IS_OFF = 10530 -ER_RPL_SLAVE_CANT_START_SLAVE_FOR_CHANNEL = 10531 -ER_RPL_SLAVE_CANT_STOP_SLAVE_FOR_CHANNEL = 10532 -ER_RPL_RECOVERY_NO_ROTATE_EVENT_FROM_MASTER = 10533 -ER_RPL_RECOVERY_ERROR_READ_RELAY_LOG = 10534 -ER_RPL_RECOVERY_ERROR_FREEING_IO_CACHE = 10535 -ER_RPL_RECOVERY_SKIPPED_GROUP_REPLICATION_CHANNEL = 10536 -ER_RPL_RECOVERY_ERROR = 10537 -ER_RPL_RECOVERY_IO_ERROR_READING_RELAY_LOG_INDEX = 10538 -ER_RPL_RECOVERY_FILE_MASTER_POS_INFO = 10539 -ER_RPL_RECOVERY_REPLICATE_SAME_SERVER_ID_REQUIRES_POSITION = 10540 -ER_RPL_MTS_RECOVERY_STARTING_COORDINATOR = 10541 -ER_RPL_MTS_RECOVERY_FAILED_TO_START_COORDINATOR = 10542 -ER_RPL_MTS_AUTOMATIC_RECOVERY_FAILED = 10543 -ER_RPL_MTS_RECOVERY_CANT_OPEN_RELAY_LOG = 10544 -ER_RPL_MTS_RECOVERY_SUCCESSFUL = 10545 -ER_RPL_SERVER_ID_MISSING = 10546 -ER_RPL_CANT_CREATE_SLAVE_THREAD = 10547 -ER_RPL_SLAVE_IO_THREAD_WAS_KILLED = 10548 -ER_RPL_SLAVE_MASTER_UUID_HAS_CHANGED = 10549 -ER_RPL_SLAVE_USES_CHECKSUM_AND_MASTER_PRE_50 = 10550 -ER_RPL_SLAVE_SECONDS_BEHIND_MASTER_DUBIOUS = 10551 -ER_RPL_SLAVE_CANT_FLUSH_MASTER_INFO_FILE = 10552 -ER_RPL_SLAVE_REPORT_HOST_TOO_LONG = 10553 -ER_RPL_SLAVE_REPORT_USER_TOO_LONG = 10554 -ER_RPL_SLAVE_REPORT_PASSWORD_TOO_LONG = 10555 -ER_RPL_SLAVE_ERROR_RETRYING = 10556 -ER_RPL_SLAVE_ERROR_READING_FROM_SERVER = 10557 -ER_RPL_SLAVE_DUMP_THREAD_KILLED_BY_MASTER = 10558 -ER_RPL_MTS_STATISTICS = 10559 -ER_RPL_MTS_RECOVERY_COMPLETE = 10560 -ER_RPL_SLAVE_CANT_INIT_RELAY_LOG_POSITION = 10561 -ER_RPL_SLAVE_CONNECTED_TO_MASTER_REPLICATION_STARTED = 10562 -ER_RPL_SLAVE_IO_THREAD_KILLED = 10563 -ER_RPL_SLAVE_IO_THREAD_CANT_REGISTER_ON_MASTER = 10564 -ER_RPL_SLAVE_FORCING_TO_RECONNECT_IO_THREAD = 10565 -ER_RPL_SLAVE_ERROR_REQUESTING_BINLOG_DUMP = 10566 -ER_RPL_LOG_ENTRY_EXCEEDS_SLAVE_MAX_ALLOWED_PACKET = 10567 -ER_RPL_SLAVE_STOPPING_AS_MASTER_OOM = 10568 -ER_RPL_SLAVE_IO_THREAD_ABORTED_WAITING_FOR_RELAY_LOG_SPACE = 10569 -ER_RPL_SLAVE_IO_THREAD_EXITING = 10570 -ER_RPL_SLAVE_CANT_INITIALIZE_SLAVE_WORKER = 10571 -ER_RPL_MTS_GROUP_RECOVERY_RELAY_LOG_INFO_FOR_WORKER = 10572 -ER_RPL_ERROR_LOOKING_FOR_LOG = 10573 -ER_RPL_MTS_GROUP_RECOVERY_RELAY_LOG_INFO = 10574 -ER_RPL_CANT_FIND_FOLLOWUP_FILE = 10575 -ER_RPL_MTS_CHECKPOINT_PERIOD_DIFFERS_FROM_CNT = 10576 -ER_RPL_SLAVE_WORKER_THREAD_CREATION_FAILED = 10577 -ER_RPL_SLAVE_WORKER_THREAD_CREATION_FAILED_WITH_ERRNO = 10578 -ER_RPL_SLAVE_FAILED_TO_INIT_PARTITIONS_HASH = 10579 -ER_RPL_SLAVE_NDB_TABLES_NOT_AVAILABLE = 10580 -ER_RPL_SLAVE_SQL_THREAD_STARTING = 10581 -ER_RPL_SLAVE_SKIP_COUNTER_EXECUTED = 10582 -ER_RPL_SLAVE_ADDITIONAL_ERROR_INFO_FROM_DA = 10583 -ER_RPL_SLAVE_ERROR_INFO_FROM_DA = 10584 -ER_RPL_SLAVE_ERROR_LOADING_USER_DEFINED_LIBRARY = 10585 -ER_RPL_SLAVE_ERROR_RUNNING_QUERY = 10586 -ER_RPL_SLAVE_SQL_THREAD_EXITING = 10587 -ER_RPL_SLAVE_READ_INVALID_EVENT_FROM_MASTER = 10588 -ER_RPL_SLAVE_QUEUE_EVENT_FAILED_INVALID_CONFIGURATION = 10589 -ER_RPL_SLAVE_IO_THREAD_DETECTED_UNEXPECTED_EVENT_SEQUENCE = 10590 -ER_RPL_SLAVE_CANT_USE_CHARSET = 10591 -ER_RPL_SLAVE_CONNECTED_TO_MASTER_REPLICATION_RESUMED = 10592 -ER_RPL_SLAVE_NEXT_LOG_IS_ACTIVE = 10593 -ER_RPL_SLAVE_NEXT_LOG_IS_INACTIVE = 10594 -ER_RPL_SLAVE_SQL_THREAD_IO_ERROR_READING_EVENT = 10595 -ER_RPL_SLAVE_ERROR_READING_RELAY_LOG_EVENTS = 10596 -ER_SLAVE_CHANGE_MASTER_TO_EXECUTED = 10597 -ER_RPL_SLAVE_NEW_MASTER_INFO_NEEDS_REPOS_TYPE_OTHER_THAN_FILE = 10598 -ER_RPL_FAILED_TO_STAT_LOG_IN_INDEX = 10599 -ER_RPL_LOG_NOT_FOUND_WHILE_COUNTING_RELAY_LOG_SPACE = 10600 -ER_SLAVE_CANT_USE_TEMPDIR = 10601 -ER_RPL_RELAY_LOG_NEEDS_FILE_NOT_DIRECTORY = 10602 -ER_RPL_RELAY_LOG_INDEX_NEEDS_FILE_NOT_DIRECTORY = 10603 -ER_RPL_PLEASE_USE_OPTION_RELAY_LOG = 10604 -ER_RPL_OPEN_INDEX_FILE_FAILED = 10605 -ER_RPL_CANT_INITIALIZE_GTID_SETS_IN_RLI_INIT_INFO = 10606 -ER_RPL_CANT_OPEN_LOG_IN_RLI_INIT_INFO = 10607 -ER_RPL_ERROR_WRITING_RELAY_LOG_CONFIGURATION = 10608 -ER_NDB_OOM_GET_NDB_BLOBS_VALUE = 10609 -ER_NDB_THREAD_TIMED_OUT = 10610 -ER_NDB_TABLE_IS_NOT_DISTRIBUTED = 10611 -ER_NDB_CREATING_TABLE = 10612 -ER_NDB_FLUSHING_TABLE_INFO = 10613 -ER_NDB_CLEANING_STRAY_TABLES = 10614 -ER_NDB_DISCOVERED_MISSING_DB = 10615 -ER_NDB_DISCOVERED_REMAINING_DB = 10616 -ER_NDB_CLUSTER_FIND_ALL_DBS_RETRY = 10617 -ER_NDB_CLUSTER_FIND_ALL_DBS_FAIL = 10618 -ER_NDB_SKIPPING_SETUP_TABLE = 10619 -ER_NDB_FAILED_TO_SET_UP_TABLE = 10620 -ER_NDB_MISSING_FRM_DISCOVERING = 10621 -ER_NDB_MISMATCH_IN_FRM_DISCOVERING = 10622 -ER_NDB_BINLOG_CLEANING_UP_SETUP_LEFTOVERS = 10623 -ER_NDB_WAITING_INFO = 10624 -ER_NDB_WAITING_INFO_WITH_MAP = 10625 -ER_NDB_TIMEOUT_WHILE_DISTRIBUTING = 10626 -ER_NDB_NOT_WAITING_FOR_DISTRIBUTING = 10627 -ER_NDB_DISTRIBUTED_INFO = 10628 -ER_NDB_DISTRIBUTION_COMPLETE = 10629 -ER_NDB_SCHEMA_DISTRIBUTION_FAILED = 10630 -ER_NDB_SCHEMA_DISTRIBUTION_REPORTS_SUBSCRIBE = 10631 -ER_NDB_SCHEMA_DISTRIBUTION_REPORTS_UNSUBSCRIBE = 10632 -ER_NDB_BINLOG_CANT_DISCOVER_TABLE_FROM_SCHEMA_EVENT = 10633 -ER_NDB_BINLOG_SIGNALLING_UNKNOWN_VALUE = 10634 -ER_NDB_BINLOG_REPLY_TO = 10635 -ER_NDB_BINLOG_CANT_RELEASE_SLOCK = 10636 -ER_NDB_CANT_FIND_TABLE = 10637 -ER_NDB_DISCARDING_EVENT_NO_OBJ = 10638 -ER_NDB_DISCARDING_EVENT_ID_VERSION_MISMATCH = 10639 -ER_NDB_CLEAR_SLOCK_INFO = 10640 -ER_NDB_BINLOG_SKIPPING_LOCAL_TABLE = 10641 -ER_NDB_BINLOG_ONLINE_ALTER_RENAME = 10642 -ER_NDB_BINLOG_CANT_REOPEN_SHADOW_TABLE = 10643 -ER_NDB_BINLOG_ONLINE_ALTER_RENAME_COMPLETE = 10644 -ER_NDB_BINLOG_SKIPPING_DROP_OF_LOCAL_TABLE = 10645 -ER_NDB_BINLOG_SKIPPING_RENAME_OF_LOCAL_TABLE = 10646 -ER_NDB_BINLOG_SKIPPING_DROP_OF_DB_CONTAINING_LOCAL_TABLES = 10647 -ER_NDB_BINLOG_GOT_DIST_PRIV_EVENT_FLUSHING_PRIVILEGES = 10648 -ER_NDB_BINLOG_GOT_SCHEMA_EVENT = 10649 -ER_NDB_BINLOG_SKIPPING_OLD_SCHEMA_OPERATION = 10650 -ER_NDB_CLUSTER_FAILURE = 10651 -ER_NDB_TABLES_INITIALLY_READ_ONLY_ON_RECONNECT = 10652 -ER_NDB_IGNORING_UNKNOWN_EVENT = 10653 -ER_NDB_BINLOG_OPENING_INDEX = 10654 -ER_NDB_BINLOG_CANT_LOCK_NDB_BINLOG_INDEX = 10655 -ER_NDB_BINLOG_INJECTING_RANDOM_WRITE_FAILURE = 10656 -ER_NDB_BINLOG_CANT_WRITE_TO_NDB_BINLOG_INDEX = 10657 -ER_NDB_BINLOG_WRITING_TO_NDB_BINLOG_INDEX = 10658 -ER_NDB_BINLOG_CANT_COMMIT_TO_NDB_BINLOG_INDEX = 10659 -ER_NDB_BINLOG_WRITE_TO_NDB_BINLOG_INDEX_FAILED_AFTER_KILL = 10660 -ER_NDB_BINLOG_USING_SERVER_ID_0_SLAVES_WILL_NOT = 10661 -ER_NDB_SERVER_ID_RESERVED_OR_TOO_LARGE = 10662 -ER_NDB_BINLOG_NDB_LOG_TRANSACTION_ID_REQUIRES_V2_ROW_EVENTS = 10663 -ER_NDB_BINLOG_NDB_LOG_APPLY_STATUS_FORCING_FULL_USE_WRITE = 10664 -ER_NDB_BINLOG_GENERIC_MESSAGE = 10665 -ER_NDB_CONFLICT_GENERIC_MESSAGE = 10666 -ER_NDB_TRANS_DEPENDENCY_TRACKER_ERROR = 10667 -ER_NDB_CONFLICT_FN_PARSE_ERROR = 10668 -ER_NDB_CONFLICT_FN_SETUP_ERROR = 10669 -ER_NDB_BINLOG_FAILED_TO_GET_TABLE = 10670 -ER_NDB_BINLOG_NOT_LOGGING = 10671 -ER_NDB_BINLOG_CREATE_TABLE_EVENT_FAILED = 10672 -ER_NDB_BINLOG_CREATE_TABLE_EVENT_INFO = 10673 -ER_NDB_BINLOG_DISCOVER_TABLE_EVENT_INFO = 10674 -ER_NDB_BINLOG_BLOB_REQUIRES_PK = 10675 -ER_NDB_BINLOG_CANT_CREATE_EVENT_IN_DB = 10676 -ER_NDB_BINLOG_CANT_CREATE_EVENT_IN_DB_AND_CANT_DROP = 10677 -ER_NDB_BINLOG_CANT_CREATE_EVENT_IN_DB_DROPPED = 10678 -ER_NDB_BINLOG_DISCOVER_REUSING_OLD_EVENT_OPS = 10679 -ER_NDB_BINLOG_CREATING_NDBEVENTOPERATION_FAILED = 10680 -ER_NDB_BINLOG_CANT_CREATE_BLOB = 10681 -ER_NDB_BINLOG_NDBEVENT_EXECUTE_FAILED = 10682 -ER_NDB_CREATE_EVENT_OPS_LOGGING_INFO = 10683 -ER_NDB_BINLOG_CANT_DROP_EVENT_FROM_DB = 10684 -ER_NDB_TIMED_OUT_IN_DROP_TABLE = 10685 -ER_NDB_BINLOG_UNHANDLED_ERROR_FOR_TABLE = 10686 -ER_NDB_BINLOG_CLUSTER_FAILURE = 10687 -ER_NDB_BINLOG_UNKNOWN_NON_DATA_EVENT = 10688 -ER_NDB_BINLOG_INJECTOR_DISCARDING_ROW_EVENT_METADATA = 10689 -ER_NDB_REMAINING_OPEN_TABLES = 10690 -ER_NDB_REMAINING_OPEN_TABLE_INFO = 10691 -ER_NDB_COULD_NOT_GET_APPLY_STATUS_SHARE = 10692 -ER_NDB_BINLOG_SERVER_SHUTDOWN_DURING_NDB_CLUSTER_START = 10693 -ER_NDB_BINLOG_CLUSTER_RESTARTED_RESET_MASTER_SUGGESTED = 10694 -ER_NDB_BINLOG_CLUSTER_HAS_RECONNECTED = 10695 -ER_NDB_BINLOG_STARTING_LOG_AT_EPOCH = 10696 -ER_NDB_BINLOG_NDB_TABLES_WRITABLE = 10697 -ER_NDB_BINLOG_SHUTDOWN_DETECTED = 10698 -ER_NDB_BINLOG_LOST_SCHEMA_CONNECTION_WAITING = 10699 -ER_NDB_BINLOG_LOST_SCHEMA_CONNECTION_CONTINUING = 10700 -ER_NDB_BINLOG_ERROR_HANDLING_SCHEMA_EVENT = 10701 -ER_NDB_BINLOG_CANT_INJECT_APPLY_STATUS_WRITE_ROW = 10702 -ER_NDB_BINLOG_ERROR_DURING_GCI_ROLLBACK = 10703 -ER_NDB_BINLOG_ERROR_DURING_GCI_COMMIT = 10704 -ER_NDB_BINLOG_LATEST_TRX_IN_EPOCH_NOT_IN_BINLOG = 10705 -ER_NDB_BINLOG_RELEASING_EXTRA_SHARE_REFERENCES = 10706 -ER_NDB_BINLOG_REMAINING_OPEN_TABLES = 10707 -ER_NDB_BINLOG_REMAINING_OPEN_TABLE_INFO = 10708 -ER_TREE_CORRUPT_PARENT_SHOULD_POINT_AT_PARENT = 10709 -ER_TREE_CORRUPT_ROOT_SHOULD_BE_BLACK = 10710 -ER_TREE_CORRUPT_2_CONSECUTIVE_REDS = 10711 -ER_TREE_CORRUPT_RIGHT_IS_LEFT = 10712 -ER_TREE_CORRUPT_INCORRECT_BLACK_COUNT = 10713 -ER_WRONG_COUNT_FOR_ORIGIN = 10714 -ER_WRONG_COUNT_FOR_KEY = 10715 -ER_WRONG_COUNT_OF_ELEMENTS = 10716 -ER_RPL_ERROR_READING_SLAVE_WORKER_CONFIGURATION = 10717 -ER_RPL_ERROR_WRITING_SLAVE_WORKER_CONFIGURATION = 10718 -ER_RPL_FAILED_TO_OPEN_RELAY_LOG = 10719 -ER_RPL_WORKER_CANT_READ_RELAY_LOG = 10720 -ER_RPL_WORKER_CANT_FIND_NEXT_RELAY_LOG = 10721 -ER_RPL_MTS_SLAVE_COORDINATOR_HAS_WAITED = 10722 -ER_BINLOG_FAILED_TO_WRITE_DROP_FOR_TEMP_TABLES = 10723 -ER_BINLOG_OOM_WRITING_DELETE_WHILE_OPENING_HEAP_TABLE = 10724 -ER_FAILED_TO_REPAIR_TABLE = 10725 -ER_FAILED_TO_REMOVE_TEMP_TABLE = 10726 -ER_SYSTEM_TABLE_NOT_TRANSACTIONAL = 10727 -ER_RPL_ERROR_WRITING_MASTER_CONFIGURATION = 10728 -ER_RPL_ERROR_READING_MASTER_CONFIGURATION = 10729 -ER_RPL_SSL_INFO_IN_MASTER_INFO_IGNORED = 10730 -ER_PLUGIN_FAILED_DEINITIALIZATION = 10731 -ER_PLUGIN_HAS_NONZERO_REFCOUNT_AFTER_DEINITIALIZATION = 10732 -ER_PLUGIN_SHUTTING_DOWN_PLUGIN = 10733 -ER_PLUGIN_REGISTRATION_FAILED = 10734 -ER_PLUGIN_CANT_OPEN_PLUGIN_TABLE = 10735 -ER_PLUGIN_CANT_LOAD = 10736 -ER_PLUGIN_LOAD_PARAMETER_TOO_LONG = 10737 -ER_PLUGIN_FORCING_SHUTDOWN = 10738 -ER_PLUGIN_HAS_NONZERO_REFCOUNT_AFTER_SHUTDOWN = 10739 -ER_PLUGIN_UNKNOWN_VARIABLE_TYPE = 10740 -ER_PLUGIN_VARIABLE_SET_READ_ONLY = 10741 -ER_PLUGIN_VARIABLE_MISSING_NAME = 10742 -ER_PLUGIN_VARIABLE_NOT_ALLOCATED_THREAD_LOCAL = 10743 -ER_PLUGIN_OOM = 10744 -ER_PLUGIN_BAD_OPTIONS = 10745 -ER_PLUGIN_PARSING_OPTIONS_FAILED = 10746 -ER_PLUGIN_DISABLED = 10747 -ER_PLUGIN_HAS_CONFLICTING_SYSTEM_VARIABLES = 10748 -ER_PLUGIN_CANT_SET_PERSISTENT_OPTIONS = 10749 -ER_MY_NET_WRITE_FAILED_FALLING_BACK_ON_STDERR = 10750 -ER_RETRYING_REPAIR_WITHOUT_QUICK = 10751 -ER_RETRYING_REPAIR_WITH_KEYCACHE = 10752 -ER_FOUND_ROWS_WHILE_REPAIRING = 10753 -ER_ERROR_DURING_OPTIMIZE_TABLE = 10754 -ER_ERROR_ENABLING_KEYS = 10755 -ER_CHECKING_TABLE = 10756 -ER_RECOVERING_TABLE = 10757 -ER_CANT_CREATE_TABLE_SHARE_FROM_FRM = 10758 -ER_CANT_LOCK_TABLE = 10759 -ER_CANT_ALLOC_TABLE_OBJECT = 10760 -ER_CANT_CREATE_HANDLER_OBJECT_FOR_TABLE = 10761 -ER_CANT_SET_HANDLER_REFERENCE_FOR_TABLE = 10762 -ER_CANT_LOCK_TABLESPACE = 10763 -ER_CANT_UPGRADE_GENERATED_COLUMNS_TO_DD = 10764 -ER_DD_ERROR_CREATING_ENTRY = 10765 -ER_DD_CANT_FETCH_TABLE_DATA = 10766 -ER_DD_CANT_FIX_SE_DATA = 10767 -ER_DD_CANT_CREATE_SP = 10768 -ER_CANT_OPEN_DB_OPT_USING_DEFAULT_CHARSET = 10769 -ER_CANT_CREATE_CACHE_FOR_DB_OPT = 10770 -ER_CANT_IDENTIFY_CHARSET_USING_DEFAULT = 10771 -ER_DB_OPT_NOT_FOUND_USING_DEFAULT_CHARSET = 10772 -ER_EVENT_CANT_GET_TIMEZONE_FROM_FIELD = 10773 -ER_EVENT_CANT_FIND_TIMEZONE = 10774 -ER_EVENT_CANT_GET_CHARSET = 10775 -ER_EVENT_CANT_GET_COLLATION = 10776 -ER_EVENT_CANT_OPEN_TABLE_MYSQL_EVENT = 10777 -ER_CANT_PARSE_STORED_ROUTINE_BODY = 10778 -ER_CANT_OPEN_TABLE_MYSQL_PROC = 10779 -ER_CANT_READ_TABLE_MYSQL_PROC = 10780 -ER_FILE_EXISTS_DURING_UPGRADE = 10781 -ER_CANT_OPEN_DATADIR_AFTER_UPGRADE_FAILURE = 10782 -ER_CANT_SET_PATH_FOR = 10783 -ER_CANT_OPEN_DIR = 10784 -ER_NDB_EMPTY_NODEID_IN_NDB_CLUSTER_CONNECTION_POOL_NODEIDS = 10785 -ER_NDB_CANT_PARSE_NDB_CLUSTER_CONNECTION_POOL_NODEIDS = 10786 -ER_NDB_INVALID_NODEID_IN_NDB_CLUSTER_CONNECTION_POOL_NODEIDS = 10787 -ER_NDB_DUPLICATE_NODEID_IN_NDB_CLUSTER_CONNECTION_POOL_NODEIDS = 10788 -ER_NDB_POOL_SIZE_MUST_MATCH_NDB_CLUSTER_CONNECTION_POOL_NODEIDS = 10789 -ER_NDB_NODEID_NOT_FIRST_IN_NDB_CLUSTER_CONNECTION_POOL_NODEIDS = 10790 -ER_NDB_USING_NODEID = 10791 -ER_NDB_CANT_ALLOC_GLOBAL_NDB_CLUSTER_CONNECTION = 10792 -ER_NDB_CANT_ALLOC_GLOBAL_NDB_OBJECT = 10793 -ER_NDB_USING_NODEID_LIST = 10794 -ER_NDB_CANT_ALLOC_NDB_CLUSTER_CONNECTION = 10795 -ER_NDB_STARTING_CONNECT_THREAD = 10796 -ER_NDB_NODE_INFO = 10797 -ER_NDB_CANT_START_CONNECT_THREAD = 10798 -ER_NDB_GENERIC_ERROR = 10799 -ER_NDB_CPU_MASK_TOO_SHORT = 10800 -ER_EVENT_ERROR_CREATING_QUERY_TO_WRITE_TO_BINLOG = 10801 -ER_EVENT_SCHEDULER_ERROR_LOADING_FROM_DB = 10802 -ER_EVENT_SCHEDULER_ERROR_GETTING_EVENT_OBJECT = 10803 -ER_EVENT_SCHEDULER_GOT_BAD_DATA_FROM_TABLE = 10804 -ER_EVENT_CANT_GET_LOCK_FOR_DROPPING_EVENT = 10805 -ER_EVENT_UNABLE_TO_DROP_EVENT = 10806 -ER_BINLOG_ATTACHING_THREAD_MEMORY_FINALLY_AVAILABLE = 10807 -ER_BINLOG_CANT_RESIZE_CACHE = 10808 -ER_BINLOG_FILE_BEING_READ_NOT_PURGED = 10809 -ER_BINLOG_IO_ERROR_READING_HEADER = 10810 -ER_BINLOG_CANT_OPEN_LOG = 10811 -ER_BINLOG_CANT_CREATE_CACHE_FOR_LOG = 10812 -ER_BINLOG_FILE_EXTENSION_NUMBER_EXHAUSTED = 10813 -ER_BINLOG_FILE_NAME_TOO_LONG = 10814 -ER_BINLOG_FILE_EXTENSION_NUMBER_RUNNING_LOW = 10815 -ER_BINLOG_CANT_OPEN_FOR_LOGGING = 10816 -ER_BINLOG_FAILED_TO_SYNC_INDEX_FILE = 10817 -ER_BINLOG_ERROR_READING_GTIDS_FROM_RELAY_LOG = 10818 -ER_BINLOG_EVENTS_READ_FROM_RELAY_LOG_INFO = 10819 -ER_BINLOG_ERROR_READING_GTIDS_FROM_BINARY_LOG = 10820 -ER_BINLOG_EVENTS_READ_FROM_BINLOG_INFO = 10821 -ER_BINLOG_CANT_GENERATE_NEW_FILE_NAME = 10822 -ER_BINLOG_FAILED_TO_SYNC_INDEX_FILE_IN_OPEN = 10823 -ER_BINLOG_CANT_USE_FOR_LOGGING = 10824 -ER_BINLOG_FAILED_TO_CLOSE_INDEX_FILE_WHILE_REBUILDING = 10825 -ER_BINLOG_FAILED_TO_DELETE_INDEX_FILE_WHILE_REBUILDING = 10826 -ER_BINLOG_FAILED_TO_RENAME_INDEX_FILE_WHILE_REBUILDING = 10827 -ER_BINLOG_FAILED_TO_OPEN_INDEX_FILE_AFTER_REBUILDING = 10828 -ER_BINLOG_CANT_APPEND_LOG_TO_TMP_INDEX = 10829 -ER_BINLOG_CANT_LOCATE_OLD_BINLOG_OR_RELAY_LOG_FILES = 10830 -ER_BINLOG_CANT_DELETE_FILE = 10831 -ER_BINLOG_CANT_SET_TMP_INDEX_NAME = 10832 -ER_BINLOG_FAILED_TO_OPEN_TEMPORARY_INDEX_FILE = 10833 -ER_BINLOG_ERROR_GETTING_NEXT_LOG_FROM_INDEX = 10834 -ER_BINLOG_CANT_OPEN_TMP_INDEX = 10835 -ER_BINLOG_CANT_COPY_INDEX_TO_TMP = 10836 -ER_BINLOG_CANT_CLOSE_TMP_INDEX = 10837 -ER_BINLOG_CANT_MOVE_TMP_TO_INDEX = 10838 -ER_BINLOG_PURGE_LOGS_CALLED_WITH_FILE_NOT_IN_INDEX = 10839 -ER_BINLOG_PURGE_LOGS_CANT_SYNC_INDEX_FILE = 10840 -ER_BINLOG_PURGE_LOGS_CANT_COPY_TO_REGISTER_FILE = 10841 -ER_BINLOG_PURGE_LOGS_CANT_FLUSH_REGISTER_FILE = 10842 -ER_BINLOG_PURGE_LOGS_CANT_UPDATE_INDEX_FILE = 10843 -ER_BINLOG_PURGE_LOGS_FAILED_TO_PURGE_LOG = 10844 -ER_BINLOG_FAILED_TO_SET_PURGE_INDEX_FILE_NAME = 10845 -ER_BINLOG_FAILED_TO_OPEN_REGISTER_FILE = 10846 -ER_BINLOG_FAILED_TO_REINIT_REGISTER_FILE = 10847 -ER_BINLOG_FAILED_TO_READ_REGISTER_FILE = 10848 -ER_CANT_STAT_FILE = 10849 -ER_BINLOG_CANT_DELETE_LOG_FILE_DOES_INDEX_MATCH_FILES = 10850 -ER_BINLOG_CANT_DELETE_FILE_AND_READ_BINLOG_INDEX = 10851 -ER_BINLOG_FAILED_TO_DELETE_LOG_FILE = 10852 -ER_BINLOG_LOGGING_INCIDENT_TO_STOP_SLAVES = 10853 -ER_BINLOG_CANT_FIND_LOG_IN_INDEX = 10854 -ER_BINLOG_RECOVERING_AFTER_CRASH_USING = 10855 -ER_BINLOG_CANT_OPEN_CRASHED_BINLOG = 10856 -ER_BINLOG_CANT_TRIM_CRASHED_BINLOG = 10857 -ER_BINLOG_CRASHED_BINLOG_TRIMMED = 10858 -ER_BINLOG_CANT_CLEAR_IN_USE_FLAG_FOR_CRASHED_BINLOG = 10859 -ER_BINLOG_FAILED_TO_RUN_AFTER_SYNC_HOOK = 10860 -ER_TURNING_LOGGING_OFF_FOR_THE_DURATION = 10861 -ER_BINLOG_FAILED_TO_RUN_AFTER_FLUSH_HOOK = 10862 -ER_BINLOG_CRASH_RECOVERY_FAILED = 10863 -ER_BINLOG_WARNING_SUPPRESSED = 10864 -ER_NDB_LOG_ENTRY = 10865 -ER_NDB_LOG_ENTRY_WITH_PREFIX = 10866 -ER_NDB_BINLOG_CANT_CREATE_PURGE_THD = 10867 -ER_INNODB_UNKNOWN_COLLATION = 10868 -ER_INNODB_INVALID_LOG_GROUP_HOME_DIR = 10869 -ER_INNODB_INVALID_INNODB_UNDO_DIRECTORY = 10870 -ER_INNODB_ILLEGAL_COLON_IN_POOL = 10871 -ER_INNODB_INVALID_PAGE_SIZE = 10872 -ER_INNODB_DIRTY_WATER_MARK_NOT_LOW = 10873 -ER_INNODB_IO_CAPACITY_EXCEEDS_MAX = 10874 -ER_INNODB_FILES_SAME = 10875 -ER_INNODB_UNREGISTERED_TRX_ACTIVE = 10876 -ER_INNODB_CLOSING_CONNECTION_ROLLS_BACK = 10877 -ER_INNODB_TRX_XLATION_TABLE_OOM = 10878 -ER_INNODB_CANT_FIND_INDEX_IN_INNODB_DD = 10879 -ER_INNODB_INDEX_COLUMN_INFO_UNLIKE_MYSQLS = 10880 -ER_INNODB_CANT_OPEN_TABLE = 10881 -ER_INNODB_CANT_BUILD_INDEX_XLATION_TABLE_FOR = 10882 -ER_INNODB_PK_NOT_IN_MYSQL = 10883 -ER_INNODB_PK_ONLY_IN_MYSQL = 10884 -ER_INNODB_CLUSTERED_INDEX_PRIVATE = 10885 -ER_INNODB_PARTITION_TABLE_LOWERCASED = 10886 -ER_ERRMSG_REPLACEMENT_DODGY = 10887 -ER_ERRMSG_REPLACEMENTS_FAILED = 10888 -ER_NPIPE_CANT_CREATE = 10889 -ER_PARTITION_MOVE_CREATED_DUPLICATE_ROW_PLEASE_FIX = 10890 -ER_AUDIT_CANT_ABORT_COMMAND = 10891 -ER_AUDIT_CANT_ABORT_EVENT = 10892 -ER_AUDIT_WARNING = 10893 -ER_NDB_NUMBER_OF_CHANNELS = 10894 -ER_NDB_SLAVE_PARALLEL_WORKERS = 10895 -ER_NDB_DISTRIBUTING_ERR = 10896 -ER_RPL_SLAVE_INSECURE_CHANGE_MASTER = 10897 -ER_RPL_SLAVE_FLUSH_RELAY_LOGS_NOT_ALLOWED = 10898 -ER_RPL_SLAVE_INCORRECT_CHANNEL = 10899 -ER_FAILED_TO_FIND_DL_ENTRY = 10900 -ER_FAILED_TO_OPEN_SHARED_LIBRARY = 10901 -ER_THREAD_PRIORITY_IGNORED = 10902 -ER_BINLOG_CACHE_SIZE_TOO_LARGE = 10903 -ER_BINLOG_STMT_CACHE_SIZE_TOO_LARGE = 10904 -ER_FAILED_TO_GENERATE_UNIQUE_LOGFILE = 10905 -ER_FAILED_TO_READ_FILE = 10906 -ER_FAILED_TO_WRITE_TO_FILE = 10907 -ER_BINLOG_UNSAFE_MESSAGE_AND_STATEMENT = 10908 -ER_FORCE_CLOSE_THREAD = 10909 -ER_SERVER_SHUTDOWN_COMPLETE = 10910 -ER_RPL_CANT_HAVE_SAME_BASENAME = 10911 -ER_RPL_GTID_MODE_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON = 10912 -ER_WARN_NO_SERVERID_SPECIFIED = 10913 -ER_ABORTING_USER_CONNECTION = 10914 -ER_SQL_MODE_MERGED_WITH_STRICT_MODE = 10915 -ER_GTID_PURGED_WAS_UPDATED = 10916 -ER_GTID_EXECUTED_WAS_UPDATED = 10917 -ER_DEPRECATE_MSG_WITH_REPLACEMENT = 10918 -ER_TRG_CREATION_CTX_NOT_SET = 10919 -ER_FILE_HAS_OLD_FORMAT = 10920 -ER_VIEW_CREATION_CTX_NOT_SET = 10921 -ER_TABLE_NAME_CAUSES_TOO_LONG_PATH = 10922 -ER_TABLE_UPGRADE_REQUIRED = 10923 -ER_GET_ERRNO_FROM_STORAGE_ENGINE = 10924 -ER_ACCESS_DENIED_ERROR_WITHOUT_PASSWORD = 10925 -ER_ACCESS_DENIED_ERROR_WITH_PASSWORD = 10926 -ER_ACCESS_DENIED_FOR_USER_ACCOUNT_LOCKED = 10927 -ER_MUST_CHANGE_EXPIRED_PASSWORD = 10928 -ER_SYSTEM_TABLES_NOT_SUPPORTED_BY_STORAGE_ENGINE = 10929 -ER_FILESORT_TERMINATED = 10930 -ER_SERVER_STARTUP_MSG = 10931 -ER_FAILED_TO_FIND_LOCALE_NAME = 10932 -ER_FAILED_TO_FIND_COLLATION_NAME = 10933 -ER_SERVER_OUT_OF_RESOURCES = 10934 -ER_SERVER_OUTOFMEMORY = 10935 -ER_INVALID_COLLATION_FOR_CHARSET = 10936 -ER_CANT_START_ERROR_LOG_SERVICE = 10937 -ER_CREATING_NEW_UUID_FIRST_START = 10938 -ER_FAILED_TO_GET_ABSOLUTE_PATH = 10939 -ER_PERFSCHEMA_COMPONENTS_INFRASTRUCTURE_BOOTSTRAP = 10940 -ER_PERFSCHEMA_COMPONENTS_INFRASTRUCTURE_SHUTDOWN = 10941 -ER_DUP_FD_OPEN_FAILED = 10942 -ER_SYSTEM_VIEW_INIT_FAILED = 10943 -ER_RESOURCE_GROUP_POST_INIT_FAILED = 10944 -ER_RESOURCE_GROUP_SUBSYSTEM_INIT_FAILED = 10945 -ER_FAILED_START_MYSQLD_DAEMON = 10946 -ER_CANNOT_CHANGE_TO_ROOT_DIR = 10947 -ER_PERSISTENT_PRIVILEGES_BOOTSTRAP = 10948 -ER_BASEDIR_SET_TO = 10949 -ER_RPL_FILTER_ADD_WILD_DO_TABLE_FAILED = 10950 -ER_RPL_FILTER_ADD_WILD_IGNORE_TABLE_FAILED = 10951 -ER_PRIVILEGE_SYSTEM_INIT_FAILED = 10952 -ER_CANNOT_SET_LOG_ERROR_SERVICES = 10953 -ER_PERFSCHEMA_TABLES_INIT_FAILED = 10954 -ER_TX_EXTRACTION_ALGORITHM_FOR_BINLOG_TX_DEPEDENCY_TRACKING = 10955 -ER_INVALID_REPLICATION_TIMESTAMPS = 10956 -ER_RPL_TIMESTAMPS_RETURNED_TO_NORMAL = 10957 -ER_BINLOG_FILE_OPEN_FAILED = 10958 -ER_BINLOG_EVENT_WRITE_TO_STMT_CACHE_FAILED = 10959 -ER_SLAVE_RELAY_LOG_TRUNCATE_INFO = 10960 -ER_SLAVE_RELAY_LOG_PURGE_FAILED = 10961 -ER_RPL_SLAVE_FILTER_CREATE_FAILED = 10962 -ER_RPL_SLAVE_GLOBAL_FILTERS_COPY_FAILED = 10963 -ER_RPL_SLAVE_RESET_FILTER_OPTIONS = 10964 -ER_MISSING_GRANT_SYSTEM_TABLE = 10965 -ER_MISSING_ACL_SYSTEM_TABLE = 10966 -ER_ANONYMOUS_AUTH_ID_NOT_ALLOWED_IN_MANDATORY_ROLES = 10967 -ER_UNKNOWN_AUTH_ID_IN_MANDATORY_ROLE = 10968 -ER_WRITE_ROW_TO_PARTITION_FAILED = 10969 -ER_RESOURCE_GROUP_METADATA_UPDATE_SKIPPED = 10970 -ER_FAILED_TO_PERSIST_RESOURCE_GROUP_METADATA = 10971 -ER_FAILED_TO_DESERIALIZE_RESOURCE_GROUP = 10972 -ER_FAILED_TO_UPDATE_RESOURCE_GROUP = 10973 -ER_RESOURCE_GROUP_VALIDATION_FAILED = 10974 -ER_FAILED_TO_ALLOCATE_MEMORY_FOR_RESOURCE_GROUP = 10975 -ER_FAILED_TO_ALLOCATE_MEMORY_FOR_RESOURCE_GROUP_HASH = 10976 -ER_FAILED_TO_ADD_RESOURCE_GROUP_TO_MAP = 10977 -ER_RESOURCE_GROUP_IS_DISABLED = 10978 -ER_FAILED_TO_APPLY_RESOURCE_GROUP_CONTROLLER = 10979 -ER_FAILED_TO_ACQUIRE_LOCK_ON_RESOURCE_GROUP = 10980 -ER_PFS_NOTIFICATION_FUNCTION_REGISTER_FAILED = 10981 -ER_RES_GRP_SET_THR_AFFINITY_FAILED = 10982 -ER_RES_GRP_SET_THR_AFFINITY_TO_CPUS_FAILED = 10983 -ER_RES_GRP_THD_UNBIND_FROM_CPU_FAILED = 10984 -ER_RES_GRP_SET_THREAD_PRIORITY_FAILED = 10985 -ER_RES_GRP_FAILED_TO_DETERMINE_NICE_CAPABILITY = 10986 -ER_RES_GRP_FAILED_TO_GET_THREAD_HANDLE = 10987 -ER_RES_GRP_GET_THREAD_PRIO_NOT_SUPPORTED = 10988 -ER_RES_GRP_FAILED_DETERMINE_CPU_COUNT = 10989 -ER_RES_GRP_FEATURE_NOT_AVAILABLE = 10990 -ER_RES_GRP_INVALID_THREAD_PRIORITY = 10991 -ER_RES_GRP_SOLARIS_PROCESSOR_BIND_TO_CPUID_FAILED = 10992 -ER_RES_GRP_SOLARIS_PROCESSOR_BIND_TO_THREAD_FAILED = 10993 -ER_RES_GRP_SOLARIS_PROCESSOR_AFFINITY_FAILED = 10994 -ER_DD_UPGRADE_RENAME_IDX_STATS_FILE_FAILED = 10995 -ER_DD_UPGRADE_DD_OPEN_FAILED = 10996 -ER_DD_UPGRADE_FAILED_TO_FETCH_TABLESPACES = 10997 -ER_DD_UPGRADE_FAILED_TO_ACQUIRE_TABLESPACE = 10998 -ER_DD_UPGRADE_FAILED_TO_RESOLVE_TABLESPACE_ENGINE = 10999 -ER_FAILED_TO_CREATE_SDI_FOR_TABLESPACE = 11000 -ER_FAILED_TO_STORE_SDI_FOR_TABLESPACE = 11001 -ER_DD_UPGRADE_FAILED_TO_FETCH_TABLES = 11002 -ER_DD_UPGRADE_DD_POPULATED = 11003 -ER_DD_UPGRADE_INFO_FILE_OPEN_FAILED = 11004 -ER_DD_UPGRADE_INFO_FILE_CLOSE_FAILED = 11005 -ER_DD_UPGRADE_TABLESPACE_MIGRATION_FAILED = 11006 -ER_DD_UPGRADE_FAILED_TO_CREATE_TABLE_STATS = 11007 -ER_DD_UPGRADE_TABLE_STATS_MIGRATE_COMPLETED = 11008 -ER_DD_UPGRADE_FAILED_TO_CREATE_INDEX_STATS = 11009 -ER_DD_UPGRADE_INDEX_STATS_MIGRATE_COMPLETED = 11010 -ER_DD_UPGRADE_FAILED_FIND_VALID_DATA_DIR = 11011 -ER_DD_UPGRADE_START = 11012 -ER_DD_UPGRADE_FAILED_INIT_DD_SE = 11013 -ER_DD_UPGRADE_FOUND_PARTIALLY_UPGRADED_DD_ABORT = 11014 -ER_DD_UPGRADE_FOUND_PARTIALLY_UPGRADED_DD_CONTINUE = 11015 -ER_DD_UPGRADE_SE_LOGS_FAILED = 11016 -ER_DD_UPGRADE_SDI_INFO_UPDATE_FAILED = 11017 -ER_SKIP_UPDATING_METADATA_IN_SE_RO_MODE = 11018 -ER_CREATED_SYSTEM_WITH_VERSION = 11019 -ER_UNKNOWN_ERROR_DETECTED_IN_SE = 11020 -ER_READ_LOG_EVENT_FAILED = 11021 -ER_ROW_DATA_TOO_BIG_TO_WRITE_IN_BINLOG = 11022 -ER_FAILED_TO_CONSTRUCT_DROP_EVENT_QUERY = 11023 -ER_FAILED_TO_BINLOG_DROP_EVENT = 11024 -ER_FAILED_TO_START_SLAVE_THREAD = 11025 -ER_RPL_IO_THREAD_KILLED = 11026 -ER_SLAVE_RECONNECT_FAILED = 11027 -ER_SLAVE_KILLED_AFTER_RECONNECT = 11028 -ER_SLAVE_NOT_STARTED_ON_SOME_CHANNELS = 11029 -ER_FAILED_TO_ADD_RPL_FILTER = 11030 -ER_PER_CHANNEL_RPL_FILTER_CONF_FOR_GRP_RPL = 11031 -ER_RPL_FILTERS_NOT_ATTACHED_TO_CHANNEL = 11032 -ER_FAILED_TO_BUILD_DO_AND_IGNORE_TABLE_HASHES = 11033 -ER_CLONE_PLUGIN_NOT_LOADED = 11034 -ER_CLONE_HANDLER_EXISTS = 11035 -ER_FAILED_TO_CREATE_CLONE_HANDLER = 11036 -ER_CYCLE_TIMER_IS_NOT_AVAILABLE = 11037 -ER_NANOSECOND_TIMER_IS_NOT_AVAILABLE = 11038 -ER_MICROSECOND_TIMER_IS_NOT_AVAILABLE = 11039 -ER_PFS_MALLOC_ARRAY_OVERFLOW = 11040 -ER_PFS_MALLOC_ARRAY_OOM = 11041 -ER_INNODB_FAILED_TO_FIND_IDX_WITH_KEY_NO = 11042 -ER_INNODB_FAILED_TO_FIND_IDX = 11043 -ER_INNODB_FAILED_TO_FIND_IDX_FROM_DICT_CACHE = 11044 -ER_INNODB_ACTIVE_INDEX_CHANGE_FAILED = 11045 -ER_INNODB_DIFF_IN_REF_LEN = 11046 -ER_WRONG_TYPE_FOR_COLUMN_PREFIX_IDX_FLD = 11047 -ER_INNODB_CANNOT_CREATE_TABLE = 11048 -ER_INNODB_INTERNAL_INDEX = 11049 -ER_INNODB_IDX_CNT_MORE_THAN_DEFINED_IN_MYSQL = 11050 -ER_INNODB_IDX_CNT_FEWER_THAN_DEFINED_IN_MYSQL = 11051 -ER_INNODB_IDX_COLUMN_CNT_DIFF = 11052 -ER_INNODB_USE_MONITOR_GROUP_NAME = 11053 -ER_INNODB_MONITOR_DEFAULT_VALUE_NOT_DEFINED = 11054 -ER_INNODB_MONITOR_IS_ENABLED = 11055 -ER_INNODB_INVALID_MONITOR_COUNTER_NAME = 11056 -ER_WIN_LOAD_LIBRARY_FAILED = 11057 -ER_PARTITION_HANDLER_ADMIN_MSG = 11058 -ER_RPL_RLI_INIT_INFO_MSG = 11059 -ER_DD_UPGRADE_TABLE_INTACT_ERROR = 11060 -ER_SERVER_INIT_COMPILED_IN_COMMANDS = 11061 -ER_MYISAM_CHECK_METHOD_ERROR = 11062 -ER_MYISAM_CRASHED_ERROR = 11063 -ER_WAITPID_FAILED = 11064 -ER_FAILED_TO_FIND_MYSQLD_STATUS = 11065 -ER_INNODB_ERROR_LOGGER_MSG = 11066 -ER_INNODB_ERROR_LOGGER_FATAL_MSG = 11067 -ER_DEPRECATED_SYNTAX_WITH_REPLACEMENT = 11068 -ER_DEPRECATED_SYNTAX_NO_REPLACEMENT = 11069 -ER_DEPRECATE_MSG_NO_REPLACEMENT = 11070 -ER_LOG_PRINTF_MSG = 11071 -ER_BINLOG_LOGGING_NOT_POSSIBLE = 11072 -ER_FAILED_TO_SET_PERSISTED_OPTIONS = 11073 -ER_COMPONENTS_FAILED_TO_ACQUIRE_SERVICE_IMPLEMENTATION = 11074 -ER_RES_GRP_INVALID_VCPU_RANGE = 11075 -ER_RES_GRP_INVALID_VCPU_ID = 11076 -ER_ERROR_DURING_FLUSH_LOG_COMMIT_PHASE = 11077 -ER_DROP_DATABASE_FAILED_RMDIR_MANUALLY = 11078 -ER_EXPIRE_LOGS_DAYS_IGNORED = 11079 -ER_BINLOG_MALFORMED_OR_OLD_RELAY_LOG = 11080 -ER_DD_UPGRADE_VIEW_COLUMN_NAME_TOO_LONG = 11081 -ER_TABLE_NEEDS_DUMP_UPGRADE = 11082 -ER_DD_UPGRADE_FAILED_TO_UPDATE_VER_NO_IN_TABLESPACE = 11083 -ER_KEYRING_MIGRATION_FAILED = 11084 -ER_KEYRING_MIGRATION_SUCCESSFUL = 11085 -ER_RESTART_RECEIVED_INFO = 11086 -ER_LCTN_CHANGED = 11087 -ER_DD_INITIALIZE = 11088 -ER_DD_RESTART = 11089 -ER_DD_UPGRADE = 11090 -ER_DD_UPGRADE_OFF = 11091 -ER_DD_UPGRADE_VERSION_NOT_SUPPORTED = 11092 -ER_DD_UPGRADE_SCHEMA_UNAVAILABLE = 11093 -ER_DD_MINOR_DOWNGRADE = 11094 -ER_DD_MINOR_DOWNGRADE_VERSION_NOT_SUPPORTED = 11095 -ER_DD_NO_VERSION_FOUND = 11096 -ER_THREAD_POOL_NOT_SUPPORTED_ON_PLATFORM = 11097 -ER_THREAD_POOL_SIZE_TOO_LOW = 11098 -ER_THREAD_POOL_SIZE_TOO_HIGH = 11099 -ER_THREAD_POOL_ALGORITHM_INVALID = 11100 -ER_THREAD_POOL_INVALID_STALL_LIMIT = 11101 -ER_THREAD_POOL_INVALID_PRIO_KICKUP_TIMER = 11102 -ER_THREAD_POOL_MAX_UNUSED_THREADS_INVALID = 11103 -ER_THREAD_POOL_CON_HANDLER_INIT_FAILED = 11104 -ER_THREAD_POOL_INIT_FAILED = 11105 -ER_THREAD_POOL_PLUGIN_STARTED = 11106 -ER_THREAD_POOL_CANNOT_SET_THREAD_SPECIFIC_DATA = 11107 -ER_THREAD_POOL_FAILED_TO_CREATE_CONNECT_HANDLER_THD = 11108 -ER_THREAD_POOL_FAILED_TO_CREATE_THD_AND_AUTH_CONN = 11109 -ER_THREAD_POOL_FAILED_PROCESS_CONNECT_EVENT = 11110 -ER_THREAD_POOL_FAILED_TO_CREATE_POOL = 11111 -ER_THREAD_POOL_RATE_LIMITED_ERROR_MSGS = 11112 -ER_TRHEAD_POOL_LOW_LEVEL_INIT_FAILED = 11113 -ER_THREAD_POOL_LOW_LEVEL_REARM_FAILED = 11114 -ER_THREAD_POOL_BUFFER_TOO_SMALL = 11115 -ER_MECAB_NOT_SUPPORTED = 11116 -ER_MECAB_NOT_VERIFIED = 11117 -ER_MECAB_CREATING_MODEL = 11118 -ER_MECAB_FAILED_TO_CREATE_MODEL = 11119 -ER_MECAB_FAILED_TO_CREATE_TRIGGER = 11120 -ER_MECAB_UNSUPPORTED_CHARSET = 11121 -ER_MECAB_CHARSET_LOADED = 11122 -ER_MECAB_PARSE_FAILED = 11123 -ER_MECAB_OOM_WHILE_PARSING_TEXT = 11124 -ER_MECAB_CREATE_LATTICE_FAILED = 11125 -ER_SEMISYNC_TRACE_ENTER_FUNC = 11126 -ER_SEMISYNC_TRACE_EXIT_WITH_INT_EXIT_CODE = 11127 -ER_SEMISYNC_TRACE_EXIT_WITH_BOOL_EXIT_CODE = 11128 -ER_SEMISYNC_TRACE_EXIT = 11129 -ER_SEMISYNC_RPL_INIT_FOR_TRX = 11130 -ER_SEMISYNC_FAILED_TO_ALLOCATE_TRX_NODE = 11131 -ER_SEMISYNC_BINLOG_WRITE_OUT_OF_ORDER = 11132 -ER_SEMISYNC_INSERT_LOG_INFO_IN_ENTRY = 11133 -ER_SEMISYNC_PROBE_LOG_INFO_IN_ENTRY = 11134 -ER_SEMISYNC_CLEARED_ALL_ACTIVE_TRANSACTION_NODES = 11135 -ER_SEMISYNC_CLEARED_ACTIVE_TRANSACTION_TILL_POS = 11136 -ER_SEMISYNC_REPLY_MAGIC_NO_ERROR = 11137 -ER_SEMISYNC_REPLY_PKT_LENGTH_TOO_SMALL = 11138 -ER_SEMISYNC_REPLY_BINLOG_FILE_TOO_LARGE = 11139 -ER_SEMISYNC_SERVER_REPLY = 11140 -ER_SEMISYNC_FUNCTION_CALLED_TWICE = 11141 -ER_SEMISYNC_RPL_ENABLED_ON_MASTER = 11142 -ER_SEMISYNC_MASTER_OOM = 11143 -ER_SEMISYNC_DISABLED_ON_MASTER = 11144 -ER_SEMISYNC_FORCED_SHUTDOWN = 11145 -ER_SEMISYNC_MASTER_GOT_REPLY_AT_POS = 11146 -ER_SEMISYNC_MASTER_SIGNAL_ALL_WAITING_THREADS = 11147 -ER_SEMISYNC_MASTER_TRX_WAIT_POS = 11148 -ER_SEMISYNC_BINLOG_REPLY_IS_AHEAD = 11149 -ER_SEMISYNC_MOVE_BACK_WAIT_POS = 11150 -ER_SEMISYNC_INIT_WAIT_POS = 11151 -ER_SEMISYNC_WAIT_TIME_FOR_BINLOG_SENT = 11152 -ER_SEMISYNC_WAIT_FOR_BINLOG_TIMEDOUT = 11153 -ER_SEMISYNC_WAIT_TIME_ASSESSMENT_FOR_COMMIT_TRX_FAILED = 11154 -ER_SEMISYNC_RPL_SWITCHED_OFF = 11155 -ER_SEMISYNC_RPL_SWITCHED_ON = 11156 -ER_SEMISYNC_NO_SPACE_IN_THE_PKT = 11157 -ER_SEMISYNC_SYNC_HEADER_UPDATE_INFO = 11158 -ER_SEMISYNC_FAILED_TO_INSERT_TRX_NODE = 11159 -ER_SEMISYNC_TRX_SKIPPED_AT_POS = 11160 -ER_SEMISYNC_MASTER_FAILED_ON_NET_FLUSH = 11161 -ER_SEMISYNC_RECEIVED_ACK_IS_SMALLER = 11162 -ER_SEMISYNC_ADD_ACK_TO_SLOT = 11163 -ER_SEMISYNC_UPDATE_EXISTING_SLAVE_ACK = 11164 -ER_SEMISYNC_FAILED_TO_START_ACK_RECEIVER_THD = 11165 -ER_SEMISYNC_STARTING_ACK_RECEIVER_THD = 11166 -ER_SEMISYNC_FAILED_TO_WAIT_ON_DUMP_SOCKET = 11167 -ER_SEMISYNC_STOPPING_ACK_RECEIVER_THREAD = 11168 -ER_SEMISYNC_FAILED_REGISTER_SLAVE_TO_RECEIVER = 11169 -ER_SEMISYNC_START_BINLOG_DUMP_TO_SLAVE = 11170 -ER_SEMISYNC_STOP_BINLOG_DUMP_TO_SLAVE = 11171 -ER_SEMISYNC_UNREGISTER_TRX_OBSERVER_FAILED = 11172 -ER_SEMISYNC_UNREGISTER_BINLOG_STORAGE_OBSERVER_FAILED = 11173 -ER_SEMISYNC_UNREGISTER_BINLOG_TRANSMIT_OBSERVER_FAILED = 11174 -ER_SEMISYNC_UNREGISTERED_REPLICATOR = 11175 -ER_SEMISYNC_SOCKET_FD_TOO_LARGE = 11176 -ER_SEMISYNC_SLAVE_REPLY = 11177 -ER_SEMISYNC_MISSING_MAGIC_NO_FOR_SEMISYNC_PKT = 11178 -ER_SEMISYNC_SLAVE_START = 11179 -ER_SEMISYNC_SLAVE_REPLY_WITH_BINLOG_INFO = 11180 -ER_SEMISYNC_SLAVE_NET_FLUSH_REPLY_FAILED = 11181 -ER_SEMISYNC_SLAVE_SEND_REPLY_FAILED = 11182 -ER_SEMISYNC_EXECUTION_FAILED_ON_MASTER = 11183 -ER_SEMISYNC_NOT_SUPPORTED_BY_MASTER = 11184 -ER_SEMISYNC_SLAVE_SET_FAILED = 11185 -ER_SEMISYNC_FAILED_TO_STOP_ACK_RECEIVER_THD = 11186 -ER_FIREWALL_FAILED_TO_READ_FIREWALL_TABLES = 11187 -ER_FIREWALL_FAILED_TO_REG_DYNAMIC_PRIVILEGES = 11188 -ER_FIREWALL_RECORDING_STMT_WAS_TRUNCATED = 11189 -ER_FIREWALL_RECORDING_STMT_WITHOUT_TEXT = 11190 -ER_FIREWALL_SUSPICIOUS_STMT = 11191 -ER_FIREWALL_ACCESS_DENIED = 11192 -ER_FIREWALL_SKIPPED_UNKNOWN_USER_MODE = 11193 -ER_FIREWALL_RELOADING_CACHE = 11194 -ER_FIREWALL_RESET_FOR_USER = 11195 -ER_FIREWALL_STATUS_FLUSHED = 11196 -ER_KEYRING_LOGGER_ERROR_MSG = 11197 -ER_AUDIT_LOG_FILTER_IS_NOT_INSTALLED = 11198 -ER_AUDIT_LOG_SWITCHING_TO_INCLUDE_LIST = 11199 -ER_AUDIT_LOG_CANNOT_SET_LOG_POLICY_WITH_OTHER_POLICIES = 11200 -ER_AUDIT_LOG_ONLY_INCLUDE_LIST_USED = 11201 -ER_AUDIT_LOG_INDEX_MAP_CANNOT_ACCESS_DIR = 11202 -ER_AUDIT_LOG_WRITER_RENAME_FILE_FAILED = 11203 -ER_AUDIT_LOG_WRITER_DEST_FILE_ALREADY_EXISTS = 11204 -ER_AUDIT_LOG_WRITER_RENAME_FILE_FAILED_REMOVE_FILE_MANUALLY = 11205 -ER_AUDIT_LOG_WRITER_INCOMPLETE_FILE_RENAMED = 11206 -ER_AUDIT_LOG_WRITER_FAILED_TO_WRITE_TO_FILE = 11207 -ER_AUDIT_LOG_EC_WRITER_FAILED_TO_INIT_ENCRYPTION = 11208 -ER_AUDIT_LOG_EC_WRITER_FAILED_TO_INIT_COMPRESSION = 11209 -ER_AUDIT_LOG_EC_WRITER_FAILED_TO_CREATE_FILE = 11210 -ER_AUDIT_LOG_RENAME_LOG_FILE_BEFORE_FLUSH = 11211 -ER_AUDIT_LOG_FILTER_RESULT_MSG = 11212 -ER_AUDIT_LOG_JSON_READER_FAILED_TO_PARSE = 11213 -ER_AUDIT_LOG_JSON_READER_BUF_TOO_SMALL = 11214 -ER_AUDIT_LOG_JSON_READER_FAILED_TO_OPEN_FILE = 11215 -ER_AUDIT_LOG_JSON_READER_FILE_PARSING_ERROR = 11216 -ER_AUDIT_LOG_FILTER_INVALID_COLUMN_COUNT = 11217 -ER_AUDIT_LOG_FILTER_INVALID_COLUMN_DEFINITION = 11218 -ER_AUDIT_LOG_FILTER_FAILED_TO_STORE_TABLE_FLDS = 11219 -ER_AUDIT_LOG_FILTER_FAILED_TO_UPDATE_TABLE = 11220 -ER_AUDIT_LOG_FILTER_FAILED_TO_INSERT_INTO_TABLE = 11221 -ER_AUDIT_LOG_FILTER_FAILED_TO_DELETE_FROM_TABLE = 11222 -ER_AUDIT_LOG_FILTER_FAILED_TO_INIT_TABLE_FOR_READ = 11223 -ER_AUDIT_LOG_FILTER_FAILED_TO_READ_TABLE = 11224 -ER_AUDIT_LOG_FILTER_FAILED_TO_CLOSE_TABLE_AFTER_READING = 11225 -ER_AUDIT_LOG_FILTER_USER_AND_HOST_CANNOT_BE_EMPTY = 11226 -ER_AUDIT_LOG_FILTER_FLD_FILTERNAME_CANNOT_BE_EMPTY = 11227 -ER_VALIDATE_PWD_DICT_FILE_NOT_SPECIFIED = 11228 -ER_VALIDATE_PWD_DICT_FILE_NOT_LOADED = 11229 -ER_VALIDATE_PWD_DICT_FILE_TOO_BIG = 11230 -ER_VALIDATE_PWD_FAILED_TO_READ_DICT_FILE = 11231 -ER_VALIDATE_PWD_FAILED_TO_GET_FLD_FROM_SECURITY_CTX = 11232 -ER_VALIDATE_PWD_FAILED_TO_GET_SECURITY_CTX = 11233 -ER_VALIDATE_PWD_LENGTH_CHANGED = 11234 -ER_REWRITER_QUERY_ERROR_MSG = 11235 -ER_REWRITER_QUERY_FAILED = 11236 -ER_XPLUGIN_STARTUP_FAILED = 11237 -ER_XPLUGIN_SERVER_EXITING = 11238 -ER_XPLUGIN_SERVER_EXITED = 11239 -ER_XPLUGIN_USING_SSL_CONF_FROM_SERVER = 11240 -ER_XPLUGIN_USING_SSL_CONF_FROM_MYSQLX = 11241 -ER_XPLUGIN_FAILED_TO_USE_SSL_CONF = 11242 -ER_XPLUGIN_USING_SSL_FOR_TLS_CONNECTION = 11243 -ER_XPLUGIN_REFERENCE_TO_SECURE_CONN_WITH_XPLUGIN = 11244 -ER_XPLUGIN_ERROR_MSG = 11245 -ER_SHA_PWD_FAILED_TO_PARSE_AUTH_STRING = 11246 -ER_SHA_PWD_FAILED_TO_GENERATE_MULTI_ROUND_HASH = 11247 -ER_SHA_PWD_AUTH_REQUIRES_RSA_OR_SSL = 11248 -ER_SHA_PWD_RSA_KEY_TOO_LONG = 11249 -ER_PLUGIN_COMMON_FAILED_TO_OPEN_FILTER_TABLES = 11250 -ER_PLUGIN_COMMON_FAILED_TO_OPEN_TABLE = 11251 -ER_AUTH_LDAP_ERROR_LOGGER_ERROR_MSG = 11252 -ER_CONN_CONTROL_ERROR_MSG = 11253 -ER_GRP_RPL_ERROR_MSG = 11254 -ER_SHA_PWD_SALT_FOR_USER_CORRUPT = 11255 -ER_SYS_VAR_COMPONENT_OOM = 11256 -ER_SYS_VAR_COMPONENT_VARIABLE_SET_READ_ONLY = 11257 -ER_SYS_VAR_COMPONENT_UNKNOWN_VARIABLE_TYPE = 11258 -ER_SYS_VAR_COMPONENT_FAILED_TO_PARSE_VARIABLE_OPTIONS = 11259 -ER_SYS_VAR_COMPONENT_FAILED_TO_MAKE_VARIABLE_PERSISTENT = 11260 -ER_COMPONENT_FILTER_CONFUSED = 11261 -ER_STOP_SLAVE_IO_THREAD_DISK_SPACE = 11262 -ER_LOG_FILE_CANNOT_OPEN = 11263 -OBSOLETE_ER_UNABLE_TO_COLLECT_INSTANCE_LOG_STATUS = 11264 -OBSOLETE_ER_DEPRECATED_UTF8_ALIAS = 11265 -OBSOLETE_ER_DEPRECATED_NATIONAL = 11266 -OBSOLETE_ER_SLAVE_POSSIBLY_DIVERGED_AFTER_DDL = 11267 -ER_PERSIST_OPTION_STATUS = 11268 -ER_NOT_IMPLEMENTED_GET_TABLESPACE_STATISTICS = 11269 -OBSOLETE_ER_UNABLE_TO_SET_OPTION = 11270 -OBSOLETE_ER_RESERVED_TABLESPACE_NAME = 11271 -ER_SSL_FIPS_MODE_ERROR = 11272 -ER_CONN_INIT_CONNECT_IGNORED = 11273 -ER_UNSUPPORTED_SQL_MODE = 11274 -ER_REWRITER_OOM = 11275 -ER_REWRITER_TABLE_MALFORMED_ERROR = 11276 -ER_REWRITER_LOAD_FAILED = 11277 -ER_REWRITER_READ_FAILED = 11278 -ER_CONN_CONTROL_EVENT_COORDINATOR_INIT_FAILED = 11279 -ER_CONN_CONTROL_STAT_CONN_DELAY_TRIGGERED_UPDATE_FAILED = 11280 -ER_CONN_CONTROL_STAT_CONN_DELAY_TRIGGERED_RESET_FAILED = 11281 -ER_CONN_CONTROL_INVALID_CONN_DELAY_TYPE = 11282 -ER_CONN_CONTROL_DELAY_ACTION_INIT_FAILED = 11283 -ER_CONN_CONTROL_FAILED_TO_SET_CONN_DELAY = 11284 -ER_CONN_CONTROL_FAILED_TO_UPDATE_CONN_DELAY_HASH = 11285 -ER_XPLUGIN_FORCE_STOP_CLIENT = 11286 -ER_XPLUGIN_MAX_AUTH_ATTEMPTS_REACHED = 11287 -ER_XPLUGIN_BUFFER_PAGE_ALLOC_FAILED = 11288 -ER_XPLUGIN_DETECTED_HANGING_CLIENTS = 11289 -ER_XPLUGIN_FAILED_TO_ACCEPT_CLIENT = 11290 -ER_XPLUGIN_FAILED_TO_SCHEDULE_CLIENT = 11291 -ER_XPLUGIN_FAILED_TO_PREPARE_IO_INTERFACES = 11292 -ER_XPLUGIN_SRV_SESSION_INIT_THREAD_FAILED = 11293 -ER_XPLUGIN_UNABLE_TO_USE_USER_SESSION_ACCOUNT = 11294 -ER_XPLUGIN_REFERENCE_TO_USER_ACCOUNT_DOC_SECTION = 11295 -ER_XPLUGIN_UNEXPECTED_EXCEPTION_DISPATCHING_CMD = 11296 -ER_XPLUGIN_EXCEPTION_IN_TASK_SCHEDULER = 11297 -ER_XPLUGIN_TASK_SCHEDULING_FAILED = 11298 -ER_XPLUGIN_EXCEPTION_IN_EVENT_LOOP = 11299 -ER_XPLUGIN_LISTENER_SETUP_FAILED = 11300 -ER_XPLUING_NET_STARTUP_FAILED = 11301 -ER_XPLUGIN_FAILED_AT_SSL_CONF = 11302 -ER_XPLUGIN_CLIENT_SSL_HANDSHAKE_FAILED = 11303 -ER_XPLUGIN_SSL_HANDSHAKE_WITH_SERVER_FAILED = 11304 -ER_XPLUGIN_FAILED_TO_CREATE_SESSION_FOR_CONN = 11305 -ER_XPLUGIN_FAILED_TO_INITIALIZE_SESSION = 11306 -ER_XPLUGIN_MESSAGE_TOO_LONG = 11307 -ER_XPLUGIN_UNINITIALIZED_MESSAGE = 11308 -ER_XPLUGIN_FAILED_TO_SET_MIN_NUMBER_OF_WORKERS = 11309 -ER_XPLUGIN_UNABLE_TO_ACCEPT_CONNECTION = 11310 -ER_XPLUGIN_ALL_IO_INTERFACES_DISABLED = 11311 -ER_XPLUGIN_INVALID_MSG_DURING_CLIENT_INIT = 11312 -ER_XPLUGIN_CLOSING_CLIENTS_ON_SHUTDOWN = 11313 -ER_XPLUGIN_ERROR_READING_SOCKET = 11314 -ER_XPLUGIN_PEER_DISCONNECTED_WHILE_READING_MSG_BODY = 11315 -ER_XPLUGIN_READ_FAILED_CLOSING_CONNECTION = 11316 -ER_XPLUGIN_INVALID_AUTH_METHOD = 11317 -ER_XPLUGIN_UNEXPECTED_MSG_DURING_AUTHENTICATION = 11318 -ER_XPLUGIN_ERROR_WRITING_TO_CLIENT = 11319 -ER_XPLUGIN_SCHEDULER_STARTED = 11320 -ER_XPLUGIN_SCHEDULER_STOPPED = 11321 -ER_XPLUGIN_LISTENER_SYS_VARIABLE_ERROR = 11322 -ER_XPLUGIN_LISTENER_STATUS_MSG = 11323 -ER_XPLUGIN_RETRYING_BIND_ON_PORT = 11324 -ER_XPLUGIN_SHUTDOWN_TRIGGERED = 11325 -ER_XPLUGIN_USER_ACCOUNT_WITH_ALL_PERMISSIONS = 11326 -ER_XPLUGIN_EXISTING_USER_ACCOUNT_WITH_INCOMPLETE_GRANTS = 11327 -ER_XPLUGIN_SERVER_STARTS_HANDLING_CONNECTIONS = 11328 -ER_XPLUGIN_SERVER_STOPPED_HANDLING_CONNECTIONS = 11329 -ER_XPLUGIN_FAILED_TO_INTERRUPT_SESSION = 11330 -ER_XPLUGIN_CLIENT_RELEASE_TRIGGERED = 11331 -ER_XPLUGIN_IPv6_AVAILABLE = 11332 -ER_XPLUGIN_UNIX_SOCKET_NOT_CONFIGURED = 11333 -ER_XPLUGIN_CLIENT_KILL_MSG = 11334 -ER_XPLUGIN_FAILED_TO_GET_SECURITY_CTX = 11335 -ER_XPLUGIN_FAILED_TO_SWITCH_SECURITY_CTX_TO_ROOT = 11336 -ER_XPLUGIN_FAILED_TO_CLOSE_SQL_SESSION = 11337 -ER_XPLUGIN_FAILED_TO_EXECUTE_ADMIN_CMD = 11338 -ER_XPLUGIN_EMPTY_ADMIN_CMD = 11339 -ER_XPLUGIN_FAILED_TO_GET_SYS_VAR = 11340 -ER_XPLUGIN_FAILED_TO_GET_CREATION_STMT = 11341 -ER_XPLUGIN_FAILED_TO_GET_ENGINE_INFO = 11342 -ER_XPLUGIN_FAIL_TO_GET_RESULT_DATA = 11343 -ER_XPLUGIN_CAPABILITY_EXPIRED_PASSWORD = 11344 -ER_XPLUGIN_FAILED_TO_SET_SO_REUSEADDR_FLAG = 11345 -ER_XPLUGIN_FAILED_TO_OPEN_INTERNAL_SESSION = 11346 -ER_XPLUGIN_FAILED_TO_SWITCH_CONTEXT = 11347 -ER_XPLUGIN_FAILED_TO_UNREGISTER_UDF = 11348 -ER_XPLUGIN_GET_PEER_ADDRESS_FAILED = 11349 -ER_XPLUGIN_CAPABILITY_CLIENT_INTERACTIVE_FAILED = 11350 -ER_XPLUGIN_FAILED_TO_RESET_IPV6_V6ONLY_FLAG = 11351 -ER_KEYRING_INVALID_KEY_TYPE = 11352 -ER_KEYRING_INVALID_KEY_LENGTH = 11353 -ER_KEYRING_FAILED_TO_CREATE_KEYRING_DIR = 11354 -ER_KEYRING_FILE_INIT_FAILED = 11355 -ER_KEYRING_INTERNAL_EXCEPTION_FAILED_FILE_INIT = 11356 -ER_KEYRING_FAILED_TO_GENERATE_KEY = 11357 -ER_KEYRING_CHECK_KEY_FAILED_DUE_TO_INVALID_KEY = 11358 -ER_KEYRING_CHECK_KEY_FAILED_DUE_TO_EMPTY_KEY_ID = 11359 -ER_KEYRING_OPERATION_FAILED_DUE_TO_INTERNAL_ERROR = 11360 -ER_KEYRING_INCORRECT_FILE = 11361 -ER_KEYRING_FOUND_MALFORMED_BACKUP_FILE = 11362 -ER_KEYRING_FAILED_TO_RESTORE_FROM_BACKUP_FILE = 11363 -ER_KEYRING_FAILED_TO_FLUSH_KEYRING_TO_FILE = 11364 -ER_KEYRING_FAILED_TO_GET_FILE_STAT = 11365 -ER_KEYRING_FAILED_TO_REMOVE_FILE = 11366 -ER_KEYRING_FAILED_TO_TRUNCATE_FILE = 11367 -ER_KEYRING_UNKNOWN_ERROR = 11368 -ER_KEYRING_FAILED_TO_SET_KEYRING_FILE_DATA = 11369 -ER_KEYRING_FILE_IO_ERROR = 11370 -ER_KEYRING_FAILED_TO_LOAD_KEYRING_CONTENT = 11371 -ER_KEYRING_FAILED_TO_FLUSH_KEYS_TO_KEYRING = 11372 -ER_KEYRING_FAILED_TO_FLUSH_KEYS_TO_KEYRING_BACKUP = 11373 -ER_KEYRING_KEY_FETCH_FAILED_DUE_TO_EMPTY_KEY_ID = 11374 -ER_KEYRING_FAILED_TO_REMOVE_KEY_DUE_TO_EMPTY_ID = 11375 -ER_KEYRING_OKV_INCORRECT_KEY_VAULT_CONFIGURED = 11376 -ER_KEYRING_OKV_INIT_FAILED_DUE_TO_INCORRECT_CONF = 11377 -ER_KEYRING_OKV_INIT_FAILED_DUE_TO_INTERNAL_ERROR = 11378 -ER_KEYRING_OKV_INVALID_KEY_TYPE = 11379 -ER_KEYRING_OKV_INVALID_KEY_LENGTH_FOR_CIPHER = 11380 -ER_KEYRING_OKV_FAILED_TO_GENERATE_KEY_DUE_TO_INTERNAL_ERROR = 11381 -ER_KEYRING_OKV_FAILED_TO_FIND_SERVER_ENTRY = 11382 -ER_KEYRING_OKV_FAILED_TO_FIND_STANDBY_SERVER_ENTRY = 11383 -ER_KEYRING_OKV_FAILED_TO_PARSE_CONF_FILE = 11384 -ER_KEYRING_OKV_FAILED_TO_LOAD_KEY_UID = 11385 -ER_KEYRING_OKV_FAILED_TO_INIT_SSL_LAYER = 11386 -ER_KEYRING_OKV_FAILED_TO_INIT_CLIENT = 11387 -ER_KEYRING_OKV_CONNECTION_TO_SERVER_FAILED = 11388 -ER_KEYRING_OKV_FAILED_TO_REMOVE_KEY = 11389 -ER_KEYRING_OKV_FAILED_TO_ADD_ATTRIBUTE = 11390 -ER_KEYRING_OKV_FAILED_TO_GENERATE_KEY = 11391 -ER_KEYRING_OKV_FAILED_TO_STORE_KEY = 11392 -ER_KEYRING_OKV_FAILED_TO_ACTIVATE_KEYS = 11393 -ER_KEYRING_OKV_FAILED_TO_FETCH_KEY = 11394 -ER_KEYRING_OKV_FAILED_TO_STORE_OR_GENERATE_KEY = 11395 -ER_KEYRING_OKV_FAILED_TO_RETRIEVE_KEY_SIGNATURE = 11396 -ER_KEYRING_OKV_FAILED_TO_RETRIEVE_KEY = 11397 -ER_KEYRING_OKV_FAILED_TO_LOAD_SSL_TRUST_STORE = 11398 -ER_KEYRING_OKV_FAILED_TO_SET_CERTIFICATE_FILE = 11399 -ER_KEYRING_OKV_FAILED_TO_SET_KEY_FILE = 11400 -ER_KEYRING_OKV_KEY_MISMATCH = 11401 -ER_KEYRING_ENCRYPTED_FILE_INCORRECT_KEYRING_FILE = 11402 -ER_KEYRING_ENCRYPTED_FILE_DECRYPTION_FAILED = 11403 -ER_KEYRING_ENCRYPTED_FILE_FOUND_MALFORMED_BACKUP_FILE = 11404 -ER_KEYRING_ENCRYPTED_FILE_FAILED_TO_RESTORE_KEYRING = 11405 -ER_KEYRING_ENCRYPTED_FILE_FAILED_TO_FLUSH_KEYRING = 11406 -ER_KEYRING_ENCRYPTED_FILE_ENCRYPTION_FAILED = 11407 -ER_KEYRING_ENCRYPTED_FILE_INVALID_KEYRING_DIR = 11408 -ER_KEYRING_ENCRYPTED_FILE_FAILED_TO_CREATE_KEYRING_DIR = 11409 -ER_KEYRING_ENCRYPTED_FILE_PASSWORD_IS_INVALID = 11410 -ER_KEYRING_ENCRYPTED_FILE_PASSWORD_IS_TOO_LONG = 11411 -ER_KEYRING_ENCRYPTED_FILE_INIT_FAILURE = 11412 -ER_KEYRING_ENCRYPTED_FILE_INIT_FAILED_DUE_TO_INTERNAL_ERROR = 11413 -ER_KEYRING_ENCRYPTED_FILE_GEN_KEY_FAILED_DUE_TO_INTERNAL_ERROR = 11414 -ER_KEYRING_AWS_FAILED_TO_SET_CMK_ID = 11415 -ER_KEYRING_AWS_FAILED_TO_SET_REGION = 11416 -ER_KEYRING_AWS_FAILED_TO_OPEN_CONF_FILE = 11417 -ER_KEYRING_AWS_FAILED_TO_ACCESS_KEY_ID_FROM_CONF_FILE = 11418 -ER_KEYRING_AWS_FAILED_TO_ACCESS_KEY_FROM_CONF_FILE = 11419 -ER_KEYRING_AWS_INVALID_CONF_FILE_PATH = 11420 -ER_KEYRING_AWS_INVALID_DATA_FILE_PATH = 11421 -ER_KEYRING_AWS_FAILED_TO_ACCESS_OR_CREATE_KEYRING_DIR = 11422 -ER_KEYRING_AWS_FAILED_TO_ACCESS_OR_CREATE_KEYRING_DATA_FILE = 11423 -ER_KEYRING_AWS_FAILED_TO_INIT_DUE_TO_INTERNAL_ERROR = 11424 -ER_KEYRING_AWS_FAILED_TO_ACCESS_DATA_FILE = 11425 -ER_KEYRING_AWS_CMK_ID_NOT_SET = 11426 -ER_KEYRING_AWS_FAILED_TO_GET_KMS_CREDENTIAL_FROM_CONF_FILE = 11427 -ER_KEYRING_AWS_INIT_FAILURE = 11428 -ER_KEYRING_AWS_FAILED_TO_INIT_DUE_TO_PLUGIN_INTERNAL_ERROR = 11429 -ER_KEYRING_AWS_INVALID_KEY_LENGTH_FOR_CIPHER = 11430 -ER_KEYRING_AWS_FAILED_TO_GENERATE_KEY_DUE_TO_INTERNAL_ERROR = 11431 -ER_KEYRING_AWS_INCORRECT_FILE = 11432 -ER_KEYRING_AWS_FOUND_MALFORMED_BACKUP_FILE = 11433 -ER_KEYRING_AWS_FAILED_TO_RESTORE_FROM_BACKUP_FILE = 11434 -ER_KEYRING_AWS_FAILED_TO_FLUSH_KEYRING_TO_FILE = 11435 -ER_KEYRING_AWS_INCORRECT_REGION = 11436 -ER_KEYRING_AWS_FAILED_TO_CONNECT_KMS = 11437 -ER_KEYRING_AWS_FAILED_TO_GENERATE_NEW_KEY = 11438 -ER_KEYRING_AWS_FAILED_TO_ENCRYPT_KEY = 11439 -ER_KEYRING_AWS_FAILED_TO_RE_ENCRYPT_KEY = 11440 -ER_KEYRING_AWS_FAILED_TO_DECRYPT_KEY = 11441 -ER_KEYRING_AWS_FAILED_TO_ROTATE_CMK = 11442 -ER_GRP_RPL_GTID_ALREADY_USED = 11443 -ER_GRP_RPL_APPLIER_THD_KILLED = 11444 -ER_GRP_RPL_EVENT_HANDLING_ERROR = 11445 -ER_GRP_RPL_ERROR_GTID_EXECUTION_INFO = 11446 -ER_GRP_RPL_CERTIFICATE_SIZE_ERROR = 11447 -ER_GRP_RPL_CREATE_APPLIER_CACHE_ERROR = 11448 -ER_GRP_RPL_UNBLOCK_WAITING_THD = 11449 -ER_GRP_RPL_APPLIER_PIPELINE_NOT_DISPOSED = 11450 -ER_GRP_RPL_APPLIER_THD_EXECUTION_ABORTED = 11451 -ER_GRP_RPL_APPLIER_EXECUTION_FATAL_ERROR = 11452 -ER_GRP_RPL_ERROR_STOPPING_CHANNELS = 11453 -ER_GRP_RPL_ERROR_SENDING_SINGLE_PRIMARY_MSSG = 11454 -ER_GRP_RPL_UPDATE_TRANS_SNAPSHOT_VER_ERROR = 11455 -ER_GRP_RPL_SIDNO_FETCH_ERROR = 11456 -ER_GRP_RPL_BROADCAST_COMMIT_TRANS_MSSG_FAILED = 11457 -ER_GRP_RPL_GROUP_NAME_PARSE_ERROR = 11458 -ER_GRP_RPL_ADD_GRPSID_TO_GRPGTIDSID_MAP_ERROR = 11459 -ER_GRP_RPL_UPDATE_GRPGTID_EXECUTED_ERROR = 11460 -ER_GRP_RPL_DONOR_TRANS_INFO_ERROR = 11461 -ER_GRP_RPL_SERVER_CONN_ERROR = 11462 -ER_GRP_RPL_ERROR_FETCHING_GTID_EXECUTED_SET = 11463 -ER_GRP_RPL_ADD_GTID_TO_GRPGTID_EXECUTED_ERROR = 11464 -ER_GRP_RPL_ERROR_FETCHING_GTID_SET = 11465 -ER_GRP_RPL_ADD_RETRIEVED_SET_TO_GRP_GTID_EXECUTED_ERROR = 11466 -ER_GRP_RPL_CERTIFICATION_INITIALIZATION_FAILURE = 11467 -ER_GRP_RPL_UPDATE_LAST_CONFLICT_FREE_TRANS_ERROR = 11468 -ER_GRP_RPL_UPDATE_TRANS_SNAPSHOT_REF_VER_ERROR = 11469 -ER_GRP_RPL_FETCH_TRANS_SIDNO_ERROR = 11470 -ER_GRP_RPL_ERROR_VERIFYING_SIDNO = 11471 -ER_GRP_RPL_CANT_GENERATE_GTID = 11472 -ER_GRP_RPL_INVALID_GTID_SET = 11473 -ER_GRP_RPL_UPDATE_GTID_SET_ERROR = 11474 -ER_GRP_RPL_RECEIVED_SET_MISSING_GTIDS = 11475 -ER_GRP_RPL_SKIP_COMPUTATION_TRANS_COMMITTED = 11476 -ER_GRP_RPL_NULL_PACKET = 11477 -ER_GRP_RPL_CANT_READ_GTID = 11478 -ER_GRP_RPL_PROCESS_GTID_SET_ERROR = 11479 -ER_GRP_RPL_PROCESS_INTERSECTION_GTID_SET_ERROR = 11480 -ER_GRP_RPL_SET_STABLE_TRANS_ERROR = 11481 -ER_GRP_RPL_CANT_READ_GRP_GTID_EXTRACTED = 11482 -ER_GRP_RPL_CANT_READ_WRITE_SET_ITEM = 11483 -ER_GRP_RPL_INIT_CERTIFICATION_INFO_FAILURE = 11484 -ER_GRP_RPL_CONFLICT_DETECTION_DISABLED = 11485 -ER_GRP_RPL_MSG_DISCARDED = 11486 -ER_GRP_RPL_MISSING_GRP_RPL_APPLIER = 11487 -ER_GRP_RPL_CERTIFIER_MSSG_PROCESS_ERROR = 11488 -ER_GRP_RPL_SRV_NOT_ONLINE = 11489 -ER_GRP_RPL_SRV_ONLINE = 11490 -ER_GRP_RPL_DISABLE_SRV_READ_MODE_RESTRICTED = 11491 -ER_GRP_RPL_MEM_ONLINE = 11492 -ER_GRP_RPL_MEM_UNREACHABLE = 11493 -ER_GRP_RPL_MEM_REACHABLE = 11494 -ER_GRP_RPL_SRV_BLOCKED = 11495 -ER_GRP_RPL_SRV_BLOCKED_FOR_SECS = 11496 -ER_GRP_RPL_CHANGE_GRP_MEM_NOT_PROCESSED = 11497 -ER_GRP_RPL_MEMBER_CONTACT_RESTORED = 11498 -ER_GRP_RPL_MEMBER_REMOVED = 11499 -ER_GRP_RPL_PRIMARY_MEMBER_LEFT_GRP = 11500 -ER_GRP_RPL_MEMBER_ADDED = 11501 -ER_GRP_RPL_MEMBER_EXIT_PLUGIN_ERROR = 11502 -ER_GRP_RPL_MEMBER_CHANGE = 11503 -ER_GRP_RPL_MEMBER_LEFT_GRP = 11504 -ER_GRP_RPL_MEMBER_EXPELLED = 11505 -ER_GRP_RPL_SESSION_OPEN_FAILED = 11506 -ER_GRP_RPL_NEW_PRIMARY_ELECTED = 11507 -ER_GRP_RPL_DISABLE_READ_ONLY_FAILED = 11508 -ER_GRP_RPL_ENABLE_READ_ONLY_FAILED = 11509 -ER_GRP_RPL_SRV_PRIMARY_MEM = 11510 -ER_GRP_RPL_SRV_SECONDARY_MEM = 11511 -ER_GRP_RPL_NO_SUITABLE_PRIMARY_MEM = 11512 -ER_GRP_RPL_SUPER_READ_ONLY_ACTIVATE_ERROR = 11513 -ER_GRP_RPL_EXCEEDS_AUTO_INC_VALUE = 11514 -ER_GRP_RPL_DATA_NOT_PROVIDED_BY_MEM = 11515 -ER_GRP_RPL_MEMBER_ALREADY_EXISTS = 11516 -ER_GRP_RPL_GRP_CHANGE_INFO_EXTRACT_ERROR = 11517 -ER_GRP_RPL_GTID_EXECUTED_EXTRACT_ERROR = 11518 -ER_GRP_RPL_GTID_SET_EXTRACT_ERROR = 11519 -ER_GRP_RPL_START_FAILED = 11520 -ER_GRP_RPL_MEMBER_VER_INCOMPATIBLE = 11521 -ER_GRP_RPL_TRANS_NOT_PRESENT_IN_GRP = 11522 -ER_GRP_RPL_TRANS_GREATER_THAN_GRP = 11523 -ER_GRP_RPL_MEMBER_VERSION_LOWER_THAN_GRP = 11524 -ER_GRP_RPL_LOCAL_GTID_SETS_PROCESS_ERROR = 11525 -ER_GRP_RPL_MEMBER_TRANS_GREATER_THAN_GRP = 11526 -ER_GRP_RPL_BLOCK_SIZE_DIFF_FROM_GRP = 11527 -ER_GRP_RPL_TRANS_WRITE_SET_EXTRACT_DIFF_FROM_GRP = 11528 -ER_GRP_RPL_MEMBER_CFG_INCOMPATIBLE_WITH_GRP_CFG = 11529 -ER_GRP_RPL_MEMBER_STOP_RPL_CHANNELS_ERROR = 11530 -ER_GRP_RPL_PURGE_APPLIER_LOGS = 11531 -ER_GRP_RPL_RESET_APPLIER_MODULE_LOGS_ERROR = 11532 -ER_GRP_RPL_APPLIER_THD_SETUP_ERROR = 11533 -ER_GRP_RPL_APPLIER_THD_START_ERROR = 11534 -ER_GRP_RPL_APPLIER_THD_STOP_ERROR = 11535 -ER_GRP_RPL_FETCH_TRANS_DATA_FAILED = 11536 -ER_GRP_RPL_SLAVE_IO_THD_PRIMARY_UNKNOWN = 11537 -ER_GRP_RPL_SALVE_IO_THD_ON_SECONDARY_MEMBER = 11538 -ER_GRP_RPL_SLAVE_SQL_THD_PRIMARY_UNKNOWN = 11539 -ER_GRP_RPL_SLAVE_SQL_THD_ON_SECONDARY_MEMBER = 11540 -ER_GRP_RPL_NEEDS_INNODB_TABLE = 11541 -ER_GRP_RPL_PRIMARY_KEY_NOT_DEFINED = 11542 -ER_GRP_RPL_FK_WITH_CASCADE_UNSUPPORTED = 11543 -ER_GRP_RPL_AUTO_INC_RESET = 11544 -ER_GRP_RPL_AUTO_INC_OFFSET_RESET = 11545 -ER_GRP_RPL_AUTO_INC_SET = 11546 -ER_GRP_RPL_AUTO_INC_OFFSET_SET = 11547 -ER_GRP_RPL_FETCH_TRANS_CONTEXT_FAILED = 11548 -ER_GRP_RPL_FETCH_FORMAT_DESC_LOG_EVENT_FAILED = 11549 -ER_GRP_RPL_FETCH_TRANS_CONTEXT_LOG_EVENT_FAILED = 11550 -ER_GRP_RPL_FETCH_SNAPSHOT_VERSION_FAILED = 11551 -ER_GRP_RPL_FETCH_GTID_LOG_EVENT_FAILED = 11552 -ER_GRP_RPL_UPDATE_SERV_CERTIFICATE_FAILED = 11553 -ER_GRP_RPL_ADD_GTID_INFO_WITH_LOCAL_GTID_FAILED = 11554 -ER_GRP_RPL_ADD_GTID_INFO_WITHOUT_LOCAL_GTID_FAILED = 11555 -ER_GRP_RPL_NOTIFY_CERTIFICATION_OUTCOME_FAILED = 11556 -ER_GRP_RPL_ADD_GTID_INFO_WITH_REMOTE_GTID_FAILED = 11557 -ER_GRP_RPL_ADD_GTID_INFO_WITHOUT_REMOTE_GTID_FAILED = 11558 -ER_GRP_RPL_FETCH_VIEW_CHANGE_LOG_EVENT_FAILED = 11559 -ER_GRP_RPL_CONTACT_WITH_SRV_FAILED = 11560 -ER_GRP_RPL_SRV_WAIT_TIME_OUT = 11561 -ER_GRP_RPL_FETCH_LOG_EVENT_FAILED = 11562 -ER_GRP_RPL_START_GRP_RPL_FAILED = 11563 -ER_GRP_RPL_CONN_INTERNAL_PLUGIN_FAIL = 11564 -ER_GRP_RPL_SUPER_READ_ON = 11565 -ER_GRP_RPL_SUPER_READ_OFF = 11566 -ER_GRP_RPL_KILLED_SESSION_ID = 11567 -ER_GRP_RPL_KILLED_FAILED_ID = 11568 -ER_GRP_RPL_INTERNAL_QUERY = 11569 -ER_GRP_RPL_COPY_FROM_EMPTY_STRING = 11570 -ER_GRP_RPL_QUERY_FAIL = 11571 -ER_GRP_RPL_CREATE_SESSION_UNABLE = 11572 -ER_GRP_RPL_MEMBER_NOT_FOUND = 11573 -ER_GRP_RPL_MAXIMUM_CONNECTION_RETRIES_REACHED = 11574 -ER_GRP_RPL_ALL_DONORS_LEFT_ABORT_RECOVERY = 11575 -ER_GRP_RPL_ESTABLISH_RECOVERY_WITH_DONOR = 11576 -ER_GRP_RPL_ESTABLISH_RECOVERY_WITH_ANOTHER_DONOR = 11577 -ER_GRP_RPL_NO_VALID_DONOR = 11578 -ER_GRP_RPL_CONFIG_RECOVERY = 11579 -ER_GRP_RPL_ESTABLISHING_CONN_GRP_REC_DONOR = 11580 -ER_GRP_RPL_CREATE_GRP_RPL_REC_CHANNEL = 11581 -ER_GRP_RPL_DONOR_SERVER_CONN = 11582 -ER_GRP_RPL_CHECK_STATUS_TABLE = 11583 -ER_GRP_RPL_STARTING_GRP_REC = 11584 -ER_GRP_RPL_DONOR_CONN_TERMINATION = 11585 -ER_GRP_RPL_STOPPING_GRP_REC = 11586 -ER_GRP_RPL_PURGE_REC = 11587 -ER_GRP_RPL_UNABLE_TO_KILL_CONN_REC_DONOR_APPLIER = 11588 -ER_GRP_RPL_UNABLE_TO_KILL_CONN_REC_DONOR_FAILOVER = 11589 -ER_GRP_RPL_FAILED_TO_NOTIFY_GRP_MEMBERSHIP_EVENT = 11590 -ER_GRP_RPL_FAILED_TO_BROADCAST_GRP_MEMBERSHIP_NOTIFICATION = 11591 -ER_GRP_RPL_FAILED_TO_BROADCAST_MEMBER_STATUS_NOTIFICATION = 11592 -ER_GRP_RPL_OOM_FAILED_TO_GENERATE_IDENTIFICATION_HASH = 11593 -ER_GRP_RPL_WRITE_IDENT_HASH_BASE64_ENCODING_FAILED = 11594 -ER_GRP_RPL_INVALID_BINLOG_FORMAT = 11595 -ER_GRP_RPL_BINLOG_CHECKSUM_SET = 11596 -ER_GRP_RPL_TRANS_WRITE_SET_EXTRACTION_NOT_SET = 11597 -ER_GRP_RPL_UNSUPPORTED_TRANS_ISOLATION = 11598 -ER_GRP_RPL_CANNOT_EXECUTE_TRANS_WHILE_STOPPING = 11599 -ER_GRP_RPL_CANNOT_EXECUTE_TRANS_WHILE_RECOVERING = 11600 -ER_GRP_RPL_CANNOT_EXECUTE_TRANS_IN_ERROR_STATE = 11601 -ER_GRP_RPL_CANNOT_EXECUTE_TRANS_IN_OFFLINE_MODE = 11602 -ER_GRP_RPL_MULTIPLE_CACHE_TYPE_NOT_SUPPORTED_FOR_SESSION = 11603 -ER_GRP_RPL_FAILED_TO_REINIT_BINLOG_CACHE_FOR_READ = 11604 -ER_GRP_RPL_FAILED_TO_CREATE_TRANS_CONTEXT = 11605 -ER_GRP_RPL_FAILED_TO_EXTRACT_TRANS_WRITE_SET = 11606 -ER_GRP_RPL_FAILED_TO_GATHER_TRANS_WRITE_SET = 11607 -ER_GRP_RPL_TRANS_SIZE_EXCEEDS_LIMIT = 11608 -ER_GRP_RPL_REINIT_OF_INTERNAL_CACHE_FOR_READ_FAILED = 11609 -ER_GRP_RPL_APPENDING_DATA_TO_INTERNAL_CACHE_FAILED = 11610 -ER_GRP_RPL_WRITE_TO_BINLOG_CACHE_FAILED = 11611 -ER_GRP_RPL_FAILED_TO_REGISTER_TRANS_OUTCOME_NOTIFICTION = 11612 -ER_GRP_RPL_MSG_TOO_LONG_BROADCASTING_TRANS_FAILED = 11613 -ER_GRP_RPL_BROADCASTING_TRANS_TO_GRP_FAILED = 11614 -ER_GRP_RPL_ERROR_WHILE_WAITING_FOR_CONFLICT_DETECTION = 11615 -ER_GRP_RPL_REINIT_OF_INTERNAL_CACHE_FOR_WRITE_FAILED = 11616 -ER_GRP_RPL_FAILED_TO_CREATE_COMMIT_CACHE = 11617 -ER_GRP_RPL_REINIT_OF_COMMIT_CACHE_FOR_WRITE_FAILED = 11618 -ER_GRP_RPL_PREV_REC_SESSION_RUNNING = 11619 -ER_GRP_RPL_FATAL_REC_PROCESS = 11620 -ER_GRP_RPL_WHILE_STOPPING_REP_CHANNEL = 11621 -ER_GRP_RPL_UNABLE_TO_EVALUATE_APPLIER_STATUS = 11622 -ER_GRP_RPL_ONLY_ONE_SERVER_ALIVE = 11623 -ER_GRP_RPL_CERTIFICATION_REC_PROCESS = 11624 -ER_GRP_RPL_UNABLE_TO_ENSURE_EXECUTION_REC = 11625 -ER_GRP_RPL_WHILE_SENDING_MSG_REC = 11626 -ER_GRP_RPL_READ_UNABLE_FOR_SUPER_READ_ONLY = 11627 -ER_GRP_RPL_READ_UNABLE_FOR_READ_ONLY_SUPER_READ_ONLY = 11628 -ER_GRP_RPL_UNABLE_TO_RESET_SERVER_READ_MODE = 11629 -ER_GRP_RPL_UNABLE_TO_CERTIFY_PLUGIN_TRANS = 11630 -ER_GRP_RPL_UNBLOCK_CERTIFIED_TRANS = 11631 -ER_GRP_RPL_SERVER_WORKING_AS_SECONDARY = 11632 -ER_GRP_RPL_FAILED_TO_START_WITH_INVALID_SERVER_ID = 11633 -ER_GRP_RPL_FORCE_MEMBERS_MUST_BE_EMPTY = 11634 -ER_GRP_RPL_PLUGIN_STRUCT_INIT_NOT_POSSIBLE_ON_SERVER_START = 11635 -ER_GRP_RPL_FAILED_TO_ENABLE_SUPER_READ_ONLY_MODE = 11636 -ER_GRP_RPL_FAILED_TO_INIT_COMMUNICATION_ENGINE = 11637 -ER_GRP_RPL_FAILED_TO_START_ON_SECONDARY_WITH_ASYNC_CHANNELS = 11638 -ER_GRP_RPL_FAILED_TO_START_COMMUNICATION_ENGINE = 11639 -ER_GRP_RPL_TIMEOUT_ON_VIEW_AFTER_JOINING_GRP = 11640 -ER_GRP_RPL_FAILED_TO_CALL_GRP_COMMUNICATION_INTERFACE = 11641 -ER_GRP_RPL_MEMBER_SERVER_UUID_IS_INCOMPATIBLE_WITH_GRP = 11642 -ER_GRP_RPL_MEMBER_CONF_INFO = 11643 -ER_GRP_RPL_FAILED_TO_CONFIRM_IF_SERVER_LEFT_GRP = 11644 -ER_GRP_RPL_SERVER_IS_ALREADY_LEAVING = 11645 -ER_GRP_RPL_SERVER_ALREADY_LEFT = 11646 -ER_GRP_RPL_WAITING_FOR_VIEW_UPDATE = 11647 -ER_GRP_RPL_TIMEOUT_RECEIVING_VIEW_CHANGE_ON_SHUTDOWN = 11648 -ER_GRP_RPL_REQUESTING_NON_MEMBER_SERVER_TO_LEAVE = 11649 -ER_GRP_RPL_IS_STOPPING = 11650 -ER_GRP_RPL_IS_STOPPED = 11651 -ER_GRP_RPL_FAILED_TO_ENABLE_READ_ONLY_MODE_ON_SHUTDOWN = 11652 -ER_GRP_RPL_RECOVERY_MODULE_TERMINATION_TIMED_OUT_ON_SHUTDOWN = 11653 -ER_GRP_RPL_APPLIER_TERMINATION_TIMED_OUT_ON_SHUTDOWN = 11654 -ER_GRP_RPL_FAILED_TO_SHUTDOWN_REGISTRY_MODULE = 11655 -ER_GRP_RPL_FAILED_TO_INIT_HANDLER = 11656 -ER_GRP_RPL_FAILED_TO_REGISTER_SERVER_STATE_OBSERVER = 11657 -ER_GRP_RPL_FAILED_TO_REGISTER_TRANS_STATE_OBSERVER = 11658 -ER_GRP_RPL_FAILED_TO_REGISTER_BINLOG_STATE_OBSERVER = 11659 -ER_GRP_RPL_FAILED_TO_START_ON_BOOT = 11660 -ER_GRP_RPL_FAILED_TO_STOP_ON_PLUGIN_UNINSTALL = 11661 -ER_GRP_RPL_FAILED_TO_UNREGISTER_SERVER_STATE_OBSERVER = 11662 -ER_GRP_RPL_FAILED_TO_UNREGISTER_TRANS_STATE_OBSERVER = 11663 -ER_GRP_RPL_FAILED_TO_UNREGISTER_BINLOG_STATE_OBSERVER = 11664 -ER_GRP_RPL_ALL_OBSERVERS_UNREGISTERED = 11665 -ER_GRP_RPL_FAILED_TO_PARSE_THE_GRP_NAME = 11666 -ER_GRP_RPL_FAILED_TO_GENERATE_SIDNO_FOR_GRP = 11667 -ER_GRP_RPL_APPLIER_NOT_STARTED_DUE_TO_RUNNING_PREV_SHUTDOWN = 11668 -ER_GRP_RPL_FAILED_TO_INIT_APPLIER_MODULE = 11669 -ER_GRP_RPL_APPLIER_INITIALIZED = 11670 -ER_GRP_RPL_COMMUNICATION_SSL_CONF_INFO = 11671 -ER_GRP_RPL_ABORTS_AS_SSL_NOT_SUPPORTED_BY_MYSQLD = 11672 -ER_GRP_RPL_SSL_DISABLED = 11673 -ER_GRP_RPL_UNABLE_TO_INIT_COMMUNICATION_ENGINE = 11674 -ER_GRP_RPL_BINLOG_DISABLED = 11675 -ER_GRP_RPL_GTID_MODE_OFF = 11676 -ER_GRP_RPL_LOG_SLAVE_UPDATES_NOT_SET = 11677 -ER_GRP_RPL_INVALID_TRANS_WRITE_SET_EXTRACTION_VALUE = 11678 -ER_GRP_RPL_RELAY_LOG_INFO_REPO_MUST_BE_TABLE = 11679 -ER_GRP_RPL_MASTER_INFO_REPO_MUST_BE_TABLE = 11680 -ER_GRP_RPL_INCORRECT_TYPE_SET_FOR_PARALLEL_APPLIER = 11681 -ER_GRP_RPL_SLAVE_PRESERVE_COMMIT_ORDER_NOT_SET = 11682 -ER_GRP_RPL_SINGLE_PRIM_MODE_NOT_ALLOWED_WITH_UPDATE_EVERYWHERE = 11683 -ER_GRP_RPL_MODULE_TERMINATE_ERROR = 11684 -ER_GRP_RPL_GRP_NAME_OPTION_MANDATORY = 11685 -ER_GRP_RPL_GRP_NAME_IS_TOO_LONG = 11686 -ER_GRP_RPL_GRP_NAME_IS_NOT_VALID_UUID = 11687 -ER_GRP_RPL_FLOW_CTRL_MIN_QUOTA_GREATER_THAN_MAX_QUOTA = 11688 -ER_GRP_RPL_FLOW_CTRL_MIN_RECOVERY_QUOTA_GREATER_THAN_MAX_QUOTA = 11689 -ER_GRP_RPL_FLOW_CTRL_MAX_QUOTA_SMALLER_THAN_MIN_QUOTAS = 11690 -ER_GRP_RPL_INVALID_SSL_RECOVERY_STRING = 11691 -ER_GRP_RPL_SUPPORTS_ONLY_ONE_FORCE_MEMBERS_SET = 11692 -ER_GRP_RPL_FORCE_MEMBERS_SET_UPDATE_NOT_ALLOWED = 11693 -ER_GRP_RPL_GRP_COMMUNICATION_INIT_WITH_CONF = 11694 -ER_GRP_RPL_UNKNOWN_GRP_RPL_APPLIER_PIPELINE_REQUESTED = 11695 -ER_GRP_RPL_FAILED_TO_BOOTSTRAP_EVENT_HANDLING_INFRASTRUCTURE = 11696 -ER_GRP_RPL_APPLIER_HANDLER_NOT_INITIALIZED = 11697 -ER_GRP_RPL_APPLIER_HANDLER_IS_IN_USE = 11698 -ER_GRP_RPL_APPLIER_HANDLER_ROLE_IS_IN_USE = 11699 -ER_GRP_RPL_FAILED_TO_INIT_APPLIER_HANDLER = 11700 -ER_GRP_RPL_SQL_SERVICE_FAILED_TO_INIT_SESSION_THREAD = 11701 -ER_GRP_RPL_SQL_SERVICE_COMM_SESSION_NOT_INITIALIZED = 11702 -ER_GRP_RPL_SQL_SERVICE_SERVER_SESSION_KILLED = 11703 -ER_GRP_RPL_SQL_SERVICE_FAILED_TO_RUN_SQL_QUERY = 11704 -ER_GRP_RPL_SQL_SERVICE_SERVER_INTERNAL_FAILURE = 11705 -ER_GRP_RPL_SQL_SERVICE_RETRIES_EXCEEDED_ON_SESSION_STATE = 11706 -ER_GRP_RPL_SQL_SERVICE_FAILED_TO_FETCH_SECURITY_CTX = 11707 -ER_GRP_RPL_SQL_SERVICE_SERVER_ACCESS_DENIED_FOR_USER = 11708 -ER_GRP_RPL_SQL_SERVICE_MAX_CONN_ERROR_FROM_SERVER = 11709 -ER_GRP_RPL_SQL_SERVICE_SERVER_ERROR_ON_CONN = 11710 -ER_GRP_RPL_UNREACHABLE_MAJORITY_TIMEOUT_FOR_MEMBER = 11711 -ER_GRP_RPL_SERVER_SET_TO_READ_ONLY_DUE_TO_ERRORS = 11712 -ER_GRP_RPL_GMS_LISTENER_FAILED_TO_LOG_NOTIFICATION = 11713 -ER_GRP_RPL_GRP_COMMUNICATION_ENG_INIT_FAILED = 11714 -ER_GRP_RPL_SET_GRP_COMMUNICATION_ENG_LOGGER_FAILED = 11715 -ER_GRP_RPL_DEBUG_OPTIONS = 11716 -ER_GRP_RPL_INVALID_DEBUG_OPTIONS = 11717 -ER_GRP_RPL_EXIT_GRP_GCS_ERROR = 11718 -ER_GRP_RPL_GRP_MEMBER_OFFLINE = 11719 -ER_GRP_RPL_GCS_INTERFACE_ERROR = 11720 -ER_GRP_RPL_FORCE_MEMBER_VALUE_SET_ERROR = 11721 -ER_GRP_RPL_FORCE_MEMBER_VALUE_SET = 11722 -ER_GRP_RPL_FORCE_MEMBER_VALUE_TIME_OUT = 11723 -ER_GRP_RPL_BROADCAST_COMMIT_MSSG_TOO_BIG = 11724 -ER_GRP_RPL_SEND_STATS_ERROR = 11725 -ER_GRP_RPL_MEMBER_STATS_INFO = 11726 -ER_GRP_RPL_FLOW_CONTROL_STATS = 11727 -ER_GRP_RPL_UNABLE_TO_CONVERT_PACKET_TO_EVENT = 11728 -ER_GRP_RPL_PIPELINE_CREATE_FAILED = 11729 -ER_GRP_RPL_PIPELINE_REINIT_FAILED_WRITE = 11730 -ER_GRP_RPL_UNABLE_TO_CONVERT_EVENT_TO_PACKET = 11731 -ER_GRP_RPL_PIPELINE_FLUSH_FAIL = 11732 -ER_GRP_RPL_PIPELINE_REINIT_FAILED_READ = 11733 -ER_GRP_RPL_STOP_REP_CHANNEL = 11734 -ER_GRP_RPL_GCS_GR_ERROR_MSG = 11735 -ER_GRP_RPL_SLAVE_IO_THREAD_UNBLOCKED = 11736 -ER_GRP_RPL_SLAVE_IO_THREAD_ERROR_OUT = 11737 -ER_GRP_RPL_SLAVE_APPLIER_THREAD_UNBLOCKED = 11738 -ER_GRP_RPL_SLAVE_APPLIER_THREAD_ERROR_OUT = 11739 -ER_LDAP_AUTH_FAILED_TO_CREATE_OR_GET_CONNECTION = 11740 -ER_LDAP_AUTH_DEINIT_FAILED = 11741 -ER_LDAP_AUTH_SKIPPING_USER_GROUP_SEARCH = 11742 -ER_LDAP_AUTH_POOL_DISABLE_MAX_SIZE_ZERO = 11743 -ER_LDAP_AUTH_FAILED_TO_CREATE_LDAP_OBJECT_CREATOR = 11744 -ER_LDAP_AUTH_FAILED_TO_CREATE_LDAP_OBJECT = 11745 -ER_LDAP_AUTH_TLS_CONF = 11746 -ER_LDAP_AUTH_TLS_CONNECTION = 11747 -ER_LDAP_AUTH_CONN_POOL_NOT_CREATED = 11748 -ER_LDAP_AUTH_CONN_POOL_INITIALIZING = 11749 -ER_LDAP_AUTH_CONN_POOL_DEINITIALIZING = 11750 -ER_LDAP_AUTH_ZERO_MAX_POOL_SIZE_UNCHANGED = 11751 -ER_LDAP_AUTH_POOL_REINITIALIZING = 11752 -ER_LDAP_AUTH_FAILED_TO_WRITE_PACKET = 11753 -ER_LDAP_AUTH_SETTING_USERNAME = 11754 -ER_LDAP_AUTH_USER_AUTH_DATA = 11755 -ER_LDAP_AUTH_INFO_FOR_USER = 11756 -ER_LDAP_AUTH_USER_GROUP_SEARCH_INFO = 11757 -ER_LDAP_AUTH_GRP_SEARCH_SPECIAL_HDL = 11758 -ER_LDAP_AUTH_GRP_IS_FULL_DN = 11759 -ER_LDAP_AUTH_USER_NOT_FOUND_IN_ANY_GRP = 11760 -ER_LDAP_AUTH_USER_FOUND_IN_MANY_GRPS = 11761 -ER_LDAP_AUTH_USER_HAS_MULTIPLE_GRP_NAMES = 11762 -ER_LDAP_AUTH_SEARCHED_USER_GRP_NAME = 11763 -ER_LDAP_AUTH_OBJECT_CREATE_TIMESTAMP = 11764 -ER_LDAP_AUTH_CERTIFICATE_NAME = 11765 -ER_LDAP_AUTH_FAILED_TO_POOL_DEINIT = 11766 -ER_LDAP_AUTH_FAILED_TO_INITIALIZE_POOL_IN_RECONSTRUCTING = 11767 -ER_LDAP_AUTH_FAILED_TO_INITIALIZE_POOL_IN_INIT_STATE = 11768 -ER_LDAP_AUTH_FAILED_TO_INITIALIZE_POOL_IN_DEINIT_STATE = 11769 -ER_LDAP_AUTH_FAILED_TO_DEINITIALIZE_POOL_IN_RECONSTRUCT_STATE = 11770 -ER_LDAP_AUTH_FAILED_TO_DEINITIALIZE_NOT_READY_POOL = 11771 -ER_LDAP_AUTH_FAILED_TO_GET_CONNECTION_AS_PLUGIN_NOT_READY = 11772 -ER_LDAP_AUTH_CONNECTION_POOL_INIT_FAILED = 11773 -ER_LDAP_AUTH_MAX_ALLOWED_CONNECTION_LIMIT_HIT = 11774 -ER_LDAP_AUTH_MAX_POOL_SIZE_SET_FAILED = 11775 -ER_LDAP_AUTH_PLUGIN_FAILED_TO_READ_PACKET = 11776 -ER_LDAP_AUTH_CREATING_LDAP_CONNECTION = 11777 -ER_LDAP_AUTH_GETTING_CONNECTION_FROM_POOL = 11778 -ER_LDAP_AUTH_RETURNING_CONNECTION_TO_POOL = 11779 -ER_LDAP_AUTH_SEARCH_USER_GROUP_ATTR_NOT_FOUND = 11780 -ER_LDAP_AUTH_LDAP_INFO_NULL = 11781 -ER_LDAP_AUTH_FREEING_CONNECTION = 11782 -ER_LDAP_AUTH_CONNECTION_PUSHED_TO_POOL = 11783 -ER_LDAP_AUTH_CONNECTION_CREATOR_ENTER = 11784 -ER_LDAP_AUTH_STARTING_TLS = 11785 -ER_LDAP_AUTH_CONNECTION_GET_LDAP_INFO_NULL = 11786 -ER_LDAP_AUTH_DELETING_CONNECTION_KEY = 11787 -ER_LDAP_AUTH_POOLED_CONNECTION_KEY = 11788 -ER_LDAP_AUTH_CREATE_CONNECTION_KEY = 11789 -ER_LDAP_AUTH_COMMUNICATION_HOST_INFO = 11790 -ER_LDAP_AUTH_METHOD_TO_CLIENT = 11791 -ER_LDAP_AUTH_SASL_REQUEST_FROM_CLIENT = 11792 -ER_LDAP_AUTH_SASL_PROCESS_SASL = 11793 -ER_LDAP_AUTH_SASL_BIND_SUCCESS_INFO = 11794 -ER_LDAP_AUTH_STARTED_FOR_USER = 11795 -ER_LDAP_AUTH_DISTINGUISHED_NAME = 11796 -ER_LDAP_AUTH_INIT_FAILED = 11797 -ER_LDAP_AUTH_OR_GROUP_RETRIEVAL_FAILED = 11798 -ER_LDAP_AUTH_USER_GROUP_SEARCH_FAILED = 11799 -ER_LDAP_AUTH_USER_BIND_FAILED = 11800 -ER_LDAP_AUTH_POOL_GET_FAILED_TO_CREATE_CONNECTION = 11801 -ER_LDAP_AUTH_FAILED_TO_CREATE_LDAP_CONNECTION = 11802 -ER_LDAP_AUTH_FAILED_TO_ESTABLISH_TLS_CONNECTION = 11803 -ER_LDAP_AUTH_FAILED_TO_SEARCH_DN = 11804 -ER_LDAP_AUTH_CONNECTION_POOL_REINIT_ENTER = 11805 -ER_SYSTEMD_NOTIFY_PATH_TOO_LONG = 11806 -ER_SYSTEMD_NOTIFY_CONNECT_FAILED = 11807 -ER_SYSTEMD_NOTIFY_WRITE_FAILED = 11808 -ER_FOUND_MISSING_GTIDS = 11809 -ER_PID_FILE_PRIV_DIRECTORY_INSECURE = 11810 -ER_CANT_CHECK_PID_PATH = 11811 -ER_VALIDATE_PWD_STATUS_VAR_REGISTRATION_FAILED = 11812 -ER_VALIDATE_PWD_STATUS_VAR_UNREGISTRATION_FAILED = 11813 -ER_VALIDATE_PWD_DICT_FILE_OPEN_FAILED = 11814 -ER_VALIDATE_PWD_COULD_BE_NULL = 11815 -ER_VALIDATE_PWD_STRING_CONV_TO_LOWERCASE_FAILED = 11816 -ER_VALIDATE_PWD_STRING_CONV_TO_BUFFER_FAILED = 11817 -ER_VALIDATE_PWD_STRING_HANDLER_MEM_ALLOCATION_FAILED = 11818 -ER_VALIDATE_PWD_STRONG_POLICY_DICT_FILE_UNSPECIFIED = 11819 -ER_VALIDATE_PWD_CONVERT_TO_BUFFER_FAILED = 11820 -ER_VALIDATE_PWD_VARIABLE_REGISTRATION_FAILED = 11821 -ER_VALIDATE_PWD_VARIABLE_UNREGISTRATION_FAILED = 11822 -ER_KEYRING_MIGRATION_EXTRA_OPTIONS = 11823 -OBSOLETE_ER_INVALID_DEFAULT_UTF8MB4_COLLATION = 11824 -ER_IB_MSG_0 = 11825 -ER_IB_MSG_1 = 11826 -ER_IB_MSG_2 = 11827 -ER_IB_MSG_3 = 11828 -ER_IB_MSG_4 = 11829 -ER_IB_MSG_5 = 11830 -ER_IB_MSG_6 = 11831 -ER_IB_MSG_7 = 11832 -ER_IB_MSG_8 = 11833 -ER_IB_MSG_9 = 11834 -ER_IB_MSG_10 = 11835 -ER_IB_MSG_11 = 11836 -ER_IB_MSG_12 = 11837 -ER_IB_MSG_13 = 11838 -ER_IB_MSG_14 = 11839 -ER_IB_MSG_15 = 11840 -ER_IB_MSG_16 = 11841 -ER_IB_MSG_17 = 11842 -ER_IB_MSG_18 = 11843 -ER_IB_MSG_19 = 11844 -ER_IB_MSG_20 = 11845 -ER_IB_MSG_21 = 11846 -ER_IB_MSG_22 = 11847 -ER_IB_MSG_23 = 11848 -ER_IB_MSG_24 = 11849 -ER_IB_MSG_25 = 11850 -ER_IB_MSG_26 = 11851 -ER_IB_MSG_27 = 11852 -ER_IB_MSG_28 = 11853 -ER_IB_MSG_29 = 11854 -ER_IB_MSG_30 = 11855 -ER_IB_MSG_31 = 11856 -ER_IB_MSG_32 = 11857 -ER_IB_MSG_33 = 11858 -ER_IB_MSG_34 = 11859 -ER_IB_MSG_35 = 11860 -ER_IB_MSG_36 = 11861 -ER_IB_MSG_37 = 11862 -ER_IB_MSG_38 = 11863 -ER_IB_MSG_39 = 11864 -ER_IB_MSG_40 = 11865 -ER_IB_MSG_41 = 11866 -ER_IB_MSG_42 = 11867 -ER_IB_MSG_43 = 11868 -ER_IB_MSG_44 = 11869 -ER_IB_MSG_45 = 11870 -ER_IB_MSG_46 = 11871 -ER_IB_MSG_47 = 11872 -ER_IB_MSG_48 = 11873 -ER_IB_MSG_49 = 11874 -ER_IB_MSG_50 = 11875 -ER_IB_MSG_51 = 11876 -ER_IB_MSG_52 = 11877 -ER_IB_MSG_53 = 11878 -ER_IB_MSG_54 = 11879 -ER_IB_MSG_55 = 11880 -ER_IB_MSG_56 = 11881 -ER_IB_MSG_57 = 11882 -ER_IB_MSG_58 = 11883 -ER_IB_MSG_59 = 11884 -ER_IB_MSG_60 = 11885 -ER_IB_MSG_61 = 11886 -ER_IB_MSG_62 = 11887 -ER_IB_MSG_63 = 11888 -ER_IB_MSG_64 = 11889 -ER_IB_MSG_65 = 11890 -ER_IB_MSG_66 = 11891 -ER_IB_MSG_67 = 11892 -ER_IB_MSG_68 = 11893 -ER_IB_MSG_69 = 11894 -ER_IB_MSG_70 = 11895 -ER_IB_MSG_71 = 11896 -ER_IB_MSG_72 = 11897 -ER_IB_MSG_73 = 11898 -ER_IB_MSG_74 = 11899 -ER_IB_MSG_75 = 11900 -ER_IB_MSG_76 = 11901 -ER_IB_MSG_77 = 11902 -ER_IB_MSG_78 = 11903 -ER_IB_MSG_79 = 11904 -ER_IB_MSG_80 = 11905 -ER_IB_MSG_81 = 11906 -ER_IB_MSG_82 = 11907 -ER_IB_MSG_83 = 11908 -ER_IB_MSG_84 = 11909 -ER_IB_MSG_85 = 11910 -ER_IB_MSG_86 = 11911 -ER_IB_MSG_87 = 11912 -ER_IB_MSG_88 = 11913 -ER_IB_MSG_89 = 11914 -ER_IB_MSG_90 = 11915 -ER_IB_MSG_91 = 11916 -ER_IB_MSG_92 = 11917 -ER_IB_MSG_93 = 11918 -ER_IB_MSG_94 = 11919 -ER_IB_MSG_95 = 11920 -ER_IB_MSG_96 = 11921 -ER_IB_MSG_97 = 11922 -ER_IB_MSG_98 = 11923 -ER_IB_MSG_99 = 11924 -ER_IB_MSG_100 = 11925 -ER_IB_MSG_101 = 11926 -ER_IB_MSG_102 = 11927 -ER_IB_MSG_103 = 11928 -ER_IB_MSG_104 = 11929 -ER_IB_MSG_105 = 11930 -ER_IB_MSG_106 = 11931 -ER_IB_MSG_107 = 11932 -ER_IB_MSG_108 = 11933 -ER_IB_MSG_109 = 11934 -ER_IB_MSG_110 = 11935 -ER_IB_MSG_111 = 11936 -ER_IB_MSG_112 = 11937 -ER_IB_MSG_113 = 11938 -ER_IB_MSG_114 = 11939 -ER_IB_MSG_115 = 11940 -ER_IB_MSG_116 = 11941 -ER_IB_MSG_117 = 11942 -ER_IB_MSG_118 = 11943 -ER_IB_MSG_119 = 11944 -ER_IB_MSG_120 = 11945 -ER_IB_MSG_121 = 11946 -ER_IB_MSG_122 = 11947 -ER_IB_MSG_123 = 11948 -ER_IB_MSG_124 = 11949 -ER_IB_MSG_125 = 11950 -ER_IB_MSG_126 = 11951 -ER_IB_MSG_127 = 11952 -ER_IB_MSG_128 = 11953 -ER_IB_MSG_129 = 11954 -ER_IB_MSG_130 = 11955 -ER_IB_MSG_131 = 11956 -ER_IB_MSG_132 = 11957 -ER_IB_MSG_133 = 11958 -ER_IB_MSG_134 = 11959 -ER_IB_MSG_135 = 11960 -ER_IB_MSG_136 = 11961 -ER_IB_MSG_137 = 11962 -ER_IB_MSG_138 = 11963 -ER_IB_MSG_139 = 11964 -ER_IB_MSG_140 = 11965 -ER_IB_MSG_141 = 11966 -ER_IB_MSG_142 = 11967 -ER_IB_MSG_143 = 11968 -ER_IB_MSG_144 = 11969 -ER_IB_MSG_145 = 11970 -ER_IB_MSG_146 = 11971 -ER_IB_MSG_147 = 11972 -ER_IB_MSG_148 = 11973 -ER_IB_MSG_149 = 11974 -ER_IB_MSG_150 = 11975 -ER_IB_MSG_151 = 11976 -ER_IB_MSG_152 = 11977 -ER_IB_MSG_153 = 11978 -ER_IB_MSG_154 = 11979 -ER_IB_MSG_155 = 11980 -ER_IB_MSG_156 = 11981 -ER_IB_MSG_157 = 11982 -ER_IB_MSG_158 = 11983 -ER_IB_MSG_159 = 11984 -ER_IB_MSG_160 = 11985 -ER_IB_MSG_161 = 11986 -ER_IB_MSG_162 = 11987 -ER_IB_MSG_163 = 11988 -ER_IB_MSG_164 = 11989 -ER_IB_MSG_165 = 11990 -ER_IB_MSG_166 = 11991 -ER_IB_MSG_167 = 11992 -ER_IB_MSG_168 = 11993 -ER_IB_MSG_169 = 11994 -ER_IB_MSG_170 = 11995 -ER_IB_MSG_171 = 11996 -ER_IB_MSG_172 = 11997 -ER_IB_MSG_173 = 11998 -ER_IB_MSG_174 = 11999 -ER_IB_MSG_175 = 12000 -ER_IB_MSG_176 = 12001 -ER_IB_MSG_177 = 12002 -ER_IB_MSG_178 = 12003 -ER_IB_MSG_179 = 12004 -ER_IB_MSG_180 = 12005 -ER_IB_MSG_181 = 12006 -ER_IB_MSG_182 = 12007 -ER_IB_MSG_183 = 12008 -ER_IB_MSG_184 = 12009 -ER_IB_MSG_185 = 12010 -ER_IB_MSG_186 = 12011 -ER_IB_MSG_187 = 12012 -ER_IB_MSG_188 = 12013 -ER_IB_MSG_189 = 12014 -ER_IB_MSG_190 = 12015 -ER_IB_MSG_191 = 12016 -ER_IB_MSG_192 = 12017 -ER_IB_MSG_193 = 12018 -ER_IB_MSG_194 = 12019 -ER_IB_MSG_195 = 12020 -ER_IB_MSG_196 = 12021 -ER_IB_MSG_197 = 12022 -ER_IB_MSG_198 = 12023 -ER_IB_MSG_199 = 12024 -ER_IB_MSG_200 = 12025 -ER_IB_MSG_201 = 12026 -ER_IB_MSG_202 = 12027 -ER_IB_MSG_203 = 12028 -ER_IB_MSG_204 = 12029 -ER_IB_MSG_205 = 12030 -ER_IB_MSG_206 = 12031 -ER_IB_MSG_207 = 12032 -ER_IB_MSG_208 = 12033 -ER_IB_MSG_209 = 12034 -ER_IB_MSG_210 = 12035 -ER_IB_MSG_211 = 12036 -ER_IB_MSG_212 = 12037 -ER_IB_MSG_213 = 12038 -ER_IB_MSG_214 = 12039 -ER_IB_MSG_215 = 12040 -ER_IB_MSG_216 = 12041 -ER_IB_MSG_217 = 12042 -ER_IB_MSG_218 = 12043 -ER_IB_MSG_219 = 12044 -ER_IB_MSG_220 = 12045 -ER_IB_MSG_221 = 12046 -ER_IB_MSG_222 = 12047 -ER_IB_MSG_223 = 12048 -ER_IB_MSG_224 = 12049 -ER_IB_MSG_225 = 12050 -ER_IB_MSG_226 = 12051 -ER_IB_MSG_227 = 12052 -ER_IB_MSG_228 = 12053 -ER_IB_MSG_229 = 12054 -ER_IB_MSG_230 = 12055 -ER_IB_MSG_231 = 12056 -ER_IB_MSG_232 = 12057 -ER_IB_MSG_233 = 12058 -ER_IB_MSG_234 = 12059 -ER_IB_MSG_235 = 12060 -ER_IB_MSG_236 = 12061 -ER_IB_MSG_237 = 12062 -ER_IB_MSG_238 = 12063 -ER_IB_MSG_239 = 12064 -ER_IB_MSG_240 = 12065 -ER_IB_MSG_241 = 12066 -ER_IB_MSG_242 = 12067 -ER_IB_MSG_243 = 12068 -ER_IB_MSG_244 = 12069 -ER_IB_MSG_245 = 12070 -ER_IB_MSG_246 = 12071 -ER_IB_MSG_247 = 12072 -ER_IB_MSG_248 = 12073 -ER_IB_MSG_249 = 12074 -ER_IB_MSG_250 = 12075 -ER_IB_MSG_251 = 12076 -ER_IB_MSG_252 = 12077 -ER_IB_MSG_253 = 12078 -ER_IB_MSG_254 = 12079 -ER_IB_MSG_255 = 12080 -ER_IB_MSG_256 = 12081 -ER_IB_MSG_257 = 12082 -ER_IB_MSG_258 = 12083 -ER_IB_MSG_259 = 12084 -ER_IB_MSG_260 = 12085 -ER_IB_MSG_261 = 12086 -ER_IB_MSG_262 = 12087 -ER_IB_MSG_263 = 12088 -ER_IB_MSG_264 = 12089 -ER_IB_MSG_265 = 12090 -ER_IB_MSG_266 = 12091 -ER_IB_MSG_267 = 12092 -ER_IB_MSG_268 = 12093 -ER_IB_MSG_269 = 12094 -ER_IB_MSG_270 = 12095 -ER_IB_MSG_271 = 12096 -ER_IB_MSG_272 = 12097 -ER_IB_MSG_273 = 12098 -ER_IB_MSG_274 = 12099 -ER_IB_MSG_275 = 12100 -ER_IB_MSG_276 = 12101 -ER_IB_MSG_277 = 12102 -ER_IB_MSG_278 = 12103 -ER_IB_MSG_279 = 12104 -ER_IB_MSG_280 = 12105 -ER_IB_MSG_281 = 12106 -ER_IB_MSG_282 = 12107 -ER_IB_MSG_283 = 12108 -ER_IB_MSG_284 = 12109 -ER_IB_MSG_285 = 12110 -ER_IB_MSG_286 = 12111 -ER_IB_MSG_287 = 12112 -ER_IB_MSG_288 = 12113 -ER_IB_MSG_289 = 12114 -ER_IB_MSG_290 = 12115 -ER_IB_MSG_291 = 12116 -ER_IB_MSG_292 = 12117 -ER_IB_MSG_293 = 12118 -ER_IB_MSG_294 = 12119 -ER_IB_MSG_295 = 12120 -ER_IB_MSG_296 = 12121 -ER_IB_MSG_297 = 12122 -ER_IB_MSG_298 = 12123 -ER_IB_MSG_299 = 12124 -ER_IB_MSG_300 = 12125 -ER_IB_MSG_301 = 12126 -ER_IB_MSG_302 = 12127 -ER_IB_MSG_303 = 12128 -ER_IB_MSG_304 = 12129 -ER_IB_MSG_305 = 12130 -ER_IB_MSG_306 = 12131 -ER_IB_MSG_307 = 12132 -ER_IB_MSG_308 = 12133 -ER_IB_MSG_309 = 12134 -ER_IB_MSG_310 = 12135 -ER_IB_MSG_311 = 12136 -ER_IB_MSG_312 = 12137 -ER_IB_MSG_313 = 12138 -ER_IB_MSG_314 = 12139 -ER_IB_MSG_315 = 12140 -ER_IB_MSG_316 = 12141 -ER_IB_MSG_317 = 12142 -ER_IB_MSG_318 = 12143 -ER_IB_MSG_319 = 12144 -ER_IB_MSG_320 = 12145 -ER_IB_MSG_321 = 12146 -ER_IB_MSG_322 = 12147 -ER_IB_MSG_323 = 12148 -ER_IB_MSG_324 = 12149 -ER_IB_MSG_325 = 12150 -ER_IB_MSG_326 = 12151 -ER_IB_MSG_327 = 12152 -ER_IB_MSG_328 = 12153 -ER_IB_MSG_329 = 12154 -ER_IB_MSG_330 = 12155 -ER_IB_MSG_331 = 12156 -ER_IB_MSG_332 = 12157 -ER_IB_MSG_333 = 12158 -ER_IB_MSG_334 = 12159 -ER_IB_MSG_335 = 12160 -ER_IB_MSG_336 = 12161 -ER_IB_MSG_337 = 12162 -ER_IB_MSG_338 = 12163 -ER_IB_MSG_339 = 12164 -ER_IB_MSG_340 = 12165 -ER_IB_MSG_341 = 12166 -ER_IB_MSG_342 = 12167 -ER_IB_MSG_343 = 12168 -ER_IB_MSG_344 = 12169 -ER_IB_MSG_345 = 12170 -ER_IB_MSG_346 = 12171 -ER_IB_MSG_347 = 12172 -ER_IB_MSG_348 = 12173 -ER_IB_MSG_349 = 12174 -ER_IB_MSG_350 = 12175 -ER_IB_MSG_351 = 12176 -ER_IB_MSG_352 = 12177 -ER_IB_MSG_353 = 12178 -ER_IB_MSG_354 = 12179 -ER_IB_MSG_355 = 12180 -ER_IB_MSG_356 = 12181 -ER_IB_MSG_357 = 12182 -ER_IB_MSG_358 = 12183 -ER_IB_MSG_359 = 12184 -ER_IB_MSG_360 = 12185 -ER_IB_MSG_361 = 12186 -ER_IB_MSG_362 = 12187 -ER_IB_MSG_363 = 12188 -ER_IB_MSG_364 = 12189 -ER_IB_MSG_365 = 12190 -ER_IB_MSG_366 = 12191 -ER_IB_MSG_367 = 12192 -ER_IB_MSG_368 = 12193 -ER_IB_MSG_369 = 12194 -ER_IB_MSG_370 = 12195 -ER_IB_MSG_371 = 12196 -ER_IB_MSG_372 = 12197 -ER_IB_MSG_373 = 12198 -ER_IB_MSG_374 = 12199 -ER_IB_MSG_375 = 12200 -ER_IB_MSG_376 = 12201 -ER_IB_MSG_377 = 12202 -ER_IB_MSG_378 = 12203 -ER_IB_MSG_379 = 12204 -ER_IB_MSG_380 = 12205 -ER_IB_MSG_381 = 12206 -ER_IB_MSG_382 = 12207 -ER_IB_MSG_383 = 12208 -ER_IB_MSG_384 = 12209 -ER_IB_MSG_385 = 12210 -ER_IB_MSG_386 = 12211 -ER_IB_MSG_387 = 12212 -ER_IB_MSG_388 = 12213 -ER_IB_MSG_389 = 12214 -ER_IB_MSG_390 = 12215 -ER_IB_MSG_391 = 12216 -ER_IB_MSG_392 = 12217 -ER_IB_MSG_393 = 12218 -ER_IB_MSG_394 = 12219 -ER_IB_MSG_395 = 12220 -ER_IB_MSG_396 = 12221 -ER_IB_MSG_397 = 12222 -ER_IB_MSG_398 = 12223 -ER_IB_MSG_399 = 12224 -ER_IB_MSG_400 = 12225 -ER_IB_MSG_401 = 12226 -ER_IB_MSG_402 = 12227 -ER_IB_MSG_403 = 12228 -ER_IB_MSG_404 = 12229 -ER_IB_MSG_405 = 12230 -ER_IB_MSG_406 = 12231 -ER_IB_MSG_407 = 12232 -ER_IB_MSG_408 = 12233 -ER_IB_MSG_409 = 12234 -ER_IB_MSG_410 = 12235 -ER_IB_MSG_411 = 12236 -ER_IB_MSG_412 = 12237 -ER_IB_MSG_413 = 12238 -ER_IB_MSG_414 = 12239 -ER_IB_MSG_415 = 12240 -ER_IB_MSG_416 = 12241 -ER_IB_MSG_417 = 12242 -ER_IB_MSG_418 = 12243 -ER_IB_MSG_419 = 12244 -ER_IB_MSG_420 = 12245 -ER_IB_MSG_421 = 12246 -ER_IB_MSG_422 = 12247 -ER_IB_MSG_423 = 12248 -ER_IB_MSG_424 = 12249 -ER_IB_MSG_425 = 12250 -ER_IB_MSG_426 = 12251 -ER_IB_MSG_427 = 12252 -ER_IB_MSG_428 = 12253 -ER_IB_MSG_429 = 12254 -ER_IB_MSG_430 = 12255 -ER_IB_MSG_431 = 12256 -ER_IB_MSG_432 = 12257 -ER_IB_MSG_433 = 12258 -ER_IB_MSG_434 = 12259 -ER_IB_MSG_435 = 12260 -ER_IB_MSG_436 = 12261 -ER_IB_MSG_437 = 12262 -ER_IB_MSG_438 = 12263 -ER_IB_MSG_439 = 12264 -ER_IB_MSG_440 = 12265 -ER_IB_MSG_441 = 12266 -ER_IB_MSG_442 = 12267 -ER_IB_MSG_443 = 12268 -ER_IB_MSG_444 = 12269 -ER_IB_MSG_445 = 12270 -ER_IB_MSG_446 = 12271 -ER_IB_MSG_447 = 12272 -ER_IB_MSG_448 = 12273 -ER_IB_MSG_449 = 12274 -ER_IB_MSG_450 = 12275 -ER_IB_MSG_451 = 12276 -ER_IB_MSG_452 = 12277 -ER_IB_MSG_453 = 12278 -ER_IB_MSG_454 = 12279 -ER_IB_MSG_455 = 12280 -ER_IB_MSG_456 = 12281 -ER_IB_MSG_457 = 12282 -ER_IB_MSG_458 = 12283 -ER_IB_MSG_459 = 12284 -ER_IB_MSG_460 = 12285 -ER_IB_MSG_461 = 12286 -ER_IB_MSG_462 = 12287 -ER_IB_MSG_463 = 12288 -ER_IB_MSG_464 = 12289 -ER_IB_MSG_465 = 12290 -ER_IB_MSG_466 = 12291 -ER_IB_MSG_467 = 12292 -ER_IB_MSG_468 = 12293 -ER_IB_MSG_469 = 12294 -ER_IB_MSG_470 = 12295 -ER_IB_MSG_471 = 12296 -ER_IB_MSG_472 = 12297 -ER_IB_MSG_473 = 12298 -ER_IB_MSG_474 = 12299 -ER_IB_MSG_475 = 12300 -ER_IB_MSG_476 = 12301 -ER_IB_MSG_477 = 12302 -ER_IB_MSG_478 = 12303 -ER_IB_MSG_479 = 12304 -ER_IB_MSG_480 = 12305 -ER_IB_MSG_481 = 12306 -ER_IB_MSG_482 = 12307 -ER_IB_MSG_483 = 12308 -ER_IB_MSG_484 = 12309 -ER_IB_MSG_485 = 12310 -ER_IB_MSG_486 = 12311 -ER_IB_MSG_487 = 12312 -ER_IB_MSG_488 = 12313 -ER_IB_MSG_489 = 12314 -ER_IB_MSG_490 = 12315 -ER_IB_MSG_491 = 12316 -ER_IB_MSG_492 = 12317 -ER_IB_MSG_493 = 12318 -ER_IB_MSG_494 = 12319 -ER_IB_MSG_495 = 12320 -ER_IB_MSG_496 = 12321 -ER_IB_MSG_497 = 12322 -ER_IB_MSG_498 = 12323 -ER_IB_MSG_499 = 12324 -ER_IB_MSG_500 = 12325 -ER_IB_MSG_501 = 12326 -ER_IB_MSG_502 = 12327 -ER_IB_MSG_503 = 12328 -ER_IB_MSG_504 = 12329 -ER_IB_MSG_505 = 12330 -ER_IB_MSG_506 = 12331 -ER_IB_MSG_507 = 12332 -ER_IB_MSG_508 = 12333 -ER_IB_MSG_509 = 12334 -ER_IB_MSG_510 = 12335 -ER_IB_MSG_511 = 12336 -ER_IB_MSG_512 = 12337 -ER_IB_MSG_513 = 12338 -ER_IB_MSG_514 = 12339 -ER_IB_MSG_515 = 12340 -ER_IB_MSG_516 = 12341 -ER_IB_MSG_517 = 12342 -ER_IB_MSG_518 = 12343 -ER_IB_MSG_519 = 12344 -ER_IB_MSG_520 = 12345 -ER_IB_MSG_521 = 12346 -ER_IB_MSG_522 = 12347 -ER_IB_MSG_523 = 12348 -ER_IB_MSG_524 = 12349 -ER_IB_MSG_525 = 12350 -ER_IB_MSG_526 = 12351 -ER_IB_MSG_527 = 12352 -ER_IB_MSG_528 = 12353 -ER_IB_MSG_529 = 12354 -ER_IB_MSG_530 = 12355 -ER_IB_MSG_531 = 12356 -ER_IB_MSG_532 = 12357 -ER_IB_MSG_533 = 12358 -ER_IB_MSG_534 = 12359 -ER_IB_MSG_535 = 12360 -ER_IB_MSG_536 = 12361 -ER_IB_MSG_537 = 12362 -ER_IB_MSG_538 = 12363 -ER_IB_MSG_539 = 12364 -ER_IB_MSG_540 = 12365 -ER_IB_MSG_541 = 12366 -ER_IB_MSG_542 = 12367 -ER_IB_MSG_543 = 12368 -ER_IB_MSG_544 = 12369 -ER_IB_MSG_545 = 12370 -ER_IB_MSG_546 = 12371 -ER_IB_MSG_547 = 12372 -ER_IB_MSG_548 = 12373 -ER_IB_MSG_549 = 12374 -ER_IB_MSG_550 = 12375 -ER_IB_MSG_551 = 12376 -ER_IB_MSG_552 = 12377 -ER_IB_MSG_553 = 12378 -ER_IB_MSG_554 = 12379 -ER_IB_MSG_555 = 12380 -ER_IB_MSG_556 = 12381 -ER_IB_MSG_557 = 12382 -ER_IB_MSG_558 = 12383 -ER_IB_MSG_559 = 12384 -ER_IB_MSG_560 = 12385 -ER_IB_MSG_561 = 12386 -ER_IB_MSG_562 = 12387 -ER_IB_MSG_563 = 12388 -ER_IB_MSG_564 = 12389 -ER_IB_MSG_565 = 12390 -ER_IB_MSG_566 = 12391 -ER_IB_MSG_567 = 12392 -ER_IB_MSG_568 = 12393 -ER_IB_MSG_569 = 12394 -ER_IB_MSG_570 = 12395 -ER_IB_MSG_571 = 12396 -ER_IB_MSG_572 = 12397 -ER_IB_MSG_573 = 12398 -ER_IB_MSG_574 = 12399 -ER_IB_MSG_575 = 12400 -ER_IB_MSG_576 = 12401 -ER_IB_MSG_577 = 12402 -ER_IB_MSG_578 = 12403 -ER_IB_MSG_579 = 12404 -ER_IB_MSG_580 = 12405 -ER_IB_MSG_581 = 12406 -ER_IB_MSG_582 = 12407 -ER_IB_MSG_583 = 12408 -ER_IB_MSG_584 = 12409 -ER_IB_MSG_585 = 12410 -ER_IB_MSG_586 = 12411 -ER_IB_MSG_587 = 12412 -ER_IB_MSG_588 = 12413 -ER_IB_MSG_589 = 12414 -ER_IB_MSG_590 = 12415 -ER_IB_MSG_591 = 12416 -ER_IB_MSG_592 = 12417 -ER_IB_MSG_593 = 12418 -ER_IB_MSG_594 = 12419 -ER_IB_MSG_595 = 12420 -ER_IB_MSG_596 = 12421 -ER_IB_MSG_597 = 12422 -ER_IB_MSG_598 = 12423 -ER_IB_MSG_599 = 12424 -ER_IB_MSG_600 = 12425 -ER_IB_MSG_601 = 12426 -ER_IB_MSG_602 = 12427 -ER_IB_MSG_603 = 12428 -ER_IB_MSG_604 = 12429 -ER_IB_MSG_605 = 12430 -ER_IB_MSG_606 = 12431 -ER_IB_MSG_607 = 12432 -ER_IB_MSG_608 = 12433 -ER_IB_MSG_609 = 12434 -ER_IB_MSG_610 = 12435 -ER_IB_MSG_611 = 12436 -ER_IB_MSG_612 = 12437 -ER_IB_MSG_613 = 12438 -ER_IB_MSG_614 = 12439 -ER_IB_MSG_615 = 12440 -ER_IB_MSG_616 = 12441 -ER_IB_MSG_617 = 12442 -ER_IB_MSG_618 = 12443 -ER_IB_MSG_619 = 12444 -ER_IB_MSG_620 = 12445 -ER_IB_MSG_621 = 12446 -ER_IB_MSG_622 = 12447 -ER_IB_MSG_623 = 12448 -ER_IB_MSG_624 = 12449 -ER_IB_MSG_625 = 12450 -ER_IB_MSG_626 = 12451 -ER_IB_MSG_627 = 12452 -ER_IB_MSG_628 = 12453 -ER_IB_MSG_629 = 12454 -ER_IB_MSG_630 = 12455 -ER_IB_MSG_631 = 12456 -ER_IB_MSG_632 = 12457 -ER_IB_MSG_633 = 12458 -ER_IB_MSG_634 = 12459 -ER_IB_MSG_635 = 12460 -ER_IB_MSG_636 = 12461 -ER_IB_MSG_637 = 12462 -ER_IB_MSG_638 = 12463 -ER_IB_MSG_639 = 12464 -ER_IB_MSG_640 = 12465 -ER_IB_MSG_641 = 12466 -ER_IB_MSG_642 = 12467 -ER_IB_MSG_643 = 12468 -ER_IB_MSG_644 = 12469 -ER_IB_MSG_645 = 12470 -ER_IB_MSG_646 = 12471 -ER_IB_MSG_647 = 12472 -ER_IB_MSG_648 = 12473 -ER_IB_MSG_649 = 12474 -ER_IB_MSG_650 = 12475 -ER_IB_MSG_651 = 12476 -ER_IB_MSG_652 = 12477 -ER_IB_MSG_653 = 12478 -ER_IB_MSG_654 = 12479 -ER_IB_MSG_655 = 12480 -ER_IB_MSG_656 = 12481 -ER_IB_MSG_657 = 12482 -ER_IB_MSG_658 = 12483 -ER_IB_MSG_659 = 12484 -ER_IB_MSG_660 = 12485 -ER_IB_MSG_661 = 12486 -ER_IB_MSG_662 = 12487 -ER_IB_MSG_663 = 12488 -ER_IB_MSG_664 = 12489 -ER_IB_MSG_665 = 12490 -ER_IB_MSG_666 = 12491 -ER_IB_MSG_667 = 12492 -ER_IB_MSG_668 = 12493 -ER_IB_MSG_669 = 12494 -ER_IB_MSG_670 = 12495 -ER_IB_MSG_671 = 12496 -ER_IB_MSG_672 = 12497 -ER_IB_MSG_673 = 12498 -ER_IB_MSG_674 = 12499 -ER_IB_MSG_675 = 12500 -ER_IB_MSG_676 = 12501 -ER_IB_MSG_677 = 12502 -ER_IB_MSG_678 = 12503 -ER_IB_MSG_679 = 12504 -ER_IB_MSG_680 = 12505 -ER_IB_MSG_681 = 12506 -ER_IB_MSG_682 = 12507 -ER_IB_MSG_683 = 12508 -ER_IB_MSG_684 = 12509 -ER_IB_MSG_685 = 12510 -ER_IB_MSG_686 = 12511 -ER_IB_MSG_687 = 12512 -ER_IB_MSG_688 = 12513 -ER_IB_MSG_689 = 12514 -ER_IB_MSG_690 = 12515 -ER_IB_MSG_691 = 12516 -ER_IB_MSG_692 = 12517 -ER_IB_MSG_693 = 12518 -ER_IB_MSG_694 = 12519 -ER_IB_MSG_695 = 12520 -ER_IB_MSG_696 = 12521 -ER_IB_MSG_697 = 12522 -ER_IB_MSG_698 = 12523 -ER_IB_MSG_699 = 12524 -ER_IB_MSG_700 = 12525 -ER_IB_MSG_701 = 12526 -ER_IB_MSG_702 = 12527 -ER_IB_MSG_703 = 12528 -ER_IB_MSG_704 = 12529 -ER_IB_MSG_705 = 12530 -ER_IB_MSG_706 = 12531 -ER_IB_MSG_707 = 12532 -ER_IB_MSG_708 = 12533 -ER_IB_MSG_709 = 12534 -ER_IB_MSG_710 = 12535 -ER_IB_MSG_711 = 12536 -ER_IB_MSG_712 = 12537 -ER_IB_MSG_713 = 12538 -ER_IB_MSG_714 = 12539 -ER_IB_MSG_715 = 12540 -ER_IB_MSG_716 = 12541 -ER_IB_MSG_717 = 12542 -ER_IB_MSG_718 = 12543 -ER_IB_MSG_719 = 12544 -ER_IB_MSG_720 = 12545 -ER_IB_MSG_721 = 12546 -ER_IB_MSG_722 = 12547 -ER_IB_MSG_723 = 12548 -ER_IB_MSG_724 = 12549 -ER_IB_MSG_725 = 12550 -ER_IB_MSG_726 = 12551 -ER_IB_MSG_727 = 12552 -ER_IB_MSG_728 = 12553 -ER_IB_MSG_729 = 12554 -ER_IB_MSG_730 = 12555 -ER_IB_MSG_731 = 12556 -ER_IB_MSG_732 = 12557 -ER_IB_MSG_733 = 12558 -ER_IB_MSG_734 = 12559 -ER_IB_MSG_735 = 12560 -ER_IB_MSG_736 = 12561 -ER_IB_MSG_737 = 12562 -ER_IB_MSG_738 = 12563 -ER_IB_MSG_739 = 12564 -ER_IB_MSG_740 = 12565 -ER_IB_MSG_741 = 12566 -ER_IB_MSG_742 = 12567 -ER_IB_MSG_743 = 12568 -ER_IB_MSG_744 = 12569 -ER_IB_MSG_745 = 12570 -ER_IB_MSG_746 = 12571 -ER_IB_MSG_747 = 12572 -ER_IB_MSG_748 = 12573 -ER_IB_MSG_749 = 12574 -ER_IB_MSG_750 = 12575 -ER_IB_MSG_751 = 12576 -ER_IB_MSG_752 = 12577 -ER_IB_MSG_753 = 12578 -ER_IB_MSG_754 = 12579 -ER_IB_MSG_755 = 12580 -ER_IB_MSG_756 = 12581 -ER_IB_MSG_757 = 12582 -ER_IB_MSG_758 = 12583 -ER_IB_MSG_759 = 12584 -ER_IB_MSG_760 = 12585 -ER_IB_MSG_761 = 12586 -ER_IB_MSG_762 = 12587 -ER_IB_MSG_763 = 12588 -ER_IB_MSG_764 = 12589 -ER_IB_MSG_765 = 12590 -ER_IB_MSG_766 = 12591 -ER_IB_MSG_767 = 12592 -ER_IB_MSG_768 = 12593 -ER_IB_MSG_769 = 12594 -ER_IB_MSG_770 = 12595 -ER_IB_MSG_771 = 12596 -ER_IB_MSG_772 = 12597 -ER_IB_MSG_773 = 12598 -ER_IB_MSG_774 = 12599 -ER_IB_MSG_775 = 12600 -ER_IB_MSG_776 = 12601 -ER_IB_MSG_777 = 12602 -ER_IB_MSG_778 = 12603 -ER_IB_MSG_779 = 12604 -ER_IB_MSG_780 = 12605 -ER_IB_MSG_781 = 12606 -ER_IB_MSG_782 = 12607 -ER_IB_MSG_783 = 12608 -ER_IB_MSG_784 = 12609 -ER_IB_MSG_785 = 12610 -ER_IB_MSG_786 = 12611 -ER_IB_MSG_787 = 12612 -ER_IB_MSG_788 = 12613 -ER_IB_MSG_789 = 12614 -ER_IB_MSG_790 = 12615 -ER_IB_MSG_791 = 12616 -ER_IB_MSG_792 = 12617 -ER_IB_MSG_793 = 12618 -ER_IB_MSG_794 = 12619 -ER_IB_MSG_795 = 12620 -ER_IB_MSG_796 = 12621 -ER_IB_MSG_797 = 12622 -ER_IB_MSG_798 = 12623 -ER_IB_MSG_799 = 12624 -ER_IB_MSG_800 = 12625 -ER_IB_MSG_801 = 12626 -ER_IB_MSG_802 = 12627 -ER_IB_MSG_803 = 12628 -ER_IB_MSG_804 = 12629 -ER_IB_MSG_805 = 12630 -ER_IB_MSG_806 = 12631 -ER_IB_MSG_807 = 12632 -ER_IB_MSG_808 = 12633 -ER_IB_MSG_809 = 12634 -ER_IB_MSG_810 = 12635 -ER_IB_MSG_811 = 12636 -ER_IB_MSG_812 = 12637 -ER_IB_MSG_813 = 12638 -ER_IB_MSG_814 = 12639 -ER_IB_MSG_815 = 12640 -ER_IB_MSG_816 = 12641 -ER_IB_MSG_817 = 12642 -ER_IB_MSG_818 = 12643 -ER_IB_MSG_819 = 12644 -ER_IB_MSG_820 = 12645 -ER_IB_MSG_821 = 12646 -ER_IB_MSG_822 = 12647 -ER_IB_MSG_823 = 12648 -ER_IB_MSG_824 = 12649 -ER_IB_MSG_825 = 12650 -ER_IB_MSG_826 = 12651 -ER_IB_MSG_827 = 12652 -ER_IB_MSG_828 = 12653 -ER_IB_MSG_829 = 12654 -ER_IB_MSG_830 = 12655 -ER_IB_MSG_831 = 12656 -ER_IB_MSG_832 = 12657 -ER_IB_MSG_833 = 12658 -ER_IB_MSG_834 = 12659 -ER_IB_MSG_835 = 12660 -ER_IB_MSG_836 = 12661 -ER_IB_MSG_837 = 12662 -ER_IB_MSG_838 = 12663 -ER_IB_MSG_839 = 12664 -ER_IB_MSG_840 = 12665 -ER_IB_MSG_841 = 12666 -ER_IB_MSG_842 = 12667 -ER_IB_MSG_843 = 12668 -ER_IB_MSG_844 = 12669 -ER_IB_MSG_845 = 12670 -ER_IB_MSG_846 = 12671 -ER_IB_MSG_847 = 12672 -ER_IB_MSG_848 = 12673 -ER_IB_MSG_849 = 12674 -ER_IB_MSG_850 = 12675 -ER_IB_MSG_851 = 12676 -ER_IB_MSG_852 = 12677 -ER_IB_MSG_853 = 12678 -ER_IB_MSG_854 = 12679 -ER_IB_MSG_855 = 12680 -ER_IB_MSG_856 = 12681 -ER_IB_MSG_857 = 12682 -ER_IB_MSG_858 = 12683 -ER_IB_MSG_859 = 12684 -ER_IB_MSG_860 = 12685 -ER_IB_MSG_861 = 12686 -ER_IB_MSG_862 = 12687 -ER_IB_MSG_863 = 12688 -ER_IB_MSG_864 = 12689 -ER_IB_MSG_865 = 12690 -ER_IB_MSG_866 = 12691 -ER_IB_MSG_867 = 12692 -ER_IB_MSG_868 = 12693 -ER_IB_MSG_869 = 12694 -ER_IB_MSG_870 = 12695 -ER_IB_MSG_871 = 12696 -ER_IB_MSG_872 = 12697 -ER_IB_MSG_873 = 12698 -ER_IB_MSG_874 = 12699 -ER_IB_MSG_875 = 12700 -ER_IB_MSG_876 = 12701 -ER_IB_MSG_877 = 12702 -ER_IB_MSG_878 = 12703 -ER_IB_MSG_879 = 12704 -ER_IB_MSG_880 = 12705 -ER_IB_MSG_881 = 12706 -ER_IB_MSG_882 = 12707 -ER_IB_MSG_883 = 12708 -ER_IB_MSG_884 = 12709 -ER_IB_MSG_885 = 12710 -ER_IB_MSG_886 = 12711 -ER_IB_MSG_887 = 12712 -ER_IB_MSG_888 = 12713 -ER_IB_MSG_889 = 12714 -ER_IB_MSG_890 = 12715 -ER_IB_MSG_891 = 12716 -ER_IB_MSG_892 = 12717 -ER_IB_MSG_893 = 12718 -ER_IB_MSG_894 = 12719 -ER_IB_MSG_895 = 12720 -ER_IB_MSG_896 = 12721 -ER_IB_MSG_897 = 12722 -ER_IB_MSG_898 = 12723 -ER_IB_MSG_899 = 12724 -ER_IB_MSG_900 = 12725 -ER_IB_MSG_901 = 12726 -ER_IB_MSG_902 = 12727 -ER_IB_MSG_903 = 12728 -ER_IB_MSG_904 = 12729 -ER_IB_MSG_905 = 12730 -ER_IB_MSG_906 = 12731 -ER_IB_MSG_907 = 12732 -ER_IB_MSG_908 = 12733 -ER_IB_MSG_909 = 12734 -ER_IB_MSG_910 = 12735 -ER_IB_MSG_911 = 12736 -ER_IB_MSG_912 = 12737 -ER_IB_MSG_913 = 12738 -ER_IB_MSG_914 = 12739 -ER_IB_MSG_915 = 12740 -ER_IB_MSG_916 = 12741 -ER_IB_MSG_917 = 12742 -ER_IB_MSG_918 = 12743 -ER_IB_MSG_919 = 12744 -ER_IB_MSG_920 = 12745 -ER_IB_MSG_921 = 12746 -ER_IB_MSG_922 = 12747 -ER_IB_MSG_923 = 12748 -ER_IB_MSG_924 = 12749 -ER_IB_MSG_925 = 12750 -ER_IB_MSG_926 = 12751 -ER_IB_MSG_927 = 12752 -ER_IB_MSG_928 = 12753 -ER_IB_MSG_929 = 12754 -ER_IB_MSG_930 = 12755 -ER_IB_MSG_931 = 12756 -ER_IB_MSG_932 = 12757 -ER_IB_MSG_933 = 12758 -ER_IB_MSG_934 = 12759 -ER_IB_MSG_935 = 12760 -ER_IB_MSG_936 = 12761 -ER_IB_MSG_937 = 12762 -ER_IB_MSG_938 = 12763 -ER_IB_MSG_939 = 12764 -ER_IB_MSG_940 = 12765 -ER_IB_MSG_941 = 12766 -ER_IB_MSG_942 = 12767 -ER_IB_MSG_943 = 12768 -ER_IB_MSG_944 = 12769 -ER_IB_MSG_945 = 12770 -ER_IB_MSG_946 = 12771 -ER_IB_MSG_947 = 12772 -ER_IB_MSG_948 = 12773 -ER_IB_MSG_949 = 12774 -ER_IB_MSG_950 = 12775 -ER_IB_MSG_951 = 12776 -ER_IB_MSG_952 = 12777 -ER_IB_MSG_953 = 12778 -ER_IB_MSG_954 = 12779 -ER_IB_MSG_955 = 12780 -ER_IB_MSG_956 = 12781 -ER_IB_MSG_957 = 12782 -ER_IB_MSG_958 = 12783 -ER_IB_MSG_959 = 12784 -ER_IB_MSG_960 = 12785 -ER_IB_MSG_961 = 12786 -ER_IB_MSG_962 = 12787 -ER_IB_MSG_963 = 12788 -ER_IB_MSG_964 = 12789 -ER_IB_MSG_965 = 12790 -ER_IB_MSG_966 = 12791 -ER_IB_MSG_967 = 12792 -ER_IB_MSG_968 = 12793 -ER_IB_MSG_969 = 12794 -ER_IB_MSG_970 = 12795 -ER_IB_MSG_971 = 12796 -ER_IB_MSG_972 = 12797 -ER_IB_MSG_973 = 12798 -ER_IB_MSG_974 = 12799 -ER_IB_MSG_975 = 12800 -ER_IB_MSG_976 = 12801 -ER_IB_MSG_977 = 12802 -ER_IB_MSG_978 = 12803 -ER_IB_MSG_979 = 12804 -ER_IB_MSG_980 = 12805 -ER_IB_MSG_981 = 12806 -ER_IB_MSG_982 = 12807 -ER_IB_MSG_983 = 12808 -ER_IB_MSG_984 = 12809 -ER_IB_MSG_985 = 12810 -ER_IB_MSG_986 = 12811 -ER_IB_MSG_987 = 12812 -ER_IB_MSG_988 = 12813 -ER_IB_MSG_989 = 12814 -ER_IB_MSG_990 = 12815 -ER_IB_MSG_991 = 12816 -ER_IB_MSG_992 = 12817 -ER_IB_MSG_993 = 12818 -ER_IB_MSG_994 = 12819 -ER_IB_MSG_995 = 12820 -ER_IB_MSG_996 = 12821 -ER_IB_MSG_997 = 12822 -ER_IB_MSG_998 = 12823 -ER_IB_MSG_999 = 12824 -ER_IB_MSG_1000 = 12825 -ER_IB_MSG_1001 = 12826 -ER_IB_MSG_1002 = 12827 -ER_IB_MSG_1003 = 12828 -ER_IB_MSG_1004 = 12829 -ER_IB_MSG_1005 = 12830 -ER_IB_MSG_1006 = 12831 -ER_IB_MSG_1007 = 12832 -ER_IB_MSG_1008 = 12833 -ER_IB_MSG_1009 = 12834 -ER_IB_MSG_1010 = 12835 -ER_IB_MSG_1011 = 12836 -ER_IB_MSG_1012 = 12837 -ER_IB_MSG_1013 = 12838 -ER_IB_MSG_1014 = 12839 -ER_IB_MSG_1015 = 12840 -ER_IB_MSG_1016 = 12841 -ER_IB_MSG_1017 = 12842 -ER_IB_MSG_1018 = 12843 -ER_IB_MSG_1019 = 12844 -ER_IB_MSG_1020 = 12845 -ER_IB_MSG_1021 = 12846 -ER_IB_MSG_1022 = 12847 -ER_IB_MSG_1023 = 12848 -ER_IB_MSG_1024 = 12849 -ER_IB_MSG_1025 = 12850 -ER_IB_MSG_1026 = 12851 -ER_IB_MSG_1027 = 12852 -ER_IB_MSG_1028 = 12853 -ER_IB_MSG_1029 = 12854 -ER_IB_MSG_1030 = 12855 -ER_IB_MSG_1031 = 12856 -ER_IB_MSG_1032 = 12857 -ER_IB_MSG_1033 = 12858 -ER_IB_MSG_1034 = 12859 -ER_IB_MSG_1035 = 12860 -ER_IB_MSG_1036 = 12861 -ER_IB_MSG_1037 = 12862 -ER_IB_MSG_1038 = 12863 -ER_IB_MSG_1039 = 12864 -ER_IB_MSG_1040 = 12865 -ER_IB_MSG_1041 = 12866 -ER_IB_MSG_1042 = 12867 -ER_IB_MSG_1043 = 12868 -ER_IB_MSG_1044 = 12869 -ER_IB_MSG_1045 = 12870 -ER_IB_MSG_1046 = 12871 -ER_IB_MSG_1047 = 12872 -ER_IB_MSG_1048 = 12873 -ER_IB_MSG_1049 = 12874 -ER_IB_MSG_1050 = 12875 -ER_IB_MSG_1051 = 12876 -ER_IB_MSG_1052 = 12877 -ER_IB_MSG_1053 = 12878 -ER_IB_MSG_1054 = 12879 -ER_IB_MSG_1055 = 12880 -ER_IB_MSG_1056 = 12881 -ER_IB_MSG_1057 = 12882 -ER_IB_MSG_1058 = 12883 -ER_IB_MSG_1059 = 12884 -ER_IB_MSG_1060 = 12885 -ER_IB_MSG_1061 = 12886 -ER_IB_MSG_1062 = 12887 -ER_IB_MSG_1063 = 12888 -ER_IB_MSG_1064 = 12889 -ER_IB_MSG_1065 = 12890 -ER_IB_MSG_1066 = 12891 -ER_IB_MSG_1067 = 12892 -ER_IB_MSG_1068 = 12893 -ER_IB_MSG_1069 = 12894 -ER_IB_MSG_1070 = 12895 -ER_IB_MSG_1071 = 12896 -ER_IB_MSG_1072 = 12897 -ER_IB_MSG_1073 = 12898 -ER_IB_MSG_1074 = 12899 -ER_IB_MSG_1075 = 12900 -ER_IB_MSG_1076 = 12901 -ER_IB_MSG_1077 = 12902 -ER_IB_MSG_1078 = 12903 -ER_IB_MSG_1079 = 12904 -ER_IB_MSG_1080 = 12905 -ER_IB_MSG_1081 = 12906 -ER_IB_MSG_1082 = 12907 -ER_IB_MSG_1083 = 12908 -ER_IB_MSG_1084 = 12909 -ER_IB_MSG_1085 = 12910 -ER_IB_MSG_1086 = 12911 -ER_IB_MSG_1087 = 12912 -ER_IB_MSG_1088 = 12913 -ER_IB_MSG_1089 = 12914 -ER_IB_MSG_1090 = 12915 -ER_IB_MSG_1091 = 12916 -ER_IB_MSG_1092 = 12917 -ER_IB_MSG_1093 = 12918 -ER_IB_MSG_1094 = 12919 -ER_IB_MSG_1095 = 12920 -ER_IB_MSG_1096 = 12921 -ER_IB_MSG_1097 = 12922 -ER_IB_MSG_1098 = 12923 -ER_IB_MSG_1099 = 12924 -ER_IB_MSG_1100 = 12925 -ER_IB_MSG_1101 = 12926 -ER_IB_MSG_1102 = 12927 -ER_IB_MSG_1103 = 12928 -ER_IB_MSG_1104 = 12929 -ER_IB_MSG_1105 = 12930 -ER_IB_MSG_1106 = 12931 -ER_IB_MSG_1107 = 12932 -ER_IB_MSG_1108 = 12933 -ER_IB_MSG_1109 = 12934 -ER_IB_MSG_1110 = 12935 -ER_IB_MSG_1111 = 12936 -ER_IB_MSG_1112 = 12937 -ER_IB_MSG_1113 = 12938 -ER_IB_MSG_1114 = 12939 -ER_IB_MSG_1115 = 12940 -ER_IB_MSG_1116 = 12941 -ER_IB_MSG_1117 = 12942 -ER_IB_MSG_1118 = 12943 -ER_IB_MSG_1119 = 12944 -ER_IB_MSG_1120 = 12945 -ER_IB_MSG_1121 = 12946 -ER_IB_MSG_1122 = 12947 -ER_IB_MSG_1123 = 12948 -ER_IB_MSG_1124 = 12949 -ER_IB_MSG_1125 = 12950 -ER_IB_MSG_1126 = 12951 -ER_IB_MSG_1127 = 12952 -ER_IB_MSG_1128 = 12953 -ER_IB_MSG_1129 = 12954 -ER_IB_MSG_1130 = 12955 -ER_IB_MSG_1131 = 12956 -ER_IB_MSG_1132 = 12957 -ER_IB_MSG_1133 = 12958 -ER_IB_MSG_1134 = 12959 -ER_IB_MSG_1135 = 12960 -ER_IB_MSG_1136 = 12961 -ER_IB_MSG_1137 = 12962 -ER_IB_MSG_1138 = 12963 -ER_IB_MSG_1139 = 12964 -ER_IB_MSG_1140 = 12965 -ER_IB_MSG_1141 = 12966 -ER_IB_MSG_1142 = 12967 -ER_IB_MSG_1143 = 12968 -ER_IB_MSG_1144 = 12969 -ER_IB_MSG_1145 = 12970 -ER_IB_MSG_1146 = 12971 -ER_IB_MSG_1147 = 12972 -ER_IB_MSG_1148 = 12973 -ER_IB_MSG_1149 = 12974 -ER_IB_MSG_1150 = 12975 -ER_IB_MSG_1151 = 12976 -ER_IB_MSG_1152 = 12977 -ER_IB_MSG_1153 = 12978 -ER_IB_MSG_1154 = 12979 -ER_IB_MSG_1155 = 12980 -ER_IB_MSG_1156 = 12981 -ER_IB_MSG_1157 = 12982 -ER_IB_MSG_1158 = 12983 -ER_IB_MSG_1159 = 12984 -ER_IB_MSG_1160 = 12985 -ER_IB_MSG_1161 = 12986 -ER_IB_MSG_1162 = 12987 -ER_IB_MSG_1163 = 12988 -ER_IB_MSG_1164 = 12989 -ER_IB_MSG_1165 = 12990 -ER_IB_MSG_1166 = 12991 -ER_IB_MSG_1167 = 12992 -ER_IB_MSG_1168 = 12993 -ER_IB_MSG_1169 = 12994 -ER_IB_MSG_1170 = 12995 -ER_IB_MSG_1171 = 12996 -ER_IB_MSG_1172 = 12997 -ER_IB_MSG_1173 = 12998 -ER_IB_MSG_1174 = 12999 -ER_IB_MSG_1175 = 13000 -ER_IB_MSG_1176 = 13001 -ER_IB_MSG_1177 = 13002 -ER_IB_MSG_1178 = 13003 -ER_IB_MSG_1179 = 13004 -ER_IB_MSG_1180 = 13005 -ER_IB_MSG_1181 = 13006 -ER_IB_MSG_1182 = 13007 -ER_IB_MSG_1183 = 13008 -ER_IB_MSG_1184 = 13009 -ER_IB_MSG_1185 = 13010 -ER_IB_MSG_1186 = 13011 -ER_IB_MSG_1187 = 13012 -ER_IB_MSG_1188 = 13013 -ER_IB_MSG_1189 = 13014 -ER_IB_MSG_1190 = 13015 -ER_IB_MSG_1191 = 13016 -ER_IB_MSG_1192 = 13017 -ER_IB_MSG_1193 = 13018 -ER_IB_MSG_1194 = 13019 -ER_IB_MSG_1195 = 13020 -ER_IB_MSG_1196 = 13021 -ER_IB_MSG_1197 = 13022 -ER_IB_MSG_1198 = 13023 -ER_IB_MSG_1199 = 13024 -ER_IB_MSG_1200 = 13025 -ER_IB_MSG_1201 = 13026 -ER_IB_MSG_1202 = 13027 -ER_IB_MSG_1203 = 13028 -ER_IB_MSG_1204 = 13029 -ER_IB_MSG_1205 = 13030 -ER_IB_MSG_1206 = 13031 -ER_IB_MSG_1207 = 13032 -ER_IB_MSG_1208 = 13033 -ER_IB_MSG_1209 = 13034 -ER_IB_MSG_1210 = 13035 -ER_IB_MSG_1211 = 13036 -ER_IB_MSG_1212 = 13037 -ER_IB_MSG_1213 = 13038 -ER_IB_MSG_1214 = 13039 -ER_IB_MSG_1215 = 13040 -ER_IB_MSG_1216 = 13041 -ER_IB_MSG_1217 = 13042 -ER_IB_MSG_1218 = 13043 -ER_IB_MSG_1219 = 13044 -ER_IB_MSG_1220 = 13045 -ER_IB_MSG_1221 = 13046 -ER_IB_MSG_1222 = 13047 -ER_IB_MSG_1223 = 13048 -ER_IB_MSG_1224 = 13049 -ER_IB_MSG_1225 = 13050 -ER_IB_MSG_1226 = 13051 -ER_IB_MSG_1227 = 13052 -ER_IB_MSG_1228 = 13053 -ER_IB_MSG_1229 = 13054 -ER_IB_MSG_1230 = 13055 -ER_IB_MSG_1231 = 13056 -ER_IB_MSG_1232 = 13057 -ER_IB_MSG_1233 = 13058 -ER_IB_MSG_1234 = 13059 -ER_IB_MSG_1235 = 13060 -ER_IB_MSG_1236 = 13061 -ER_IB_MSG_1237 = 13062 -ER_IB_MSG_1238 = 13063 -ER_IB_MSG_1239 = 13064 -ER_IB_MSG_1240 = 13065 -ER_IB_MSG_1241 = 13066 -ER_IB_MSG_1242 = 13067 -ER_IB_MSG_1243 = 13068 -ER_IB_MSG_1244 = 13069 -ER_IB_MSG_1245 = 13070 -ER_IB_MSG_1246 = 13071 -ER_IB_MSG_1247 = 13072 -ER_IB_MSG_1248 = 13073 -ER_IB_MSG_1249 = 13074 -ER_IB_MSG_1250 = 13075 -ER_IB_MSG_1251 = 13076 -ER_IB_MSG_1252 = 13077 -ER_IB_MSG_1253 = 13078 -ER_IB_MSG_1254 = 13079 -ER_IB_MSG_1255 = 13080 -ER_IB_MSG_1256 = 13081 -ER_IB_MSG_1257 = 13082 -ER_IB_MSG_1258 = 13083 -ER_IB_MSG_1259 = 13084 -ER_IB_MSG_1260 = 13085 -ER_IB_MSG_1261 = 13086 -ER_IB_MSG_1262 = 13087 -ER_IB_MSG_1263 = 13088 -ER_IB_MSG_1264 = 13089 -ER_IB_MSG_1265 = 13090 -ER_IB_MSG_1266 = 13091 -ER_IB_MSG_1267 = 13092 -ER_IB_MSG_1268 = 13093 -ER_IB_MSG_1269 = 13094 -ER_IB_MSG_1270 = 13095 -ER_RPL_SLAVE_SQL_THREAD_STOP_CMD_EXEC_TIMEOUT = 13096 -ER_RPL_SLAVE_IO_THREAD_STOP_CMD_EXEC_TIMEOUT = 13097 -ER_RPL_GTID_UNSAFE_STMT_ON_NON_TRANS_TABLE = 13098 -ER_RPL_GTID_UNSAFE_STMT_CREATE_SELECT = 13099 -ER_RPL_GTID_UNSAFE_STMT_ON_TEMPORARY_TABLE = 13100 -ER_BINLOG_ROW_VALUE_OPTION_IGNORED = 13101 -ER_BINLOG_USE_V1_ROW_EVENTS_IGNORED = 13102 -ER_BINLOG_ROW_VALUE_OPTION_USED_ONLY_FOR_AFTER_IMAGES = 13103 -ER_CONNECTION_ABORTED = 13104 -ER_NORMAL_SERVER_SHUTDOWN = 13105 -ER_KEYRING_MIGRATE_FAILED = 13106 -ER_GRP_RPL_LOWER_CASE_TABLE_NAMES_DIFF_FROM_GRP = 13107 -ER_OOM_SAVE_GTIDS = 13108 -ER_LCTN_NOT_FOUND = 13109 -ER_REGEXP_INVALID_CAPTURE_GROUP_NAME = 13110 -ER_COMPONENT_FILTER_WRONG_VALUE = 13111 -ER_XPLUGIN_FAILED_TO_STOP_SERVICES = 13112 -ER_INCONSISTENT_ERROR = 13113 -ER_SERVER_MASTER_FATAL_ERROR_READING_BINLOG = 13114 -ER_NETWORK_READ_EVENT_CHECKSUM_FAILURE = 13115 -ER_SLAVE_CREATE_EVENT_FAILURE = 13116 -ER_SLAVE_FATAL_ERROR = 13117 -ER_SLAVE_HEARTBEAT_FAILURE = 13118 -ER_SLAVE_INCIDENT = 13119 -ER_SLAVE_MASTER_COM_FAILURE = 13120 -ER_SLAVE_RELAY_LOG_READ_FAILURE = 13121 -ER_SLAVE_RELAY_LOG_WRITE_FAILURE = 13122 -ER_SERVER_SLAVE_MI_INIT_REPOSITORY = 13123 -ER_SERVER_SLAVE_RLI_INIT_REPOSITORY = 13124 -ER_SERVER_NET_PACKET_TOO_LARGE = 13125 -ER_SERVER_NO_SYSTEM_TABLE_ACCESS = 13126 -ER_SERVER_UNKNOWN_ERROR = 13127 -ER_SERVER_UNKNOWN_SYSTEM_VARIABLE = 13128 -ER_SERVER_NO_SESSION_TO_SEND_TO = 13129 -ER_SERVER_NEW_ABORTING_CONNECTION = 13130 -ER_SERVER_OUT_OF_SORTMEMORY = 13131 -ER_SERVER_RECORD_FILE_FULL = 13132 -ER_SERVER_DISK_FULL_NOWAIT = 13133 -ER_SERVER_HANDLER_ERROR = 13134 -ER_SERVER_NOT_FORM_FILE = 13135 -ER_SERVER_CANT_OPEN_FILE = 13136 -ER_SERVER_FILE_NOT_FOUND = 13137 -ER_SERVER_FILE_USED = 13138 -ER_SERVER_CANNOT_LOAD_FROM_TABLE_V2 = 13139 -ER_ERROR_INFO_FROM_DA = 13140 -ER_SERVER_TABLE_CHECK_FAILED = 13141 -ER_SERVER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE_V2 = 13142 -ER_SERVER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2 = 13143 -ER_SERVER_ACL_TABLE_ERROR = 13144 -ER_SERVER_SLAVE_INIT_QUERY_FAILED = 13145 -ER_SERVER_SLAVE_CONVERSION_FAILED = 13146 -ER_SERVER_SLAVE_IGNORED_TABLE = 13147 -ER_CANT_REPLICATE_ANONYMOUS_WITH_AUTO_POSITION = 13148 -ER_CANT_REPLICATE_ANONYMOUS_WITH_GTID_MODE_ON = 13149 -ER_CANT_REPLICATE_GTID_WITH_GTID_MODE_OFF = 13150 -ER_SERVER_TEST_MESSAGE = 13151 -ER_AUDIT_LOG_JSON_FILTER_PARSING_ERROR = 13152 -ER_AUDIT_LOG_JSON_FILTERING_NOT_ENABLED = 13153 -ER_PLUGIN_FAILED_TO_OPEN_TABLES = 13154 -ER_PLUGIN_FAILED_TO_OPEN_TABLE = 13155 -ER_AUDIT_LOG_JSON_FILTER_NAME_CANNOT_BE_EMPTY = 13156 -ER_AUDIT_LOG_USER_NAME_INVALID_CHARACTER = 13157 -ER_AUDIT_LOG_UDF_INSUFFICIENT_PRIVILEGE = 13158 -ER_AUDIT_LOG_NO_KEYRING_PLUGIN_INSTALLED = 13159 -ER_AUDIT_LOG_HOST_NAME_INVALID_CHARACTER = 13160 -ER_AUDIT_LOG_ENCRYPTION_PASSWORD_HAS_NOT_BEEN_SET = 13161 -ER_AUDIT_LOG_COULD_NOT_CREATE_AES_KEY = 13162 -ER_AUDIT_LOG_ENCRYPTION_PASSWORD_CANNOT_BE_FETCHED = 13163 -ER_COULD_NOT_REINITIALIZE_AUDIT_LOG_FILTERS = 13164 -ER_AUDIT_LOG_JSON_USER_NAME_CANNOT_BE_EMPTY = 13165 -ER_AUDIT_LOG_USER_FIRST_CHARACTER_MUST_BE_ALPHANUMERIC = 13166 -ER_AUDIT_LOG_JSON_FILTER_DOES_NOT_EXIST = 13167 -CR_UNKNOWN_ERROR = 2000 -CR_SOCKET_CREATE_ERROR = 2001 -CR_CONNECTION_ERROR = 2002 -CR_CONN_HOST_ERROR = 2003 -CR_IPSOCK_ERROR = 2004 -CR_UNKNOWN_HOST = 2005 -CR_SERVER_GONE_ERROR = 2006 -CR_VERSION_ERROR = 2007 -CR_OUT_OF_MEMORY = 2008 -CR_WRONG_HOST_INFO = 2009 -CR_LOCALHOST_CONNECTION = 2010 -CR_TCP_CONNECTION = 2011 -CR_SERVER_HANDSHAKE_ERR = 2012 -CR_SERVER_LOST = 2013 -CR_COMMANDS_OUT_OF_SYNC = 2014 -CR_NAMEDPIPE_CONNECTION = 2015 -CR_NAMEDPIPEWAIT_ERROR = 2016 -CR_NAMEDPIPEOPEN_ERROR = 2017 -CR_NAMEDPIPESETSTATE_ERROR = 2018 -CR_CANT_READ_CHARSET = 2019 -CR_NET_PACKET_TOO_LARGE = 2020 -CR_EMBEDDED_CONNECTION = 2021 -CR_PROBE_SLAVE_STATUS = 2022 -CR_PROBE_SLAVE_HOSTS = 2023 -CR_PROBE_SLAVE_CONNECT = 2024 -CR_PROBE_MASTER_CONNECT = 2025 -CR_SSL_CONNECTION_ERROR = 2026 -CR_MALFORMED_PACKET = 2027 -CR_WRONG_LICENSE = 2028 -CR_NULL_POINTER = 2029 -CR_NO_PREPARE_STMT = 2030 -CR_PARAMS_NOT_BOUND = 2031 -CR_DATA_TRUNCATED = 2032 -CR_NO_PARAMETERS_EXISTS = 2033 -CR_INVALID_PARAMETER_NO = 2034 -CR_INVALID_BUFFER_USE = 2035 -CR_UNSUPPORTED_PARAM_TYPE = 2036 -CR_SHARED_MEMORY_CONNECTION = 2037 -CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR = 2038 -CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR = 2039 -CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = 2040 -CR_SHARED_MEMORY_CONNECT_MAP_ERROR = 2041 -CR_SHARED_MEMORY_FILE_MAP_ERROR = 2042 -CR_SHARED_MEMORY_MAP_ERROR = 2043 -CR_SHARED_MEMORY_EVENT_ERROR = 2044 -CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR = 2045 -CR_SHARED_MEMORY_CONNECT_SET_ERROR = 2046 -CR_CONN_UNKNOW_PROTOCOL = 2047 -CR_INVALID_CONN_HANDLE = 2048 -CR_UNUSED_1 = 2049 -CR_FETCH_CANCELED = 2050 -CR_NO_DATA = 2051 -CR_NO_STMT_METADATA = 2052 -CR_NO_RESULT_SET = 2053 -CR_NOT_IMPLEMENTED = 2054 -CR_SERVER_LOST_EXTENDED = 2055 -CR_STMT_CLOSED = 2056 -CR_NEW_STMT_METADATA = 2057 -CR_ALREADY_CONNECTED = 2058 -CR_AUTH_PLUGIN_CANNOT_LOAD = 2059 -CR_DUPLICATE_CONNECTION_ATTR = 2060 -CR_AUTH_PLUGIN_ERR = 2061 -CR_INSECURE_API_ERR = 2062 -CR_FILE_NAME_TOO_LONG = 2063 -CR_SSL_FIPS_MODE_ERR = 2064 -# End MySQL Errors - diff --git a/modules/mysql-connector-python/mysql/connector/errors.py b/modules/mysql-connector-python/mysql/connector/errors.py deleted file mode 100644 index 4accce235..000000000 --- a/modules/mysql-connector-python/mysql/connector/errors.py +++ /dev/null @@ -1,307 +0,0 @@ -# Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Python exceptions -""" - -from . import utils -from .locales import get_client_error -from .catch23 import PY2 - -# _CUSTOM_ERROR_EXCEPTIONS holds custom exceptions and is ued by the -# function custom_error_exception. _ERROR_EXCEPTIONS (at bottom of module) -# is similar, but hardcoded exceptions. -_CUSTOM_ERROR_EXCEPTIONS = {} - - -def custom_error_exception(error=None, exception=None): - """Define custom exceptions for MySQL server errors - - This function defines custom exceptions for MySQL server errors and - returns the current set customizations. - - If error is a MySQL Server error number, then you have to pass also the - exception class. - - The error argument can also be a dictionary in which case the key is - the server error number, and value the exception to be raised. - - If none of the arguments are given, then custom_error_exception() will - simply return the current set customizations. - - To reset the customizations, simply supply an empty dictionary. - - Examples: - import mysql.connector - from mysql.connector import errorcode - - # Server error 1028 should raise a DatabaseError - mysql.connector.custom_error_exception( - 1028, mysql.connector.DatabaseError) - - # Or using a dictionary: - mysql.connector.custom_error_exception({ - 1028: mysql.connector.DatabaseError, - 1029: mysql.connector.OperationalError, - }) - - # Reset - mysql.connector.custom_error_exception({}) - - Returns a dictionary. - """ - global _CUSTOM_ERROR_EXCEPTIONS # pylint: disable=W0603 - - if isinstance(error, dict) and not error: - _CUSTOM_ERROR_EXCEPTIONS = {} - return _CUSTOM_ERROR_EXCEPTIONS - - if not error and not exception: - return _CUSTOM_ERROR_EXCEPTIONS - - if not isinstance(error, (int, dict)): - raise ValueError( - "The error argument should be either an integer or dictionary") - - if isinstance(error, int): - error = {error: exception} - - for errno, _exception in error.items(): - if not isinstance(errno, int): - raise ValueError("error number should be an integer") - try: - if not issubclass(_exception, Exception): - raise TypeError - except TypeError: - raise ValueError("exception should be subclass of Exception") - _CUSTOM_ERROR_EXCEPTIONS[errno] = _exception - - return _CUSTOM_ERROR_EXCEPTIONS - -def get_mysql_exception(errno, msg=None, sqlstate=None): - """Get the exception matching the MySQL error - - This function will return an exception based on the SQLState. The given - message will be passed on in the returned exception. - - The exception returned can be customized using the - mysql.connector.custom_error_exception() function. - - Returns an Exception - """ - try: - return _CUSTOM_ERROR_EXCEPTIONS[errno]( - msg=msg, errno=errno, sqlstate=sqlstate) - except KeyError: - # Error was not mapped to particular exception - pass - - try: - return _ERROR_EXCEPTIONS[errno]( - msg=msg, errno=errno, sqlstate=sqlstate) - except KeyError: - # Error was not mapped to particular exception - pass - - if not sqlstate: - return DatabaseError(msg=msg, errno=errno) - - try: - return _SQLSTATE_CLASS_EXCEPTION[sqlstate[0:2]]( - msg=msg, errno=errno, sqlstate=sqlstate) - except KeyError: - # Return default InterfaceError - return DatabaseError(msg=msg, errno=errno, sqlstate=sqlstate) - -def get_exception(packet): - """Returns an exception object based on the MySQL error - - Returns an exception object based on the MySQL error in the given - packet. - - Returns an Error-Object. - """ - errno = errmsg = None - - try: - if packet[4] != 255: - raise ValueError("Packet is not an error packet") - except IndexError as err: - return InterfaceError("Failed getting Error information (%r)" % err) - - sqlstate = None - try: - packet = packet[5:] - (packet, errno) = utils.read_int(packet, 2) - if packet[0] != 35: - # Error without SQLState - if isinstance(packet, (bytes, bytearray)): - errmsg = packet.decode('utf8') - else: - errmsg = packet - else: - (packet, sqlstate) = utils.read_bytes(packet[1:], 5) - sqlstate = sqlstate.decode('utf8') - errmsg = packet.decode('utf8') - except Exception as err: # pylint: disable=W0703 - return InterfaceError("Failed getting Error information (%r)" % err) - else: - return get_mysql_exception(errno, errmsg, sqlstate) - - -class Error(Exception): - """Exception that is base class for all other error exceptions""" - def __init__(self, msg=None, errno=None, values=None, sqlstate=None): - super(Error, self).__init__() - self.msg = msg - self._full_msg = self.msg - self.errno = errno or -1 - self.sqlstate = sqlstate - - if not self.msg and (2000 <= self.errno < 3000): - self.msg = get_client_error(self.errno) - if values is not None: - try: - self.msg = self.msg % values - except TypeError as err: - self.msg = "{0} (Warning: {1})".format(self.msg, str(err)) - elif not self.msg: - self._full_msg = self.msg = 'Unknown error' - - if self.msg and self.errno != -1: - fields = { - 'errno': self.errno, - 'msg': self.msg.encode('utf8') if PY2 else self.msg - } - if self.sqlstate: - fmt = '{errno} ({state}): {msg}' - fields['state'] = self.sqlstate - else: - fmt = '{errno}: {msg}' - self._full_msg = fmt.format(**fields) - - self.args = (self.errno, self._full_msg, self.sqlstate) - - def __str__(self): - return self._full_msg - - -class Warning(Exception): # pylint: disable=W0622 - """Exception for important warnings""" - pass - - -class InterfaceError(Error): - """Exception for errors related to the interface""" - pass - - -class DatabaseError(Error): - """Exception for errors related to the database""" - pass - - -class InternalError(DatabaseError): - """Exception for errors internal database errors""" - pass - - -class OperationalError(DatabaseError): - """Exception for errors related to the database's operation""" - pass - - -class ProgrammingError(DatabaseError): - """Exception for errors programming errors""" - pass - - -class IntegrityError(DatabaseError): - """Exception for errors regarding relational integrity""" - pass - - -class DataError(DatabaseError): - """Exception for errors reporting problems with processed data""" - pass - - -class NotSupportedError(DatabaseError): - """Exception for errors when an unsupported database feature was used""" - pass - - -class PoolError(Error): - """Exception for errors relating to connection pooling""" - pass - - -_SQLSTATE_CLASS_EXCEPTION = { - '02': DataError, # no data - '07': DatabaseError, # dynamic SQL error - '08': OperationalError, # connection exception - '0A': NotSupportedError, # feature not supported - '21': DataError, # cardinality violation - '22': DataError, # data exception - '23': IntegrityError, # integrity constraint violation - '24': ProgrammingError, # invalid cursor state - '25': ProgrammingError, # invalid transaction state - '26': ProgrammingError, # invalid SQL statement name - '27': ProgrammingError, # triggered data change violation - '28': ProgrammingError, # invalid authorization specification - '2A': ProgrammingError, # direct SQL syntax error or access rule violation - '2B': DatabaseError, # dependent privilege descriptors still exist - '2C': ProgrammingError, # invalid character set name - '2D': DatabaseError, # invalid transaction termination - '2E': DatabaseError, # invalid connection name - '33': DatabaseError, # invalid SQL descriptor name - '34': ProgrammingError, # invalid cursor name - '35': ProgrammingError, # invalid condition number - '37': ProgrammingError, # dynamic SQL syntax error or access rule violation - '3C': ProgrammingError, # ambiguous cursor name - '3D': ProgrammingError, # invalid catalog name - '3F': ProgrammingError, # invalid schema name - '40': InternalError, # transaction rollback - '42': ProgrammingError, # syntax error or access rule violation - '44': InternalError, # with check option violation - 'HZ': OperationalError, # remote database access - 'XA': IntegrityError, - '0K': OperationalError, - 'HY': DatabaseError, # default when no SQLState provided by MySQL server -} - -_ERROR_EXCEPTIONS = { - 1243: ProgrammingError, - 1210: ProgrammingError, - 2002: InterfaceError, - 2013: OperationalError, - 2049: NotSupportedError, - 2055: OperationalError, - 2061: InterfaceError, - 2026: InterfaceError, -} diff --git a/modules/mysql-connector-python/mysql/connector/locales/__init__.py b/modules/mysql-connector-python/mysql/connector/locales/__init__.py deleted file mode 100644 index c1a737bf8..000000000 --- a/modules/mysql-connector-python/mysql/connector/locales/__init__.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Translations -""" - -__all__ = [ - 'get_client_error' -] - -from .. import errorcode - -def get_client_error(error, language='eng'): - """Lookup client error - - This function will lookup the client error message based on the given - error and return the error message. If the error was not found, - None will be returned. - - Error can be either an integer or a string. For example: - error: 2000 - error: CR_UNKNOWN_ERROR - - The language attribute can be used to retrieve a localized message, when - available. - - Returns a string or None. - """ - try: - tmp = __import__('mysql.connector.locales.{0}'.format(language), - globals(), locals(), ['client_error']) - except ImportError: - raise ImportError("No localization support for language '{0}'".format( - language)) - client_error = tmp.client_error - - if isinstance(error, int): - errno = error - for key, value in errorcode.__dict__.items(): - if value == errno: - error = key - break - - if isinstance(error, (str)): - try: - return getattr(client_error, error) - except AttributeError: - return None - - raise ValueError("error argument needs to be either an integer or string") diff --git a/modules/mysql-connector-python/mysql/connector/locales/eng/__init__.py b/modules/mysql-connector-python/mysql/connector/locales/eng/__init__.py deleted file mode 100644 index 2e1c02b1e..000000000 --- a/modules/mysql-connector-python/mysql/connector/locales/eng/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -"""English Content -""" diff --git a/modules/mysql-connector-python/mysql/connector/locales/eng/client_error.py b/modules/mysql-connector-python/mysql/connector/locales/eng/client_error.py deleted file mode 100644 index a9ed8b95d..000000000 --- a/modules/mysql-connector-python/mysql/connector/locales/eng/client_error.py +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -# This file was auto-generated. -_GENERATED_ON = '2018-03-16' -_MYSQL_VERSION = (8, 0, 11) - -# Start MySQL Error messages -CR_UNKNOWN_ERROR = u"Unknown MySQL error" -CR_SOCKET_CREATE_ERROR = u"Can't create UNIX socket (%s)" -CR_CONNECTION_ERROR = u"Can't connect to local MySQL server through socket '%-.100s' (%s)" -CR_CONN_HOST_ERROR = u"Can't connect to MySQL server on '%-.100s' (%s)" -CR_IPSOCK_ERROR = u"Can't create TCP/IP socket (%s)" -CR_UNKNOWN_HOST = u"Unknown MySQL server host '%-.100s' (%s)" -CR_SERVER_GONE_ERROR = u"MySQL server has gone away" -CR_VERSION_ERROR = u"Protocol mismatch; server version = %s, client version = %s" -CR_OUT_OF_MEMORY = u"MySQL client ran out of memory" -CR_WRONG_HOST_INFO = u"Wrong host info" -CR_LOCALHOST_CONNECTION = u"Localhost via UNIX socket" -CR_TCP_CONNECTION = u"%-.100s via TCP/IP" -CR_SERVER_HANDSHAKE_ERR = u"Error in server handshake" -CR_SERVER_LOST = u"Lost connection to MySQL server during query" -CR_COMMANDS_OUT_OF_SYNC = u"Commands out of sync; you can't run this command now" -CR_NAMEDPIPE_CONNECTION = u"Named pipe: %-.32s" -CR_NAMEDPIPEWAIT_ERROR = u"Can't wait for named pipe to host: %-.64s pipe: %-.32s (%s)" -CR_NAMEDPIPEOPEN_ERROR = u"Can't open named pipe to host: %-.64s pipe: %-.32s (%s)" -CR_NAMEDPIPESETSTATE_ERROR = u"Can't set state of named pipe to host: %-.64s pipe: %-.32s (%s)" -CR_CANT_READ_CHARSET = u"Can't initialize character set %-.32s (path: %-.100s)" -CR_NET_PACKET_TOO_LARGE = u"Got packet bigger than 'max_allowed_packet' bytes" -CR_EMBEDDED_CONNECTION = u"Embedded server" -CR_PROBE_SLAVE_STATUS = u"Error on SHOW SLAVE STATUS:" -CR_PROBE_SLAVE_HOSTS = u"Error on SHOW SLAVE HOSTS:" -CR_PROBE_SLAVE_CONNECT = u"Error connecting to slave:" -CR_PROBE_MASTER_CONNECT = u"Error connecting to master:" -CR_SSL_CONNECTION_ERROR = u"SSL connection error: %-.100s" -CR_MALFORMED_PACKET = u"Malformed packet" -CR_WRONG_LICENSE = u"This client library is licensed only for use with MySQL servers having '%s' license" -CR_NULL_POINTER = u"Invalid use of null pointer" -CR_NO_PREPARE_STMT = u"Statement not prepared" -CR_PARAMS_NOT_BOUND = u"No data supplied for parameters in prepared statement" -CR_DATA_TRUNCATED = u"Data truncated" -CR_NO_PARAMETERS_EXISTS = u"No parameters exist in the statement" -CR_INVALID_PARAMETER_NO = u"Invalid parameter number" -CR_INVALID_BUFFER_USE = u"Can't send long data for non-string/non-binary data types (parameter: %s)" -CR_UNSUPPORTED_PARAM_TYPE = u"Using unsupported buffer type: %s (parameter: %s)" -CR_SHARED_MEMORY_CONNECTION = u"Shared memory: %-.100s" -CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR = u"Can't open shared memory; client could not create request event (%s)" -CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR = u"Can't open shared memory; no answer event received from server (%s)" -CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = u"Can't open shared memory; server could not allocate file mapping (%s)" -CR_SHARED_MEMORY_CONNECT_MAP_ERROR = u"Can't open shared memory; server could not get pointer to file mapping (%s)" -CR_SHARED_MEMORY_FILE_MAP_ERROR = u"Can't open shared memory; client could not allocate file mapping (%s)" -CR_SHARED_MEMORY_MAP_ERROR = u"Can't open shared memory; client could not get pointer to file mapping (%s)" -CR_SHARED_MEMORY_EVENT_ERROR = u"Can't open shared memory; client could not create %s event (%s)" -CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR = u"Can't open shared memory; no answer from server (%s)" -CR_SHARED_MEMORY_CONNECT_SET_ERROR = u"Can't open shared memory; cannot send request event to server (%s)" -CR_CONN_UNKNOW_PROTOCOL = u"Wrong or unknown protocol" -CR_INVALID_CONN_HANDLE = u"Invalid connection handle" -CR_UNUSED_1 = u"Connection using old (pre-4.1.1) authentication protocol refused (client option 'secure_auth' enabled)" -CR_FETCH_CANCELED = u"Row retrieval was canceled by mysql_stmt_close() call" -CR_NO_DATA = u"Attempt to read column without prior row fetch" -CR_NO_STMT_METADATA = u"Prepared statement contains no metadata" -CR_NO_RESULT_SET = u"Attempt to read a row while there is no result set associated with the statement" -CR_NOT_IMPLEMENTED = u"This feature is not implemented yet" -CR_SERVER_LOST_EXTENDED = u"Lost connection to MySQL server at '%s', system error: %s" -CR_STMT_CLOSED = u"Statement closed indirectly because of a preceding %s() call" -CR_NEW_STMT_METADATA = u"The number of columns in the result set differs from the number of bound buffers. You must reset the statement, rebind the result set columns, and execute the statement again" -CR_ALREADY_CONNECTED = u"This handle is already connected. Use a separate handle for each connection." -CR_AUTH_PLUGIN_CANNOT_LOAD = u"Authentication plugin '%s' cannot be loaded: %s" -CR_DUPLICATE_CONNECTION_ATTR = u"There is an attribute with the same name already" -CR_AUTH_PLUGIN_ERR = u"Authentication plugin '%s' reported error: %s" -CR_INSECURE_API_ERR = u"Insecure API function call: '%s' Use instead: '%s'" -CR_FILE_NAME_TOO_LONG = u"File name is too long" -CR_SSL_FIPS_MODE_ERR = u"Set FIPS mode ON/STRICT failed" -# End MySQL Error messages - diff --git a/modules/mysql-connector-python/mysql/connector/network.py b/modules/mysql-connector-python/mysql/connector/network.py deleted file mode 100644 index 3e7b99a24..000000000 --- a/modules/mysql-connector-python/mysql/connector/network.py +++ /dev/null @@ -1,534 +0,0 @@ -# Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Module implementing low-level socket communication with MySQL servers. -""" - -from collections import deque -import socket -import struct -import sys -import zlib - -try: - import ssl -except: - # If import fails, we don't have SSL support. - pass - -from . import constants, errors -from .catch23 import PY2, init_bytearray, struct_unpack - - -def _strioerror(err): - """Reformat the IOError error message - - This function reformats the IOError error message. - """ - if not err.errno: - return str(err) - return '{errno} {strerr}'.format(errno=err.errno, strerr=err.strerror) - - -def _prepare_packets(buf, pktnr): - """Prepare a packet for sending to the MySQL server""" - pkts = [] - pllen = len(buf) - maxpktlen = constants.MAX_PACKET_LENGTH - while pllen > maxpktlen: - pkts.append(b'\xff\xff\xff' + struct.pack(' 255: - self._packet_number = 0 - return self._packet_number - - @property - def next_compressed_packet_number(self): - """Increments the compressed packet number""" - self._compressed_packet_number = self._compressed_packet_number + 1 - if self._compressed_packet_number > 255: - self._compressed_packet_number = 0 - return self._compressed_packet_number - - def open_connection(self): - """Open the socket""" - raise NotImplementedError - - def get_address(self): - """Get the location of the socket""" - raise NotImplementedError - - def shutdown(self): - """Shut down the socket before closing it""" - try: - self.sock.shutdown(socket.SHUT_RDWR) - self.sock.close() - del self._packet_queue - except (socket.error, AttributeError): - pass - - def close_connection(self): - """Close the socket""" - try: - self.sock.close() - del self._packet_queue - except (socket.error, AttributeError): - pass - - def __del__(self): - self.shutdown() - - def send_plain(self, buf, packet_number=None, - compressed_packet_number=None): - """Send packets to the MySQL server""" - if packet_number is None: - self.next_packet_number # pylint: disable=W0104 - else: - self._packet_number = packet_number - packets = _prepare_packets(buf, self._packet_number) - for packet in packets: - try: - if PY2: - self.sock.sendall(buffer(packet)) # pylint: disable=E0602 - else: - self.sock.sendall(packet) - except IOError as err: - raise errors.OperationalError( - errno=2055, values=(self.get_address(), _strioerror(err))) - except AttributeError: - raise errors.OperationalError(errno=2006) - - send = send_plain - - def send_compressed(self, buf, packet_number=None, - compressed_packet_number=None): - """Send compressed packets to the MySQL server""" - if packet_number is None: - self.next_packet_number # pylint: disable=W0104 - else: - self._packet_number = packet_number - if compressed_packet_number is None: - self.next_compressed_packet_number # pylint: disable=W0104 - else: - self._compressed_packet_number = compressed_packet_number - - pktnr = self._packet_number - pllen = len(buf) - zpkts = [] - maxpktlen = constants.MAX_PACKET_LENGTH - if pllen > maxpktlen: - pkts = _prepare_packets(buf, pktnr) - if PY2: - tmpbuf = bytearray() - for pkt in pkts: - tmpbuf += pkt - tmpbuf = buffer(tmpbuf) # pylint: disable=E0602 - else: - tmpbuf = b''.join(pkts) - del pkts - zbuf = zlib.compress(tmpbuf[:16384]) - header = (struct.pack(' maxpktlen: - zbuf = zlib.compress(tmpbuf[:maxpktlen]) - header = (struct.pack(' 50: - zbuf = zlib.compress(pkt) - zpkts.append(struct.pack(' 0: - raise errors.InterfaceError(errno=2013) - packet_view = packet_view[read:] - rest -= read - return packet - except IOError as err: - raise errors.OperationalError( - errno=2055, values=(self.get_address(), _strioerror(err))) - - def recv_py26_plain(self): - """Receive packets from the MySQL server""" - try: - # Read the header of the MySQL packet, 4 bytes - header = bytearray(b'') - header_len = 0 - while header_len < 4: - chunk = self.sock.recv(4 - header_len) - if not chunk: - raise errors.InterfaceError(errno=2013) - header += chunk - header_len = len(header) - - # Save the packet number and payload length - self._packet_number = header[3] - payload_len = struct_unpack(" 0: - chunk = self.sock.recv(rest) - if not chunk: - raise errors.InterfaceError(errno=2013) - payload += chunk - rest = payload_len - len(payload) - return header + payload - except IOError as err: - raise errors.OperationalError( - errno=2055, values=(self.get_address(), _strioerror(err))) - - if sys.version_info[0:2] == (2, 6): - recv = recv_py26_plain - recv_plain = recv_py26_plain - else: - recv = recv_plain - - def _split_zipped_payload(self, packet_bunch): - """Split compressed payload""" - while packet_bunch: - if PY2: - payload_length = struct.unpack_from( - "[^:=\s][^:=]*)' - r'\s*(?:' - r'(?P[:=])\s*' - r'(?P.*))?$' - ) - - self._options_dict = {} - - if PY2: - SafeConfigParser.__init__(self) - else: - SafeConfigParser.__init__(self, strict=False) - - self.default_extension = DEFAULT_EXTENSIONS[os.name] - self.keep_dashes = keep_dashes - - if not files: - raise ValueError('files argument should be given') - if isinstance(files, str): - self.files = [files] - else: - self.files = files - - self._parse_options(list(self.files)) - self._sections = self.get_groups_as_dict() - - def optionxform(self, optionstr): - """Converts option strings - - Converts option strings to lower case and replaces dashes(-) with - underscores(_) if keep_dashes variable is set. - """ - if not self.keep_dashes: - optionstr = optionstr.replace('-', '_') - return optionstr.lower() - - def _parse_options(self, files): - """Parse options from files given as arguments. - This method checks for !include or !inculdedir directives and if there - is any, those files included by these directives are also parsed - for options. - - Raises ValueError if any of the included or file given in arguments - is not readable. - """ - index = 0 - err_msg = "Option file '{0}' being included again in file '{1}'" - - for file_ in files: - try: - if file_ in files[index+1:]: - raise ValueError("Same option file '{0}' occurring more " - "than once in the list".format(file_)) - with open(file_, 'r') as op_file: - for line in op_file.readlines(): - if line.startswith('!includedir'): - _, dir_path = line.split(None, 1) - dir_path = dir_path.strip() - for entry in os.listdir(dir_path): - entry = os.path.join(dir_path, entry) - if entry in files: - raise ValueError(err_msg.format( - entry, file_)) - if (os.path.isfile(entry) and - entry.endswith(self.default_extension)): - files.insert(index+1, entry) - - elif line.startswith('!include'): - _, filename = line.split(None, 1) - filename = filename.strip() - if filename in files: - raise ValueError(err_msg.format( - filename, file_)) - files.insert(index+1, filename) - - index += 1 - - except (IOError, OSError) as exc: - raise ValueError("Failed reading file '{0}': {1}".format( - file_, str(exc))) - - read_files = self.read(files) - not_read_files = set(files) - set(read_files) - if not_read_files: - raise ValueError("File(s) {0} could not be read.".format( - ', '.join(not_read_files))) - - def read(self, filenames): # pylint: disable=W0221 - """Read and parse a filename or a list of filenames. - - Overridden from ConfigParser and modified so as to allow options - which are not inside any section header - - Return list of successfully read files. - """ - if isinstance(filenames, str): - filenames = [filenames] - read_ok = [] - for priority, filename in enumerate(filenames): - try: - out_file = io.StringIO() - for line in codecs.open(filename, encoding='utf-8'): - line = line.strip() - match_obj = self.OPTCRE.match(line) - if not self.SECTCRE.match(line) and match_obj: - optname, delimiter, optval = match_obj.group('option', - 'vi', - 'value') - if optname and not optval and not delimiter: - out_file.write(line + "=\n") - else: - out_file.write(line + '\n') - else: - out_file.write(line + '\n') - out_file.seek(0) - except IOError: - continue - try: - self._read(out_file, filename) - for group in self._sections.keys(): - try: - self._options_dict[group] - except KeyError: - self._options_dict[group] = {} - for option, value in self._sections[group].items(): - self._options_dict[group][option] = (value, priority) - - self._sections = self._dict() - - except MissingSectionHeaderError: - self._read(out_file, filename) - out_file.close() - read_ok.append(filename) - return read_ok - - def get_groups(self, *args): - """Returns options as a dictionary. - - Returns options from all the groups specified as arguments, returns - the options from all groups if no argument provided. Options are - overridden when they are found in the next group. - - Returns a dictionary - """ - if not args: - args = self._options_dict.keys() - - options = {} - priority = {} - for group in args: - try: - for option, value in [(key, value,) for key, value in - self._options_dict[group].items() if - key != "__name__" and - not key.startswith("!")]: - if option not in options or priority[option] <= value[1]: - priority[option] = value[1] - options[option] = value[0] - except KeyError: - pass - - return options - - def get_groups_as_dict_with_priority(self, *args): # pylint: disable=C0103 - """Returns options as dictionary of dictionaries. - - Returns options from all the groups specified as arguments. For each - group the option are contained in a dictionary. The order in which - the groups are specified is unimportant. Also options are not - overridden in between the groups. - - The value is a tuple with two elements, first being the actual value - and second is the priority of the value which is higher for a value - read from a higher priority file. - - Returns an dictionary of dictionaries - """ - if not args: - args = self._options_dict.keys() - - options = dict() - for group in args: - try: - options[group] = dict((key, value,) for key, value in - self._options_dict[group].items() if - key != "__name__" and - not key.startswith("!")) - except KeyError: - pass - - return options - - def get_groups_as_dict(self, *args): - """Returns options as dictionary of dictionaries. - - Returns options from all the groups specified as arguments. For each - group the option are contained in a dictionary. The order in which - the groups are specified is unimportant. Also options are not - overridden in between the groups. - - Returns an dictionary of dictionaries - """ - if not args: - args = self._options_dict.keys() - - options = dict() - for group in args: - try: - options[group] = dict((key, value[0],) for key, value in - self._options_dict[group].items() if - key != "__name__" and - not key.startswith("!")) - except KeyError: - pass - - return options diff --git a/modules/mysql-connector-python/mysql/connector/pooling.py b/modules/mysql-connector-python/mysql/connector/pooling.py deleted file mode 100644 index 3be8b3d4e..000000000 --- a/modules/mysql-connector-python/mysql/connector/pooling.py +++ /dev/null @@ -1,361 +0,0 @@ -# Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Implementing pooling of connections to MySQL servers. -""" - -import re -from uuid import uuid4 -# pylint: disable=F0401 -try: - import queue -except ImportError: - # Python v2 - import Queue as queue -# pylint: enable=F0401 -import threading - -from . import errors -from .connection import MySQLConnection - -CONNECTION_POOL_LOCK = threading.RLock() -CNX_POOL_MAXSIZE = 32 -CNX_POOL_MAXNAMESIZE = 64 -CNX_POOL_NAMEREGEX = re.compile(r'[^a-zA-Z0-9._:\-*$#]') - - -def generate_pool_name(**kwargs): - """Generate a pool name - - This function takes keyword arguments, usually the connection - arguments for MySQLConnection, and tries to generate a name for - a pool. - - Raises PoolError when no name can be generated. - - Returns a string. - """ - parts = [] - for key in ('host', 'port', 'user', 'database'): - try: - parts.append(str(kwargs[key])) - except KeyError: - pass - - if not parts: - raise errors.PoolError( - "Failed generating pool name; specify pool_name") - - return '_'.join(parts) - - -class PooledMySQLConnection(object): - """Class holding a MySQL Connection in a pool - - PooledMySQLConnection is used by MySQLConnectionPool to return an - instance holding a MySQL connection. It works like a MySQLConnection - except for methods like close() and config(). - - The close()-method will add the connection back to the pool rather - than disconnecting from the MySQL server. - - Configuring the connection have to be done through the MySQLConnectionPool - method set_config(). Using config() on pooled connection will raise a - PoolError. - """ - def __init__(self, pool, cnx): - """Initialize - - The pool argument must be an instance of MySQLConnectionPoll. cnx - if an instance of MySQLConnection. - """ - if not isinstance(pool, MySQLConnectionPool): - raise AttributeError( - "pool should be a MySQLConnectionPool") - if not isinstance(cnx, MySQLConnection): - raise AttributeError( - "cnx should be a MySQLConnection") - self._cnx_pool = pool - self._cnx = cnx - - def __getattr__(self, attr): - """Calls attributes of the MySQLConnection instance""" - return getattr(self._cnx, attr) - - def close(self): - """Do not close, but add connection back to pool - - The close() method does not close the connection with the - MySQL server. The connection is added back to the pool so it - can be reused. - - When the pool is configured to reset the session, the session - state will be cleared by re-authenticating the user. - """ - try: - cnx = self._cnx - if self._cnx_pool.reset_session: - cnx.reset_session() - finally: - self._cnx_pool.add_connection(cnx) - self._cnx = None - - def config(self, **kwargs): - """Configuration is done through the pool""" - raise errors.PoolError( - "Configuration for pooled connections should " - "be done through the pool itself." - ) - - @property - def pool_name(self): - """Return the name of the connection pool""" - return self._cnx_pool.pool_name - - -class MySQLConnectionPool(object): - """Class defining a pool of MySQL connections""" - def __init__(self, pool_size=5, pool_name=None, pool_reset_session=True, - **kwargs): - """Initialize - - Initialize a MySQL connection pool with a maximum number of - connections set to pool_size. The rest of the keywords - arguments, kwargs, are configuration arguments for MySQLConnection - instances. - """ - self._pool_size = None - self._pool_name = None - self._reset_session = pool_reset_session - self._set_pool_size(pool_size) - self._set_pool_name(pool_name or generate_pool_name(**kwargs)) - self._cnx_config = {} - self._cnx_queue = queue.Queue(self._pool_size) - self._config_version = uuid4() - - if kwargs: - self.set_config(**kwargs) - cnt = 0 - while cnt < self._pool_size: - self.add_connection() - cnt += 1 - - @property - def pool_name(self): - """Return the name of the connection pool""" - return self._pool_name - - @property - def pool_size(self): - """Return number of connections managed by the pool""" - return self._pool_size - - @property - def reset_session(self): - """Return whether to reset session""" - return self._reset_session - - def set_config(self, **kwargs): - """Set the connection configuration for MySQLConnection instances - - This method sets the configuration used for creating MySQLConnection - instances. See MySQLConnection for valid connection arguments. - - Raises PoolError when a connection argument is not valid, missing - or not supported by MySQLConnection. - """ - if not kwargs: - return - - with CONNECTION_POOL_LOCK: - try: - test_cnx = MySQLConnection() - if "use_pure" in kwargs: - del kwargs["use_pure"] - test_cnx.config(**kwargs) - self._cnx_config = kwargs - self._config_version = uuid4() - except AttributeError as err: - raise errors.PoolError( - "Connection configuration not valid: {0}".format(err)) - - def _set_pool_size(self, pool_size): - """Set the size of the pool - - This method sets the size of the pool but it will not resize the pool. - - Raises an AttributeError when the pool_size is not valid. Invalid size - is 0, negative or higher than pooling.CNX_POOL_MAXSIZE. - """ - if pool_size <= 0 or pool_size > CNX_POOL_MAXSIZE: - raise AttributeError( - "Pool size should be higher than 0 and " - "lower or equal to {0}".format(CNX_POOL_MAXSIZE)) - self._pool_size = pool_size - - def _set_pool_name(self, pool_name): - r"""Set the name of the pool - - This method checks the validity and sets the name of the pool. - - Raises an AttributeError when pool_name contains illegal characters - ([^a-zA-Z0-9._\-*$#]) or is longer than pooling.CNX_POOL_MAXNAMESIZE. - """ - if CNX_POOL_NAMEREGEX.search(pool_name): - raise AttributeError( - "Pool name '{0}' contains illegal characters".format(pool_name)) - if len(pool_name) > CNX_POOL_MAXNAMESIZE: - raise AttributeError( - "Pool name '{0}' is too long".format(pool_name)) - self._pool_name = pool_name - - def _queue_connection(self, cnx): - """Put connection back in the queue - - This method is putting a connection back in the queue. It will not - acquire a lock as the methods using _queue_connection() will have it - set. - - Raises PoolError on errors. - """ - if not isinstance(cnx, MySQLConnection): - raise errors.PoolError( - "Connection instance not subclass of MySQLConnection.") - - try: - self._cnx_queue.put(cnx, block=False) - except queue.Full: - errors.PoolError("Failed adding connection; queue is full") - - def add_connection(self, cnx=None): - """Add a connection to the pool - - This method instantiates a MySQLConnection using the configuration - passed when initializing the MySQLConnectionPool instance or using - the set_config() method. - If cnx is a MySQLConnection instance, it will be added to the - queue. - - Raises PoolError when no configuration is set, when no more - connection can be added (maximum reached) or when the connection - can not be instantiated. - """ - with CONNECTION_POOL_LOCK: - if not self._cnx_config: - raise errors.PoolError( - "Connection configuration not available") - - if self._cnx_queue.full(): - raise errors.PoolError( - "Failed adding connection; queue is full") - - if not cnx: - cnx = MySQLConnection(**self._cnx_config) - try: - if (self._reset_session and self._cnx_config['compress'] - and cnx.get_server_version() < (5, 7, 3)): - raise errors.NotSupportedError("Pool reset session is " - "not supported with " - "compression for MySQL " - "server version 5.7.2 " - "or earlier.") - except KeyError: - pass - - # pylint: disable=W0201,W0212 - cnx._pool_config_version = self._config_version - # pylint: enable=W0201,W0212 - else: - if not isinstance(cnx, MySQLConnection): - raise errors.PoolError( - "Connection instance not subclass of MySQLConnection.") - - self._queue_connection(cnx) - - def get_connection(self): - """Get a connection from the pool - - This method returns an PooledMySQLConnection instance which - has a reference to the pool that created it, and the next available - MySQL connection. - - When the MySQL connection is not connect, a reconnect is attempted. - - Raises PoolError on errors. - - Returns a PooledMySQLConnection instance. - """ - with CONNECTION_POOL_LOCK: - try: - cnx = self._cnx_queue.get(block=False) - except queue.Empty: - raise errors.PoolError( - "Failed getting connection; pool exhausted") - - # pylint: disable=W0201,W0212 - if not cnx.is_connected() \ - or self._config_version != cnx._pool_config_version: - cnx.config(**self._cnx_config) - try: - cnx.reconnect() - except errors.InterfaceError: - # Failed to reconnect, give connection back to pool - self._queue_connection(cnx) - raise - cnx._pool_config_version = self._config_version - # pylint: enable=W0201,W0212 - - return PooledMySQLConnection(self, cnx) - - def _remove_connections(self): - """Close all connections - - This method closes all connections. It returns the number - of connections it closed. - - Used mostly for tests. - - Returns int. - """ - with CONNECTION_POOL_LOCK: - cnt = 0 - cnxq = self._cnx_queue - while cnxq.qsize(): - try: - cnx = cnxq.get(block=False) - cnx.disconnect() - cnt += 1 - except queue.Empty: - return cnt - except errors.PoolError: - raise - except errors.Error: - # Any other error when closing means connection is closed - pass - - return cnt diff --git a/modules/mysql-connector-python/mysql/connector/protocol.py b/modules/mysql-connector-python/mysql/connector/protocol.py deleted file mode 100644 index 9e70c924b..000000000 --- a/modules/mysql-connector-python/mysql/connector/protocol.py +++ /dev/null @@ -1,762 +0,0 @@ -# Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Implements the MySQL Client/Server protocol -""" - -import struct -import datetime -from decimal import Decimal - -from .constants import ( - FieldFlag, ServerCmd, FieldType, ClientFlag) -from . import errors, utils -from .authentication import get_auth_plugin -from .catch23 import PY2, struct_unpack -from .errors import DatabaseError, get_exception - -PROTOCOL_VERSION = 10 - - -class MySQLProtocol(object): - """Implements MySQL client/server protocol - - Create and parses MySQL packets. - """ - - def _connect_with_db(self, client_flags, database): - """Prepare database string for handshake response""" - if client_flags & ClientFlag.CONNECT_WITH_DB and database: - return database.encode('utf8') + b'\x00' - return b'\x00' - - def _auth_response(self, client_flags, username, password, database, - auth_plugin, auth_data, ssl_enabled): - """Prepare the authentication response""" - if not password: - return b'\x00' - - try: - auth = get_auth_plugin(auth_plugin)( - auth_data, - username=username, password=password, database=database, - ssl_enabled=ssl_enabled) - plugin_auth_response = auth.auth_response() - except (TypeError, errors.InterfaceError) as exc: - raise errors.InterfaceError( - "Failed authentication: {0}".format(str(exc))) - - if client_flags & ClientFlag.SECURE_CONNECTION: - resplen = len(plugin_auth_response) - auth_response = struct.pack('= 7: - mcs = 0 - if length == 11: - mcs = struct_unpack(' 8: - mcs = struct_unpack('= -128: - format_ = '= -32768: - format_ = '= -2147483648: - format_ = ' 0: - packed += utils.int4store(value.microsecond) - - packed = utils.int1store(len(packed)) + packed - return (packed, field_type) - - def _prepare_binary_time(self, value): - """Prepare a time object for the MySQL binary protocol - - This method prepares a time object of type datetime.timedelta or - datetime.time for sending over the MySQL binary protocol. - A tuple is returned with the prepared value and field type - as elements. - - Raises ValueError when the argument value is of invalid type. - - Returns a tuple. - """ - if not isinstance(value, (datetime.timedelta, datetime.time)): - raise ValueError( - "Argument must a datetime.timedelta or datetime.time") - - field_type = FieldType.TIME - negative = 0 - mcs = None - packed = b'' - - if isinstance(value, datetime.timedelta): - if value.days < 0: - negative = 1 - (hours, remainder) = divmod(value.seconds, 3600) - (mins, secs) = divmod(remainder, 60) - packed += (utils.int4store(abs(value.days)) + - utils.int1store(hours) + - utils.int1store(mins) + - utils.int1store(secs)) - mcs = value.microseconds - else: - packed += (utils.int4store(0) + - utils.int1store(value.hour) + - utils.int1store(value.minute) + - utils.int1store(value.second)) - mcs = value.microsecond - if mcs: - packed += utils.int4store(mcs) - - packed = utils.int1store(negative) + packed - packed = utils.int1store(len(packed)) + packed - - return (packed, field_type) - - def _prepare_stmt_send_long_data(self, statement, param, data): - """Prepare long data for prepared statements - - Returns a string. - """ - packet = ( - utils.int4store(statement) + - utils.int2store(param) + - data) - return packet - - def make_stmt_execute(self, statement_id, data=(), parameters=(), - flags=0, long_data_used=None, charset='utf8'): - """Make a MySQL packet with the Statement Execute command""" - iteration_count = 1 - null_bitmap = [0] * ((len(data) + 7) // 8) - values = [] - types = [] - packed = b'' - if charset == 'utf8mb4': - charset = 'utf8' - if long_data_used is None: - long_data_used = {} - if parameters and data: - if len(data) != len(parameters): - raise errors.InterfaceError( - "Failed executing prepared statement: data values does not" - " match number of parameters") - for pos, _ in enumerate(parameters): - value = data[pos] - flags = 0 - if value is None: - null_bitmap[(pos // 8)] |= 1 << (pos % 8) - types.append(utils.int1store(FieldType.NULL) + - utils.int1store(flags)) - continue - elif pos in long_data_used: - if long_data_used[pos][0]: - # We suppose binary data - field_type = FieldType.BLOB - else: - # We suppose text data - field_type = FieldType.STRING - elif isinstance(value, int): - (packed, field_type, - flags) = self._prepare_binary_integer(value) - values.append(packed) - elif isinstance(value, str): - if PY2: - values.append(utils.lc_int(len(value)) + - value) - else: - value = value.encode(charset) - values.append( - utils.lc_int(len(value)) + value) - field_type = FieldType.VARCHAR - elif isinstance(value, bytes): - values.append(utils.lc_int(len(value)) + value) - field_type = FieldType.BLOB - elif PY2 and \ - isinstance(value, unicode): # pylint: disable=E0602 - value = value.encode(charset) - values.append(utils.lc_int(len(value)) + value) - field_type = FieldType.VARCHAR - elif isinstance(value, Decimal): - values.append( - utils.lc_int(len(str(value).encode( - charset))) + str(value).encode(charset)) - field_type = FieldType.DECIMAL - elif isinstance(value, float): - values.append(struct.pack(' 255: - raise ValueError('int1store requires 0 <= i <= 255') - else: - return bytearray(struct.pack(' 65535: - raise ValueError('int2store requires 0 <= i <= 65535') - else: - return bytearray(struct.pack(' 16777215: - raise ValueError('int3store requires 0 <= i <= 16777215') - else: - return bytearray(struct.pack(' 4294967295: - raise ValueError('int4store requires 0 <= i <= 4294967295') - else: - return bytearray(struct.pack(' 18446744073709551616: - raise ValueError('int8store requires 0 <= i <= 2^64') - else: - return bytearray(struct.pack(' 18446744073709551616: - raise ValueError('intstore requires 0 <= i <= 2^64') - - if i <= 255: - formed_string = int1store - elif i <= 65535: - formed_string = int2store - elif i <= 16777215: - formed_string = int3store - elif i <= 4294967295: - formed_string = int4store - else: - formed_string = int8store - - return formed_string(i) - - -def lc_int(i): - """ - Takes an unsigned integer and packs it as bytes, - with the information of how much bytes the encoded int takes. - """ - if i < 0 or i > 18446744073709551616: - raise ValueError('Requires 0 <= i <= 2^64') - - if i < 251: - return bytearray(struct.pack(' - +----------+------------------------- - | length | a string goes here - +----------+------------------------- - - If the string is bigger than 250, then it looks like this: - - <- 1b -><- 2/3/8 -> - +------+-----------+------------------------- - | type | length | a string goes here - +------+-----------+------------------------- - - if type == \xfc: - length is code in next 2 bytes - elif type == \xfd: - length is code in next 3 bytes - elif type == \xfe: - length is code in next 8 bytes - - NULL has a special value. If the buffer starts with \xfb then - it's a NULL and we return None as value. - - Returns a tuple (trucated buffer, bytes). - """ - if buf[0] == 251: # \xfb - # NULL value - return (buf[1:], None) - - length = lsize = 0 - fst = buf[0] - - if fst <= 250: # \xFA - length = fst - return (buf[1 + length:], buf[1:length + 1]) - elif fst == 252: - lsize = 2 - elif fst == 253: - lsize = 3 - if fst == 254: - lsize = 8 - - length = intread(buf[1:lsize + 1]) - return (buf[lsize + length + 1:], buf[lsize + 1:length + lsize + 1]) - - -def read_lc_string_list(buf): - """Reads all length encoded strings from the given buffer - - Returns a list of bytes - """ - byteslst = [] - - sizes = {252: 2, 253: 3, 254: 8} - - buf_len = len(buf) - pos = 0 - - while pos < buf_len: - first = buf[pos] - if first == 255: - # Special case when MySQL error 1317 is returned by MySQL. - # We simply return None. - return None - if first == 251: - # NULL value - byteslst.append(None) - pos += 1 - else: - if first <= 250: - length = first - byteslst.append(buf[(pos + 1):length + (pos + 1)]) - pos += 1 + length - else: - lsize = 0 - try: - lsize = sizes[first] - except KeyError: - return None - length = intread(buf[(pos + 1):lsize + (pos + 1)]) - byteslst.append( - buf[pos + 1 + lsize:length + lsize + (pos + 1)]) - pos += 1 + lsize + length - - return tuple(byteslst) - - -def read_string(buf, end=None, size=None): - """ - Reads a string up until a character or for a given size. - - Returns a tuple (trucated buffer, string). - """ - if end is None and size is None: - raise ValueError('read_string() needs either end or size') - - if end is not None: - try: - idx = buf.index(end) - except ValueError: - raise ValueError("end byte not present in buffer") - return (buf[idx + 1:], buf[0:idx]) - elif size is not None: - return read_bytes(buf, size) - - raise ValueError('read_string() needs either end or size (weird)') - - -def read_int(buf, size): - """Read an integer from buffer - - Returns a tuple (truncated buffer, int) - """ - - try: - res = intread(buf[0:size]) - except: - raise - - return (buf[size:], res) - - -def read_lc_int(buf): - """ - Takes a buffer and reads an length code string from the start. - - Returns a tuple with buffer less the integer and the integer read. - """ - if not buf: - raise ValueError("Empty buffer.") - - lcbyte = buf[0] - if lcbyte == 251: - return (buf[1:], None) - elif lcbyte < 251: - return (buf[1:], int(lcbyte)) - elif lcbyte == 252: - return (buf[3:], struct_unpack(' 0: - digest = _digest_buffer(abuffer[0:limit]) - else: - digest = _digest_buffer(abuffer) - print(prefix + ': ' + digest) - else: - print(_digest_buffer(abuffer)) diff --git a/modules/mysql-connector-python/mysql/connector/version.py b/modules/mysql-connector-python/mysql/connector/version.py deleted file mode 100644 index d75315fcf..000000000 --- a/modules/mysql-connector-python/mysql/connector/version.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is also distributed with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have included with -# MySQL. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA - -"""MySQL Connector/Python version information - -The file version.py gets installed and is available after installation -as mysql.connector.version. -""" - -VERSION = (8, 0, 17, '', 1) - -if VERSION[3] and VERSION[4]: - VERSION_TEXT = '{0}.{1}.{2}{3}{4}'.format(*VERSION) -else: - VERSION_TEXT = '{0}.{1}.{2}'.format(*VERSION[0:3]) - -VERSION_EXTRA = '' -LICENSE = 'GPLv2 with FOSS License Exception' -EDITION = '' # Added in package names, after the version diff --git a/requirements.txt b/requirements.txt index ddc4cbfa3..713305dad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,11 @@ codecov kodi-addon-checker +mysql-connector-python polib -pycrypto==2.6.1 -pycryptodomex==3.4.5 -pydes==2.0.1 -pylint==1.6.5 -requests==2.12.4 -tox-travis +pycryptodomex +pylint +requests +setuptools +tox +xmlschema +Kodistubs==19.0.3 diff --git a/resources/__init__.py b/resources/__init__.py index e69de29bb..bc287c5ac 100644 --- a/resources/__init__.py +++ b/resources/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + +# SPDX-License-Identifier: MIT +# See LICENSES/MIT.md for more information. diff --git a/resources/language/resource.language.cs_cz/strings.po b/resources/language/resource.language.cs_cz/strings.po new file mode 100644 index 000000000..c06a1b005 --- /dev/null +++ b/resources/language/resource.language.cs_cz/strings.po @@ -0,0 +1,1257 @@ +# Kodi Media Center language file +# Addon Name: Netflix +# Addon id: plugin.video.netflix +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait +msgid "" +msgstr "" +"Project-Id-Version: plugin.video.netflix\n" +"Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" +"POT-Creation-Date: 2017-07-24 16:15+0000\n" +"PO-Revision-Date: 2023-01-30 12:44+0100\n" +"Last-Translator: tajnymag\n" +"Language-Team: Czech (Czech Republic)\n" +"Language: cs_CZ\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" +"X-Generator: Poedit 3.2.2\n" + +msgctxt "Addon Summary" +msgid "Netflix" +msgstr "Netflix" + +msgctxt "Addon Description" +msgid "Netflix VOD Services Add-on" +msgstr "Doplněk Netflix VOD" + +msgctxt "Addon Disclaimer" +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "Použití tohoto doplňku nemusí být ve vaší zemi pobytu legální - před instalací se prosím obraťte na místní zákony." + +msgctxt "#30001" +msgid "Recommendations" +msgstr "Doporučení" + +msgctxt "#30002" +msgid "Enter PIN to watch restricted content" +msgstr "Zadejte PIN pro sledování omezeného obsahu" + +#. Unused 30003 +msgctxt "#30004" +msgid "Password" +msgstr "Heslo" + +msgctxt "#30005" +msgid "E-mail" +msgstr "E-mail" + +msgctxt "#30006" +msgid "Enter PIN to access this profile" +msgstr "Pro přístup k tomuto profilu zadejte PIN" + +msgctxt "#30007" +msgid "Parental Control PIN (4 digits)" +msgstr "PIN rodičovské kontroly (4 číslice)" + +msgctxt "#30008" +msgid "Login failed" +msgstr "Přihlášení selhalo" + +msgctxt "#30009" +msgid "Please check your e-mail and password" +msgstr "Zkontrolujte prosím svůj e-mail a heslo" + +msgctxt "#30010" +msgid "Lists of all kinds" +msgstr "Všechny kategorie" + +#. Unused 30011 +msgctxt "#30012" +msgid "Profiles options and parental control" +msgstr "Možnosti profilu a rodičovská kontrola" + +msgctxt "#30013" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "V seznamu profilů, otevřete kontextové menu vybraného profilu" + +msgctxt "#30014" +msgid "Account" +msgstr "Účet" + +msgctxt "#30015" +msgid "Other options" +msgstr "Další možnosti" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "Pokud se odhlásíte, vše vaše data budou smazána. Vaše exportované video soubory v knihovně Kodi bude ale zachovány.[CR]Přejete si pokračovat?" + +msgctxt "#30017" +msgid "Logout" +msgstr "Odhlášení" + +msgctxt "#30018" +msgid "Export to library" +msgstr "Exportovat do knihovny" + +msgctxt "#30019" +msgid "Rate on Netflix" +msgstr "Hodnocení na Netflixu" + +msgctxt "#30020" +msgid "Remove from 'My list'" +msgstr "Odebrat z 'Můj seznam'" + +msgctxt "#30021" +msgid "Add to 'My list'" +msgstr "Přidat do 'Můj seznam'" + +msgctxt "#30022" +msgid "(between 0 and 10)" +msgstr "(mezi 0 a 10)" + +msgctxt "#30023" +msgid "Expert" +msgstr "Expertní" + +msgctxt "#30024" +msgid "SSL verification" +msgstr "SSL ověření" + +msgctxt "#30025" +msgid "Library" +msgstr "Knihovna" + +msgctxt "#30026" +msgid "Enable custom library folder" +msgstr "Povolit vlastní složku knihovny" + +msgctxt "#30027" +msgid "Custom library path" +msgstr "Cesta k vlastní knihovně" + +msgctxt "#30028" +msgid "Playback error" +msgstr "Chyba přehrávání" + +#. Unused 30029 +msgctxt "#30030" +msgid "Remove from library" +msgstr "Odebrat z knihovny" + +msgctxt "#30031" +msgid "General" +msgstr "Obecné" + +msgctxt "#30032" +msgid "Codecs" +msgstr "" + +msgctxt "#30033" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "" + +#. Unused 30034 +msgctxt "#30035" +msgid "InputStream Adaptive settings..." +msgstr "Nastavení InputStream Adaptive ..." + +#. Unused 30036 +msgctxt "#30037" +msgid "Skin viewtypes" +msgstr "Druhy zobrazení vzhledu" + +msgctxt "#30038" +msgid "Enable custom viewtypes" +msgstr "Povolit vlastní druh zobrazení" + +msgctxt "#30039" +msgid "View ID for folders" +msgstr "Zobrazit ID pro složky" + +msgctxt "#30040" +msgid "View ID for movies" +msgstr "Zobrazit ID pro filmy" + +msgctxt "#30041" +msgid "View ID for shows" +msgstr "Zobrazit ID pro pořady" + +msgctxt "#30042" +msgid "View ID for seasons" +msgstr "Zobrazit ID pro série" + +msgctxt "#30043" +msgid "View ID for episodes" +msgstr "Zobrazit ID pro epizody" + +msgctxt "#30044" +msgid "View ID for profiles" +msgstr "Zobrazit ID pro profily" + +msgctxt "#30045" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "Odstraněno hodnocení|Hodnocení jako nelíbí|Hodnocení jako líbí" + +msgctxt "#30046" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "Došlo k potížím s doplňkem InputStream Adaptive nebo s knihovnou Widevine. Může chybět nebo není povoleno, operace bude zrušena." + +msgctxt "#30047" +msgid "Library update in progress" +msgstr "Probíhá aktualizace knihovny" + +msgctxt "#30048" +msgid "Exported" +msgstr "Exportováno" + +msgctxt "#30049" +msgid "Library" +msgstr "Knihovna" + +msgctxt "#30050" +msgid "Enable Kodi library management" +msgstr "Povolit správu knihovny" + +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30052" +msgid "{} Set for library playback" +msgstr "{} Nastavit pro přehrávání z knihovny" + +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30055" +msgid "{} Set at start-up" +msgstr "{} Nastavit při spuštění" + +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30057" +msgid "{} Remember PIN" +msgstr "{} Zapamatovat PIN" + +#. Unused 30058 +msgctxt "#30059" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "Buďte opatrní, chystáte se aktualizovat mnoho titulů {}.[CR]To může způsobit problémy se službami nebo dočasný zákaz účtu.[CR]Chcete pokračovat?" + +msgctxt "#30060" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "" + +msgctxt "#30061" +msgid "Update inside library" +msgstr "Aktualizovat vnitřní knihovnu" + +msgctxt "#30062" +msgid "Parental controls" +msgstr "Rodičovská kontrola" + +msgctxt "#30063" +msgid "An update is already in progress" +msgstr "Aktualizace již probíhá" + +msgctxt "#30064" +msgid "Perform auto-update" +msgstr "Provést automatickou aktualizaci" + +msgctxt "#30065" +msgid "Video library update" +msgstr "Aktualizace video knihovny" + +msgctxt "#30066" +msgid "Enable debug logging" +msgstr "Povolit záznam ladicích dat" + +msgctxt "#30067" +msgid "daily" +msgstr "denně" + +msgctxt "#30068" +msgid "every other day" +msgstr "každý druhý den" + +msgctxt "#30069" +msgid "every 5 days" +msgstr "každých 5 dní" + +msgctxt "#30070" +msgid "weekly" +msgstr "týdně" + +msgctxt "#30071" +msgid "Time of Day" +msgstr "Čas dne" + +msgctxt "#30072" +msgid "Only start after 5 minutes of idle" +msgstr "Začít až po 5 minutách nečinnosti" + +msgctxt "#30073" +msgid "Always show codec information during video playback" +msgstr "Vždy zobrazovat informace o kodeku při přehrávání videa" + +msgctxt "#30074" +msgid "View for exported" +msgstr "Zobrazení pro export" + +msgctxt "#30075" +msgid "Ask to skip intro and recap" +msgstr "Přeskočení úvodu a rekapitulace" + +msgctxt "#30076" +msgid "Skip intro" +msgstr "Přeskočit úvod" + +msgctxt "#30077" +msgid "Skip recap" +msgstr "Přeskočit rekapitulaci" + +msgctxt "#30078" +msgid "Playback" +msgstr "Přehrávání" + +msgctxt "#30079" +msgid "Skip automatically" +msgstr "Přeskočit automaticky" + +msgctxt "#30080" +msgid "Pause when skipping" +msgstr "Pauza při přeskočení" + +msgctxt "#30081" +msgid "Force HDCP version on video streams" +msgstr "" + +msgctxt "#30082" +msgid "Remember audio / subtitle preferences" +msgstr "Zapamatovat si předvolby zvuku / titulků" + +msgctxt "#30083" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "Vynutit zobrazování vynucených titulků pouze při specifikovaném jazyce" + +msgctxt "#30084" +msgid "Cache objects TTL (minutes)" +msgstr "Cache TTL (minuty)" + +msgctxt "#30085" +msgid "Cache metadata TTL (days)" +msgstr "Cache pro metadata TTL (dny)" + +msgctxt "#30086" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "Cache pro 'Můj seznam' TTL (minuty)" + +msgctxt "#30087" +msgid "Contains {} and more..." +msgstr "Obsahuje {} a další .." + +msgctxt "#30088" +msgid "Browse related content" +msgstr "Procházet podobný obsah" + +msgctxt "#30089" +msgid "Browse subgenres" +msgstr "Procházet podžánry" + +msgctxt "#30090" +msgid "Browse through related video lists and discover more content." +msgstr "Procházet podobné seznamy videí a objevovat další obsah." + +msgctxt "#30091" +msgid "Browse and manage contents that were exported to the Kodi library." +msgstr "Procházet a spravovat obsah, který byl exportován do knihovny Kodi." + +msgctxt "#30092" +msgid "Search for content and easily find what you want to watch." +msgstr "Hledejte obsah a snadno najděte, co chcete sledovat." + +msgctxt "#30093" +msgid "Browse content by genre and easily discover related content." +msgstr "Procházet podle žánru a snadno objevit podobný obsah." + +msgctxt "#30094" +msgid "Browse recommendations and pick up on something new you might like." +msgstr "Prohlédněte si doporučení a koukněte na něco nového, co by se vám mohlo líbit." + +msgctxt "#30095" +msgid "All TV shows" +msgstr "Všechny pořady" + +msgctxt "#30096" +msgid "All Movies" +msgstr "Všechny filmy" + +msgctxt "#30097" +msgid "Main Menu" +msgstr "Hlavní menu" + +msgctxt "#30098" +msgid "Enable HDR10" +msgstr "" + +msgctxt "#30099" +msgid "Enable Dolby Vision" +msgstr "" + +msgctxt "#30100" +msgid "Results for '{}'" +msgstr "Výsledky pro '{}'" + +msgctxt "#30101" +msgid "Netflix returned an unknown error." +msgstr "Netflix vrátil neznámou chybu." + +msgctxt "#30102" +msgid "Netflix returned the following error:" +msgstr "Netflix vrátil následující chybu:" + +msgctxt "#30103" +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "Do protokolu Kodi byly zapsány podrobnosti o chybě. Pokud chybová zpráva neposkytuje návod, jak problém vyřešit, nebo tuto chybu nadále dostáváte, nahlaste ji prostřednictvím GitHubu. S chybovým hlášením zadejte úplný protokol ladění. (povolte 'Protokolování ladění' v nastavení Kodi)." + +msgctxt "#30104" +msgid "The add-on encountered the following error:" +msgstr "Doplňek zaznamenal následující chybu:" + +msgctxt "#30105" +msgid "Netflix Add-on Error" +msgstr "Chyba doplňku Netflix" + +msgctxt "#30106" +msgid "Invalid PIN" +msgstr "Neplatný PIN" + +msgctxt "#30107" +msgid "PIN protection is off." +msgstr "Ochrana PIN je vypnutá." + +msgctxt "#30108" +msgid "All content is protected by PIN." +msgstr "Veškerý obsah je chráněn PINem." + +msgctxt "#30109" +msgid "Login successful" +msgstr "Přihlášení úspěšné" + +#. Unused 30110 +msgctxt "#30111" +msgid "There is no contents" +msgstr "Není žádný obsah" + +msgctxt "#30112" +msgid "You need to be logged in to use Netflix" +msgstr "Pro použití Netflixu musíte být přihlášeni" + +msgctxt "#30113" +msgid "Logout successful" +msgstr "Odhlášení úspěšné" + +#. Unused 30114, 30115 +msgctxt "#30116" +msgid "Advanced Add-on Configuration" +msgstr "Pokročilá konfigurace doplňku" + +msgctxt "#30117" +msgid "Cache" +msgstr "Cache" + +#. Unused 30118, 30119 +msgctxt "#30120" +msgid "Cannot automatically remove a season from Kodi library, please do it manually" +msgstr "Nelze automaticky odstranit řadu z knihovny Kodi, prosím, proveďte ručně" + +msgctxt "#30121" +msgid "Perform full sync now" +msgstr "Nyní provést úplnou synchronizaci" + +msgctxt "#30122" +msgid "Sync 'My list' to Kodi library" +msgstr "Synchronizovat Můj seznam s knihovnou Kodi" + +msgctxt "#30123" +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "Chcete pokračovat?[CR]Plná synchronizace odstraní všechny dříve exportované tituly.[CR]Pokud existuje mnoho titulů, může tato operace chvíli trvat." + +msgctxt "#30124" +msgid "Do you want to remove this item from the library?" +msgstr "Opravdu chcete tuto položku odebrat?" + +msgctxt "#30125" +msgid "Delete library contents" +msgstr "Vymazat knihovnu" + +msgctxt "#30126" +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "Opravdu chcete vymazat knihovnu?[CR]Všechny exportované položky budou smazány." + +msgctxt "#30127" +msgid "Awarded rating of {}" +msgstr "Udělené hodnocení {}" + +msgctxt "#30128" +msgid "Select a profile" +msgstr "Vyberte profil" + +msgctxt "#30129" +msgid "Enable Up Next integration" +msgstr "Povolit další integraci" + +msgctxt "#30130" +msgid "Install Up Next add-on" +msgstr "Nainstalovat Up Next dolpněk" + +msgctxt "#30131" +msgid "Check the logfile for detailed information" +msgstr "Zkontrolujte podrobné informace v souboru protokolu" + +msgctxt "#30132" +msgid "Clear in-memory cache" +msgstr "Vymazat cache z RAM" + +msgctxt "#30133" +msgid "Clear in-memory and on-disk cache" +msgstr "Vymazat RAM a cache z disku" + +msgctxt "#30134" +msgid "Enable execution timing" +msgstr "Povolit časování provedení" + +msgctxt "#30135" +msgid "Cache cleared" +msgstr "Cache vymazána" + +msgctxt "#30136" +msgid "Waiting for service start-up..." +msgstr "Čekání na spuštění služby..." + +msgctxt "#30137" +msgid "Enable VP9 codec" +msgstr "" + +msgctxt "#30138" +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "Pokud jste spustili Kodi a hned doplněk, nemusí být služby na pozadí ještě k dispozici. V tomto případě to zkuste za chvíli znovu." + +msgctxt "#30139" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "Povolit IPC přes HTTP (použijte, když se objeví zprávy o vypršení časového limitu doplňkových signálů)" + +msgctxt "#30140" +msgid "Import existing library" +msgstr "Importovat existující knihovnu" + +#. Unused 30141 to 30143 +msgctxt "#30144" +msgid "Disable WebVTT subtitle support" +msgstr "Zakázat podporu titulků WebVTT" + +msgctxt "#30145" +msgid "Recently added" +msgstr "Nedávno přidané" + +msgctxt "#30146" +msgid "Discover the latest content added." +msgstr "Objevte nejnovější přidaný obsah." + +msgctxt "#30147" +msgid "Next page »" +msgstr "Další stránka »" + +msgctxt "#30148" +msgid "« Previous page" +msgstr "« Předchozí stránka" + +msgctxt "#30149" +msgid "Sort results by" +msgstr "Seřadit výsledky podle" + +msgctxt "#30150" +msgid "Alphabetical order (A-Z)" +msgstr "Abecední pořadí (A-Z)" + +msgctxt "#30151" +msgid "Alphabetical order (Z-A)" +msgstr "Abecední pořadí (Z-A)" + +msgctxt "#30152" +msgid "Suggestions for you" +msgstr "Doporučení pro vás" + +msgctxt "#30153" +msgid "Year released" +msgstr "Rok vydání" + +msgctxt "#30154" +msgid "Netflix configuration wizard" +msgstr "Konfigurace doplňku Netflix" + +msgctxt "#30155" +msgid "Do you have a Netflix Premium account?" +msgstr "Máte účet Netflix Premium?" + +#. Unused 30156 +msgctxt "#30157" +msgid "The configuration has been completed" +msgstr "Konfigurace byla dokončena" + +msgctxt "#30158" +msgid "Restore the add-on default configuration" +msgstr "" + +msgctxt "#30159" +msgid "View ID for main menu" +msgstr "Zobrazit ID pro hlavní menu" + +msgctxt "#30160" +msgid "View ID for search" +msgstr "Zobrazit ID pro vyhledávání" + +msgctxt "#30161" +msgid "View ID for exported" +msgstr "Zobrazit ID pro exportované" + +msgctxt "#30162" +msgid "Last used" +msgstr "Naposledy sledované" + +msgctxt "#30163" +msgid "Audio description" +msgstr "Zvukový popis" + +msgctxt "#30164" +msgid "Browse contents with audio description." +msgstr "Procházet obsah s audio popisem." + +msgctxt "#30165" +msgid "View ID for 'My list'" +msgstr "Zobrazi ID pro \"Můj seznam\"" + +msgctxt "#30166" +msgid "Main menu items" +msgstr "Položky hlavní nabídky" + +msgctxt "#30167" +msgid "My list" +msgstr "Můj seznam" + +msgctxt "#30168" +msgid "Continue watching" +msgstr "Pokračovat v sledování" + +msgctxt "#30169" +msgid "Top picks" +msgstr "Nejlepší tipy" + +msgctxt "#30170" +msgid "New releases" +msgstr "Nově přidané" + +msgctxt "#30171" +msgid "Trending now" +msgstr "Aktuální trendy" + +msgctxt "#30172" +msgid "Popular on Netflix" +msgstr "Populární na Netflixu" + +msgctxt "#30173" +msgid "Netflix Originals" +msgstr "Originály Netflixu" + +msgctxt "#30174" +msgid "TV show genres" +msgstr "Žánry pořadů" + +msgctxt "#30175" +msgid "Movie genres" +msgstr "Žánry filmu" + +msgctxt "#30176" +msgid "Enable STRM resume workaround" +msgstr "Povolit STRM pokračování řešení" + +msgctxt "#30177" +msgid "Ask to resume the video" +msgstr "Zeptat se pro obnovení videa" + +msgctxt "#30178" +msgid "Open Up Next add-on settings" +msgstr "Otevřít další nastavení doplňku" + +msgctxt "#30179" +msgid "Show trailers and more" +msgstr "Zobrazit upoutávky a další" + +msgctxt "#30180" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "Došlo k potížím s přihlášením. Je možné, že váš účet nebyl potvrzen ani reaktivován." + +msgctxt "#30181" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "Vždy zobrazit titulky, pokud není preferovaný jazyk audio stopy k dispozici" + +msgctxt "#30182" +msgid "Export NFO files" +msgstr "Exportovat NFO soubory" + +msgctxt "#30183" +msgid "Do you want to export NFO files for the {}?" +msgstr "Chcete exportovat soubory NFO pro {}?" + +msgctxt "#30184" +msgid "NFO Files" +msgstr "Soubory NFO" + +msgctxt "#30185" +msgid "Enable NFO files export" +msgstr "Povolit export souborů NFO" + +msgctxt "#30186" +msgid "For Movies" +msgstr "Pro filmy" + +msgctxt "#30187" +msgid "For TV show" +msgstr "Pro pořady" + +msgctxt "#30188" +msgid "Ask" +msgstr "Zeptat se" + +msgctxt "#30189" +msgid "movie" +msgstr "film" + +msgctxt "#30190" +msgid "TV show" +msgstr "Pořad" + +#. Unused 30191 +msgctxt "#30192" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]Tyto soubory používají poskytovatelé scraperů pro integraci informací o filmech a pořadech, užitečné s bonusovými epizodami nebo režisérským střihem" + +msgctxt "#30193" +msgid "Include tv show NFO details" +msgstr "Zahrnout data z NFO souborů pořadu" + +msgctxt "#30194" +msgid "Limit video stream resolution to" +msgstr "Omezit rozlišení videa na" + +msgctxt "#30195" +msgid "Export new episodes" +msgstr "Exportovat nové epizody" + +msgctxt "#30196" +msgid "Exclude from auto update" +msgstr "Vyloučit z automatické aktualizace" + +msgctxt "#30197" +msgid "Include in auto update" +msgstr "Zahrnout do automatické aktualizace" + +msgctxt "#30198" +msgid "Exporting new episodes" +msgstr "Export nových epizody" + +msgctxt "#30199" +msgid "Shared library (Kodi MySQL server is required)" +msgstr "Sdílená knihovna (je vyžadován server Kodi MySQL)" + +msgctxt "#30200" +msgid "Use shared library MySQL-database" +msgstr "Použít databázi sdílených knihoven MySQL" + +msgctxt "#30201" +msgid "Username" +msgstr "Uživatelské jméno" + +msgctxt "#30202" +msgid "Connection to the MySQL-database was successful" +msgstr "Připojení k databázi MySQL bylo úspěšné" + +msgctxt "#30203" +msgid "Host IP" +msgstr "Hostitelská IP" + +msgctxt "#30204" +msgid "Port" +msgstr "Port" + +msgctxt "#30205" +msgid "Test database connection" +msgstr "Testovat připojení k databázi" + +msgctxt "#30206" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" +msgstr "CHYBA: Databáze MySQL není dostupná - použije se lokální databáze" + +msgctxt "#30207" +msgid "Set this device as main auto-updates manager" +msgstr "Nastavit toto zařízení jako hlavního správce automatických aktualizací" + +msgctxt "#30208" +msgid "Check if this device is the main auto-update manager" +msgstr "Zkontrolujte, zda je toto zařízení hlavním správcem automatické aktualizace" + +msgctxt "#30209" +msgid "This device now handles the auto-updates of the shared library" +msgstr "Toto zařízení nyní zpracovává automatické aktualizace sdílené knihovny" + +msgctxt "#30210" +msgid "This device handles the auto-updates of the shared library" +msgstr "Toto zařízení zpracovává automatické aktualizace sdílené knihovny" + +msgctxt "#30211" +msgid "Another device handles the auto-updates of the shared library" +msgstr "Jiné zařízení zpracovává automatické aktualizace sdílené knihovny" + +msgctxt "#30212" +msgid "There is no device that handles the auto-updates of the shared library" +msgstr "Neexistuje žádné zařízení které by zpracovávalo automatické aktualizace sdílené knihovny" + +msgctxt "#30213" +msgid "InputStream Helper settings..." +msgstr "Nastevení InputStream Helper..." + +msgctxt "#30214" +msgid "Force refresh" +msgstr "Vynutit aktualizaci" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "Barva titulů v \"Můj seznam\"" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "Barva titulů označených jako \"Připomenout\"" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "Zakázat notifikaci o dokončené synchronizaci" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "Knihovna Kodi byla aktualizována" + +msgctxt "#30221" +msgid "Owner account" +msgstr "Účet vlastníka" + +msgctxt "#30222" +msgid "Kid account" +msgstr "Dětský účet" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "Režim automatické aktualizace" + +msgctxt "#30225" +msgid "Manual" +msgstr "Manuální" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "Naplánováno" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "Synchronizovat knihovnu Kodi s mým seznamem" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "Nastavte profil pro synchronizaci" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "Při spuštění Kodi" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "Zkontrolovat nyní aktualizace" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "Chcete nyní zkontrolovat aktualizaci?" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "Hodnocení vyspělosti profilu pro {}" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "Zobrazit pouze hodnocené tituly [B]{}[/B]." + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "Synchronizujte sledovaný stav videí s Netflixem" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "Změnit sledovaný stav (místně)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "Označeno jako zhlédnuté|Označeno jako nezhlédnuté|Obnoven zhlédnutý stav Netflixu" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Up Next notifikace (sledovat další video)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "Barva doplňujících informací v postranním panelu" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "Z důvodu tohoto problému nelze spustit služby na pozadí:[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Otevřete Connect CDN (dolní index je lepší)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "Top 10" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "Vyberte první nezhlédnutou epizodu pořadu" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "Mazání souborů exportovaných do knihovny" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "Existují {} tituly, které nelze importovat.[CR]Chcete tyto soubory odstranit?" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "Výsledky na stránku (v případě timeout chyby ji snižte)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "Chcete odebrat zhlédnuté z \"{}\"?" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "Zahrnout knihovnu Kodi (pouze odesílání dat)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "Vyberte metodu přihlášení[CR](pokud přihlášení E-Mail/Heslo vždy vyhodí \"incorrect password\", použijte Autentizační klíč, dle návodu v ReadME na GitHubu)" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "E-Mail/Heslo" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "Autentizační klíč" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "Vybraný soubor není kompatibilní nebo je poškozen" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "Platnost autentizačního klíče vypršela" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "Zadejte PIN" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "Řadit historii dle" + +msgctxt "#30400" +msgid "Search" +msgstr "Hledat" + +msgctxt "#30401" +msgid "Type of search" +msgstr "Typ vyhledávání" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "Hledejte tituly, lidi, žánry" + +msgctxt "#30403" +msgid "New search..." +msgstr "Nové hledání..." + +msgctxt "#30404" +msgid "Clear search history" +msgstr "Vymazat historii vyhledávání" + +msgctxt "#30405" +msgid "Select the language" +msgstr "Vyberte jazyk" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "Chcete smazat celý obsah?" + +msgctxt "#30407" +msgid "No matches found" +msgstr "Nebyly nalezeny žádné epizody" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "Podle výrazu" + +msgctxt "#30411" +msgid "By audio language" +msgstr "Podle jazyka zvuku" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "Podle jazyka titulků" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "Podle ID žánru/podžánru" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "Preferovat jazyk zvuku/titulků s kódem země (pokud je k dispozici)" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "Preferovat stereo stopy bez zeptání" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "Nastavení ESN / Widevine" + +msgctxt "#30601" +msgid "ESN:" +msgstr "ESN:" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "Změnit ESN" + +msgctxt "#30603" +msgid "Save system info" +msgstr "Uložit systémové informace" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "Widevine - Vynutit úroveň zabezpečení" + +msgctxt "#30605" +msgid "Force {}" +msgstr "Vynutit {}" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "ESN byla úspěšně nastavena[CR]Zkuste přehrát nějaké video" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "Tato ESN není aplikovatelná[CR]Zkuste ESN změnit nebo resetovat.[CR][CR]Podrobnosti chyby:[CR]{}" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "Neplatný formát ESN" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "Přejete si resetovat všechna nastavení?" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "Použít systémové zabezpečení uživatelských údajů" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "[B]Úroveň zabezpečení bude snížena, pokud je vypnuto[/B]. Pokud váš systém tuto funkci nepodporuje, budete vyzváni k opakovanému přihlášení po každém restartu přístroje, [B]pouze[/B] v takovém případě vypněte. Pokud volbu změníte, budete se muset při dalším restartu přihlásit znovu." + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "Verze MSL manifestu" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "Tento titul bude dostupný až od:[CR]{}" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "Přejete si přehrát upoutávku?" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "{} Připomenout mi" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "Nové a oblíbené" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "Označí rozkoukané pořady jako zhlédnuté" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "Dbejte, prosím, na to, že po zapnutí této funkce a zapnutém filtrování v nastavení vašeho motivu Kodi, budou zhlédnuté položky skryty v seznamu médií." + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "Vynutit způsob výběru streamu" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "Vynucení specifického logiky výběru streamu doplňku InputStream Adaptive pro výběr kvality zvuku / videa při přehrávání." + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "Adaptivní kvalita" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "Pevná kvalita videa" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "Zeptat se na kvalitu videa" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "Berte na vědomí, že povolením této možnosti dojde k vynucenému odebrání streamů, které nezapadají do zvoleného rozsahu, což může ovlivnit funkčnost InputStream Adaptive. Pokud to je možné, zkuste nejdříve omezit kvalitu přehrávání v nastavení InputStream Adaptive." + +msgctxt "#30721" +msgid "Size" +msgstr "" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "" + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "" + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "Přidat složky Netflix do zdrojů knihovny Kodi" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "Složky \"Netflix-Movies\" a \"Netflix-Shows\" byly úspěšně přidány do zdrojů knihovny. Pro jejich zobrazení v menu, restartujte Kodi. Poté u nich nastavte vhodný druh obsahu a scraper." + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "[Ručně] aktualizace musí být prováděny ručně spuštěním \"Exportovat nové epizody\" v kontextové nabídce každého pořadu, [Naplánované] můžete nastavit pravidelné aktualizace ve specifický čas, [Při spuštění Kodi] automatická aktualizace bude spuštěna se startem Kodi a bude provedena jen jednou denně" + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "Synchronizuje knihovnu s Můj seznam, pokud byl můj seznam externě modifikován, např. skrze mobilní aplikaci nebo webové rozhraní. Aktualizace bude naplánována na následující aktualizační okno." + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "Pokud je povoleno, přidá informace o epizodách a filmech v případě, kdy Kodi není schopno stáhnout vlatní data. Tato možnost navíc přidá délku videí." + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "Povolte pouze, pokud používáte scraper \"Místní soubory informací\" nebo jeho alternativu, případně pokud některé tituly se v knihovně nezobrazují správně." + +msgctxt "#30734" +msgid "Support" +msgstr "Podpora" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "O doplňku Netflix" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "Automaticky generuje nová ESN (workaround při maximálním rozlišení 540p)" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "POZOR: Nepoužívejte originální ESN oficiální aplikace." + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "Povolit posun zvuku" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "Posun zvuku" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "Nastaví vlastní posunu zvuku nehledě na nastavení v Kodi" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "" + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "" + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "" + +msgctxt "#30747" +msgid "Fixed" +msgstr "" + +msgctxt "#30748" +msgid "Relative" +msgstr "" + +msgctxt "#30749" +msgid "Reset" +msgstr "" diff --git a/resources/language/resource.language.de_de/strings.po b/resources/language/resource.language.de_de/strings.po index c8eff7d7e..05c209472 100644 --- a/resources/language/resource.language.de_de/strings.po +++ b/resources/language/resource.language.de_de/strings.po @@ -1,45 +1,42 @@ # Kodi Media Center language file # Addon Name: Netflix # Addon id: plugin.video.netflix -# Addon Provider: libdev + jojo + asciidisco + caphm + CastagnaIT +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait msgid "" msgstr "" "Project-Id-Version: plugin.video.netflix\n" "Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" "POT-Creation-Date: 2017-07-24 16:15+0000\n" -"PO-Revision-Date: 2019-07-08 22:15+0000\n" -"Last-Translator: Sebastian Golasch \n" +"PO-Revision-Date: 2020-07-24 21:39+0000\n" +"Last-Translator: Tobias Weimer\n" "Language-Team: German\n" +"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 "Addon Summary" msgid "Netflix" -msgstr "" +msgstr "Netflix" msgctxt "Addon Description" -msgid "Netflix VOD Services Addon" +msgid "Netflix VOD Services Add-on" msgstr "Addon für Netflix VOD-Dienste" msgctxt "Addon Disclaimer" -msgid "Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing." -msgstr "Möglicherweise sind einge Teile dieses Addons in Ihrem Land illegal, Sie sollten dies unbedingt vor der Installation überprüfen." +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "Möglicherweise ist die Verwendung dieses Addons in Ihrem Land illegal, Sie sollten dies unbedingt vor der Installation überprüfen." msgctxt "#30001" msgid "Recommendations" msgstr "Vorschläge" msgctxt "#30002" -msgid "Adult Pin" -msgstr "Kindersicherungs-PIN" - -msgctxt "#30003" -msgid "Search term" -msgstr "Suchbegriff" +msgid "Enter PIN to watch restricted content" +msgstr "Geben Sie Ihre PIN ein um beschränkten Inhalt anzusehen" +#. Unused 30003 msgctxt "#30004" msgid "Password" msgstr "Passwort" @@ -49,41 +46,46 @@ msgid "E-mail" msgstr "E-Mail" msgctxt "#30006" -msgid "Adult verification failed" -msgstr "Kindersicherungs-PIN nicht korrekt" +msgid "Enter PIN to access this profile" +msgstr "Geben Sie die PIN zu diesem Profil ein" msgctxt "#30007" -msgid "Please Check your adult pin" -msgstr "Bitter überprüfen Sie Ihre Kindersicherungs-PIN" +msgid "Parental Control PIN (4 digits)" +msgstr "Kindersicherungs-PIN (4-stellig)" msgctxt "#30008" msgid "Login failed" -msgstr "Anmeldung nicht erfolgreich" +msgstr "Anmeldung fehlgeschlagen" msgctxt "#30009" -msgid "Please Check your credentials" -msgstr "Bitte die Anmeldedaten überprüfen" +msgid "Please check your e-mail and password" +msgstr "Bitte überprüfen Sie Ihre E-Mail und Ihr Passwort" msgctxt "#30010" msgid "Lists of all kinds" msgstr "Alle Kategorien" -msgctxt "#30011" -msgid "Search" -msgstr "Suche" - +#. Unused 30011 msgctxt "#30012" -msgid "No seasons available" -msgstr "Keine Staffeln verfügbar" +msgid "Profiles options and parental control" +msgstr "Profileinstellungen und Kindersicherung" msgctxt "#30013" -msgid "No matches found" -msgstr "Keine Titel gefunden" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "Öffnen Sie in der Profilliste das Kontextmenü zum ausgewählten Profil" msgctxt "#30014" msgid "Account" msgstr "Konto" +msgctxt "#30015" +msgid "Other options" +msgstr "Andere Einstellungen" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "Wenn Sie sich abmelden werden alle Daten zu Ihrem Konto gelöscht, aber exportierte Video-Dateien in der Kodi-Bibliothek bleiben erhalten.[CR]Möchten Sie fortfahren?" + msgctxt "#30017" msgid "Logout" msgstr "Abmelden" @@ -98,15 +100,15 @@ msgstr "Auf Netflix bewerten" msgctxt "#30020" msgid "Remove from 'My list'" -msgstr "Von 'Meiner Liste' entfernen" +msgstr "Von 'Meine Liste' entfernen" msgctxt "#30021" msgid "Add to 'My list'" -msgstr "Zu 'Meiner Liste' hinzufügen" +msgstr "Zu 'Meine Liste' hinzufügen" msgctxt "#30022" -msgid "(between 0 & 10)" -msgstr "(zwischen 0 & 10)" +msgid "(between 0 and 10)" +msgstr "(zwischen 0 und 10)" msgctxt "#30023" msgid "Expert" @@ -132,141 +134,135 @@ msgctxt "#30028" msgid "Playback error" msgstr "Fehler beim Abspielen" -msgctxt "#30029" -msgid "Missing Inputstream addon" -msgstr "Inputstream-Addon nicht gefunden" - +#. Unused 30029 msgctxt "#30030" msgid "Remove from library" msgstr "Aus Bibliothek entfernen" msgctxt "#30031" -msgid "Change library title" -msgstr "Export Titel ändern" +msgid "General" +msgstr "Allgemein" msgctxt "#30032" -msgid "Tracking" -msgstr "Nachverfolgung" +msgid "Codecs" +msgstr "Codecs" -msgctxt "#30034" -msgid "ESN (set automatically, can be changed manually)" -msgstr "ESN (änderbar, wird autom. gesetzt)" +msgctxt "#30033" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "Dolby-Digital-Plus-Codec aktivieren (Atmos mit Premiumkonto)" +#. Unused 30034 msgctxt "#30035" msgid "InputStream Adaptive settings..." msgstr "InputStream Adaptive Einstellungen..." -msgctxt "#30036" -msgid "Always use the original title on export" -msgstr "Immer den Originaltitel beim Export nutzen" - +#. Unused 30036 msgctxt "#30037" -msgid "Views" -msgstr "Ansichten" +msgid "Skin viewtypes" +msgstr "Skin-Ansichtstypen" msgctxt "#30038" -msgid "Enable custom views" -msgstr "Eigene Ansichten aktivieren" +msgid "Enable custom viewtypes" +msgstr "Eigene Ansichtstypen aktivieren" msgctxt "#30039" -msgid "View for folders" -msgstr "Ansicht für Ordner" +msgid "View ID for folders" +msgstr "Ansichts-ID für Verzeichnisse" msgctxt "#30040" -msgid "View for movies" -msgstr "Ansicht für Filme" +msgid "View ID for movies" +msgstr "Ansichts-ID für Filme" msgctxt "#30041" -msgid "View for shows" -msgstr "Ansicht für Serien" +msgid "View ID for shows" +msgstr "Ansichts-ID für Serien" msgctxt "#30042" -msgid "View for seasons" -msgstr "Ansicht für Staffeln" +msgid "View ID for seasons" +msgstr "Ansichts-ID für Staffeln" msgctxt "#30043" -msgid "View for episodes" -msgstr "Ansicht für Episoden" +msgid "View ID for episodes" +msgstr "Ansichts-ID für Episoden" msgctxt "#30044" -msgid "View for profiles" -msgstr "Ansicht für Profile" +msgid "View ID for profiles" +msgstr "Ansichts-ID für Profile" msgctxt "#30045" -msgid "[COLOR cyan][B]-- NEXT PAGE --[/B][/COLOR]" -msgstr "[COLOR cyan][B]-- NÄCHSTE SEITE --[/B][/COLOR]" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "Daumen-Bewertung entfernt|Sie haben mit einem Daumen runter bewertet|Sie haben mit einem Daumen hoch bewertet" + +msgctxt "#30046" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "Ein Problem mit Addon InputStream Adaptive oder der Widevine-Bibliothek ist aufgetreten: sind nicht vorhanden oder nicht aktiviert. Der Vorgang wird abgebrochen." msgctxt "#30047" -msgid "Finally remove?" -msgstr "Endgültig entfernen?" +msgid "Library update in progress" +msgstr "Bibliothek wird gerade aktualisiert" msgctxt "#30048" msgid "Exported" msgstr "Exportiert" msgctxt "#30049" -msgid "Update DB" -msgstr "Aktualisiere DB" +msgid "Library" +msgstr "Bibliothek" msgctxt "#30050" -msgid "Update successful" -msgstr "Aktualisierung erfolgreich" - -msgctxt "#30053" -msgid "Auto Login" -msgstr "Automatisch Anmelden" +msgid "Enable Kodi library management" +msgstr "Verwaltung der Kodi-Bibliothek aktivieren" -msgctxt "#30054" -msgid "Enable Auto Login" -msgstr "Aktiviere automatisches Anmelden" +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30052" +msgid "{} Set for library playback" +msgstr "{} Wiedergabe aus Bibliothek setzen" +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30055" -msgid "Profile" -msgstr "Profil" - -msgctxt "#30056" -msgid "ID" -msgstr "" +msgid "{} Set at start-up" +msgstr "{} Beim Start setzen" +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30057" -msgid "Select profile in profile listing -> context menu" -msgstr "Wähle Profil in der Übersicht -> Kontextmenü" - -msgctxt "#30058" -msgid "Auto Login enabled!" -msgstr "Automatische Anmeldung aktiviert!" +msgid "{} Remember PIN" +msgstr "{} PIN merken" +#. Unused 30058 msgctxt "#30059" -msgid "Switch accounts" -msgstr "Accounts wechseln" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "Seien Sie vorsichtig, sie aktualisieren viele Titel {}.[CR]Dies kann ernste Probleme oder eine temporäre Sperre Ihres Kontos verursachen.[CR]Möchten Sie fortfahren?" msgctxt "#30060" -msgid "Enable HEVC profiles (4k for Android/HDR/DolbyVision)" -msgstr "Aktiviere HEVC profile (4k in Android/HDR/DolbyVision)" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "HEVC-Codec aktivieren (4k/HDR/DolbyVision in Android)" msgctxt "#30061" msgid "Update inside library" msgstr "In Bibliothek aktualisieren" msgctxt "#30062" -msgid "Enable/disable adult pin. Active:" -msgstr "Kindersicherungs PIN Abfrage ein-/ausschalten. Aktiv:" +msgid "Parental controls" +msgstr "Kindersicherung" msgctxt "#30063" -msgid "new episodes added to library" -msgstr "neue Folgen zur Bilbiothek hinzugefügt" +msgid "An update is already in progress" +msgstr "Es wird bereits eine Aktualisierung durchgeführt." msgctxt "#30064" -msgid "Export new episodes" -msgstr "Neue Folgen der Bibliothek hinzufügen" +msgid "Perform auto-update" +msgstr "Automatische Aktualisierung durchführen" msgctxt "#30065" -msgid "Auto-update" -msgstr "Automatische Aktualisierung" +msgid "Video library update" +msgstr "Aktualisierung der Video-Bibliothek" msgctxt "#30066" -msgid "never" -msgstr "niemals" +msgid "Enable debug logging" +msgstr "Fehler-Aufzeichnungen aktivieren" msgctxt "#30067" msgid "daily" @@ -293,8 +289,8 @@ msgid "Only start after 5 minutes of idle" msgstr "Nur nach 5 Minuten vorherigem Leerlauf starten" msgctxt "#30073" -msgid "Check every X minutes if update scheduled" -msgstr "Prüfe alle X Minuten ob Startzeitpunkt erreicht" +msgid "Always show codec information during video playback" +msgstr "Immer Codec-Information während Video-Wiedergabe anzeigen" msgctxt "#30074" msgid "View for exported" @@ -302,7 +298,7 @@ msgstr "Ansicht für Exportiert" msgctxt "#30075" msgid "Ask to skip intro and recap" -msgstr "Aktiviere Intro und Recap überspringen Funktion" +msgstr "Fragen, ob Intro und Recap übersprungen werden sollen" msgctxt "#30076" msgid "Skip intro" @@ -322,31 +318,31 @@ msgstr "Automatisch überspringen" msgctxt "#30080" msgid "Pause when skipping" -msgstr "Beim Weiterspringen pausieren" +msgstr "Beim Überspringen pausieren" + +msgctxt "#30081" +msgid "Force HDCP version on video streams" +msgstr "HDCP-Version in Videostreams erzwingen" msgctxt "#30082" msgid "Remember audio / subtitle preferences" msgstr "Audio- und Untertitel-Einstellungen beibehalten" msgctxt "#30083" -msgid "Save bookmarks for library items / mark as watched" -msgstr "Fortschritt für Bibliothekseinträge speichern und als gesehen markieren" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "Anzeige erzwungener Untertitel nur erzwingen, wenn Audiosprache gesetzt ist" msgctxt "#30084" -msgid "Advanced" -msgstr "Fortgeschritten" - -msgctxt "#30084" -msgid "Cache item time-to-live (minutes)" +msgid "Cache objects TTL (minutes)" msgstr "Time-to-live der Cache-Einträge (Minuten)" msgctxt "#30085" -msgid "Cache item time-to-live for metadata (days)" +msgid "Cache metadata TTL (days)" msgstr "Time-to-live für Metadaten Cache-Einträge (Tage)" msgctxt "#30086" -msgid "Invalidate entire cache on modification of My List" -msgstr "Gesamten Cache bei Änderungen an Meine Liste invalidieren" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "Time-to-live der Cache-Einträge für 'Meine Liste' (Minuten)" msgctxt "#30087" msgid "Contains {} and more..." @@ -354,19 +350,19 @@ msgstr "Enthält {} und weitere..." msgctxt "#30088" msgid "Browse related content" -msgstr "Verwandte Inhalte durchstöbern" +msgstr "Durchsuche verwandte Inhalte" msgctxt "#30089" msgid "Browse subgenres" -msgstr "Subgenres durchstöbern" +msgstr "Durchsuche Subgenres" msgctxt "#30090" msgid "Browse through related video lists and discover more content." -msgstr "Durchstöbere verwandte Videolisten und entdecke weitere Inhalte." +msgstr "Durchsuche verwandte Videolisten und entdecke weitere Inhalte." msgctxt "#30091" msgid "Browse and manage contents that were exported to the Kodi library." -msgstr "Durchsuche und verwalte Inhalte die der Kodi-Datenbank hinzugefügt wurden." +msgstr "Durchsuche und verwalte Inhalte die der Kodi-Bibliothek hinzugefügt wurden." msgctxt "#30092" msgid "Search for content and easily find what you want to watch." @@ -374,14 +370,14 @@ msgstr "Suche gezielt nach Inhalten und finde was du sehen willst." msgctxt "#30093" msgid "Browse content by genre and easily discover related content." -msgstr "Durchstöbere die Inhalte nach Genre und entdecke bequem verwandte Inhalte " +msgstr "Durchsuche Inhalte nach Genre und entdecke bequem verwandte Inhalte." msgctxt "#30094" msgid "Browse recommendations and pick up on something new you might like." -msgstr "Sieh dir Empfehlungen an und entdecke neue Inhalte die dir gefallen könnten." +msgstr "Durchsuche Empfehlungen und entdecke neue Inhalte die dir gefallen könnten." msgctxt "#30095" -msgid "All TV Shows" +msgid "All TV shows" msgstr "Alle Serien" msgctxt "#30096" @@ -393,16 +389,16 @@ msgid "Main Menu" msgstr "Hauptmenü" msgctxt "#30098" -msgid "Enable HDR profiles" -msgstr "Aktiviere HDR" +msgid "Enable HDR10" +msgstr "HDR10 aktivieren" msgctxt "#30099" -msgid "Enable DolbyVision profiles" -msgstr "Aktiviere DolbyVision" +msgid "Enable Dolby Vision" +msgstr "Dolby Vision aktivieren" msgctxt "#30100" -msgid "Results for \"{}\"" -msgstr "Ergebnisse für \"{}\"" +msgid "Results for '{}'" +msgstr "Ergebnisse für '{}'" msgctxt "#30101" msgid "Netflix returned an unknown error." @@ -413,15 +409,15 @@ msgid "Netflix returned the following error:" msgstr "Netflix hat den folgenden Fehler gemeldet:" msgctxt "#30103" -msgid "Error details have been written to the Kodi logfile. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it via GitHub. Please provide a full debug log with your error report (enable \"Debug Logging\" in Kodi settings)." -msgstr "Details zum Fehler wurden in die Kodi Logdatei geschrieben. Falls die Fehlermeldung keine Rückschlüsse auf die Ursache und mögliche Lösung des Fehlers zulässt oder der Fehler dauerhaft auftritt, melde den Fehler bitte über GitHub. Bitte liefere ein vollständiges Debug-Log mit deinem Fehler-Report (Option \"Debug Logging\" in den Kodi-Einstellungen aktivieren)." +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "Details zum Fehler wurden in die Kodi Logdatei geschrieben. Falls die Fehlermeldung keine Rückschlüsse auf die Ursache und mögliche Lösung des Fehlers zulässt oder der Fehler dauerhaft auftritt, melde den Fehler bitte über GitHub gemäß den Anweisungen in der Readme." msgctxt "#30104" -msgid "The addon encountered to following error:" +msgid "The add-on encountered the following error:" msgstr "Bei der Ausführung des Addons trat folgender Fehler auf:" msgctxt "#30105" -msgid "Netflix Addon Error" +msgid "Netflix Add-on Error" msgstr "Netflix Addon Fehler" msgctxt "#30106" @@ -429,20 +425,21 @@ msgid "Invalid PIN" msgstr "Falscher PIN" msgctxt "#30107" -msgid "Disabled PIN verfication" -msgstr "PIN-Abfrage deaktiviert" +msgid "PIN protection is off." +msgstr "PIN-Schutz ist aus." msgctxt "#30108" -msgid "Enabled PIN verfication" -msgstr "PIN-Abfrage aktiviert" +msgid "All content is protected by PIN." +msgstr "Sämtliche Inhalte werden durch eine PIN geschützt." msgctxt "#30109" msgid "Login successful" -msgstr "Login erfolgreich" +msgstr "Erfolgreich angemeldet" -msgctxt "#30110" -msgid "Background services started" -msgstr "Hintergrunddienste gestartet" +#. Unused 30110 +msgctxt "#30111" +msgid "There is no contents" +msgstr "Es gibt keine Inhalte" msgctxt "#30112" msgid "You need to be logged in to use Netflix" @@ -450,86 +447,72 @@ msgstr "Sie müssen angemeldet sein um Netflix zu benutzen" msgctxt "#30113" msgid "Logout successful" -msgstr "Logout erfolgreich" - -msgctxt "#30114" -msgid "Keep My List and Kodi Library in sync" -msgstr "Meine Liste und Kodi Library synchronisieren" - -msgctxt "#30115" -msgid "Content Profiles" -msgstr "Inhaltsprofile" +msgstr "Erfolgreich abgemeldet" +#. Unused 30114, 30115 msgctxt "#30116" -msgid "Advanced Addon Configuration" +msgid "Advanced Add-on Configuration" msgstr "Erweiterte Addon-Konfiguration" msgctxt "#30117" msgid "Cache" msgstr "Cache" -msgctxt "#30118" -msgid "API operation failed: {}" -msgstr "API-Aktion fehlgeschlagen: {}" - -msgctxt "#30119" -msgid "My List operation successful" -msgstr "Meine Liste erfolgreich aktualisiert" - +#. Unused 30118, 30119 msgctxt "#30120" msgid "Cannot automatically remove a season from Kodi library, please do it manually" -msgstr "Staffeln können nicht automatisch aus der Kodi Bilbiothek entfernt werden. Bitte tue dies manuell." +msgstr "Staffeln können nicht automatisch aus der Kodi-Bibliothek entfernt werden. Bitte tue dies manuell." msgctxt "#30121" msgid "Perform full sync now" msgstr "Vollständige Synchronisation jetzt durchführen" msgctxt "#30122" -msgid "Sync My List to Kodi library" -msgstr "Meine Liste in Kodi Bilbiothek synchronisieren" +msgid "Sync 'My list' to Kodi library" +msgstr "'Meine Liste' mit Kodi-Bibliothek synchronisieren" msgctxt "#30123" -msgid "Do you really want to proceed?\nThis will delete all previously exported items and export all items from My List for the currently active profile.\nThis operation may take some time..." -msgstr "Möchtest du wirklich fortfahren?\nDiese Aktion wird alle vormals exportierten Einträge löschen und alle Einträge aus Meine Liste des aktuell aktiven Profils exportieren.\nDer Vorgang kann einige Zeit in Anspruch nehmen..." +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "Möchten Sie fortfahren?[CR]Die vollständige Synchronisation wird alle vormals exportierten Titel löschen.[CR]Wenn es da viele Titel gibt, kann diese Operation etwas dauern." msgctxt "#30124" -msgid "Do you really want to remove this item?" -msgstr "Wollen Sie diesen Eintrag wirklich entfernen?" +msgid "Do you want to remove this item from the library?" +msgstr "Wollen Sie diesen Eintrag aus der Bibliothek entfernen?" msgctxt "#30125" -msgid "Purge library" -msgstr "Bibliothek-Exporte zurücksetzen" +msgid "Delete library contents" +msgstr "Bibliotheksinhalte löschen" msgctxt "#30126" -msgid "Do you really want to purge your library?\nAll exported items will be deleted." -msgstr "Möchtest du wirklich deine Bibliothek-Exporte zurücksetzen?\nAlle vormals exportierten Einträge werden hierdurch gelöscht." +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "Möchten Sie wirklich Ihre Bibliothek-Exporte zurücksetzen?[CR]Alle vormals exportierten Einträge werden hierdurch gelöscht." msgctxt "#30127" msgid "Awarded rating of {}" msgstr "Mit {} bewertet" msgctxt "#30128" -msgid "No contained titles yet..." -msgstr "(Noch) keine enthaltenen Titel..." +msgid "Select a profile" +msgstr "Profil auswählen" msgctxt "#30129" msgid "Enable Up Next integration" msgstr "Up Next Integration aktivieren" msgctxt "#30130" -msgid "Show notification instead of modal dialog for errors" -msgstr "Zeige Benachrichtigungen anstatt Dialogfenstern bei Fehlern" +msgid "Install Up Next add-on" +msgstr "Up Next Addon installieren" msgctxt "#30131" msgid "Check the logfile for detailed information" msgstr "Prüfe die Logdatei für detaillierte Informationen" msgctxt "#30132" -msgid "Purge in-memory cache" +msgid "Clear in-memory cache" msgstr "In-Memory Cache löschen" msgctxt "#30133" -msgid "Purge in-memory and on-disk cache" +msgid "Clear in-memory and on-disk cache" msgstr "In-Memory und Festplatten-Cache löschen" msgctxt "#30134" @@ -541,33 +524,26 @@ msgid "Cache cleared" msgstr "Cache geleert" msgctxt "#30136" -msgid "Enable 1080p Unlock (not recommended for Android)" -msgstr "Aktiviere 1080p Unlock (nicht empfohlen auf Android)" +msgid "Waiting for service start-up..." +msgstr "Warte auf Start der Dienste..." + +msgctxt "#30137" +msgid "Enable VP9 codec" +msgstr "VP9-Codec aktivieren" msgctxt "#30138" -msgid "The requested action timed out. The background services may not yet be available if you just started Kodi/the addon. In this case, try again in a moment." -msgstr "Die angeforderte Aktion konnte nicht abgeschlossen werden. Falls du Kodi oder das Addon gerade erst gestartet hast, kann es sein, dass die Hintergrundservices noch nicht verfügbar sind. Versuche es in diesem Fall in einem kurzen Moment einfach nochmal." +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "Falls Sie Kodi oder das Addon gerade erst gestartet haben, kann es sein, dass die Hintergrunddienste noch nicht verfügbar sind. Versuchen Sie es in diesem Fall in einem kurzen Moment einfach nochmal." msgctxt "#30139" -msgid "Enable IPC over HTTP (use when experiencing AddonSignals timeouts)" -msgstr "Aktiviere IPC über HTTP (bei AddonSignals Timeouts benutzen)" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "Aktiviere IPC über HTTP (bei Addon Signals Timeouts benutzen)" msgctxt "#30140" -msgid "Migrate Library to new format" -msgstr "Bibliothek auf neues Format migrieren" - -msgctxt "#30141" -msgid "Disable startup notification" -msgstr "Deaktiviere Benachrichtigung beim Start der Hintergrunddienste" - -msgctxt "#30142" -msgid "Custom" -msgstr "Individuelle Ansicht" - -msgctxt "#30143" -msgid "Custom view ID" -msgstr "Individuelle Ansicht ID" +msgid "Import existing library" +msgstr "Existierende Bibliothek importieren" +#. Unused 30141 to 30143 msgctxt "#30144" msgid "Disable WebVTT subtitle support" msgstr "WebVTT-Untertitel deaktivieren" @@ -581,16 +557,16 @@ msgid "Discover the latest content added." msgstr "Zeige zuletzt hinzugefügte Inhalte" msgctxt "#30147" -msgid "Next page >>" -msgstr "Nächste Seite >>" +msgid "Next page »" +msgstr "Nächste Seite »" msgctxt "#30148" -msgid "<< Previous page" -msgstr "<< Letzte Seite" +msgid "« Previous page" +msgstr "« Vorherige Seite" msgctxt "#30149" msgid "Sort results by" -msgstr "Sortiere Ergebnisse nach" +msgstr "Ergebnisse sortieren nach" msgctxt "#30150" msgid "Alphabetical order (A-Z)" @@ -609,36 +585,33 @@ msgid "Year released" msgstr "Erscheinungsjahr" msgctxt "#30154" -msgid "Netflix addon configuration" -msgstr "Netflix Adon-Konfiguration" +msgid "Netflix configuration wizard" +msgstr "Konfigurationsassistent für Netflix" msgctxt "#30155" msgid "Do you have a Netflix Premium account?" -msgstr "Haben Sie einen Netflix Premiumkonto?" - -msgctxt "#30156" -msgid "Does your device support the 4K standard?\r\nThe device must:\r\nBe Netflix certified, support for Widevine Security level L1 and have a 4K display HDCP 2.2" -msgstr "Unterstützt Ihr Gerät den 4K-Standard?\r\nGeräte-Anforderung:\r\nNetflix-Zertifiziert, Unterstützung für Widevine Sicherheitslevel L1 und ein 4K-Bildschirm mit Unterstützung von HDCP 2.2" +msgstr "Haben Sie ein Netflix-Premiumkonto?" +#. Unused 30156 msgctxt "#30157" -msgid "The InputStream Adaptive settings panel will now be opened, so you have to change this option \"Override HDCP status\" to ON" -msgstr "Die Einstellungen des Inputstream Adaptive-Addons werden jetzt geöffnet. Ändern Sie die Einstellung \"HDCP-Status überschreiben\" auf EIN" +msgid "The configuration has been completed" +msgstr "Die Einrichtung wurde abgeschlossen" msgctxt "#30158" -msgid "Show configuration screen at addon startup" -msgstr "Zeige Einstellungen beim Start des Addons" +msgid "Restore the add-on default configuration" +msgstr "Standardeinstellungen des Addons wiederherstellen" msgctxt "#30159" -msgid "View for main menu" -msgstr "Ansicht für Hauptmenü" +msgid "View ID for main menu" +msgstr "Ansichts-ID für Hauptmenü" msgctxt "#30160" -msgid "View for search" -msgstr "Ansicht für Suche" +msgid "View ID for search" +msgstr "Ansichts-ID für Suche" msgctxt "#30161" -msgid "View for exported" -msgstr "Ansicht für Exportiert" +msgid "View ID for exported" +msgstr "Ansichts-ID für Exportiert" msgctxt "#30162" msgid "Last used" @@ -653,8 +626,8 @@ msgid "Browse contents with audio description." msgstr "Durchsuche Inhalte mit Audiobeschreibung" msgctxt "#30165" -msgid "View for my list" -msgstr "Ansicht für Meine Liste" +msgid "View ID for 'My list'" +msgstr "Ansichts-ID für 'Meine Liste'" msgctxt "#30166" msgid "Main menu items" @@ -686,10 +659,10 @@ msgstr "Beliebt auf Netflix" msgctxt "#30173" msgid "Netflix Originals" -msgstr "Netflix Original" +msgstr "Netflix-Originale" msgctxt "#30174" -msgid "Tv Show genres" +msgid "TV show genres" msgstr "Serien-Kategorien" msgctxt "#30175" @@ -697,28 +670,28 @@ msgid "Movie genres" msgstr "Film-Kategorien" msgctxt "#30176" -msgid "Enable STRM resume workaround (only library)" -msgstr "Aktiviere Workaround für STRM-Wiedergabe-Fortsetzung (nur Datenbank)" +msgid "Enable STRM resume workaround" +msgstr "Aktiviere Workaround für STRM-Wiedergabe-Fortsetzung" msgctxt "#30177" msgid "Ask to resume the video" -msgstr "Frage zum Fortsetzen des Videos" +msgstr "Fragen, ob Video fortgesetzt werden soll" msgctxt "#30178" -msgid "Open UpNext Addon settings" -msgstr "UpNext Addon-Einstellungen öffnen" +msgid "Open Up Next add-on settings" +msgstr "Up Next Addon-Einstellungen öffnen" msgctxt "#30179" -msgid "Show the trailers" -msgstr "Zeige die Trailer" +msgid "Show trailers and more" +msgstr "Zeige Trailer und mehr" msgctxt "#30180" -msgid "No trailers available" -msgstr "Keine Trailer verfügbar" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "Es gab ein Problem bei der Anmeldung, möglicherweise ist Ihr Account nicht bestätigt oder reaktiviert." msgctxt "#30181" -msgid "Disable subtitles if there are no forced subtitles streams (Works only if Kodi Player is set to \"Only forced\" subtitles)" -msgstr "Deaktiviere Untertitel wenn keine erzwungenen Untertitel vorhanden sind (funktioniert nur wenn der Kodi-Player auf \"Nur erzwungen\" eingesellt ist" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "Untertitel immer anzeigen, wenn bevorzugte Audiosprache nicht verfügbar ist" msgctxt "#30182" msgid "Export NFO files" @@ -726,7 +699,7 @@ msgstr "Exportiere NFO-Dateien" msgctxt "#30183" msgid "Do you want to export NFO files for the {}?" -msgstr "Wollen sie NFO-Dateien für {} exportieren?" +msgstr "Wollen Sie NFO-Dateien für {} exportieren?" msgctxt "#30184" msgid "NFO Files" @@ -741,7 +714,7 @@ msgid "For Movies" msgstr "Für Filme" msgctxt "#30187" -msgid "For TV Show" +msgid "For TV show" msgstr "Für Serien" msgctxt "#30188" @@ -756,17 +729,14 @@ msgctxt "#30190" msgid "TV show" msgstr "Serie" -msgctxt "#30191" -msgid "Do you want to export NFO files for the {} added to 'My List'?" -msgstr "Wollen Sie NFO-Dateien für {} exportieren hinzugefügt zu 'Meine Liste'?" - +#. Unused 30191 msgctxt "#30192" -msgid "\r\nThese files are used by the provider information scrapers to integrate movies and tv shows info, useful with bonus episodes or director's cut" -msgstr "\r\nDiese Dateien werden benutzt um Informationen über Filme und Serien an den Scraper zu übergeben - Hilfreich für Bonus-Episoden oder Director's Cut" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]Diese Dateien werden benutzt um Informationen über Filme und Serien an den Scraper zu übergeben - Hilfreich für Bonus-Episoden oder Director's Cut" msgctxt "#30193" -msgid "Include all information in NFO files (use only with 'Local Information' scraper)" -msgstr "Alle Information in NFO-Datei hinzufügen (wird nur bei lokal arbeitenden Scrapern benutzt)" +msgid "Include tv show NFO details" +msgstr "NFO-Details in Serien einschließen" msgctxt "#30194" msgid "Limit video stream resolution to" @@ -790,18 +760,18 @@ msgstr "Exportiere neue Episoden" msgctxt "#30199" msgid "Shared library (Kodi MySQL server is required)" -msgstr "Datenbank (Kodi MySQL-Server wird benötigt)" +msgstr "Geteilte Bibliothek (Kodi MySQL-Server wird benötigt)" msgctxt "#30200" -msgid "Use MySQL shared library database" -msgstr "Benutze MySQL-Datenbank" +msgid "Use shared library MySQL-database" +msgstr "Benutze MySQL-Datenbank für geteilte Bibliothek" msgctxt "#30201" msgid "Username" msgstr "Benutzername" msgctxt "#30202" -msgid "Connection to the MySQL database was successful" +msgid "Connection to the MySQL-database was successful" msgstr "Verbindung zu MySQL-Datenbank war erfolgreich" msgctxt "#30203" @@ -817,7 +787,7 @@ msgid "Test database connection" msgstr "Teste Datenbankverbindung" msgctxt "#30206" -msgid "ERROR: The MySQL database is not reachable - the local database will be used" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" msgstr "FEHLER: Die MySQL-Datenbank ist nicht erreichbar - die lokale Datenbank wird benutzt" msgctxt "#30207" @@ -826,24 +796,461 @@ msgstr "Definiere dieses Gerät als Haupt-Autoupdate-Manager" msgctxt "#30208" msgid "Check if this device is the main auto-update manager" -msgstr "Pürfe ob dieses Gerät der Haupt-Autoupdate-Manager ist" +msgstr "Prüfe ob dieses Gerät der Haupt-Autoupdate-Manager ist" msgctxt "#30209" -msgid "This device now handles the auto-updates of shared-library" -msgstr "Dieses Gerät verwaltet jetzt das Autoupdate der Datenbank" +msgid "This device now handles the auto-updates of the shared library" +msgstr "Dieses Gerät verwaltet jetzt das Autoupdate der geteilten Bibliothek" msgctxt "#30210" -msgid "This device handles the auto-updates of shared-library" -msgstr "Dieses Gerät verwaltet das Autoupdate der Datenbank" +msgid "This device handles the auto-updates of the shared library" +msgstr "Dieses Gerät verwaltet das Autoupdate der geteilten Bibliothek" msgctxt "#30211" -msgid "Another device handles the auto-updates of shared-library" -msgstr "Ein anderes Gerät verwaltet das Autoupdate der Datenbank" +msgid "Another device handles the auto-updates of the shared library" +msgstr "Ein anderes Gerät verwaltet das Autoupdate der geteilten Bibliothek" msgctxt "#30212" -msgid "There is no device that handles the auto-updates of shared-library" -msgstr "Es ist kein Gerät definiert für die Verwaltung des Autoupdates der Datenbank" +msgid "There is no device that handles the auto-updates of the shared library" +msgstr "Kein Gerät verwaltet das Autoupdate der geteilten Bibliothek" msgctxt "#30213" msgid "InputStream Helper settings..." msgstr "InputStream Helper Einstellungen..." + +msgctxt "#30214" +msgid "Force refresh" +msgstr "Aktualisierung erzwingen" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "Farbe der Titel aus \"Meine Liste\"" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "Farbe der als \"Erinnere mich\" markierten Titel" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "Deaktiviere Benachrichtigung für abgeschlossene Synchronisierung" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "Die Kodi-Bibliothek wurde aktualisiert" + +msgctxt "#30221" +msgid "Owner account" +msgstr "Besitzer-Account" + +msgctxt "#30222" +msgid "Kid account" +msgstr "Kinder-Account" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "Auto-Aktualisierungs-Modus" + +msgctxt "#30225" +msgid "Manual" +msgstr "Manuell" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "Geplant" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "Synchronisiere Kodi-Bibliothek mit 'Meine Liste'" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "Profil zur Synchronisierung auswählen" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "Wenn Kodi startet" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "Jetzt auf Aktualisierungen prüfen" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "Möchten Sie jetzt auf Aktualisierungen prüfen?" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "Alterseinstufung für Profil {}" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "Nur Titel mit Einstufung [B]{}[/B] anzeigen" + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "Gesehen-Status der Videos mit Netflix synchronisieren" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "Gesehen-Status ändern (lokal)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "Als gesehen markiert|Als nicht gesehen markiert|Gesehen-Status von Netflix wiederherstellen" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Up Next Benachrichtigungen (nächstes Video ansehen)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "Farbe der zusätzlichen Handlungsinformation" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "Die Hintergrunddienste konnten wegen folgendem Problem nicht gestartet werden:[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Open Connect CDN (niedriger Index ist besser)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "Top 10" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "Erste ungesehene Serien-Episode auswählen" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "Dateien löschen, die in die Bibliothek exportiert wurden" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "Es gibt {} Titel, die nicht importiert werden können.[CR]Möchten Sie diese Dateien löschen?" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "Ergebnisse pro Seite (bei Zeitüberschreitungen reduzieren)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "Möchten Sie den Gesehen-Status von \"{}\" entfernen?" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "Inklusive Kodi-Bibliothek (nur ausgehend)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "Wählen Sie Ihre Anmeldemethode aus[CR](falls die Anmeldung mit E-Mail/Passwort immer \"falsches Passwort\" zurückgibt, verwenden Sie einen Authentifizierungsschlüssel gemäß der Anweisungen im GitHub-ReadMe)" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "E-Mail/Passwort" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "Authentifizierungsschlüssel" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "Die ausgewählte Datei ist inkompatibel oder beschädigt" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "Der Authentifizierungsschlüssel ist abgelaufen" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "PIN eingeben" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "Verlauf sortieren nach" + +msgctxt "#30400" +msgid "Search" +msgstr "Suche" + +msgctxt "#30401" +msgid "Type of search" +msgstr "Art der Suche" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "Titel, Leute, Genre suchen" + +msgctxt "#30403" +msgid "New search..." +msgstr "Neue Suche..." + +msgctxt "#30404" +msgid "Clear search history" +msgstr "Suchverlauf löschen" + +msgctxt "#30405" +msgid "Select the language" +msgstr "Sprache auswählen" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "Wollen Sie alle Inhalte löschen?" + +msgctxt "#30407" +msgid "No matches found" +msgstr "Keine Titel gefunden" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "Nach Suchbegriff" + +msgctxt "#30411" +msgid "By audio language" +msgstr "Nach Audiosprache" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "Nach Untertitelsprache" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "Nach Genre/Subgenre ID" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "Audio-/Untertitelsprache mit Landescode bevorzugen (falls verfügbar)" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "Standardmäßig Stereo-Audiospur bevorzugen" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "ESN-/Widevine-Einstellungen" + +msgctxt "#30601" +msgid "ESN:" +msgstr "ESN:" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "ESN ändern" + +msgctxt "#30603" +msgid "Save system info" +msgstr "Systeminfo speichern" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "Widevine - Sicherheitslevel erzwingen" + +msgctxt "#30605" +msgid "Force {}" +msgstr "{} erzwingen" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "ESN erfolgreich gesetzt[CR]Versuchen Sie, ein Video abzuspielen." + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "Diese ESN kann nicht verwendet werden.[CR]Versuchen Sie, die ESN zu ändern oder zurückzusetzen.[CR][CR]Details zum Fehler:[CR]{}" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "Falsches Format der ESN" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "Möchten Sie wirklich alle Einstellungen zurücksetzen?" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "System-basierte Verschlüsselung für Anmeldedaten verwenden" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "[B]Deaktivieren ist ein Sicherheitsrisiko![/B] Falls Ihr Betriebssystem dies nicht unterstützt, werden Sie jedesmal nach ihren Anmeldedaten gefragt, wenn Sie ihr Gerät neu starten. Deaktivieren Sie dies [B]nur[/B] in diesem Fall. Bei Änderung müssen Sie sich erneut anmelden wenn Sie das Gerät neu starten." + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "MSL-Manifest-Version" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "Dieser Titel ist verfügbar ab:[CR]{}" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "Möchten Sie den Vorschau-Trailer abspielen?" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "{} Erinnere mich" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "Neu und beliebt" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "Angefangene Serien als gesehen markieren" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "Bitte beachten, dass gesehene Einträge mit aktivierten Filtern in den Skin-Einstellungen eventuell nicht sichtbar sind." + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "Art der Stream Auswahl überschreiben" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "Einstellung der Stream Auswahl des InputStream-Adaptive-Addons überschreiben, wie Audio- und Videoqualität während der Wiedergabe ausgewählt werden." + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "Angepasste Qualität" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "Feste Videoqualität" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "Videoqualität nachfragen" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "Bitte beachten, dass die Aktivierung dieser Einstellung die Entfernung von Streams erzwingt, die nicht im eingestellten Bereich liegen, und dass dies die Funktionalität von InputStream Adaptive beeinflusst. Nach Möglichkeit die Qualität direkt in den Einstellungen von InputStream Adaptive beschränken." + +msgctxt "#30721" +msgid "Size" +msgstr "Größe" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "Minimiert die schwarzen Balken durch Anwendung eines Zoomeffektes. [ Fest ] Setzt den Prozentteil, den die schwarzen Balken belegen können, konstant für alle Videos. [ Relativ ] Setzt den Prozentteil der schwarzen Balken abhängig von der Videogröße. [ Zurücksetzen ] Setzt einen zuvor gesetzten Zoomeffekt zurück." + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "Diese Funktion nutzt den Zoomeffekt von Kodi, der dauerhaft in der Videoeinstellung \"Ansichtsmodus\" gespeichert wird. Wenn Sie diese Funktion in der Zukunft deaktivieren dann bleibt der Zoom erhalten. Um ihn zurückzusetzen, wählen Sie die Einstellung \"Zurücksetzen\" aus." + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "AV1-Codec aktivieren" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "Netflix-Verzeichnisse zu Kodi-Bibliotheksquellen hinzufügen" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "Die Verzeichnisse \"Netflix-Movies\" und \"Netflix-Shows\" wurden zu den Kodi-Quellen hinzugefügt. Starten Sie Kodi neu um diese in Kodi Menü [Serien] / [Filme] zu sehen. Dann bearbeiten Sie diese Verzeichnisse um den gewünschten Content-Type und Informationsbeschaffungsscraper einzustellen." + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "[Manuell] Aktualisierungen müssen manuell über das Kontextmenü \"Exportiere neue Episoden\" für jede Serie gemacht werden. [Geplant] Sie können automatische Updates zu bestimmten Zeiten einrichten. [Wenn Kodi startet] Automatische Aktualisierungen werden einmal am Tag beim Start von Kodi durchgeführt." + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "Synchronisiert die Bibliothek mit Meiner Liste. Wenn Meine Liste extern geändert wurde, bspw. über die mobile App oder die Webseite, dann wird die Aktualisierung zur geplanten Zeit durchgeführt." + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "Wenn aktiviert, werden Informationen zu Episoden und Filmen hinzugefügt, die nützlich sind, falls der Kodi-Informationsbeschaffungsscraper keine Daten liefert. Dies beinhaltet die Dauer der Videos." + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "Nur aktivieren falls \"Lokale Information\"-Informationsbeschaffungsscraper verwendet wird, oder gewisse Titel nicht in den Kodi-Bibliothen angezeigt werden obwohl sie hinzugefügt wurden." + +msgctxt "#30734" +msgid "Support" +msgstr "Unterstützung" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "Über das Netflix-Addon" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "Automatisch neue ESNs generieren (Workaround für 540p Begrenzung)" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "WARNUNG: Nutzen Sie nicht die originale Geräte-ESN der offiziellen App." + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "Audio-Offset aktivieren" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "Audio-Offset" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "Setzt den Audio-Offset und überschreibt die Kodi-Einstellung" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "VP9 Profil 2 aktivieren (10/12 Bit Farbtiefe)" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "Möchten Sie Unterstützung für HDR10 aktivieren?" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "Möchten Sie Unterstützung für HDR Dolby Vision aktivieren?" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "Das Addon stellt bereits die geeignete HDCP-Version ein. Bei Bedarf können Sie die Abfrage von Videostreams in einer bestimmten HDCP-Version erzwingen." + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "Falls Ihr Gerät diesen Codec nicht unterstützt, können ein schwarzer Bildschirm, fehlende oder beschädigte Bilder auftreten. Deaktivieren Sie dann diesen Codec." + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "Minimierungsmodus schwarze Balken" + +msgctxt "#30747" +msgid "Fixed" +msgstr "Fest" + +msgctxt "#30748" +msgid "Relative" +msgstr "Relativ" + +msgctxt "#30749" +msgid "Reset" +msgstr "Zurücksetzen" diff --git a/resources/language/resource.language.el_gr/strings.po b/resources/language/resource.language.el_gr/strings.po new file mode 100644 index 000000000..5fad1ea8d --- /dev/null +++ b/resources/language/resource.language.el_gr/strings.po @@ -0,0 +1,1256 @@ +# Kodi Media Center language file +# Addon Name: Netflix +# Addon id: plugin.video.netflix +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait +msgid "" +msgstr "" +"Project-Id-Version: plugin.video.netflix\n" +"Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" +"POT-Creation-Date: 2017-07-24 16:15+0000\n" +"PO-Revision-Date: 2020-03-09 20:40+0000\n" +"Last-Translator: Twilight0 <>\n" +"Language-Team: Greek (Greece)\n" +"Language: el_GR\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 "Netflix" +msgstr "Νέτφλιξ" + +msgctxt "Addon Description" +msgid "Netflix VOD Services Add-on" +msgstr "Πρόσθετο υπηρεσιών κατά παραγγελία του Νέτφλιξ" + +msgctxt "Addon Disclaimer" +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "Η χρήση αυτού του προσθέτου μπορεί να μην είναι νόμιμη στην χώρα που κατοικείτε - παρακαλώ ελέγξτε τους τοπικούς νόμους πριν την εγκατάσταση." + +msgctxt "#30001" +msgid "Recommendations" +msgstr "Προτάσεις" + +msgctxt "#30002" +msgid "Enter PIN to watch restricted content" +msgstr "Εισάγετε το ΠΙΝ για την παρακολούθηση περιορισμένου περιεχομένου" + +#. Unused 30003 +msgctxt "#30004" +msgid "Password" +msgstr "Κωδικός" + +msgctxt "#30005" +msgid "E-mail" +msgstr "Ηλ.ταχυδρομείο" + +msgctxt "#30006" +msgid "Enter PIN to access this profile" +msgstr "Εισάγετε το PIN για να προσπελάσετε αυτό το προφίλ" + +msgctxt "#30007" +msgid "Parental Control PIN (4 digits)" +msgstr "ΠΙΝ γονικού ελέγχου (4 ψηφία)" + +msgctxt "#30008" +msgid "Login failed" +msgstr "Αποτυχία εισόδου" + +msgctxt "#30009" +msgid "Please check your e-mail and password" +msgstr "Παρακαλώ ελέγξτε την διεύθυνση ηλ.ταχυδρομείου και τον κωδικό" + +msgctxt "#30010" +msgid "Lists of all kinds" +msgstr "Λίστες όλων των ειδών" + +#. Unused 30011 +msgctxt "#30012" +msgid "Profiles options and parental control" +msgstr "Επιλογές προφίλ και γονικού ελέγχου" + +msgctxt "#30013" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "Άνοιγμα εναλλακτικού μενού στο επιλεγμένο προφίλ μέσα στην λίστα τους" + +msgctxt "#30014" +msgid "Account" +msgstr "Λογαριασμός" + +msgctxt "#30015" +msgid "Other options" +msgstr "Άλλες επιλογές" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "" + +msgctxt "#30017" +msgid "Logout" +msgstr "Αποσύνδεση" + +msgctxt "#30018" +msgid "Export to library" +msgstr "Εξαγωγή στην βιβλιοθήκη" + +msgctxt "#30019" +msgid "Rate on Netflix" +msgstr "Βαθμολόγηση στο Νέτφλιξ" + +msgctxt "#30020" +msgid "Remove from 'My list'" +msgstr "Αφαίρεση από την 'Λίστα μου'" + +msgctxt "#30021" +msgid "Add to 'My list'" +msgstr "Προσθήκη στην 'Λίστα μου'" + +msgctxt "#30022" +msgid "(between 0 and 10)" +msgstr "(μεταξύ 0 & 10)" + +msgctxt "#30023" +msgid "Expert" +msgstr "Ειδικός" + +msgctxt "#30024" +msgid "SSL verification" +msgstr "Επαλήθευση SSL" + +msgctxt "#30025" +msgid "Library" +msgstr "Βιβλιοθήκη" + +msgctxt "#30026" +msgid "Enable custom library folder" +msgstr "Ενεργοποίηση προσαρμοσμένου φακέλου βιβλιοθήκης" + +msgctxt "#30027" +msgid "Custom library path" +msgstr "Διαδρομή προσαρμοσμένου φακέλου βιβλιοθήκης" + +msgctxt "#30028" +msgid "Playback error" +msgstr "Αποτυχία αναπαραγωγής" + +#. Unused 30029 +msgctxt "#30030" +msgid "Remove from library" +msgstr "Αφαίρεση από την βιβλιοθήκη" + +msgctxt "#30031" +msgid "General" +msgstr "Γενικά" + +msgctxt "#30032" +msgid "Codecs" +msgstr "" + +msgctxt "#30033" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "" + +#. Unused 30034 +msgctxt "#30035" +msgid "InputStream Adaptive settings..." +msgstr "Ρυθμίσεις προσθέτου InputStream" + +#. Unused 30036 +msgctxt "#30037" +msgid "Skin viewtypes" +msgstr "Τύποι εμφάνισης του κελύφους" + +msgctxt "#30038" +msgid "Enable custom viewtypes" +msgstr "Ενεργοποίηση τροποποιημένων τύπων εμφάνισης κελύφους" + +msgctxt "#30039" +msgid "View ID for folders" +msgstr "ID εμφάνισης για φακέλους" + +msgctxt "#30040" +msgid "View ID for movies" +msgstr "ID εμφάνισης για ταινίες" + +msgctxt "#30041" +msgid "View ID for shows" +msgstr "ID εμφάνισης για σειρές" + +msgctxt "#30042" +msgid "View ID for seasons" +msgstr "ID εμφάνισης για σεζόν" + +msgctxt "#30043" +msgid "View ID for episodes" +msgstr "ID εμφάνισης για επεισόδια" + +msgctxt "#30044" +msgid "View ID for profiles" +msgstr "ID εμφάνισης για προφίλ" + +msgctxt "#30045" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "Αφαιρέσατε την βαθμολόγηση|Βαθμολογήσατε αρνητικά|Βαθμολογήσατε θετικά" + +msgctxt "#30046" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "Υπάρχει πρόβλημα με το πρόσθετο InputStream ή την βιβλιοθήκη Widevine. Μπορεί να λείπει ή να μην είναι ενεργοποιημένα, η διαδικασία θα ακυρωθεί." + +msgctxt "#30047" +msgid "Library update in progress" +msgstr "Ενημέρωση βιβλιοθήκης σε εξέλιξη" + +msgctxt "#30048" +msgid "Exported" +msgstr "Εξήχθη" + +msgctxt "#30049" +msgid "Library" +msgstr "Βιβλιοθήκη" + +msgctxt "#30050" +msgid "Enable Kodi library management" +msgstr "" + +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30052" +msgid "{} Set for library playback" +msgstr "{} Εφαρμογή για αναπαραγογή από βιβλιοθήκη" + +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30055" +msgid "{} Set at start-up" +msgstr "" + +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30057" +msgid "{} Remember PIN" +msgstr "" + +#. Unused 30058 +msgctxt "#30059" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "Να είστε προσεκτικοί, πρόκειτε να ενημερώσετε αρκετούς τιτλους {}.[CR]Αυτό μπορεί να προκαλέσει προβλήματα υπηρεσίας ή προσωρινή φραγή του λογαριασμού.[CR]Θέλετε να συνεχίσετε;" + +msgctxt "#30060" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "" + +msgctxt "#30061" +msgid "Update inside library" +msgstr "Ενημέρωση εντός της βιβλιοθήκης" + +msgctxt "#30062" +msgid "Parental controls" +msgstr "Γονικός έλεγχος" + +msgctxt "#30063" +msgid "An update is already in progress" +msgstr "Μία ενημέρωση βρίσκεται ήδη σε εξέλιξη" + +msgctxt "#30064" +msgid "Perform auto-update" +msgstr "Εκτέλεση αυτόματης ενημέρωσης" + +msgctxt "#30065" +msgid "Video library update" +msgstr "" + +msgctxt "#30066" +msgid "Enable debug logging" +msgstr "Ενεργοποίηση καταγραφής σφαλμάτων" + +msgctxt "#30067" +msgid "daily" +msgstr "καθημερινά" + +msgctxt "#30068" +msgid "every other day" +msgstr "κάθε δεύτερη μέρα" + +msgctxt "#30069" +msgid "every 5 days" +msgstr "κάθε 5 μέρες" + +msgctxt "#30070" +msgid "weekly" +msgstr "εβδομαδιαίως" + +msgctxt "#30071" +msgid "Time of Day" +msgstr "Ώρα ημέρας" + +msgctxt "#30072" +msgid "Only start after 5 minutes of idle" +msgstr "Εκκίνηση μόνο έπειτα από 5 λεπτά αδράνειας" + +msgctxt "#30073" +msgid "Always show codec information during video playback" +msgstr "Δείξε πάντα τις πληροφορίες αποκωδικοποίησης κατά την διάρκεια αναπαραγωγής" + +msgctxt "#30074" +msgid "View for exported" +msgstr "Όψη για τα εξαγώμενα" + +msgctxt "#30075" +msgid "Ask to skip intro and recap" +msgstr "Ερώτηση για την αποφυγή εισαγωγής και σύνοψης" + +msgctxt "#30076" +msgid "Skip intro" +msgstr "Αποφυγή εισαγωγής" + +msgctxt "#30077" +msgid "Skip recap" +msgstr "Αποφυγή σύνοψης" + +msgctxt "#30078" +msgid "Playback" +msgstr "Αναπαραγωγή" + +msgctxt "#30079" +msgid "Skip automatically" +msgstr "Κάνε την αποφυγή αυτόματα" + +msgctxt "#30080" +msgid "Pause when skipping" +msgstr "Παύση κατά την αποφυγή" + +msgctxt "#30081" +msgid "Force HDCP version on video streams" +msgstr "" + +msgctxt "#30082" +msgid "Remember audio / subtitle preferences" +msgstr "Απομνημόνευση προτιμήσεων για ήχο / υποτίτλους" + +msgctxt "#30083" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "Επιβολή απεικόνισης των επιβεβλημένων υποτίτλων μόνο με την καθορισμένη γλώσσα ήχου" + +msgctxt "#30084" +msgid "Cache objects TTL (minutes)" +msgstr "Αποθήκευσε προσωρινά τα αντικείμενα στο χρόνο-για-ζωντανά (λεπτά)" + +msgctxt "#30085" +msgid "Cache metadata TTL (days)" +msgstr "Αποθήκευσε προσωρινά το αντικείμενο με τα μεταδεδομένα στο χρόνο-για-ζωντανά (μέρες)" + +msgctxt "#30086" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "Αποθήκευσε προσωρινά τα αντικείμενα της 'Λίστας μου' στο χρόνο-για-ζωντανά (λεπτά)" + +msgctxt "#30087" +msgid "Contains {} and more..." +msgstr "Περιέχει {} και περισσότερα..." + +msgctxt "#30088" +msgid "Browse related content" +msgstr "Περιήγηση σχετικού περιεχομένου" + +msgctxt "#30089" +msgid "Browse subgenres" +msgstr "Περιήγηση υποειδών" + +msgctxt "#30090" +msgid "Browse through related video lists and discover more content." +msgstr "Περιήγηση μέσω σχετικών λιστών βίντεο και ανακαλυψτε περισσότερο περιεχόμενο." + +msgctxt "#30091" +msgid "Browse and manage contents that were exported to the Kodi library." +msgstr "Περιήγηση και διαχείριση ότι έχει εξαχθεί στην βιβλιοθήκη του Kodi." + +msgctxt "#30092" +msgid "Search for content and easily find what you want to watch." +msgstr "Αναζήτηση περιεχομένου και εύκολα βρείτε κάτι για να παρακολουθείστε." + +msgctxt "#30093" +msgid "Browse content by genre and easily discover related content." +msgstr "Περιήγηση περιεχομένου με βάση το είδος και εύκολα ανακαλυψτε σχετικό περιεχόμενο" + +msgctxt "#30094" +msgid "Browse recommendations and pick up on something new you might like." +msgstr "Περιήγηση προτάσεων και επιλογή κάτι νέου που μπορεί να σας αρέσει." + +msgctxt "#30095" +msgid "All TV shows" +msgstr "Όλες οι σειρές" + +msgctxt "#30096" +msgid "All Movies" +msgstr "Όλες οι ταινίες" + +msgctxt "#30097" +msgid "Main Menu" +msgstr "Κυρίως μενού" + +msgctxt "#30098" +msgid "Enable HDR10" +msgstr "" + +msgctxt "#30099" +msgid "Enable Dolby Vision" +msgstr "" + +msgctxt "#30100" +msgid "Results for '{}'" +msgstr "Αποτελέσματα για '{}'" + +msgctxt "#30101" +msgid "Netflix returned an unknown error." +msgstr "Το Νέτφλιξ επέστρεψε ένα άγνωστο σφάλμα." + +msgctxt "#30102" +msgid "Netflix returned the following error:" +msgstr "Το Νέτφλιξ επέστρεψε το ακόλουθο σφάλμα:" + +msgctxt "#30103" +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "Λεπτομέρεις για το σφάλμα έχουν γραφτεί στο αρχείο καταγραφής του Kodi. Εάν το σφάλμα δεν παρέχει οδηγίες για το πως θα προκύψει η επίλυση του η συνεχίζετε να λαμβάνετε αυτο το σφάλμα, παρακαλώ αναφέρετέ το μέσω του Github. Παρακαλώ όπως παρέχετε πλήρες το αρχείο καταγραφής σε επίπεδο αποσφαλμάτωσης στην αναφορά σας (Να ενεργοποιήσετε την Καταγραφή σφαλμάτων στις ρυθμίσεις του Kodi)." + +msgctxt "#30104" +msgid "The add-on encountered the following error:" +msgstr "Το πρόσθετο αντιμετώπισε το ακόλουθο σφάλμα:" + +msgctxt "#30105" +msgid "Netflix Add-on Error" +msgstr "Σφάλμα του προσθέτου Νετφλιξ" + +msgctxt "#30106" +msgid "Invalid PIN" +msgstr "Λανθασμένο ΠΙΝ" + +msgctxt "#30107" +msgid "PIN protection is off." +msgstr "Η προστασία ΠΙΝ είναι απενεργοποιημένη." + +msgctxt "#30108" +msgid "All content is protected by PIN." +msgstr "Όλο το περιεχόμενο είναι προστατευμένο από ΠΙΝ." + +msgctxt "#30109" +msgid "Login successful" +msgstr "Είσοδος επιτυχής" + +#. Unused 30110 +msgctxt "#30111" +msgid "There is no contents" +msgstr "Δεν υπάρχουν περιεχόμενα" + +msgctxt "#30112" +msgid "You need to be logged in to use Netflix" +msgstr "Θα πρέπει να είστε συνδεδεμένος προκειμένουν να κάνετε χρηση του Νέτφλιξ" + +msgctxt "#30113" +msgid "Logout successful" +msgstr "Αποσύνδεση επιτυχής" + +#. Unused 30114, 30115 +msgctxt "#30116" +msgid "Advanced Add-on Configuration" +msgstr "Ρυθμίσεις του προσθέτου για προχωρημένους" + +msgctxt "#30117" +msgid "Cache" +msgstr "Προσωρινή μνήμη" + +#. Unused 30118, 30119 +msgctxt "#30120" +msgid "Cannot automatically remove a season from Kodi library, please do it manually" +msgstr "Αδυναμία αυτόματης αφαίρεσης της σεζόν από την βιβλιοθήκη του Kodi, παρακαλώ κάντε το χειροκίνητα" + +msgctxt "#30121" +msgid "Perform full sync now" +msgstr "Εκτέλεση πλήρους συγχρονισμού τώρα" + +msgctxt "#30122" +msgid "Sync 'My list' to Kodi library" +msgstr "Συγχρονισμός της Λίστας μου στην βιβλιοθήκη του Kodi" + +msgctxt "#30123" +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "Θέλετε να συνεχίσετε;[CR]Ο πλήρης συγχρονισμός θα διαγραψει όλα τα προηγούμενα εξηγμένους τίτλους.[CR]Εάν υπάρχουν πολλοί τίτλοι αυτή η διεργασία μπορεί να πάρει αρκετό χρόνο." + +msgctxt "#30124" +msgid "Do you want to remove this item from the library?" +msgstr "Θέλετε να αφαιρέσετε αυτο το αντικείμενο;" + +msgctxt "#30125" +msgid "Delete library contents" +msgstr "Εκκαθάριση βιβλιοθήκης" + +msgctxt "#30126" +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "Θέλετε να κάνετε εκκαθάριση της βιβλιοθήκης;[CR]Όλα τα εξηχθημένα αντικείμενα θα διαγραφτούν." + +msgctxt "#30127" +msgid "Awarded rating of {}" +msgstr "Απονεμήθει βαθμολόγηση για {}" + +msgctxt "#30128" +msgid "Select a profile" +msgstr "Επιλέξτε ένα προφίλ" + +msgctxt "#30129" +msgid "Enable Up Next integration" +msgstr "Ενεργοποίηση ενσωμάτωσης επομένου" + +msgctxt "#30130" +msgid "Install Up Next add-on" +msgstr "Εγκατάσταση του προσθέτου για τα επόμενα" + +msgctxt "#30131" +msgid "Check the logfile for detailed information" +msgstr "Έλεγχος του αρχείου καταγραφής για λεπτομερείς πληροφορίες" + +msgctxt "#30132" +msgid "Clear in-memory cache" +msgstr "Εκκαθάριση κρυφής μνήμης" + +msgctxt "#30133" +msgid "Clear in-memory and on-disk cache" +msgstr "Εκκαθάριση κρυφής μνήμης και προσωρινής μνήμης στον δίσκο" + +msgctxt "#30134" +msgid "Enable execution timing" +msgstr "Ενεργοποίηση εκτελεστικού χρονισμού" + +msgctxt "#30135" +msgid "Cache cleared" +msgstr "Προσωρινή μνήμη εκκαθαρίστηκε" + +msgctxt "#30136" +msgid "Waiting for service start-up..." +msgstr "" + +msgctxt "#30137" +msgid "Enable VP9 codec" +msgstr "" + +msgctxt "#30138" +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "Οι υπηρεσίες παρασκηνίου μπορει να μην είναι ακόμη διαθέσιμες την στιγμή που εκκινείται το πρόσθετο στο Kodi. Σε αυτήν την περίπτωση θα πρέπει να ξαναδοκιμάσετε εντός ολίγου." + +msgctxt "#30139" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "Ενεργοποίηση του IPC πάνω απο HTTP (κάνε χρηση όταν εμφανιστούν τα μηνύματα λήξης χρόνου από το Addon Signals)" + +msgctxt "#30140" +msgid "Import existing library" +msgstr "Εισαγωγή υπάρχουσας βιβλιοθήκης" + +#. Unused 30141 to 30143 +msgctxt "#30144" +msgid "Disable WebVTT subtitle support" +msgstr "Απενεργοποίηση υποστήριξης υποτίτλων WebVTT" + +msgctxt "#30145" +msgid "Recently added" +msgstr "Προστέθηκαν πρόσφατα" + +msgctxt "#30146" +msgid "Discover the latest content added." +msgstr "Ανακαλύψτε περιεχόμενο που προστέθηκε τελευταια." + +msgctxt "#30147" +msgid "Next page »" +msgstr "Επόμενη σελίδα »" + +msgctxt "#30148" +msgid "« Previous page" +msgstr "« Προηγούμενη σελίδα" + +msgctxt "#30149" +msgid "Sort results by" +msgstr "Ταξινόμηση αποτελεσμάτων κατά" + +msgctxt "#30150" +msgid "Alphabetical order (A-Z)" +msgstr "Αλφαβητική ταξινόμηση (Α-Ω)" + +msgctxt "#30151" +msgid "Alphabetical order (Z-A)" +msgstr "Αλφαβητική ταξινόμηση (Ω-Α)" + +msgctxt "#30152" +msgid "Suggestions for you" +msgstr "Προτάσεις για εσάς" + +msgctxt "#30153" +msgid "Year released" +msgstr "Έτος ανακοίνωσης" + +msgctxt "#30154" +msgid "Netflix configuration wizard" +msgstr "Μάγος ρύθμισης του Νέτφλιξ" + +msgctxt "#30155" +msgid "Do you have a Netflix Premium account?" +msgstr "Έχετε πλήρη λογαριασμό του Νέτφλιξ;" + +#. Unused 30156 +msgctxt "#30157" +msgid "The configuration has been completed" +msgstr "Η ρύθμιση έχει ολοκληρωθεί" + +msgctxt "#30158" +msgid "Restore the add-on default configuration" +msgstr "" + +msgctxt "#30159" +msgid "View ID for main menu" +msgstr "ID εμφάνισης για το κυρίως μενού" + +msgctxt "#30160" +msgid "View ID for search" +msgstr "ID εμφάνισης για αναζήτηση" + +msgctxt "#30161" +msgid "View ID for exported" +msgstr "ID εμφάνισης για εξαγόμενα" + +msgctxt "#30162" +msgid "Last used" +msgstr "Τελεταία χρησιμοποιημένο" + +msgctxt "#30163" +msgid "Audio description" +msgstr "Περιγραφή ήχου" + +msgctxt "#30164" +msgid "Browse contents with audio description." +msgstr "Περιήγηση περιεχομένων με ηχητική περιγραφή." + +msgctxt "#30165" +msgid "View ID for 'My list'" +msgstr "ID εμφάνισης για 'Η λίστα μου'" + +msgctxt "#30166" +msgid "Main menu items" +msgstr "Αντικείμενα κυρίως μενού" + +msgctxt "#30167" +msgid "My list" +msgstr "Η Λίστα μου" + +msgctxt "#30168" +msgid "Continue watching" +msgstr "Συνέχιση παρακολούθησης" + +msgctxt "#30169" +msgid "Top picks" +msgstr "Κορυφαίες επιλογές" + +msgctxt "#30170" +msgid "New releases" +msgstr "Νέες κυκλοφορίες" + +msgctxt "#30171" +msgid "Trending now" +msgstr "Τάσεις της στιγμής" + +msgctxt "#30172" +msgid "Popular on Netflix" +msgstr "Δημοφιλή στο Νέτφλιξ" + +msgctxt "#30173" +msgid "Netflix Originals" +msgstr "Τα πρωτότυπα του Νέτφλιξ" + +msgctxt "#30174" +msgid "TV show genres" +msgstr "Είδη τηλεοπτικών σειρών" + +msgctxt "#30175" +msgid "Movie genres" +msgstr "Είδη ταινιών" + +msgctxt "#30176" +msgid "Enable STRM resume workaround" +msgstr "Ενεργοποίηση ελιγμού για την συνέχιση" + +msgctxt "#30177" +msgid "Ask to resume the video" +msgstr "Ερώτηση για την συνέχιση του βίντεο" + +msgctxt "#30178" +msgid "Open Up Next add-on settings" +msgstr "Άνοιγμα των ρυθμίσεων του προσθέτου για το Επόμενο" + +msgctxt "#30179" +msgid "Show trailers and more" +msgstr "Δείξε τα τρέιλερ & περισσότερα" + +msgctxt "#30180" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "Υπάρχει πρόβλημα με την είσοδο σας, ενδεχομένως ο λογαριασμός σας να μην έχει (επάν)ενεργοποιηθεί" + +msgctxt "#30181" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "Δείχνε πάντα τους υποτίτλους όταν η επιλεγμένη γλώσσα ήχου δεν είναι διαθέσιμη" + +msgctxt "#30182" +msgid "Export NFO files" +msgstr "Εξαγωγή των αρχείων NFO" + +msgctxt "#30183" +msgid "Do you want to export NFO files for the {}?" +msgstr "Θέλετε να εξάγετε σε αρχεία NFO για το {};" + +msgctxt "#30184" +msgid "NFO Files" +msgstr "Αρχεία NFO" + +msgctxt "#30185" +msgid "Enable NFO files export" +msgstr "Ενεργοποίηση εξαγωγής σε αρχεία NFO" + +msgctxt "#30186" +msgid "For Movies" +msgstr "Για ταινίες" + +msgctxt "#30187" +msgid "For TV show" +msgstr "Για τηλεοπτικές σειρές" + +msgctxt "#30188" +msgid "Ask" +msgstr "Ερώτηση" + +msgctxt "#30189" +msgid "movie" +msgstr "ταινία" + +msgctxt "#30190" +msgid "TV show" +msgstr "Τηλεοπτική σειρά" + +#. Unused 30191 +msgctxt "#30192" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]Αυτά τα αρχεία χρησιμοποιούνται από τους φιλτραριστές παρόχων πληροφοριών για την ενσωμάτωση πληροφοριών σε ταινίες και τηλεοπτικές σειρές, χρήσιμο για έξτρα επεισόδια ή τις περικοπές του σκηνοθέτη" + +msgctxt "#30193" +msgid "Include tv show NFO details" +msgstr "" + +msgctxt "#30194" +msgid "Limit video stream resolution to" +msgstr "Περιορισμός ανάλυσης ροής βίντεο σε" + +msgctxt "#30195" +msgid "Export new episodes" +msgstr "Εξαγωγή νέων επεισοδίων" + +msgctxt "#30196" +msgid "Exclude from auto update" +msgstr "Απέκλεισε από την αυτόματη ενημέρωση" + +msgctxt "#30197" +msgid "Include in auto update" +msgstr "Συμπεριέλαβε στην αυτόματη ενημέρωση" + +msgctxt "#30198" +msgid "Exporting new episodes" +msgstr "Εξαγωγή νέων επεισοδίων" + +msgctxt "#30199" +msgid "Shared library (Kodi MySQL server is required)" +msgstr "Μοιρασμένη βιβλιοθήκη (Απαιτείται ο εξυπηρετητής Kodi MySQL)" + +msgctxt "#30200" +msgid "Use shared library MySQL-database" +msgstr "Χρησιμοποιήσε την μοιρασμένη βάση δεδομένων βιβλιοθήκης MySQL" + +msgctxt "#30201" +msgid "Username" +msgstr "Όνομα χρήστη" + +msgctxt "#30202" +msgid "Connection to the MySQL-database was successful" +msgstr "Η σύνδεση στην βάση δεδομένων MySQL ήταν επιτυχής" + +msgctxt "#30203" +msgid "Host IP" +msgstr "IP φιλοξενητή" + +msgctxt "#30204" +msgid "Port" +msgstr "Θύρα" + +msgctxt "#30205" +msgid "Test database connection" +msgstr "Δοκιμή σύνδεσης σε βάση δεδομένων" + +msgctxt "#30206" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" +msgstr "ΣΦΑΛΜΑ: Η βάση δεδομένων MySQL δεν είναι προσβάσιμη - θα χρησιμοποιηθεί η τοπική βάση δεδομένων" + +msgctxt "#30207" +msgid "Set this device as main auto-updates manager" +msgstr "Εφαρμόστε αυτήν την συσκευή ως τον διαχειριστή αυτόματων ενημερώσεων" + +msgctxt "#30208" +msgid "Check if this device is the main auto-update manager" +msgstr "Ελέγξτε εάν αυτή η συσκευή είναι ο διαχειριστής των αυτόματων ενημερώσεων" + +msgctxt "#30209" +msgid "This device now handles the auto-updates of the shared library" +msgstr "Αυτή η συσκευή πλέον διαχειρίζεται τις αυτόματες ενημερώσεις της μοιρασμένης-βιβλιοθήκης" + +msgctxt "#30210" +msgid "This device handles the auto-updates of the shared library" +msgstr "Αυτή η συσκευή διαχειρίζεται τις αυτόματες ενημερώσεις της μοιρασμένης-βιβλιοθήκης" + +msgctxt "#30211" +msgid "Another device handles the auto-updates of the shared library" +msgstr "Άλλη συσκευή διαχειρίζεται τις αυτόματες ενημερώσεις της μοιρασμένης-βιβλιοθήκης" + +msgctxt "#30212" +msgid "There is no device that handles the auto-updates of the shared library" +msgstr "Δεν υπάρχει συσκευή που να διαχειρίζεται τις αυτόματες ενημερώσεις της μοιρασμένης-βιβλιοθήκης" + +msgctxt "#30213" +msgid "InputStream Helper settings..." +msgstr "Ρυθμίσεις βοηθού InputStream" + +msgctxt "#30214" +msgid "Force refresh" +msgstr "Επιβολή ανανέωσης" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "Επισήμανε τίτλους συμπεριλαμβανομένους στην λίστα μου με ενα χρώμα" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "Απενεργοποίηση ειδοποίησης για ολοκλήρωση συγχρονισμού" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "Η βιβλιοθήκη του Kodi έχει ενημερωθεί" + +msgctxt "#30221" +msgid "Owner account" +msgstr "Λογαριασμός ιδιοκτήτη" + +msgctxt "#30222" +msgid "Kid account" +msgstr "Λογαριασμός παιδικός" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "Λειτουργία αυτόματης ενημέρωσης" + +msgctxt "#30225" +msgid "Manual" +msgstr "Χειροκίνητα" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "Προγραμματισμένα" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "Συγχρονισμός της βιβλιοθήκης του Kodi με την Λίστα μου" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "Εφαρμογή προφίλ για συγχρονισμό" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "Ελέγξε για ενημερώσεις τώρα" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "Θέλετε να ελέγξετε τώρα για ενημερώσεις;" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "Προφίλ βαθμολόγησης ωριμότητας για το {}" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "Δείξε μόνο τίτλους βαθμολογημένοι [B]{}[/B]." + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "Συγχρονισμός της κατάστασης προβολής των βίντεο με το Νέτφλιξ" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "Αλλαγή κατάστασης προβολής (τοπικά)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "Σημείωση ως προβληθέν|Σημείωση ως μη προβληθέν|Ανάκτηση κατάστασης προβληθέντων απο Νέτφλιξ" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Ειδοποιήσεις επομένου (δείτε το επόμενο βίντεο)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "Χρωματισμός συμπληρωματικών πληροφοριών πλάνου" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "Οι υπηρεσίες παρασκηνίου δεν μπορούν να εκτελεστούν λόγω αυτού του προβήματος:[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Άνοιγμα του CDN Connect (ο χαμηλότερος δείκτης είναι καλύτερος)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "Κορυφαία 10" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "Επιλογή του πρώτου μη-παρακολουθημένου επεισοδίου σειράς" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "Διαγραφή αρχείων εξηγμένων στην βιβλιοθήκη" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "Υπάρχουν {} τίτλοι με αδυναμία εισαγωγής.[CR]Θέλετε να διαγράψετε τα αρχεία τους;" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "Αποτελέσματα ανά σελίδα (σε περίπτωση σφάλματος από λήξη χρόνου μειώστε τα)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "Θέλετε να αφαιρέσετε την κατάσταση παρακολούθησης για το \"{}\";" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "Συμπεριέλαβε στην βιβλιοθήκη του Kodi (μόνο αποστολή δεδομένων)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "Επιλέξτε την μέθοδο εισόδου[CR](εάν η είσοδος με E-mail/Κωδικό επιστρέφει πάντα \"λανθασμένος κωδικός\", πάντα να χρησιμοποιείτε το Κλειδί ταυτοποίησης, οδηγίες στο GitHub Readme)" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "E-mail/Κωδικός" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "Κλειδί ταυτοποίησης" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "Το επιλεγμένο αρχείο δεν είναι συμβατό ή έχει καταστραφεί" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "Το αρχείο κλειδιού ταυτοποίησης έχει λήξει" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "Εισαγωγή PIN" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "Ταξινόμιση ιστορικού από" + +msgctxt "#30400" +msgid "Search" +msgstr "Αναζήτηση" + +msgctxt "#30401" +msgid "Type of search" +msgstr "Τύπος αναζήτησης" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "Αναζήτηση τίτλων, ανθρώπων, ειδών" + +msgctxt "#30403" +msgid "New search..." +msgstr "Νέα αναζήτηση..." + +msgctxt "#30404" +msgid "Clear search history" +msgstr "Καθαρισμός ιστορικού αναζήτησης" + +msgctxt "#30405" +msgid "Select the language" +msgstr "Επιλογή της γλώσσας" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "Θέλετε να διαγράψετε όλα τα περιεχόμενα;" + +msgctxt "#30407" +msgid "No matches found" +msgstr "Δεν βρέθηκαν αποτελέσματα" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "Μέσω όρου" + +msgctxt "#30411" +msgid "By audio language" +msgstr "Μέσω γλώσσας ήχου" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "Μέσω γλώσσας υποτίτλων" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "Μέσω είδους/ID υπόειδους" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "Προτίμηση της γλώσσας ήχων/υποτίτλων με κωδικό χώρας (εάν είναι διαθέσιμοι)" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "Προτίμηση των στέρεο ηχητικών κλιπ εξ'ορισμού" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "ESN / Ρυθμίσεις Widevine" + +msgctxt "#30601" +msgid "ESN:" +msgstr "ESN:" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "Αλλαγή ESN" + +msgctxt "#30603" +msgid "Save system info" +msgstr "Αποθήκευση πληροφοριών συστήματος" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "Widevine - Επιβολή επιπέδου ασφάλειας" + +msgctxt "#30605" +msgid "Force {}" +msgstr "Επιβολή {}" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "Το ESN ετέθει επιτυχώς[CR]Δοκιμάστε να αναπαράξετε ένα βίντεο" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "Το παρόν ESN δεν μπορεί να χρησιμοποιηθεί[CR]Δοκιμάστε αλλαγή του ESN ή επαναφορά του.[CR][CR]Λεπτομέρειες σφάλματος:[CR]{}" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "Λανθασμένη μορφή ESN" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "Θέλετε να επαναφέρετε όλες τις ρυθμίσεις;" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "Χρησιμοποιήστε κρυπτογράφηση βάσει συστήματος για τα διαπιστευτήρια" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "[B]Εάν απενεργοποιηθεί, θα προκαλέσει διαρροή ασφαλείας[/B]. Εάν το λειτουργικό σας σύστημα δεν το υποστηρίζει, θα σας ζητείται να συνδέεστε κάθε φορά που κάνετε επανεκκίνηση της συσκευής, [B]μόνο[/B] σε αυτήν την περίπτωση απενεργοποιήστε την. Εάν αλλάξετε αυτήν τη ρύθμιση, θα σας ζητηθεί να συνδεθείτε ξανά την επόμενη φορά που θα κάνετε επανεκκίνηση." + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "Έκδοση MSL δήλωσης" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "Αυτός ο τίτλος θα είναι διαθέσιμος από:[CR]{}" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "Θέλετε να αναπράγετε το promo trailer;" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "{} Θύμισε μου" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "Νέα και δημοφιλή" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "Επισημαίνει τηλεοπτικές εκπομπές που έχετε ξεκινήσει ως ήδη παρακολουθούμενες" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "" + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "Παράκαμψη τύπου επιλογής ροής" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "Αντικαταστήστε τη ρύθμιση τύπου επιλογής ροής του πρόσθετου InputStream Adaptive, για να ορίσετε πώς θα επιλέγεται η ποιότητα των ροών ήχου/βίντεο κατά την αναπαραγωγή." + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "Προσαρμόσιμη ποιότητα" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "Διορθώθηκε η ποιότητα βίντεο" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "Ρωτήστε την ποιότητα βίντεο" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "Λάβετε υπόψη ότι η ενεργοποίηση αυτής της ρύθμισης θα αναγκάσει την αφαίρεση των ροών που δεν βρίσκονται εντός του καθορισμένου εύρους και αυτό μπορεί να επηρεάσει τη λειτουργία του InputStream Adaptive. Εάν είναι δυνατόν, δοκιμάστε πρώτα να περιορίσετε την ποιότητα από τις ρυθμίσεις InputStream Adaptive." + +msgctxt "#30721" +msgid "Size" +msgstr "" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "" + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "" + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "" + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "" + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "" + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "" + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "" + +msgctxt "#30734" +msgid "Support" +msgstr "" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "" + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "" + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "" + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "" + +msgctxt "#30747" +msgid "Fixed" +msgstr "" + +msgctxt "#30748" +msgid "Relative" +msgstr "" + +msgctxt "#30749" +msgid "Reset" +msgstr "" diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index e821251b5..6d440339b 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -1,13 +1,13 @@ # Kodi Media Center language file # Addon Name: Netflix # Addon id: plugin.video.netflix -# Addon Provider: libdev + jojo + asciidisco + caphm + CastagnaIT +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait msgid "" msgstr "" "Project-Id-Version: plugin.video.netflix\n" "Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" "POT-Creation-Date: 2017-07-24 16:15+0000\n" -"PO-Revision-Date: 2019-07-15 20:40+0000\n" +"PO-Revision-Date: 2020-07-21 20:40+0000\n" "Last-Translator: Stefano Gottardo <>\n" "Language-Team: English\n" "MIME-Version: 1.0\n" @@ -21,11 +21,11 @@ msgid "Netflix" msgstr "" msgctxt "Addon Description" -msgid "Netflix VOD Services Addon" +msgid "Netflix VOD Services Add-on" msgstr "" msgctxt "Addon Disclaimer" -msgid "Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing." +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." msgstr "" msgctxt "#30001" @@ -33,12 +33,10 @@ msgid "Recommendations" msgstr "" msgctxt "#30002" -msgid "Adult Pin" +msgid "Enter PIN to watch restricted content" msgstr "" -msgctxt "#30003" -msgid "Search term" -msgstr "" +#. Unused 30003 msgctxt "#30004" msgid "Password" @@ -49,11 +47,11 @@ msgid "E-mail" msgstr "" msgctxt "#30006" -msgid "Adult verification failed" +msgid "Enter PIN to access this profile" msgstr "" msgctxt "#30007" -msgid "Please Check your adult pin" +msgid "Parental Control PIN (4 digits)" msgstr "" msgctxt "#30008" @@ -61,29 +59,35 @@ msgid "Login failed" msgstr "" msgctxt "#30009" -msgid "Please Check your credentials" +msgid "Please check your e-mail and password" msgstr "" msgctxt "#30010" msgid "Lists of all kinds" msgstr "" -msgctxt "#30011" -msgid "Search" -msgstr "" +#. Unused 30011 msgctxt "#30012" -msgid "No seasons available" +msgid "Profiles options and parental control" msgstr "" msgctxt "#30013" -msgid "No matches found" +msgid "In the profile list, open the context menu in the chosen profile" msgstr "" msgctxt "#30014" msgid "Account" msgstr "" +msgctxt "#30015" +msgid "Other options" +msgstr "" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "" + msgctxt "#30017" msgid "Logout" msgstr "" @@ -105,7 +109,7 @@ msgid "Add to 'My list'" msgstr "" msgctxt "#30022" -msgid "(between 0 & 10)" +msgid "(between 0 and 10)" msgstr "" msgctxt "#30023" @@ -132,80 +136,74 @@ msgctxt "#30028" msgid "Playback error" msgstr "" -msgctxt "#30029" -msgid "Missing Inputstream addon" -msgstr "" +#. Unused 30029 msgctxt "#30030" msgid "Remove from library" msgstr "" msgctxt "#30031" -msgid "Change library title" +msgid "General" msgstr "" msgctxt "#30032" -msgid "Tracking" +msgid "Codecs" msgstr "" msgctxt "#30033" -msgid "Enable Dolby Digital Plus (DDPlus HQ and Atmos on Premium)" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" msgstr "" -msgctxt "#30034" -msgid "ESN (set automatically, can be changed manually)" -msgstr "" +#. Unused 30034 msgctxt "#30035" msgid "InputStream Adaptive settings..." msgstr "" -msgctxt "#30036" -msgid "Always use the original title on export" -msgstr "" +#. Unused 30036 msgctxt "#30037" -msgid "Views" +msgid "Skin viewtypes" msgstr "" msgctxt "#30038" -msgid "Enable custom views" +msgid "Enable custom viewtypes" msgstr "" msgctxt "#30039" -msgid "View for folders" +msgid "View ID for folders" msgstr "" msgctxt "#30040" -msgid "View for movies" +msgid "View ID for movies" msgstr "" msgctxt "#30041" -msgid "View for shows" +msgid "View ID for shows" msgstr "" msgctxt "#30042" -msgid "View for seasons" +msgid "View ID for seasons" msgstr "" msgctxt "#30043" -msgid "View for episodes" +msgid "View ID for episodes" msgstr "" msgctxt "#30044" -msgid "View for profiles" +msgid "View ID for profiles" msgstr "" msgctxt "#30045" -msgid "[COLOR cyan][B]-- NEXT PAGE --[/B][/COLOR]" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" msgstr "" msgctxt "#30046" -msgid "Inputstream addon is not enabled" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." msgstr "" msgctxt "#30047" -msgid "Finally remove?" +msgid "Library update in progress" msgstr "" msgctxt "#30048" @@ -213,51 +211,42 @@ msgid "Exported" msgstr "" msgctxt "#30049" -msgid "Update DB" +msgid "Library" msgstr "" msgctxt "#30050" -msgid "Update successful" +msgid "Enable Kodi library management" msgstr "" -msgctxt "#30051" -msgid "Request Error" -msgstr "" +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30052" -msgid "Unable to complete the request at this time" -msgstr "" - -msgctxt "#30053" -msgid "Auto Login" +msgid "{} Set for library playback" msgstr "" -msgctxt "#30054" -msgid "Enable Auto Login" -msgstr "" +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30055" -msgid "Profile" +msgid "{} Set at start-up" msgstr "" -msgctxt "#30056" -msgid "ID" -msgstr "" +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30057" -msgid "Select profile in profile listing -> context menu" +msgid "{} Remember PIN" msgstr "" -msgctxt "#30058" -msgid "Auto Login enabled!" -msgstr "" +#. Unused 30058 msgctxt "#30059" -msgid "Switch accounts" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" msgstr "" msgctxt "#30060" -msgid "Enable HEVC profiles (4k for Android/HDR/DolbyVision)" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" msgstr "" msgctxt "#30061" @@ -265,23 +254,23 @@ msgid "Update inside library" msgstr "" msgctxt "#30062" -msgid "Enable/disable adult pin. Active:" +msgid "Parental controls" msgstr "" msgctxt "#30063" -msgid "new episodes added to library" +msgid "An update is already in progress" msgstr "" msgctxt "#30064" -msgid "Export new episodes" +msgid "Perform auto-update" msgstr "" msgctxt "#30065" -msgid "Auto-update" +msgid "Video library update" msgstr "" msgctxt "#30066" -msgid "never" +msgid "Enable debug logging" msgstr "" msgctxt "#30067" @@ -309,7 +298,7 @@ msgid "Only start after 5 minutes of idle" msgstr "" msgctxt "#30073" -msgid "Check every X minutes if update scheduled" +msgid "Always show codec information during video playback" msgstr "" msgctxt "#30074" @@ -341,7 +330,7 @@ msgid "Pause when skipping" msgstr "" msgctxt "#30081" -msgid "Force support of HDCP 2.2 (enable 4K content)" +msgid "Force HDCP version on video streams" msgstr "" msgctxt "#30082" @@ -349,19 +338,19 @@ msgid "Remember audio / subtitle preferences" msgstr "" msgctxt "#30083" -msgid "Save bookmarks for library items / mark as watched" +msgid "Force the display of forced subtitles only with the audio language set" msgstr "" msgctxt "#30084" -msgid "Cache item time-to-live (minutes)" +msgid "Cache objects TTL (minutes)" msgstr "" msgctxt "#30085" -msgid "Cache item time-to-live for metadata (days)" +msgid "Cache metadata TTL (days)" msgstr "" msgctxt "#30086" -msgid "Invalidate entire cache on modification of My List" +msgid "Cache objects 'My list' TTL (minutes)" msgstr "" msgctxt "#30087" @@ -397,7 +386,7 @@ msgid "Browse recommendations and pick up on something new you might like." msgstr "" msgctxt "#30095" -msgid "All TV Shows" +msgid "All TV shows" msgstr "" msgctxt "#30096" @@ -409,15 +398,15 @@ msgid "Main Menu" msgstr "" msgctxt "#30098" -msgid "Enable HDR profiles" +msgid "Enable HDR10" msgstr "" msgctxt "#30099" -msgid "Enable DolbyVision profiles" +msgid "Enable Dolby Vision" msgstr "" msgctxt "#30100" -msgid "Results for \"{}\"" +msgid "Results for '{}'" msgstr "" msgctxt "#30101" @@ -429,15 +418,15 @@ msgid "Netflix returned the following error:" msgstr "" msgctxt "#30103" -msgid "Error details have been written to the Kodi logfile. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it via GitHub. Please provide a full debug log with your error report (enable \"Debug Logging\" in Kodi settings)." +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." msgstr "" msgctxt "#30104" -msgid "The addon encountered to following error:" +msgid "The add-on encountered the following error:" msgstr "" msgctxt "#30105" -msgid "Netflix Addon Error" +msgid "Netflix Add-on Error" msgstr "" msgctxt "#30106" @@ -445,20 +434,18 @@ msgid "Invalid PIN" msgstr "" msgctxt "#30107" -msgid "Disabled PIN verfication" +msgid "PIN protection is off." msgstr "" msgctxt "#30108" -msgid "Enabled PIN verfication" +msgid "All content is protected by PIN." msgstr "" msgctxt "#30109" msgid "Login successful" msgstr "" -msgctxt "#30110" -msgid "Background services started" -msgstr "" +#. Unused 30110 msgctxt "#30111" msgid "There is no contents" @@ -472,29 +459,17 @@ msgctxt "#30113" msgid "Logout successful" msgstr "" -msgctxt "#30114" -msgid "Keep My List and Kodi Library in sync" -msgstr "" - -msgctxt "#30115" -msgid "Content Profiles" -msgstr "" +#. Unused 30114, 30115 msgctxt "#30116" -msgid "Advanced Addon Configuration" +msgid "Advanced Add-on Configuration" msgstr "" msgctxt "#30117" msgid "Cache" msgstr "" -msgctxt "#30118" -msgid "API operation failed: {}" -msgstr "" - -msgctxt "#30119" -msgid "My List operation successful" -msgstr "" +#. Unused 30118, 30119 msgctxt "#30120" msgid "Cannot automatically remove a season from Kodi library, please do it manually" @@ -505,23 +480,23 @@ msgid "Perform full sync now" msgstr "" msgctxt "#30122" -msgid "Sync My List to Kodi library" +msgid "Sync 'My list' to Kodi library" msgstr "" msgctxt "#30123" -msgid "Do you really want to proceed?\nThis will delete all previously exported items and export all items from My List for the currently active profile.\nThis operation may take some time..." +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." msgstr "" msgctxt "#30124" -msgid "Do you really want to remove this item?" +msgid "Do you want to remove this item from the library?" msgstr "" msgctxt "#30125" -msgid "Purge library" +msgid "Delete library contents" msgstr "" msgctxt "#30126" -msgid "Do you really want to purge your library?\nAll exported items will be deleted." +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." msgstr "" msgctxt "#30127" @@ -529,7 +504,7 @@ msgid "Awarded rating of {}" msgstr "" msgctxt "#30128" -msgid "No contained titles yet..." +msgid "Select a profile" msgstr "" msgctxt "#30129" @@ -537,7 +512,7 @@ msgid "Enable Up Next integration" msgstr "" msgctxt "#30130" -msgid "Show notification instead of modal dialog for errors" +msgid "Install Up Next add-on" msgstr "" msgctxt "#30131" @@ -545,11 +520,11 @@ msgid "Check the logfile for detailed information" msgstr "" msgctxt "#30132" -msgid "Purge in-memory cache" +msgid "Clear in-memory cache" msgstr "" msgctxt "#30133" -msgid "Purge in-memory and on-disk cache" +msgid "Clear in-memory and on-disk cache" msgstr "" msgctxt "#30134" @@ -561,36 +536,26 @@ msgid "Cache cleared" msgstr "" msgctxt "#30136" -msgid "Enable 1080p Unlock (not recommended for Android)" +msgid "Waiting for service start-up..." msgstr "" msgctxt "#30137" -msgid "Enable VP9 profiles (disable if it causes artifacts)" +msgid "Enable VP9 codec" msgstr "" msgctxt "#30138" -msgid "The requested action timed out. The background services may not yet be available if you just started Kodi/the addon. In this case, try again in a moment." +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." msgstr "" msgctxt "#30139" -msgid "Enable IPC over HTTP (use when experiencing AddonSignals timeouts)" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" msgstr "" msgctxt "#30140" -msgid "Migrate Library to new format" -msgstr "" - -msgctxt "#30141" -msgid "Disable startup notification" +msgid "Import existing library" msgstr "" -msgctxt "#30142" -msgid "Custom" -msgstr "" - -msgctxt "#30143" -msgid "Custom view ID" -msgstr "" +#. Unused 30141 to 30143 msgctxt "#30144" msgid "Disable WebVTT subtitle support" @@ -605,11 +570,11 @@ msgid "Discover the latest content added." msgstr "" msgctxt "#30147" -msgid "Next page >>" +msgid "Next page »" msgstr "" msgctxt "#30148" -msgid "<< Previous page" +msgid "« Previous page" msgstr "" msgctxt "#30149" @@ -633,35 +598,33 @@ msgid "Year released" msgstr "" msgctxt "#30154" -msgid "Netflix addon configuration" +msgid "Netflix configuration wizard" msgstr "" msgctxt "#30155" msgid "Do you have a Netflix Premium account?" msgstr "" -msgctxt "#30156" -msgid "Does your device support the 4K standard?\r\nThe device must:\r\nBe Netflix certified, support for Widevine Security level L1 and have a 4K display HDCP 2.2" -msgstr "" +#. Unused 30156 msgctxt "#30157" -msgid "The InputStream Adaptive settings panel will now be opened, so you have to change this option \"Override HDCP status\" to ON" +msgid "The configuration has been completed" msgstr "" msgctxt "#30158" -msgid "Show configuration screen at addon startup" +msgid "Restore the add-on default configuration" msgstr "" msgctxt "#30159" -msgid "View for main menu" +msgid "View ID for main menu" msgstr "" msgctxt "#30160" -msgid "View for search" +msgid "View ID for search" msgstr "" msgctxt "#30161" -msgid "View for exported" +msgid "View ID for exported" msgstr "" msgctxt "#30162" @@ -677,7 +640,7 @@ msgid "Browse contents with audio description." msgstr "" msgctxt "#30165" -msgid "View for my list" +msgid "View ID for 'My list'" msgstr "" msgctxt "#30166" @@ -713,7 +676,7 @@ msgid "Netflix Originals" msgstr "" msgctxt "#30174" -msgid "Tv Show genres" +msgid "TV show genres" msgstr "" msgctxt "#30175" @@ -721,7 +684,7 @@ msgid "Movie genres" msgstr "" msgctxt "#30176" -msgid "Enable STRM resume workaround (only library)" +msgid "Enable STRM resume workaround" msgstr "" msgctxt "#30177" @@ -729,19 +692,19 @@ msgid "Ask to resume the video" msgstr "" msgctxt "#30178" -msgid "Open UpNext Addon settings" +msgid "Open Up Next add-on settings" msgstr "" msgctxt "#30179" -msgid "Show the trailers" +msgid "Show trailers and more" msgstr "" msgctxt "#30180" -msgid "No trailers available" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." msgstr "" msgctxt "#30181" -msgid "Disable subtitles if there are no forced subtitles streams (Works only if Kodi Player is set to \"Only forced\" subtitles)" +msgid "Always show the subtitles when the preferred audio language is not available" msgstr "" msgctxt "#30182" @@ -765,7 +728,7 @@ msgid "For Movies" msgstr "" msgctxt "#30187" -msgid "For TV Show" +msgid "For TV show" msgstr "" msgctxt "#30188" @@ -780,16 +743,14 @@ msgctxt "#30190" msgid "TV show" msgstr "" -msgctxt "#30191" -msgid "Do you want to export NFO files for the {} added to 'My List'?" -msgstr "" +#. Unused 30191 msgctxt "#30192" -msgid "\r\nThese files are used by the provider information scrapers to integrate movies and tv shows info, useful with bonus episodes or director's cut" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" msgstr "" msgctxt "#30193" -msgid "Include all information in NFO files (use only with 'Local Information' scraper)" +msgid "Include tv show NFO details" msgstr "" msgctxt "#30194" @@ -817,7 +778,7 @@ msgid "Shared library (Kodi MySQL server is required)" msgstr "" msgctxt "#30200" -msgid "Use MySQL shared library database" +msgid "Use shared library MySQL-database" msgstr "" msgctxt "#30201" @@ -825,7 +786,7 @@ msgid "Username" msgstr "" msgctxt "#30202" -msgid "Connection to the MySQL database was successful" +msgid "Connection to the MySQL-database was successful" msgstr "" msgctxt "#30203" @@ -841,7 +802,7 @@ msgid "Test database connection" msgstr "" msgctxt "#30206" -msgid "ERROR: The MySQL database is not reachable - the local database will be used" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" msgstr "" msgctxt "#30207" @@ -853,21 +814,472 @@ msgid "Check if this device is the main auto-update manager" msgstr "" msgctxt "#30209" -msgid "This device now handles the auto-updates of shared-library" +msgid "This device now handles the auto-updates of the shared library" msgstr "" msgctxt "#30210" -msgid "This device handles the auto-updates of shared-library" +msgid "This device handles the auto-updates of the shared library" msgstr "" msgctxt "#30211" -msgid "Another device handles the auto-updates of shared-library" +msgid "Another device handles the auto-updates of the shared library" msgstr "" msgctxt "#30212" -msgid "There is no device that handles the auto-updates of shared-library" +msgid "There is no device that handles the auto-updates of the shared library" msgstr "" msgctxt "#30213" msgid "InputStream Helper settings..." msgstr "" + +msgctxt "#30214" +msgid "Force refresh" +msgstr "" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "" + +#. Unused 30217 to 30218 + +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "" + +msgctxt "#30221" +msgid "Owner account" +msgstr "" + +msgctxt "#30222" +msgid "Kid account" +msgstr "" + +#. Unused 30223 + +msgctxt "#30224" +msgid "Auto update mode" +msgstr "" + +msgctxt "#30225" +msgid "Manual" +msgstr "" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "" + +#. Unused 30234 + +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "" + +msgctxt "#30242" +msgid "Top 10" +msgstr "" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "" + +#. Unused 30244 + +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "" + +#. Unused 30248 to 30299 + +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "" + +#. Unused 30302 to 30339 + +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "" + +#. Unused 30346 to 30398 + +msgctxt "#30399" +msgid "Sort history by" +msgstr "" + +msgctxt "#30400" +msgid "Search" +msgstr "" + +msgctxt "#30401" +msgid "Type of search" +msgstr "" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "" + +msgctxt "#30403" +msgid "New search..." +msgstr "" + +msgctxt "#30404" +msgid "Clear search history" +msgstr "" + +msgctxt "#30405" +msgid "Select the language" +msgstr "" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "" + +msgctxt "#30407" +msgid "No matches found" +msgstr "" + +#. Unused 30408, 30409 + +msgctxt "#30410" +msgid "By term" +msgstr "" + +msgctxt "#30411" +msgid "By audio language" +msgstr "" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "" + +#. Unused 30414 to 30499 + +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "" + +#. Unused 30502 to 30599 + +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "" + +msgctxt "#30601" +msgid "ESN:" +msgstr "" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "" + +msgctxt "#30603" +msgid "Save system info" +msgstr "" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "" + +msgctxt "#30605" +msgid "Force {}" +msgstr "" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "" + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "" + +#. Unused 30613 to 30619 + +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "" + +#. Unused 30623 to 30699 + +msgctxt "#30700" +msgid "New and popular" +msgstr "" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "" + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "" + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "" + +msgctxt "#30721" +msgid "Size" +msgstr "" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "" + +#. Unused 30723 + +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "" + +#. Unused 30725, 30726 + +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "" + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "" + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "" + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "" + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "" + +msgctxt "#30734" +msgid "Support" +msgstr "" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "" + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "" + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "" + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "" + +msgctxt "#30747" +msgid "Fixed" +msgstr "" + +msgctxt "#30748" +msgid "Relative" +msgstr "" + +msgctxt "#30749" +msgid "Reset" +msgstr "" diff --git a/resources/language/resource.language.es_es/strings.po b/resources/language/resource.language.es_es/strings.po index d8c7ceb4e..399710924 100644 --- a/resources/language/resource.language.es_es/strings.po +++ b/resources/language/resource.language.es_es/strings.po @@ -1,89 +1,91 @@ # Kodi Media Center language file # Addon Name: Netflix # Addon id: plugin.video.netflix -# Addon Provider: libdev + jojo + asciidisco + caphm + CastagnaIT +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait msgid "" msgstr "" "Project-Id-Version: plugin.video.netflix\n" "Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" -"POT-Creation-Date: 2017-07-24 16:15+0000\n" -"PO-Revision-Date: 2019-07-08 22:15+0000\n" -"Last-Translator: danielchc\n" +"POT-Creation-Date: 2017-11-12 00:00+0000\n" +"PO-Revision-Date: 2020-04-13 18:45+0001\n" +"Last-Translator: roliverosc\n" "Language-Team: Spanish\n" +"Language: es_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 "Addon Summary" msgid "Netflix" -msgstr "" +msgstr "Netflix" msgctxt "Addon Description" -msgid "Netflix VOD Services Addon" -msgstr "Addon de servicios VBD de Netflix" +msgid "Netflix VOD Services Add-on" +msgstr "Complemento para los Servicios VOD de Netflix" msgctxt "Addon Disclaimer" -msgid "Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing." -msgstr "Algunas partes de este addon puede que sean ilegales en tu país de residencia - por favor, consulta con tus leyes locales antes de instalarlo." +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "El uso de este complemento pueden no ser legal en su país de residencia - Por favor, comprueba tu legislación local antes de instalarlo." msgctxt "#30001" msgid "Recommendations" msgstr "Recomendaciones" msgctxt "#30002" -msgid "Adult Pin" -msgstr "Pin del Control Parental" - -msgctxt "#30003" -msgid "Search term" -msgstr "Término de búsqueda" +msgid "Enter PIN to watch restricted content" +msgstr "Introduzca el PIN para ver contenido restringido" +#. Unused 30003 msgctxt "#30004" msgid "Password" msgstr "Contraseña" msgctxt "#30005" msgid "E-mail" -msgstr "" +msgstr "E-mail" msgctxt "#30006" -msgid "Adult verification failed" -msgstr "La comprobación del control parental ha fallado" +msgid "Enter PIN to access this profile" +msgstr "Introduzca el PIN para acceder a este perfil" msgctxt "#30007" -msgid "Please Check your adult pin" -msgstr "Por favor, comprueba que el pin del control parental es correcto" +msgid "Parental Control PIN (4 digits)" +msgstr "PIN de control parental (4 dígitos)" msgctxt "#30008" msgid "Login failed" msgstr "Error al iniciar sesión" msgctxt "#30009" -msgid "Please Check your credentials" -msgstr "Por favor, comprueba que el e-mail y la contraseña son correctos" +msgid "Please check your e-mail and password" +msgstr "Por favor, comprueba tu e-mail y contraseña" msgctxt "#30010" msgid "Lists of all kinds" -msgstr "Listas de todos tipos" - -msgctxt "#30011" -msgid "Search" -msgstr "Buscar" +msgstr "Listas de todos los tipos" +#. Unused 30011 msgctxt "#30012" -msgid "No seasons available" -msgstr "No hay temporadas disponibles" +msgid "Profiles options and parental control" +msgstr "Opciones perfiles y control parental" msgctxt "#30013" -msgid "No matches found" -msgstr "No se encontraron coincidencias" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "En la lista de perfiles, abra el menú contextual en el perfil elegido" msgctxt "#30014" msgid "Account" msgstr "Cuenta" +msgctxt "#30015" +msgid "Other options" +msgstr "Otras opciones" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "" + msgctxt "#30017" msgid "Logout" msgstr "Cerrar sesión" @@ -94,18 +96,18 @@ msgstr "Exportar a biblioteca" msgctxt "#30019" msgid "Rate on Netflix" -msgstr "Clasificar en Netflix" +msgstr "Valorar en Netflix" msgctxt "#30020" msgid "Remove from 'My list'" -msgstr "Quitar de 'Mi Lista'" +msgstr "Quitar de 'Mi lista'" msgctxt "#30021" msgid "Add to 'My list'" -msgstr "Añadir a 'Mi Lista'" +msgstr "Añadir a 'Mi lista'" msgctxt "#30022" -msgid "(between 0 & 10)" +msgid "(between 0 and 10)" msgstr "(entre 0 y 10)" msgctxt "#30023" @@ -122,163 +124,145 @@ msgstr "Biblioteca" msgctxt "#30026" msgid "Enable custom library folder" -msgstr "Activar un carpeta para la biblioteca personalizada" +msgstr "Habilitar carpeta para biblioteca personalizada" msgctxt "#30027" msgid "Custom library path" -msgstr "Directorio personalizado de la biblioteca" +msgstr "Ruta" msgctxt "#30028" msgid "Playback error" -msgstr "Ocurrió un error en la reproducción" - -msgctxt "#30029" -msgid "Missing InputStream addon" -msgstr "No se encuentra el addon InputStream" +msgstr "Error al reproducir" +#. Unused 30029 msgctxt "#30030" msgid "Remove from library" -msgstr "Eliminar de la biblioteca" +msgstr "Quitar de la biblioteca" msgctxt "#30031" -msgid "Change library title" -msgstr "Cambiar nombre de la biblioteca" +msgid "General" +msgstr "General" msgctxt "#30032" -msgid "Tracking" -msgstr "Reastrando" +msgid "Codecs" +msgstr "" -msgctxt "#30034" -msgid "ESN (set automatically, can be changed manually)" -msgstr "ESN (establecido automáticamente, se puede cambiar manualmente)" +msgctxt "#30033" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "" +#. Unused 30034 msgctxt "#30035" msgid "InputStream Adaptive settings..." -msgstr "Configuración del addon InputStream..." - -msgctxt "#30036" -msgid "Always use the original title on export" -msgstr "Utilizar siempre el título original para exportar" +msgstr "Configurar InputStream Adaptive..." +#. Unused 30036 msgctxt "#30037" -msgid "Views" -msgstr "Vistas" +msgid "Skin viewtypes" +msgstr "" msgctxt "#30038" -msgid "Enable custom views" -msgstr "Activar vistas personalizadas" +msgid "Enable custom viewtypes" +msgstr "" msgctxt "#30039" -msgid "View for folders" -msgstr "Vista para las carpetas" +msgid "View ID for folders" +msgstr "" msgctxt "#30040" -msgid "View for movies" -msgstr "Vista para las películas" +msgid "View ID for movies" +msgstr "" msgctxt "#30041" -msgid "View for shows" -msgstr "Vista para las series" +msgid "View ID for shows" +msgstr "" msgctxt "#30042" -msgid "View for seasons" -msgstr "Vista para las temporadas" +msgid "View ID for seasons" +msgstr "" msgctxt "#30043" -msgid "View for episodes" -msgstr "Vista para los episodios" +msgid "View ID for episodes" +msgstr "" msgctxt "#30044" -msgid "View for profiles" -msgstr "Vista para los perfiles" +msgid "View ID for profiles" +msgstr "" msgctxt "#30045" -msgid "[COLOR cyan][B]-- NEXT PAGE --[/B][/COLOR]" -msgstr "[COLOR cyan][B]-- SIGUIENTE --[/B][/COLOR]" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "Valoración eliminada|Valoraste con un pulgar abajo|Valoraste con un pulgar arriba" msgctxt "#30046" -msgid "InputStream addon is not enabled" -msgstr "El addon InputStream no está activado" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "Hay un problema con el complemento InputStream Adaptive o el módulo Widevine. Puede faltar o no estar habilitados, la operación se cancelará." msgctxt "#30047" -msgid "Finally remove?" -msgstr "¿Estás seguro de eliminar?" +msgid "Library update in progress" +msgstr "Actualización de biblioteca en progreso" msgctxt "#30048" msgid "Exported" msgstr "Exportado" msgctxt "#30049" -msgid "Update DB" -msgstr "Actualizar base de datos" +msgid "Library" +msgstr "Biblioteca" msgctxt "#30050" -msgid "Update successful" -msgstr "Actualización exitoso" - -msgctxt "#30051" -msgid "Request Error" -msgstr "Error en la solicitud" +msgid "Enable Kodi library management" +msgstr "" +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30052" -msgid "Unable to complete the request at this time" -msgstr "No se puede completar la solicitud en este momento" - -msgctxt "#30053" -msgid "Auto Login" -msgstr "Inicio de sesión automático" - -msgctxt "#30054" -msgid "Enable Auto Login" -msgstr "Activar inicio de sesión automático" +msgid "{} Set for library playback" +msgstr "{} Establecer para reproducción de biblioteca" +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30055" -msgid "Profile" -msgstr "Perfil" - -msgctxt "#30056" -msgid "ID" +msgid "{} Set at start-up" msgstr "" +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30057" -msgid "Select profile in profile listing -> context menu" -msgstr "Seleccionar perfil en la lista de perfiles -> menú contextual" - -msgctxt "#30058" -msgid "Auto Login enabled!" -msgstr "¡Inicio de sesión automático activado!" +msgid "{} Remember PIN" +msgstr "" +#. Unused 30058 msgctxt "#30059" -msgid "Switch accounts" -msgstr "Cambiar de cuenta" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "Cuidado, está a punto de actualizar muchos títulos {}.[CR]Esto puede causar problemas de servicio o prohibición temporal de la cuenta.[CR]¿Quieres continuar?" msgctxt "#30060" -msgid "Enable HEVC profiles (4k for Android/HDR/DolbyVision)" -msgstr "Habilitar perfiles HEVC (4k para Android/HDR/DolbyVision)" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "" msgctxt "#30061" msgid "Update inside library" msgstr "Actualizar dentro de la biblioteca" msgctxt "#30062" -msgid "Enable/disable adult pin. Active:" -msgstr "Habilitar /deshabilitar el pin de Control Parental. Activo:" +msgid "Parental controls" +msgstr "Controles parentales" msgctxt "#30063" -msgid "new episodes added to library" -msgstr "nuevos episodios añadidos a la biblioteca" +msgid "An update is already in progress" +msgstr "Ya hay una actualización en curso." msgctxt "#30064" -msgid "Export new episodes" -msgstr "Exportar nuevos episodios" +msgid "Perform auto-update" +msgstr "Actualizar automáticamente" msgctxt "#30065" -msgid "Auto-update" -msgstr "Actualizar automáticamente" +msgid "Video library update" +msgstr "" msgctxt "#30066" -msgid "never" -msgstr "nunca" +msgid "Enable debug logging" +msgstr "" msgctxt "#30067" msgid "daily" @@ -286,7 +270,7 @@ msgstr "diariamente" msgctxt "#30068" msgid "every other day" -msgstr "cada dos días" +msgstr "cada 2 días" msgctxt "#30069" msgid "every 5 days" @@ -302,16 +286,971 @@ msgstr "Hora del día" msgctxt "#30072" msgid "Only start after 5 minutes of idle" -msgstr "Solo empezar después de 5 minutos de inactividad" +msgstr "Iniciar sólo después de 5 minutos de inactividad" msgctxt "#30073" -msgid "Check every X minutes if update scheduled" -msgstr "Comprobar cada X minutos si la actualización está programada" +msgid "Always show codec information during video playback" +msgstr "Mostrar siempre información del códec durante la reproducción" msgctxt "#30074" msgid "View for exported" -msgstr "Ver para exportar" +msgstr "Vista para los exportados" + +msgctxt "#30075" +msgid "Ask to skip intro and recap" +msgstr "Preguntar si se omite la introducción y el resumen" + +msgctxt "#30076" +msgid "Skip intro" +msgstr "Omitir introducción" + +msgctxt "#30077" +msgid "Skip recap" +msgstr "Omitir resumen" + +msgctxt "#30078" +msgid "Playback" +msgstr "Reproducción" + +msgctxt "#30079" +msgid "Skip automatically" +msgstr "Omitir automáticamente" + +msgctxt "#30080" +msgid "Pause when skipping" +msgstr "Pausar al omitir" + +msgctxt "#30081" +msgid "Force HDCP version on video streams" +msgstr "" + +msgctxt "#30082" +msgid "Remember audio / subtitle preferences" +msgstr "Recordar preferencias de audio / subtítulos" + +msgctxt "#30083" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "Forzar la visualización de subtítulos forzados solo con el idioma de audio configurado" + +msgctxt "#30084" +msgid "Cache objects TTL (minutes)" +msgstr "TTL para objetos en caché (minutos)" + +msgctxt "#30085" +msgid "Cache metadata TTL (days)" +msgstr "TTL para metadatos en caché (días)" + +msgctxt "#30086" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "TTL para elementos de 'Mi lista' en caché (minutos)" + +msgctxt "#30087" +msgid "Contains {} and more..." +msgstr "Contiene {} y más..." + +msgctxt "#30088" +msgid "Browse related content" +msgstr "Examinar contenido relacionado" + +msgctxt "#30089" +msgid "Browse subgenres" +msgstr "Examinar subgéneros" + +msgctxt "#30090" +msgid "Browse through related video lists and discover more content." +msgstr "Navega por listas de vídeos relacionadas y descubre más contenido." + +msgctxt "#30091" +msgid "Browse and manage contents that were exported to the Kodi library." +msgstr "Examina y gestiona contenidos que se exportaron a la biblioteca de Kodi." + +msgctxt "#30092" +msgid "Search for content and easily find what you want to watch." +msgstr "Busca contenido y encuentra fácilmente lo que quieres ver." + +msgctxt "#30093" +msgid "Browse content by genre and easily discover related content." +msgstr "Examina contenido por género y descubre fácilmente contenido relacionado." + +msgctxt "#30094" +msgid "Browse recommendations and pick up on something new you might like." +msgstr "Examina recomendaciones y elige algo nuevo que podría gustarte." + +msgctxt "#30095" +msgid "All TV shows" +msgstr "Todas las series" + +msgctxt "#30096" +msgid "All Movies" +msgstr "Todas las películas" + +msgctxt "#30097" +msgid "Main Menu" +msgstr "Menú principal" + +msgctxt "#30098" +msgid "Enable HDR10" +msgstr "" + +msgctxt "#30099" +msgid "Enable Dolby Vision" +msgstr "" + +msgctxt "#30100" +msgid "Results for '{}'" +msgstr "Resultados para '{}'" + +msgctxt "#30101" +msgid "Netflix returned an unknown error." +msgstr "Netflix devolvió un error desconocido." + +msgctxt "#30102" +msgid "Netflix returned the following error:" +msgstr "Netflix devolvió el siguiente error:" + +msgctxt "#30103" +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "Los detalles del error se han escrito en el archivo de registro de Kodi. Si el mensaje de error no proporciona orientación sobre cómo resolver el problema o si continúa recibiendo este error, infórmelo en GitHub siguiendo las instrucciones del archivo Léame." + +msgctxt "#30104" +msgid "The add-on encountered the following error:" +msgstr "El complemento encontró el siguiente error:" + +msgctxt "#30105" +msgid "Netflix Add-on Error" +msgstr "Error del complemento Netflix" + +msgctxt "#30106" +msgid "Invalid PIN" +msgstr "PIN inválido" + +msgctxt "#30107" +msgid "PIN protection is off." +msgstr "La protección con PIN está desactivada." + +msgctxt "#30108" +msgid "All content is protected by PIN." +msgstr "Todo el contenido está protegido por PIN." + +msgctxt "#30109" +msgid "Login successful" +msgstr "Inicio de sesión completado" + +#. Unused 30110 +msgctxt "#30111" +msgid "There is no contents" +msgstr "No hay contenidos" + +msgctxt "#30112" +msgid "You need to be logged in to use Netflix" +msgstr "Necesitas haber iniciado sesión para usar Netflix" + +msgctxt "#30113" +msgid "Logout successful" +msgstr "Cierre de sesión completado" + +#. Unused 30114, 30115 +msgctxt "#30116" +msgid "Advanced Add-on Configuration" +msgstr "Configuración avanzada del complemento" + +msgctxt "#30117" +msgid "Cache" +msgstr "Caché" + +#. Unused 30118, 30119 +msgctxt "#30120" +msgid "Cannot automatically remove a season from Kodi library, please do it manually" +msgstr "No se puede eliminar automáticamente una temporada de la biblioteca de Kodi. Por favor, hágalo manualmente" + +msgctxt "#30121" +msgid "Perform full sync now" +msgstr "Sincronizar todo ahora" + +msgctxt "#30122" +msgid "Sync 'My list' to Kodi library" +msgstr "Sincronizar 'Mi lista' con la biblioteca de Kodi" + +msgctxt "#30123" +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "¿Quieres continuar?[CR]La sincronización completa eliminará todos los títulos exportados previamente.[CR]Si hay muchos títulos, esta operación puede llevar algún tiempo." + +msgctxt "#30124" +msgid "Do you want to remove this item from the library?" +msgstr "¿Deseas realmente eliminar este elemento?" + +msgctxt "#30125" +msgid "Delete library contents" +msgstr "Eliminar contenidos de la biblioteca" + +msgctxt "#30126" +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "¿Deseas realmente purgar tu biblioteca?[CR]Todos los elementos exportados se borrarán." + +msgctxt "#30127" +msgid "Awarded rating of {}" +msgstr "Valoración premiada de {}" + +msgctxt "#30128" +msgid "Select a profile" +msgstr "Seleccionar perfil" + +msgctxt "#30129" +msgid "Enable Up Next integration" +msgstr "Habilitar integración con Up Next" + +msgctxt "#30130" +msgid "Install Up Next add-on" +msgstr "Instalar complemento Up Next" + +msgctxt "#30131" +msgid "Check the logfile for detailed information" +msgstr "Comprueba el archivo de registro para más información" + +msgctxt "#30132" +msgid "Clear in-memory cache" +msgstr "Limpiar caché en memoria" + +msgctxt "#30133" +msgid "Clear in-memory and on-disk cache" +msgstr "Limpiar caché en memoria y en disco" + +msgctxt "#30134" +msgid "Enable execution timing" +msgstr "Habilitar tiempo de ejecución" + +msgctxt "#30135" +msgid "Cache cleared" +msgstr "Caché borrada" + +msgctxt "#30136" +msgid "Waiting for service start-up..." +msgstr "" + +msgctxt "#30137" +msgid "Enable VP9 codec" +msgstr "" + +msgctxt "#30138" +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "Los servicios en segundo plano puede que no estén todavía disponibles si acabas de iniciar Kodi o el complemento. Inténtalo de nuevo en un momento." + +msgctxt "#30139" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "Habilitar IPC sobre HTTP (usar cuando aparezcan mensajes de interrupciones en Addon Signals)" + +msgctxt "#30140" +msgid "Import existing library" +msgstr "Importar biblioteca existente" + +#. Unused 30141 to 30143 +msgctxt "#30144" +msgid "Disable WebVTT subtitle support" +msgstr "Deshabilitar soporte de subtítulos WebVTT" + +msgctxt "#30145" +msgid "Recently added" +msgstr "Añadido recientemente" + +msgctxt "#30146" +msgid "Discover the latest content added." +msgstr "Descubre el último contenido añadido." + +msgctxt "#30147" +msgid "Next page »" +msgstr "Página siguiente »" + +msgctxt "#30148" +msgid "« Previous page" +msgstr "« Página anterior" + +msgctxt "#30149" +msgid "Sort results by" +msgstr "Ordenar resultados por" + +msgctxt "#30150" +msgid "Alphabetical order (A-Z)" +msgstr "Orden alfabético (A-Z)" + +msgctxt "#30151" +msgid "Alphabetical order (Z-A)" +msgstr "Orden alfabético (Z-A)" + +msgctxt "#30152" +msgid "Suggestions for you" +msgstr "Sugerencias para ti" + +msgctxt "#30153" +msgid "Year released" +msgstr "Año de lanzamiento" + +msgctxt "#30154" +msgid "Netflix configuration wizard" +msgstr "Asistente de configuración de Netflix" + +msgctxt "#30155" +msgid "Do you have a Netflix Premium account?" +msgstr "¿Tienes una cuenta Premium de Netflix?" + +#. Unused 30156 +msgctxt "#30157" +msgid "The configuration has been completed" +msgstr "La configuración se ha completado" + +msgctxt "#30158" +msgid "Restore the add-on default configuration" +msgstr "" + +msgctxt "#30159" +msgid "View ID for main menu" +msgstr "" + +msgctxt "#30160" +msgid "View ID for search" +msgstr "" + +msgctxt "#30161" +msgid "View ID for exported" +msgstr "" + +msgctxt "#30162" +msgid "Last used" +msgstr "Último uso" + +msgctxt "#30163" +msgid "Audio description" +msgstr "Audiodescripción" + +msgctxt "#30164" +msgid "Browse contents with audio description." +msgstr "Examina los contenidos audiodescritos." + +msgctxt "#30165" +msgid "View ID for 'My list'" +msgstr "" + +msgctxt "#30166" +msgid "Main menu items" +msgstr "Elementos de menú" + +msgctxt "#30167" +msgid "My list" +msgstr "Mi lista" + +msgctxt "#30168" +msgid "Continue watching" +msgstr "Continuar viendo" + +msgctxt "#30169" +msgid "Top picks" +msgstr "Favoritos" + +msgctxt "#30170" +msgid "New releases" +msgstr "Nuevos lanzamientos" + +msgctxt "#30171" +msgid "Trending now" +msgstr "Tendencias ahora" + +msgctxt "#30172" +msgid "Popular on Netflix" +msgstr "Popular en Netflix" + +msgctxt "#30173" +msgid "Netflix Originals" +msgstr "Originales de Netflix" + +msgctxt "#30174" +msgid "TV show genres" +msgstr "Géneros de series" + +msgctxt "#30175" +msgid "Movie genres" +msgstr "Géneros de películas" + +msgctxt "#30176" +msgid "Enable STRM resume workaround" +msgstr "Habilitar alternativa para reanudar STRM" + +msgctxt "#30177" +msgid "Ask to resume the video" +msgstr "Preguntar si reanudar el vídeo" + +msgctxt "#30178" +msgid "Open Up Next add-on settings" +msgstr "Abrir los ajustes del complemento Up Next" + +msgctxt "#30179" +msgid "Show trailers and more" +msgstr "Mostrar tráilers y más" + +msgctxt "#30180" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "Ha habido un problema con el inicio de sesión, es posible que su cuenta no haya sido confirmada o reactivada." + +msgctxt "#30181" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "Mostrar siempre los subtítulos cuando el idioma de audio preferido no esté disponible" + +msgctxt "#30182" +msgid "Export NFO files" +msgstr "Exportar archivos NFO" + +msgctxt "#30183" +msgid "Do you want to export NFO files for the {}?" +msgstr "¿Deseas exportar archivos NFO para el {}?" + +msgctxt "#30184" +msgid "NFO Files" +msgstr "Archivos NFO" + +msgctxt "#30185" +msgid "Enable NFO files export" +msgstr "Habilitar exportación de archivos NFO" + +msgctxt "#30186" +msgid "For Movies" +msgstr "Para películas" + +msgctxt "#30187" +msgid "For TV show" +msgstr "Para series" + +msgctxt "#30188" +msgid "Ask" +msgstr "Preguntar" + +msgctxt "#30189" +msgid "movie" +msgstr "película" + +msgctxt "#30190" +msgid "TV show" +msgstr "serie" + +#. Unused 30191 +msgctxt "#30192" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]Estos archivos los usan los extractores de información de proveedores para integrar información de películas y series, útil con episodios extra o montajes del director" + +msgctxt "#30193" +msgid "Include tv show NFO details" +msgstr "" + +msgctxt "#30194" +msgid "Limit video stream resolution to" +msgstr "Limitar resolución de la transmisión de vídeo a" + +msgctxt "#30195" +msgid "Export new episodes" +msgstr "Exportar nuevos episodios" + +msgctxt "#30196" +msgid "Exclude from auto update" +msgstr "Excluir de actualización automática" + +msgctxt "#30197" +msgid "Include in auto update" +msgstr "Incluir en actualización automática" + +msgctxt "#30198" +msgid "Exporting new episodes" +msgstr "Exportando nuevos episodios" + +msgctxt "#30199" +msgid "Shared library (Kodi MySQL server is required)" +msgstr "Biblioteca compartida (se requiere servidor MySQL en Kodi)" + +msgctxt "#30200" +msgid "Use shared library MySQL-database" +msgstr "Usar base de datos MySQL de la biblioteca compartida" + +msgctxt "#30201" +msgid "Username" +msgstr "Nombre de usuario" + +msgctxt "#30202" +msgid "Connection to the MySQL-database was successful" +msgstr "Conexión a la base de datos MySQL completada" + +msgctxt "#30203" +msgid "Host IP" +msgstr "IP del host" + +msgctxt "#30204" +msgid "Port" +msgstr "Puerto" + +msgctxt "#30205" +msgid "Test database connection" +msgstr "Probar conexión a la base de datos" + +msgctxt "#30206" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" +msgstr "ERROR: La base de datos MySQL no es accesible - se usará la base de datos local" + +msgctxt "#30207" +msgid "Set this device as main auto-updates manager" +msgstr "Establecer este dispositivo como gestor principal de actualizaciones automáticas" + +msgctxt "#30208" +msgid "Check if this device is the main auto-update manager" +msgstr "Comprobar si este dispositivo es el gestor principal de actualizaciones automáticas" + +msgctxt "#30209" +msgid "This device now handles the auto-updates of the shared library" +msgstr "Este dispositivo maneja ahora las actualizaciones automáticas de la biblioteca compartida" + +msgctxt "#30210" +msgid "This device handles the auto-updates of the shared library" +msgstr "Este dispositivo maneja las actualizaciones automáticas de la biblioteca compartida" + +msgctxt "#30211" +msgid "Another device handles the auto-updates of the shared library" +msgstr "Otro dispositivo maneja las actualizaciones automáticas de la biblioteca compartida" + +msgctxt "#30212" +msgid "There is no device that handles the auto-updates of the shared library" +msgstr "No hay un dispositivo que maneje las actualizaciones automáticas de la biblioteca compartida" msgctxt "#30213" msgid "InputStream Helper settings..." -msgstr "Configuración del addon InputStream Helper..." +msgstr "Configurar InputStream Helper..." + +msgctxt "#30214" +msgid "Force refresh" +msgstr "Forzar actualización" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "Color para títulos incluidos en \"Mi lista\"" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "Deshabilitar notificación para sincronización completada" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "La biblioteca de Kodi se ha actualizado" + +msgctxt "#30221" +msgid "Owner account" +msgstr "Cuenta del propietario" + +msgctxt "#30222" +msgid "Kid account" +msgstr "Cuenta para niños" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "Modo de actualización automática" + +msgctxt "#30225" +msgid "Manual" +msgstr "Manual" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "Programada" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "Sincronizar la biblioteca de Kodi con 'Mi Lista'" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "Establecer un perfil para sincronización" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "Comprobar actualizaciones ahora" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "¿Deseas comprobar las actualizaciones ahora?" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "Clasificación por edades para {}" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "Mostrar solo títulos calificados como [B]{}[/B]." + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "Sincronizar el estado de visto de los vídeos con Netflix" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "Cambiar el estado de visto (localmente)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "Marcado como visto|Marcado como no visto|Estado de visto de Netflix restaurado" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Notificaciones de Up Next (ver vídeo siguiente)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "Color para información extra del argumento" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "Los servicios en segundo plano no se pueden iniciar debido a este problema:[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Open Connect CDN (el índice más bajo es mejor)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "Top 10" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "Seleccionar el primer episodio de serie TV no visto" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "Eliminando archivos exportados a la biblioteca" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "Hay {} títulos que no se pueden importar.[CR]¿Desea eliminar estos archivos?" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "Resultados por página (en caso de error de tiempo de espera, disminuir)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "¿Desea eliminar el estado visto de \"{}\"?" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "Incluir biblioteca Kodi (solo enviando datos)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "Elija el método de inicio de sesión[CR](si el inicio de sesión con correo electrónico/contraseña siempre devuelve \"contraseña incorrecta\", utilice la clave de autenticación, instrucciones en el archivo Léame de GitHub)" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "Correo electrónico/contraseña" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "Clave de autenticación" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "El archivo seleccionado no es compatible o está dañado" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "El archivo de clave de autenticación ha caducado" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "Introduzca PIN" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "Ordenar historial por" + +msgctxt "#30400" +msgid "Search" +msgstr "Buscar" + +msgctxt "#30401" +msgid "Type of search" +msgstr "Tipo de búsqueda" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "Buscar títulos, personas, géneros" + +msgctxt "#30403" +msgid "New search..." +msgstr "Nueva búsqueda..." + +msgctxt "#30404" +msgid "Clear search history" +msgstr "Limpiar historial de búsqueda" + +msgctxt "#30405" +msgid "Select the language" +msgstr "Selecciona el idioma" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "¿Quieres eliminar todo el contenido?" + +msgctxt "#30407" +msgid "No matches found" +msgstr "No se encontraron coincidencias" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "Por término" + +msgctxt "#30411" +msgid "By audio language" +msgstr "Por idioma de audio" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "Por idioma de subtítulos" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "Por ID de género/subgénero" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "Preferir el idioma de audio/subtítulos con código de país (si está disponible)" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "Preferir pistas de audio estéreo por defecto" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "Ajustes ESN / Widevine" + +msgctxt "#30601" +msgid "ESN:" +msgstr "ESN:" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "Cambiar ESN:" + +msgctxt "#30603" +msgid "Save system info" +msgstr "Guardar info del sistema" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "Widevine - Forzar nivel de seguridad" + +msgctxt "#30605" +msgid "Force {}" +msgstr "Forzar {}" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "ESN configurado correctamente[CR]Intente reproducir un vídeo" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "Este ESN no se puede utilizar[CR]Intente cambiar ESN o restablecer ESN.[CR][CR]Detalles del error:[CR]{}" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "Formato de ESN incorrecto" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "¿Quieres restablecer todos los ajustes?" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "" + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "" + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "" + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "" + +msgctxt "#30721" +msgid "Size" +msgstr "" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "" + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "" + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "" + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "" + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "" + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "" + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "" + +msgctxt "#30734" +msgid "Support" +msgstr "" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "" + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "" + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "" + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "" + +msgctxt "#30747" +msgid "Fixed" +msgstr "" + +msgctxt "#30748" +msgid "Relative" +msgstr "" + +msgctxt "#30749" +msgid "Reset" +msgstr "" diff --git a/resources/language/resource.language.fi_fi/strings.po b/resources/language/resource.language.fi_fi/strings.po deleted file mode 100644 index 9f0c02f89..000000000 --- a/resources/language/resource.language.fi_fi/strings.po +++ /dev/null @@ -1,317 +0,0 @@ -# Kodi Media Center language file -# Addon Name: Netflix -# Addon id: plugin.video.netflix -# Addon Provider: libdev + jojo + asciidisco + caphm + CastagnaIT -msgid "" -msgstr "" -"Project-Id-Version: plugin.video.netflix\n" -"Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" -"POT-Creation-Date: 2017-07-24 16:15+0000\n" -"PO-Revision-Date: 2019-07-08 22:15+0000\n" -"Last-Translator: Sebastian Golasch \n" -"Language-Team: Finnish\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: fi\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgctxt "Addon Summary" -msgid "Netflix" -msgstr "" - -msgctxt "Addon Description" -msgid "Netflix VOD Services Addon" -msgstr "Netflix-suoratoistopalvelun lisäosa" - -msgctxt "Addon Disclaimer" -msgid "Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing." -msgstr "Jotkin tämän lisäosan ominaisuuksista eivät välttämättä ole laillisia kotimaassasi - tarkista käytön laillisuus ennen asentamista." - -msgctxt "#30001" -msgid "Recommendations" -msgstr "Suositukset" - -msgctxt "#30002" -msgid "Adult Pin" -msgstr "Lapsilukon PIN-koodi" - -msgctxt "#30003" -msgid "Search term" -msgstr "Hakusana" - -msgctxt "#30004" -msgid "Password" -msgstr "Salasana" - -msgctxt "#30005" -msgid "E-mail" -msgstr "Sähköpostiosoite" - -msgctxt "#30006" -msgid "Adult verification failed" -msgstr "Lapsilukon poistaminen epäonnistui" - -msgctxt "#30007" -msgid "Please Check your adult pin" -msgstr "Tarkista PIN-koodisi" - -msgctxt "#30008" -msgid "Login failed" -msgstr "Kirjautuminen epäonnistui" - -msgctxt "#30009" -msgid "Please Check your credentials" -msgstr "Tarkista käyttäjätietosi" - -msgctxt "#30010" -msgid "Lists of all kinds" -msgstr "" - -msgctxt "#30011" -msgid "Search" -msgstr "Haku" - -msgctxt "#30012" -msgid "No seasons available" -msgstr "Ei katsottavissa olevia kausia" - -msgctxt "#30013" -msgid "No matches found" -msgstr "Hakusi ei tuottanut tuloksia" - -msgctxt "#30014" -msgid "Account" -msgstr "Käyttäjätili" - -msgctxt "#30017" -msgid "Logout" -msgstr "Kirjaudu ulos" - -msgctxt "#30018" -msgid "Export to library" -msgstr "Vie kirjastoon" - -msgctxt "#30019" -msgid "Rate on Netflix" -msgstr "Anna arvosana" - -msgctxt "#30020" -msgid "Remove from 'My list'" -msgstr "Poista 'Omalta listalta'" - -msgctxt "#30021" -msgid "Add to 'My list'" -msgstr "Lisää 'Omalle listalle'" - -msgctxt "#30022" -msgid "(between 0 & 10)" -msgstr "(väliltä 0-10)" - -msgctxt "#30023" -msgid "Expert" -msgstr "Asiantuntija" - -msgctxt "#30024" -msgid "SSL verification" -msgstr "SSL-varmennus" - -msgctxt "#30025" -msgid "Library" -msgstr "Kirjasto" - -msgctxt "#30026" -msgid "Enable custom library folder" -msgstr "Käytä omaa kirjastokansiota" - -msgctxt "#30027" -msgid "Custom library path" -msgstr "Kirjastokansion polku" - -msgctxt "#30028" -msgid "Playback error" -msgstr "Toistovirhe" - -msgctxt "#30029" -msgid "Missing InputStream addon" -msgstr "InputStream-lisäosa puuttuu" - -msgctxt "#30030" -msgid "Remove from library" -msgstr "Poista kirjastosta" - -msgctxt "#30031" -msgid "Change library title" -msgstr "Muokkaa kirjastonimeä" - -msgctxt "#30032" -msgid "Tracking" -msgstr "Seuranta" - -msgctxt "#30034" -msgid "ESN (set automatically, can be changed manually)" -msgstr "ESN (luotu automaattisesti, käyttäjän muokattavissa)" - -msgctxt "#30035" -msgid "InputStream Adaptive settings..." -msgstr "InputStream-lisäosan asetukset..." - -msgctxt "#30036" -msgid "Always use the original title on export" -msgstr "Käytä aina alkuperäisiä nimiä" - -msgctxt "#30037" -msgid "Views" -msgstr "Näkymät" - -msgctxt "#30038" -msgid "Enable custom views" -msgstr "Käytä omia näkymiä" - -msgctxt "#30039" -msgid "View for folders" -msgstr "Kansioiden näkymä" - -msgctxt "#30040" -msgid "View for movies" -msgstr "Elokuvien näkymä" - -msgctxt "#30041" -msgid "View for shows" -msgstr "Tv-sarjojen näkymä" - -msgctxt "#30042" -msgid "View for seasons" -msgstr "Kausien näkymä" - -msgctxt "#30043" -msgid "View for episodes" -msgstr "Jaksojen näkymä" - -msgctxt "#30044" -msgid "View for profiles" -msgstr "Profiilien näkymä" - -msgctxt "#30045" -msgid "[COLOR cyan][B]-- NEXT PAGE --[/B][/COLOR]" -msgstr "[COLOR cyan][B]-- SEURAAVA SIVU --[/B][/COLOR]" - -msgctxt "#30046" -msgid "InputStream addon is not enabled" -msgstr "InputStream-lisäosa ei ole käytössä" - -msgctxt "#30047" -msgid "Finally remove?" -msgstr "Poistetaanko kirjastosta?" - -msgctxt "#30048" -msgid "Exported" -msgstr "Kirjastoon viedyt" - -msgctxt "#30049" -msgid "Update DB" -msgstr "Päivitä tietokanta" - -msgctxt "#30050" -msgid "Update successful" -msgstr "Päivitys onnistui" - -msgctxt "#30051" -msgid "Request Error" -msgstr "Virhe pyynnössä" - -msgctxt "#30052" -msgid "Unable to complete the request at this time" -msgstr "Pyyntöä ei voida suorittaa tällä hetkellä" - -msgctxt "#30053" -msgid "Auto Login" -msgstr "Automaattinen kirjautuminen" - -msgctxt "#30054" -msgid "Enable Auto Login" -msgstr "Kirjaudu automaattisesti" - -msgctxt "#30055" -msgid "Profile" -msgstr "Käyttäjäprofiili" - -msgctxt "#30056" -msgid "ID" -msgstr "ID-numero" - -msgctxt "#30057" -msgid "Select profile in profile listing -> context menu" -msgstr "Valitse 'Automaattinen kirjautuminen' profiililistan sisältövalikosta" - -msgctxt "#30058" -msgid "Auto Login enabled!" -msgstr "Automaattinen kirjautuminen käytössä!" - -msgctxt "#30059" -msgid "Switch accounts" -msgstr "Vaihda käyttäjätiliä" - -msgctxt "#30060" -msgid "Enable HEVC profiles (4k for Android/HDR/DolbyVision)" -msgstr "Käytä HEVC-pakkausta (4K-laadun Androidilla/HDR/DolbyVision)" - -msgctxt "#30061" -msgid "Update inside library" -msgstr "Päivitä kirjastossa" - -msgctxt "#30062" -msgid "Enable/disable adult pin. Active:" -msgstr "Lapsilukon PIN-koodin määritys. Tila:" - -msgctxt "#30063" -msgid "new episodes added to library" -msgstr "uutta jaksoa lisätty kirjastoon" - -msgctxt "#30064" -msgid "Export new episodes" -msgstr "Vie uudet jaksot" - -msgctxt "#30065" -msgid "Auto-update" -msgstr "Automaattinen päivitys" - -msgctxt "#30066" -msgid "never" -msgstr "ei koskaan" - -msgctxt "#30067" -msgid "daily" -msgstr "päivittäin" - -msgctxt "#30068" -msgid "every other day" -msgstr "joka toinen päivä" - -msgctxt "#30069" -msgid "every 5 days" -msgstr "joka viides päivä" - -msgctxt "#30070" -msgid "weekly" -msgstr "viikoittain" - -msgctxt "#30071" -msgid "Time of Day" -msgstr "Kellonaika" - -msgctxt "#30072" -msgid "Only start after 5 minutes of idle" -msgstr "Aloita, kun laite on ollut 5 minuuttia käyttämättä" - -msgctxt "#30073" -msgid "Check every X minutes if update scheduled" -msgstr "Tarkista X minuutin välein, onko päivitystä ajastettu" - -msgctxt "#30074" -msgid "View for exported" -msgstr "Kirjastoon lisättyjen näkymä" - -msgctxt "#30213" -msgid "InputStream Helper settings..." -msgstr "" diff --git a/resources/language/resource.language.fr_fr/strings.po b/resources/language/resource.language.fr_fr/strings.po index 64b6e5a51..c8d5c90e4 100644 --- a/resources/language/resource.language.fr_fr/strings.po +++ b/resources/language/resource.language.fr_fr/strings.po @@ -1,89 +1,91 @@ # Kodi Media Center language file # Addon Name: Netflix # Addon id: plugin.video.netflix -# Addon Provider: libdev + jojo + asciidisco + caphm + CastagnaIT +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait msgid "" msgstr "" "Project-Id-Version: plugin.video.netflix\n" "Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" "POT-Creation-Date: 2017-11-23 16:15+0000\n" -"PO-Revision-Date: 2019-08-17 02:45+0000\n" -"Last-Translator: Smeulf \n" +"PO-Revision-Date: 2021-02-21 20:00+0000\n" +"Last-Translator: thombet\n" "Language-Team: Français\n" +"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 "Addon Summary" msgid "Netflix" -msgstr "" +msgstr "Netflix" msgctxt "Addon Description" -msgid "Netflix VOD Services Addon" +msgid "Netflix VOD Services Add-on" msgstr "Extension Netflix SVoD" msgctxt "Addon Disclaimer" -msgid "Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing." -msgstr "Certaines parties de cette extension peuvent ne pas être légales dans votre pays de résidence - renseignez-vous avant de l'installer" +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "L'utilisation de cette extension n'est peut-être pas légale dans votre pays de résidence - renseignez-vous avant de l'installer" msgctxt "#30001" msgid "Recommendations" msgstr "Recommandations" msgctxt "#30002" -msgid "Adult Pin" -msgstr "Code PIN adulte" - -msgctxt "#30003" -msgid "Search term" -msgstr "Terme à rechercher" +msgid "Enter PIN to watch restricted content" +msgstr "Saisir le code pour accéder à certaines catégories d'âge" +#. Unused 30003 msgctxt "#30004" msgid "Password" msgstr "Mot de passe" msgctxt "#30005" msgid "E-mail" -msgstr "Mail" +msgstr "Adresse mail" msgctxt "#30006" -msgid "Adult verification failed" -msgstr "Échec de la vérification adulte" +msgid "Enter PIN to access this profile" +msgstr "Saisir le code pour accéder à ce profil" msgctxt "#30007" -msgid "Please Check your adult pin" -msgstr "Vérifiez votre code PIN adulte" +msgid "Parental Control PIN (4 digits)" +msgstr "Code du contrôle parental (4 chiffres)" msgctxt "#30008" msgid "Login failed" msgstr "Échec de connexion" msgctxt "#30009" -msgid "Please Check your credentials" +msgid "Please check your e-mail and password" msgstr "Vérifiez vos identifiants" msgctxt "#30010" msgid "Lists of all kinds" msgstr "Listes de toutes sortes" -msgctxt "#30011" -msgid "Search" -msgstr "Recherche" - +#. Unused 30011 msgctxt "#30012" -msgid "No seasons available" -msgstr "Pas de saison disponible" +msgid "Profiles options and parental control" +msgstr "Options des profils et contrôle parental" msgctxt "#30013" -msgid "No matches found" -msgstr "Pas de résultat" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "Dans la liste des profils, ouvrir le menu contextuel pour le profil choisi" msgctxt "#30014" msgid "Account" msgstr "Compte" +msgctxt "#30015" +msgid "Other options" +msgstr "Options supplémentaires" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "" + msgctxt "#30017" msgid "Logout" msgstr "Déconnexion" @@ -105,7 +107,7 @@ msgid "Add to 'My list'" msgstr "Ajouter à 'Ma Liste'" msgctxt "#30022" -msgid "(between 0 & 10)" +msgid "(between 0 and 10)" msgstr "(entre 0 et 10)" msgctxt "#30023" @@ -122,175 +124,153 @@ msgstr "Médiathèque" msgctxt "#30026" msgid "Enable custom library folder" -msgstr "Choisir un dossier personnalisé pour la médiathèque" +msgstr "Utiliser un dossier personnalisé pour la médiathèque" msgctxt "#30027" msgid "Custom library path" -msgstr "Chemin de votre médiathèque" +msgstr "Chemin du dossier personnalisé" msgctxt "#30028" msgid "Playback error" msgstr "Échec de lecture" -msgctxt "#30029" -msgid "Missing InputStream addon" -msgstr "Extension InputStream manquante" - +#. Unused 30029 msgctxt "#30030" msgid "Remove from library" msgstr "Supprimer de la médiathèque" msgctxt "#30031" -msgid "Change library title" -msgstr "Changer le titre dans la médiathèque" +msgid "General" +msgstr "Général" msgctxt "#30032" -msgid "Tracking" -msgstr "Traceur" +msgid "Codecs" +msgstr "" msgctxt "#30033" -msgid "Enable Dolby Digital Plus (DDPlus HQ and Atmos on Premium)" -msgstr "Activer Dolby Digital Plus (DDPlus HQ et Atmos sur Premium)" - -msgctxt "#30034" -msgid "ESN (set automatically, can be changed manually)" -msgstr "ESN (généré automatiquement, peut être changé manuellement)" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "" +#. Unused 30034 msgctxt "#30035" msgid "InputStream Adaptive settings..." -msgstr "Paramètres de l'extension InputStream" - -msgctxt "#30036" -msgid "Always use the original title on export" -msgstr "Toujours utiliser le titre original lors de l'export" +msgstr "Paramètres de l'extension InputStream Adaptive..." +#. Unused 30036 msgctxt "#30037" -msgid "Views" -msgstr "Vues" +msgid "Skin viewtypes" +msgstr "Type de vue de l'habillage" msgctxt "#30038" -msgid "Enable custom views" -msgstr "Activer les vues personnalisées" +msgid "Enable custom viewtypes" +msgstr "Activer les types de vue personnalisés" msgctxt "#30039" -msgid "View for folders" -msgstr "Vue pour les dossiers" +msgid "View ID for folders" +msgstr "Identifiant de la vue pour les dossiers" msgctxt "#30040" -msgid "View for movies" -msgstr "Vue pour les films" +msgid "View ID for movies" +msgstr "Identifiant de la vue pour les films" msgctxt "#30041" -msgid "View for shows" -msgstr "Vue pour les séries" +msgid "View ID for shows" +msgstr "Identifiant de la vue pour les séries" msgctxt "#30042" -msgid "View for seasons" -msgstr "Vue pour les saisons" +msgid "View ID for seasons" +msgstr "Identifiant de la vue pour les saisons" msgctxt "#30043" -msgid "View for episodes" -msgstr "Vue pour les épisodes" +msgid "View ID for episodes" +msgstr "Identifiant de la vue pour les épisodes" msgctxt "#30044" -msgid "View for profiles" -msgstr "Vue pour les profils" +msgid "View ID for profiles" +msgstr "Identifiant de la vue pour les profils" msgctxt "#30045" -msgid "[COLOR cyan][B]-- NEXT PAGE --[/B][/COLOR]" -msgstr "[COLOR cyan][B]-- PAGE SUIVANTE --[/B][/COLOR]" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "Évaluation supprimée|'Pouce baissé' enregistré|'Pouce levé' enregistré" msgctxt "#30046" -msgid "InputStream addon is not enabled" -msgstr "L'extension InputStream n'est pas activée" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "Il y a un problème avec l'extension InputStrean Adaptive ou avec la bibliothèque Widevine. Impossible de les trouver ou ils sont désactivés, l'opération va être annulée." msgctxt "#30047" -msgid "Finally remove?" -msgstr "Supprimer définitivement ?" +msgid "Library update in progress" +msgstr "Mise à jour de la médiathèque en cours" msgctxt "#30048" msgid "Exported" -msgstr "Exporté" +msgstr "Contenu exporté" msgctxt "#30049" -msgid "Update DB" -msgstr "Mettre à jour la base de donnée" +msgid "Library" +msgstr "Médiathèque" msgctxt "#30050" -msgid "Update successful" -msgstr "Mise à jour réussie" - -msgctxt "#30051" -msgid "Request Error" -msgstr "La requête a échoué" +msgid "Enable Kodi library management" +msgstr "Activer la gestion de la médiathèque Kodi" +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30052" -msgid "Unable to complete the request at this time" -msgstr "Impossible de finir la requête" - -msgctxt "#30053" -msgid "Auto Login" -msgstr "Connexion automatique" - -msgctxt "#30054" -msgid "Enable Auto Login" -msgstr "Activer la connexion automatique" +msgid "{} Set for library playback" +msgstr "{} Utiliser lors de la lecture depuis la médiathèque" +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30055" -msgid "Profile" -msgstr "Profil" - -msgctxt "#30056" -msgid "ID" -msgstr "ID" +msgid "{} Set at start-up" +msgstr "{} Utiliser au démarrage" +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30057" -msgid "Select profile in profile listing -> context menu" -msgstr "Selectionnez le profil dans la liste des profils -> menu contextuel" - -msgctxt "#30058" -msgid "Auto Login enabled!" -msgstr "Connexion automatique activée !" +msgid "{} Remember PIN" +msgstr "{} Se souvenir du code" +#. Unused 30058 msgctxt "#30059" -msgid "Switch accounts" -msgstr "Changer de compte" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "Attention, vous allez mettre à jour plusieurs titres {}.[CR]Cela pourrait entrainer des problèmes d'utilisation ou le blocage temporaire du compte.[CR]Voulez-vous continuer ?" msgctxt "#30060" -msgid "Enable HEVC profiles (4k for Android/HDR/DolbyVision)" -msgstr "Activer les profils HEVC (4k sur Android/HDR/DolbyVision)" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "" msgctxt "#30061" msgid "Update inside library" -msgstr "Mettre à jour la médiathèque" +msgstr "Mettre à jour dans la médiathèque" msgctxt "#30062" -msgid "Enable/disable adult pin. Active:" -msgstr "Activer/Désactiver le code PIN adulte. Actif : " +msgid "Parental controls" +msgstr "Contrôle parental" msgctxt "#30063" -msgid "new episodes added to library" -msgstr "Nouveaux épisodes ajoutés à la médiathèque" +msgid "An update is already in progress" +msgstr "Une mise à jour est déjà en cours" msgctxt "#30064" -msgid "Export new episodes" -msgstr "Exporter les nouveaux épisodes" +msgid "Perform auto-update" +msgstr "Faire une mise à jour" msgctxt "#30065" -msgid "Auto-update" -msgstr "Mise à jour automatique" +msgid "Video library update" +msgstr "Mise à jour de la médiathèque vidéo" msgctxt "#30066" -msgid "never" -msgstr "jamais" +msgid "Enable debug logging" +msgstr "Activer la journalisation des informations de débogage" msgctxt "#30067" msgid "daily" -msgstr "quotidien" +msgstr "tous les jours" msgctxt "#30068" msgid "every other day" -msgstr "tous les autres jours" +msgstr "un jour sur deux" msgctxt "#30069" msgid "every 5 days" @@ -298,19 +278,19 @@ msgstr "tous les 5 jours" msgctxt "#30070" msgid "weekly" -msgstr "hebdomadaire" +msgstr "chaque semaine" msgctxt "#30071" msgid "Time of Day" -msgstr "Heure du jour" +msgstr "Heure de la mise à jour" msgctxt "#30072" msgid "Only start after 5 minutes of idle" -msgstr "Démarrer après 5 minutes d'inactivité" +msgstr "Démarrer uniquement après 5 minutes d'inactivité" msgctxt "#30073" -msgid "Check every X minutes if update scheduled" -msgstr "Vérifier toutes les X minutes si mise à jour programmée" +msgid "Always show codec information during video playback" +msgstr "Toujours afficher les infos des codecs pendant la lecture" msgctxt "#30074" msgid "View for exported" @@ -318,7 +298,7 @@ msgstr "Vue pour les exports" msgctxt "#30075" msgid "Ask to skip intro and recap" -msgstr "Demander pour passer l'intro et le résumé" +msgstr "Demander à passer l'intro et le récap" msgctxt "#30076" msgid "Skip intro" @@ -341,28 +321,28 @@ msgid "Pause when skipping" msgstr "Mettre en pause après avoir passé" msgctxt "#30081" -msgid "Force support of HDCP 2.2 (enable 4K content)" -msgstr "Forcer l'utilisation du HDCP 2.2 (permet l'accès au contenu 4K)" +msgid "Force HDCP version on video streams" +msgstr "" msgctxt "#30082" msgid "Remember audio / subtitle preferences" msgstr "Se souvenir des préférences audio / sous-titres" msgctxt "#30083" -msgid "Save bookmarks for library items / mark as watched" -msgstr "Sauvegarder les points de reprise dans la médiathèque / marquer comme vu" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "Afficher les sous-titres forcés uniquement dans la langue audio sélectionnée" msgctxt "#30084" -msgid "Cache item time-to-live (minutes)" -msgstr "Durée de vie du cache des données (minutes)" +msgid "Cache objects TTL (minutes)" +msgstr "Durée de vie du cache de données (minutes)" msgctxt "#30085" -msgid "Cache item time-to-live for metadata (days)" -msgstr "Durée de vie du cache des métadonnées (jours)" +msgid "Cache metadata TTL (days)" +msgstr "Durée de vie du cache de métadonnées (jours)" msgctxt "#30086" -msgid "Invalidate entire cache on modification of My List" -msgstr "Invalider le cache lors d'une modification de 'Ma Liste'" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "Durée de vie du cache pour 'Ma Liste' (minutes)" msgctxt "#30087" msgid "Contains {} and more..." @@ -370,7 +350,7 @@ msgstr "Contient {} et plus encore..." msgctxt "#30088" msgid "Browse related content" -msgstr "Parcourir le contenu similaire" +msgstr "Parcourez les programmes associés" msgctxt "#30089" msgid "Browse subgenres" @@ -378,26 +358,26 @@ msgstr "Parcourir les sous-genres" msgctxt "#30090" msgid "Browse through related video lists and discover more content." -msgstr "Parcourir les listes de vidéos similaires et découvrez plus de contenu." +msgstr "Parcourez les vidéos associées et découvrez plus de contenus." msgctxt "#30091" msgid "Browse and manage contents that were exported to the Kodi library." -msgstr "Parcourir et gérer les contenus exportés vers la médiathèque Kodi." +msgstr "Parcourez et gérez les contenus exportés vers la médiathèque Kodi." msgctxt "#30092" msgid "Search for content and easily find what you want to watch." -msgstr "Rechercher du contenu et trouver facilement ce que vous voulez regarder." +msgstr "Recherchez des titres et trouvez facilement ce que vous voulez regarder." msgctxt "#30093" msgid "Browse content by genre and easily discover related content." -msgstr "Parcourir le contenu par genre et découvrir facilement le contenu similaire." +msgstr "Parcourez les programmes par genre et découvrez facilement des contenus similaires." msgctxt "#30094" msgid "Browse recommendations and pick up on something new you might like." -msgstr "Parcourir les recommandations et trouver une nouveauté qui pourrait vous plaire." +msgstr "Parcourez les recommandations et trouvez quelque chose qui pourrait vous plaire." msgctxt "#30095" -msgid "All TV Shows" +msgid "All TV shows" msgstr "Toutes les séries" msgctxt "#30096" @@ -409,16 +389,16 @@ msgid "Main Menu" msgstr "Menu principal" msgctxt "#30098" -msgid "Enable HDR profiles" -msgstr "Activer les profiles HDR" +msgid "Enable HDR10" +msgstr "" msgctxt "#30099" -msgid "Enable DolbyVision profiles" -msgstr "Activer les profils DoblyVision" +msgid "Enable Dolby Vision" +msgstr "" msgctxt "#30100" -msgid "Results for \"{}\"" -msgstr "Résultats pour \"{}\"" +msgid "Results for '{}'" +msgstr "Résultats pour '{}'" msgctxt "#30101" msgid "Netflix returned an unknown error." @@ -429,188 +409,160 @@ msgid "Netflix returned the following error:" msgstr "Netflix a renvoyé l'erreur suivante : " msgctxt "#30103" -msgid "Error details have been written to the Kodi logfile. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it via GitHub. Please provide a full debug log with your error report (enable \"Debug Logging\" in Kodi settings)." -msgstr "Les détails de l'erreur ont été écrits dans le fichier journal de Kodi. Si le message d'erreur ne vous indique pas comment résoudre le problème ou si vous continuez à recevoir cette erreur, signalez-le via GitHub. Veuillez fournir un journal de débogage complet avec votre rapport d'erreur (activez \"Journalisation du débogage\" dans les paramètres Kodi)." +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "Les détails de l'erreur ont été écrits dans le fichier journal de Kodi. Si le message de l'erreur ne vous indique pas comment résoudre le problème ou si vous continuez à recevoir cette erreur, signalez-le sur GitHub en suivant les instructions du Readme." msgctxt "#30104" -msgid "The addon encountered to following error:" +msgid "The add-on encountered the following error:" msgstr "L'extension a rencontré l'erreur suivante : " msgctxt "#30105" -msgid "Netflix Addon Error" +msgid "Netflix Add-on Error" msgstr "Erreur de l'extension Netflix" msgctxt "#30106" msgid "Invalid PIN" -msgstr "Code PIN invalide" +msgstr "Code invalide" msgctxt "#30107" -msgid "Disabled PIN verfication" -msgstr "Vérification du code PIN désactivée" +msgid "PIN protection is off." +msgstr "La protection par code est désactivée." msgctxt "#30108" -msgid "Enabled PIN verfication" -msgstr "Vérification du code PIN activée" +msgid "All content is protected by PIN." +msgstr "Tout le contenu est protégé par code." msgctxt "#30109" msgid "Login successful" -msgstr "Authentification réussie" - -msgctxt "#30110" -msgid "Background services started" -msgstr "Services d'arrière-plan démarrés" +msgstr "Connexion réussie" +#. Unused 30110 msgctxt "#30111" msgid "There is no contents" msgstr "Aucun contenu" msgctxt "#30112" msgid "You need to be logged in to use Netflix" -msgstr "Vous devez être connecté pour utiliser Netflix" +msgstr "Vous devez être connecté(e) pour utiliser Netflix" msgctxt "#30113" msgid "Logout successful" msgstr "Déconnexion réussie" -msgctxt "#30114" -msgid "Keep My List and Kodi Library in sync" -msgstr "Maintenir 'Ma Liste' et la médiathèque synchronisées" - -msgctxt "#30115" -msgid "Content Profiles" -msgstr "Profils de contenu" - +#. Unused 30114, 30115 msgctxt "#30116" -msgid "Advanced Addon Configuration" +msgid "Advanced Add-on Configuration" msgstr "Configuration avancée de l'extension" msgctxt "#30117" msgid "Cache" msgstr "Cache" -msgctxt "#30118" -msgid "API operation failed: {}" -msgstr "Echec de l'opération d'API : {}" - -msgctxt "#30119" -msgid "My List operation successful" -msgstr "Opération sur 'Ma Liste' réussie" - +#. Unused 30118, 30119 msgctxt "#30120" msgid "Cannot automatically remove a season from Kodi library, please do it manually" -msgstr "Impossible de supprimer automatiquemet une saison de la médiathèque Kodi. Merci de le faire manuellement." +msgstr "Impossible de supprimer automatiquement une saison de la médiathèque Kodi. Merci de le faire manuellement." msgctxt "#30121" msgid "Perform full sync now" -msgstr "Effectuer une synchronisation complète maintenant" +msgstr "Faire une synchronisation complète maintenant" msgctxt "#30122" -msgid "Sync My List to Kodi library" +msgid "Sync 'My list' to Kodi library" msgstr "Synchroniser 'Ma Liste' vers la médiathèque Kodi" msgctxt "#30123" -msgid "Do you really want to proceed?\nThis will delete all previously exported items and export all items from My List for the currently active profile.\nThis operation may take some time..." -msgstr "Voulez-vous vraiment continuer ?\nCeci supprimera tous les éléments précédemment exportés et exportera tous les éléments de 'Ma Liste' pour le profil actif.\nCette opération peut prendre un certain temps..." +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "Voulez-vous continuer ?[CR]La synchronisation complète va supprimer tous les titres précédemment exportés.[CR]S'il y a beaucoup de titres cette opération peut prendre du temps." msgctxt "#30124" -msgid "Do you really want to remove this item?" -msgstr "Voulez-vous vraiment supprimer cet élement ?" +msgid "Do you want to remove this item from the library?" +msgstr "Voulez-vous supprimer cet élement de la médiathèque ?" msgctxt "#30125" -msgid "Purge library" +msgid "Delete library contents" msgstr "Purger la médiathèque" msgctxt "#30126" -msgid "Do you really want to purge your library?\nAll exported items will be deleted." -msgstr "Voulez-vous vraiment purger la médiathèque ?\n Tous les éléments exportés seront supprimés." +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "Voulez-vous vraiment purger la médiathèque ?[CR]Tous les éléments exportés seront supprimés." msgctxt "#30127" msgid "Awarded rating of {}" msgstr "Note attribuée : {}" msgctxt "#30128" -msgid "No contained titles yet..." -msgstr "Pas encore de titres..." +msgid "Select a profile" +msgstr "Choisissez un profil" msgctxt "#30129" msgid "Enable Up Next integration" -msgstr "Activer l'intégration 'Up Next'" +msgstr "Activer l'intégration avec l'extension 'Up Next'" msgctxt "#30130" -msgid "Show notification instead of modal dialog for errors" -msgstr "Afficher une notification au lieu d'une fenêtre modale pour les erreurs" +msgid "Install Up Next add-on" +msgstr "Installer l'extension 'Up Next'" msgctxt "#30131" msgid "Check the logfile for detailed information" -msgstr "Vérifier le fichier de log pour les informations détaillées" +msgstr "Consultez le fichier de journalisaiton pour obtenir des informations détaillées" msgctxt "#30132" -msgid "Purge in-memory cache" +msgid "Clear in-memory cache" msgstr "Purger le cache en mémoire" msgctxt "#30133" -msgid "Purge in-memory and on-disk cache" -msgstr "Purger le cache en mémoire et sur le disque" +msgid "Clear in-memory and on-disk cache" +msgstr "Purger le cache en mémoire et le cache sur le disque" msgctxt "#30134" msgid "Enable execution timing" -msgstr "Activer le temps d'excution" +msgstr "Activer l'horodotage de l'exécution" msgctxt "#30135" msgid "Cache cleared" -msgstr "Cache supprimé" +msgstr "Cache purgé" msgctxt "#30136" -msgid "Enable 1080p Unlock (not recommended for Android)" -msgstr "Activer le déblocage 1080p (non recommandé pour Android)" +msgid "Waiting for service start-up..." +msgstr "" msgctxt "#30137" -msgid "Enable VP9 profiles (disable if it causes artifacts)" -msgstr "Activer les profils VP9 (à désactiver en cas d'artefacts)" +msgid "Enable VP9 codec" +msgstr "" msgctxt "#30138" -msgid "The requested action timed out. The background services may not yet be available if you just started Kodi/the addon. In this case, try again in a moment." -msgstr "L'action demandée a expiré. Les services d'arrière-plan peuvent ne pas être encore disponible si vous venez de démarrer Kodi ou l'extension. Dans ce cas, réessayez dans un instant." +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "Les services d'arrière-plan peuvent ne pas être encore disponible si vous venez de démarrer Kodi ou l'extension. Dans ce cas, réessayez dans un instant." msgctxt "#30139" -msgid "Enable IPC over HTTP (use when experiencing AddonSignals timeouts)" -msgstr "Activer IPC sur HTTP (à activer en cas erreurs 'AddonSignals timeouts')" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "Activer IPC via HTTP (à activer si l'extension 'Addon Signals' retourne des erreurs de timeout)" msgctxt "#30140" -msgid "Migrate Library to new format" -msgstr "Convertir la médiathèque au nouveau format" - -msgctxt "#30141" -msgid "Disable startup notification" -msgstr "Désactiver la notification au démarrage" - -msgctxt "#30142" -msgid "Custom" -msgstr "Personalisé" - -msgctxt "#30143" -msgid "Custom view ID" -msgstr "ID de la vue personalisée" +msgid "Import existing library" +msgstr "Importer la médiathèque existante" +#. Unused 30141 to 30143 msgctxt "#30144" msgid "Disable WebVTT subtitle support" msgstr "Désactiver la prise en charge des sous-titres WebVTT" msgctxt "#30145" msgid "Recently added" -msgstr "Récemment ajouté" +msgstr "Récemment ajoutés" msgctxt "#30146" msgid "Discover the latest content added." -msgstr "Découvrir les derniers contenus ajoutés" +msgstr "Découvrez les derniers programmes ajoutés." msgctxt "#30147" -msgid "Next page >>" -msgstr "Page suivante >>" +msgid "Next page »" +msgstr "Page suivante »" msgctxt "#30148" -msgid "<< Previous page" -msgstr "<< Page précédente" +msgid "« Previous page" +msgstr "« Page précédente" msgctxt "#30149" msgid "Sort results by" @@ -633,40 +585,37 @@ msgid "Year released" msgstr "Année de sortie" msgctxt "#30154" -msgid "Netflix addon configuration" -msgstr "Configuration de l'extension Netflix" +msgid "Netflix configuration wizard" +msgstr "Assistant de configuration Netflix" msgctxt "#30155" msgid "Do you have a Netflix Premium account?" msgstr "Avez-vous un compte Netflix Premium ?" -msgctxt "#30156" -msgid "Does your device support the 4K standard?\r\nThe device must:\r\nBe Netflix certified, support for Widevine Security level L1 and have a 4K display HDCP 2.2" -msgstr "Votre appareil prend-il en charge la norme 4K ?\r\nL'appareil doit :\r\nÊtre certifié Netflix, prendre en charge le niveau de sécurité Widevine L1 et disposer d’un écran 4K HDCP 2.2." - +#. Unused 30156 msgctxt "#30157" -msgid "The InputStream Adaptive settings panel will now be opened, so you have to change this option \"Override HDCP status\" to ON" -msgstr "Le panneau de configuration de InputStream Adaptive va maintenant s'ouvrir. Vous devez activer l'option \"Override HDCP status\"." +msgid "The configuration has been completed" +msgstr "Configuration terminée" msgctxt "#30158" -msgid "Show configuration screen at addon startup" -msgstr "Afficher l'écran de configuration au démarrage de l'extension" +msgid "Restore the add-on default configuration" +msgstr "" msgctxt "#30159" -msgid "View for main menu" -msgstr "Vue pour le menu principal" +msgid "View ID for main menu" +msgstr "Identifiant de vue pour le menu principal" msgctxt "#30160" -msgid "View for search" -msgstr "Vue pour la recherche" +msgid "View ID for search" +msgstr "Identifiant de vue pour la recherche" msgctxt "#30161" -msgid "View for exported" -msgstr "Vue pour l'export" +msgid "View ID for exported" +msgstr "Identifiant de vue pour le contenu exporté" msgctxt "#30162" msgid "Last used" -msgstr "Dernière utilisation" +msgstr "Dernière utilisée" msgctxt "#30163" msgid "Audio description" @@ -674,11 +623,11 @@ msgstr "Audiodescription" msgctxt "#30164" msgid "Browse contents with audio description." -msgstr "Parcourir le contenu avec Audiodescription" +msgstr "Parcourez les programmes disponibles en audiodescription." msgctxt "#30165" -msgid "View for my list" -msgstr "Vue pour 'Ma Liste'" +msgid "View ID for 'My list'" +msgstr "Identifiant de vue pour 'Ma Liste'" msgctxt "#30166" msgid "Main menu items" @@ -694,7 +643,7 @@ msgstr "Reprendre avec le profil de" msgctxt "#30169" msgid "Top picks" -msgstr "Notre sélection pour" +msgstr "Recommandés pour vous" msgctxt "#30170" msgid "New releases" @@ -706,43 +655,43 @@ msgstr "Tendances actuelles" msgctxt "#30172" msgid "Popular on Netflix" -msgstr "Les plus gros succès Netflix" +msgstr "Les plus gros succès sur Netflix" msgctxt "#30173" msgid "Netflix Originals" -msgstr "Programmes Originaux Netflix" +msgstr "Programmes originaux Netflix" msgctxt "#30174" -msgid "Tv Show genres" -msgstr "Genres de séries TV" +msgid "TV show genres" +msgstr "Séries par genre" msgctxt "#30175" msgid "Movie genres" -msgstr "Genres de films" +msgstr "Films par genre" msgctxt "#30176" -msgid "Enable STRM resume workaround (only library)" -msgstr "Activer la reprise des fichiers STRM (bibliothèque uniquement)" +msgid "Enable STRM resume workaround" +msgstr "Activer la solution de rechange pour la reprise STRM" msgctxt "#30177" msgid "Ask to resume the video" -msgstr "Demander à reprendre la vidéo" +msgstr "Proposer de reprendre la lecture de la video" msgctxt "#30178" -msgid "Open UpNext Addon settings" -msgstr "Ouvrir les paramètres de l'addon 'UpNext'" +msgid "Open Up Next add-on settings" +msgstr "Ouvrir les paramètres de l'extension 'Up Next'" msgctxt "#30179" -msgid "Show the trailers" -msgstr "Montrer les bandes annonces" +msgid "Show trailers and more" +msgstr "Bandes annonces et plus" msgctxt "#30180" -msgid "No trailers available" -msgstr "Pas de bande annonce" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "Il y a eu un problème de connexion, peut-être que votre compte n'a pas été confirmé ou réactivé." msgctxt "#30181" -msgid "Disable subtitles if there are no forced subtitles streams (Works only if Kodi Player is set to \"Only forced\" subtitles)" -msgstr "Désactiver les sous-titres s'il n'y a pas de piste forcée (Fonctionne seulement si Kodi est configuré sur \"Forcé seulement\" pour les sous-titres" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "Toujours afficher les sous-titres quand la langue audio préférée n'est pas disponible" msgctxt "#30182" msgid "Export NFO files" @@ -758,15 +707,15 @@ msgstr "Fichiers NFO" msgctxt "#30185" msgid "Enable NFO files export" -msgstr "Activer l'export des fichiers NFO" +msgstr "Activer l'exportation des fichiers NFO" msgctxt "#30186" msgid "For Movies" -msgstr "Pour les Films" +msgstr "Pour les films" msgctxt "#30187" -msgid "For TV Show" -msgstr "Pour les Séries Télé" +msgid "For TV show" +msgstr "Pour les séries" msgctxt "#30188" msgid "Ask" @@ -778,23 +727,20 @@ msgstr "le film" msgctxt "#30190" msgid "TV show" -msgstr "la série télé" - -msgctxt "#30191" -msgid "Do you want to export NFO files for the {} added to 'My List'?" -msgstr "Voulez-vous exporter les fichiers NFO pour {} ajouté à 'Ma Liste' ?" +msgstr "la série" +#. Unused 30191 msgctxt "#30192" -msgid "\r\nThese files are used by the provider information scrapers to integrate movies and tv shows info, useful with bonus episodes or director's cut" -msgstr "\r\nCes fichiers sont utilisés par le fournisseur d'information pour intégrer les infos des films et des séries télé. Utile pour des épisodes bonus ou des découpages spéciaux" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]Ces fichiers sont utilisés par les fournisseurs d'informations pour intégrer les infos des films et des séries, ils sont utiles pour les épisodes bonus ou les versions intégrales" msgctxt "#30193" -msgid "Include all information in NFO files (use only with 'Local Information' scraper)" -msgstr "Inclure toutes les informations dans les fichiers NFO (utiliser uniquement avec le fournisseur d'information 'Local Information only')" +msgid "Include tv show NFO details" +msgstr "Inclure les détails NFO de la série" msgctxt "#30194" msgid "Limit video stream resolution to" -msgstr "Limiter la résolution du flux vidéo à" +msgstr "Limiter la résolution des flux vidéos à" msgctxt "#30195" msgid "Export new episodes" @@ -802,30 +748,30 @@ msgstr "Exporter les nouveaux épisodes" msgctxt "#30196" msgid "Exclude from auto update" -msgstr "Exclure de la maj auto" +msgstr "Exclure de la mise à jour automatique" msgctxt "#30197" msgid "Include in auto update" -msgstr "Inclure dans la maj auto" +msgstr "Inclure dans la mise à jour automatique" msgctxt "#30198" msgid "Exporting new episodes" -msgstr "Exportation des nouveaux épisodes" +msgstr "Exportation des nouveaux épisodes en cours" msgctxt "#30199" msgid "Shared library (Kodi MySQL server is required)" -msgstr "Bibliothèque partagée (Serveur de base de données MySQL Kodi requis)" +msgstr "Médiathèque partagée (un serveur MySQL Kodi est nécessaire)" msgctxt "#30200" -msgid "Use MySQL shared library database" -msgstr "Utiliser la bibliothèque partagée MySQL" +msgid "Use shared library MySQL-database" +msgstr "Utiliser la médiathèque partagée MySQL" msgctxt "#30201" msgid "Username" msgstr "Nom d'utilisateur" msgctxt "#30202" -msgid "Connection to the MySQL database was successful" +msgid "Connection to the MySQL-database was successful" msgstr "Connexion à la base de données MySQL réussie" msgctxt "#30203" @@ -841,33 +787,470 @@ msgid "Test database connection" msgstr "Tester la connexion à la base de données" msgctxt "#30206" -msgid "ERROR: The MySQL database is not reachable - the local database will be used" -msgstr "ERREUR : Le serveur de bases de données MySQL est injoiagnable - la base de données locale sera utilisée" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" +msgstr "ERREUR : La base de données MySQL est injoignable - la base de données locale sera utilisée" msgctxt "#30207" msgid "Set this device as main auto-updates manager" -msgstr "Rendre cet ordinateur responsable de la mise à jour automatique" +msgstr "Rendre cet appareil responsable de la mise à jour automatique" msgctxt "#30208" msgid "Check if this device is the main auto-update manager" -msgstr "Vérifier si cet ordinateur est le responsable de la mise à jour automatique" +msgstr "Vérifier si cet appareil est responsable de la mise à jour automatique" msgctxt "#30209" -msgid "This device now handles the auto-updates of shared-library" -msgstr "Cet ordinateur est maintenant responsable de la mise à jour automatique de la bibliothèque partagée" +msgid "This device now handles the auto-updates of the shared library" +msgstr "Cet appareil gère maintenant les mises à jour automatiques de la médiathèque partagée" msgctxt "#30210" -msgid "This device handles the auto-updates of shared-library" -msgstr "Cet ordinateur est responsable de la mise à jour automatique de la bibliothèque partagée" +msgid "This device handles the auto-updates of the shared library" +msgstr "Cet appareil gère les mises à jour automatiques de la médiathèque partagée" msgctxt "#30211" -msgid "Another device handles the auto-updates of shared-library" -msgstr "Un autre ordinateur est responsable de la mise à jour de la bibliothèque partagée" +msgid "Another device handles the auto-updates of the shared library" +msgstr "Un autre appareil gère les mises à jour automatiques de la médiathèque partagée" msgctxt "#30212" -msgid "There is no device that handles the auto-updates of shared-library" -msgstr "Aucun ordinateur n'est responsable de la mise à jour automatique de la bibliothèque partagée" +msgid "There is no device that handles the auto-updates of the shared library" +msgstr "Aucun appareil ne gère les mises à jour automatiques de la médiathèque partagée" msgctxt "#30213" msgid "InputStream Helper settings..." msgstr "Paramètres de l'extension InputStream Helper..." + +msgctxt "#30214" +msgid "Force refresh" +msgstr "Forcer la mise à jour" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "Couleur des titres déjà dans \"Ma Liste\"" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "Couleur des titres possédant un rappel" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "Désactiver la notification de fin de synchronisation" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "La médiathèque Kodi a été mise à jour" + +msgctxt "#30221" +msgid "Owner account" +msgstr "Compte principal" + +msgctxt "#30222" +msgid "Kid account" +msgstr "Compte enfant" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "Mode de mise à jour" + +msgctxt "#30225" +msgid "Manual" +msgstr "Manuel" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "Programmé" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "Synchronisation de la médiathèque Kodi avec 'Ma Liste'" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "Choisir le profil pour la synchronisation" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "Quand Kodi démarre" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "Vérifier la présence de mises à jour maintenant" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "Voulez-vous vérifier la présence de mises à jour maintenant?" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "Catégories d'âge du profil {}" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "Afficher uniquement les titres classés [B]{}[/B]." + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "Synchroniser l'état 'vu' des vidéos avec Netflix" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "Changer l'état 'vu' (localement)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "Marqué comme vu|Marquer comme non vu|Restaurer l'état 'vu' depuis Netflix" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Notifications 'Up Next' (regarder la vidéo suivante)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "Couleur des info supplémentaires dans le résumé" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "Les services d'arrière-plan n'ont pas pu être démarrés à cause de ce problème :[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Open Connect CDN (préférez les valeurs basses)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "Top 10" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "Sélectionner le premier épisode non vu" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "Suppression des fichers exportés dans la médiathèque" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "{} titres n'ont pas pu être importés.[CR]Voulez-vous supprimer ces fichiers ?" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "Nombre de résultats par page (en cas d'erreur réduisez cette valeur)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "Voulez-vous supprimer l'état 'vu' de \"{}\" ?" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "Inclure la médiathèque Kodi (envoi de données seulement)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "Choisir la méthode de connexion[CR](si la connexion avec E-mail/Mot de passe affiche \"Mot de passe incorrect\", utilisez Clé d'authentification, plus d'infos sur GitHub dans le ReadMe)" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "E-mail/Mot de passe" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "Clé d'authentification" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "Le fichier sélectionné est incompatible ou corrompu" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "La clé d'authentification a expiré" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "Entrez le code" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "Trier l´historique par" + +msgctxt "#30400" +msgid "Search" +msgstr "Recherche" + +msgctxt "#30401" +msgid "Type of search" +msgstr "Type de recherche" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "Rechercher des titres, des personnes, des genres" + +msgctxt "#30403" +msgid "New search..." +msgstr "Nouvelle recherche..." + +msgctxt "#30404" +msgid "Clear search history" +msgstr "Supprimer l'historique de recherche" + +msgctxt "#30405" +msgid "Select the language" +msgstr "Sélectionner la langue" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "Voulez-vous supprimer l'historique complet ?" + +msgctxt "#30407" +msgid "No matches found" +msgstr "Aucun résultat" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "Par mot-clé" + +msgctxt "#30411" +msgid "By audio language" +msgstr "Par langue audio" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "Par langue de sous-titres" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "Par identifiant de genre/sous-genre" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "Préférer les langues audio/sous-titres avec code pays (si disponible)" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "Préférer les pistes audio stéréo par défaut" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "Paramètres ESN / Widevine" + +msgctxt "#30601" +msgid "ESN:" +msgstr "ESN :" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "Changer l'ESN" + +msgctxt "#30603" +msgid "Save system info" +msgstr "Sauvegarder les informations système" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "Widevine - Forcer le niveau de sécurité" + +msgctxt "#30605" +msgid "Force {}" +msgstr "Forcer {}" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "ESN défini avec succès[CR]Essayez de lire une vidéo" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "Cet ESN ne peut pas être utilisé[CR]Essayez de changer ou de réinitialiser l'ESN.[CR][CR]Détails de l'erreur:[CR]{}" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "Mauvais format d'ESN" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "Voulez-vous réinitialiser tous les paramètres ?" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "Utiliser le cryptage système pour les identifiants" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "[B]Désactiver ce paramètre causera une faille de sécurité[/B]. Si votre système d'exploitation n'est pas compatible, il vous sera demandé de vous connecter à chaque fois que vous redémarrerez votre machine, désactivez le [B]uniquement[/B] dans ce cas. Si vous changez ce paramètre, il vous sera demandé de vous reconnecter au prochain redémarrage." + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "Version du manifeste MSL" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "Le titre sera disponible à partir de :[CR]{}" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "Voulez-vous voir la bande-annonce ?" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "{} Ajouter un rappel" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "Nouveautés les plus regardées" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "Marquer les séries démarrées comme vues" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "En activant ce paramètre si des filtres sont activés dans les paramètres de votre thème, les élements marqués comme vus peuvent ne pas être visibles." + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "Forcer le type de sélection du flux " + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "Forcer le paramètre de type de sélection du flux de l'extension InputStream Adaptive, pour configurer comment la qualité audio / vidéo des flux sera choisie pendant la lecture." + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "Qualité adaptable" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "Qualité des vidéos fixe" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "Demander la qualité des vidéos" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "En activant ce paramètre les flux qui ne sont pas dans la plage définie vont être supprimés ce qui peut affecter le fonctionnement d'InputStream Adaptive. Si possible, essayez d'abord de limiter la qualité depuis les paramètres d'InputStream Adaptive." + +msgctxt "#30721" +msgid "Size" +msgstr "" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "" + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "" + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "Ajouter les dossier Netflix aux sources de la médiathèque Kodi" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "Les dossiers \"Netflix-Films\" and \"Netflix-Séries\" ont été ajoutés aux sources Kodi, redémarrez Kodi pour les voir dans les menus [Séries] / [Films]. Puis modifier les dossiers pour configurer le type de contenu et le fournisseur d'informations." + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "[Manuel] mises à jour manuelles en utilisant \"Exporter les nouveaux épisodes\" dans le menu contextuel sur chaque série, [Programmé] vous pouvez programmer les mises à jour à certaines heures, [Quand Kodi démarre] mises à jour automatiques au démarrage de Kodi et une seule fois par jour." + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "Synchronise la médiathèque avec \"Ma Liste\", si \"Ma Liste\" a été modifiée en dehors de Kodi (par ex appli mobile ou site internet), la mise à jour sera décalée à l'heure planifiée." + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "Si activé, ajoute des informations aux épisodes et films ce qui est utile quand les fournisseurs d'informations n'ont pas de données. Ajoutera aussi la durée des vidéos." + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "A activer qu'avec le fournisseur d'informations \"Local information only\", ou si certains titres ne s'affichent pas dans la médiathèque après leur ajout." + +msgctxt "#30734" +msgid "Support" +msgstr "" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "" + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "" + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "" + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "" + +msgctxt "#30747" +msgid "Fixed" +msgstr "" + +msgctxt "#30748" +msgid "Relative" +msgstr "" + +msgctxt "#30749" +msgid "Reset" +msgstr "" diff --git a/resources/language/resource.language.gl_es/strings.po b/resources/language/resource.language.gl_es/strings.po new file mode 100644 index 000000000..feba09242 --- /dev/null +++ b/resources/language/resource.language.gl_es/strings.po @@ -0,0 +1,1258 @@ +# Kodi Media Center language file +# Addon Name: Netflix +# Addon id: plugin.video.netflix +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait +msgid "" +msgstr "" +"Project-Id-Version: plugin.video.netflix\n" +"Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" +"POT-Creation-Date: 2020-10-22 11:24+0200\n" +"PO-Revision-Date: 2023-05-07 11:20+0200\n" +"Last-Translator: Ignacio Hernández Rial \n" +"Language-Team: Galego\n" +"Language: gl_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" +"X-Poedit-SourceCharset: UTF-8\n" + +msgctxt "Addon Summary" +msgid "Netflix" +msgstr "Netflix" + +msgctxt "Addon Description" +msgid "Netflix VOD Services Add-on" +msgstr "Netflix, un servizo de Vídeo Baixo Demanda" + +msgctxt "Addon Disclaimer" +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "O uso deste complemento pode non ser legal no seu país de residencia - por favor, verifique as súas leis locais antes de instalar." + +msgctxt "#30001" +msgid "Recommendations" +msgstr "Recomendacións" + +msgctxt "#30002" +msgid "Enter PIN to watch restricted content" +msgstr "Insira o PIN para ver o contido restrinxido" + +#. Unused 30003 +msgctxt "#30004" +msgid "Password" +msgstr "Contrasinal" + +msgctxt "#30005" +msgid "E-mail" +msgstr "Correo electrónico" + +msgctxt "#30006" +msgid "Enter PIN to access this profile" +msgstr "Insira o PIN para acceder a este perfil" + +msgctxt "#30007" +msgid "Parental Control PIN (4 digits)" +msgstr "PIN de Control Parental (4 díxitos)" + +msgctxt "#30008" +msgid "Login failed" +msgstr "Erro ao iniciar sesión" + +msgctxt "#30009" +msgid "Please check your e-mail and password" +msgstr "Por favor, revise as súas credenciais de acceso" + +msgctxt "#30010" +msgid "Lists of all kinds" +msgstr "Listar todos os tipos" + +#. Unused 30011 +msgctxt "#30012" +msgid "Profiles options and parental control" +msgstr "Opcións de perfís e control parental" + +msgctxt "#30013" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "Na lista de perfís, abre o menú contextual no perfil escollido" + +msgctxt "#30014" +msgid "Account" +msgstr "Conta" + +msgctxt "#30015" +msgid "Other options" +msgstr "Outras opcións" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "Se pechas sesión se borrará toda a información da túa conta; só se conservarán os arquivos de vídeo exportados na biblioteca do Kodi.[CR]Queres continuar?" + +msgctxt "#30017" +msgid "Logout" +msgstr "Rematar sesión" + +msgctxt "#30018" +msgid "Export to library" +msgstr "Exportar para a biblioteca" + +msgctxt "#30019" +msgid "Rate on Netflix" +msgstr "Valorar o Netflix" + +msgctxt "#30020" +msgid "Remove from 'My list'" +msgstr "Eliminar da 'Miña Lista'" + +msgctxt "#30021" +msgid "Add to 'My list'" +msgstr "Engadir a 'Miña Lista'" + +msgctxt "#30022" +msgid "(between 0 and 10)" +msgstr "(Entre 0 e 10)" + +msgctxt "#30023" +msgid "Expert" +msgstr "Experto" + +msgctxt "#30024" +msgid "SSL verification" +msgstr "Verificación SSL" + +msgctxt "#30025" +msgid "Library" +msgstr "Biblioteca" + +msgctxt "#30026" +msgid "Enable custom library folder" +msgstr "Activar o cartafol de biblioteca personalizada" + +msgctxt "#30027" +msgid "Custom library path" +msgstr "Ruta da biblioteca personalizada" + +msgctxt "#30028" +msgid "Playback error" +msgstr "Erro de reprodución" + +#. Unused 30029 +msgctxt "#30030" +msgid "Remove from library" +msgstr "Eliminar da biblioteca" + +msgctxt "#30031" +msgid "General" +msgstr "Xeral" + +msgctxt "#30032" +msgid "Codecs" +msgstr "Codecs" + +msgctxt "#30033" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "Activar o codec Dolby Digital Plus (Atmos na conta Premium)" + +#. Unused 30034 +msgctxt "#30035" +msgid "InputStream Adaptive settings..." +msgstr "Axustes do complemento InputStream..." + +#. Unused 30036 +msgctxt "#30037" +msgid "Skin viewtypes" +msgstr "Aspecto para modos de vista" + +msgctxt "#30038" +msgid "Enable custom viewtypes" +msgstr "Activar modos de vista personalizados" + +msgctxt "#30039" +msgid "View ID for folders" +msgstr "ID de vista para cartafoles" + +msgctxt "#30040" +msgid "View ID for movies" +msgstr "ID de vista para filmes" + +msgctxt "#30041" +msgid "View ID for shows" +msgstr "ID de vista para series TV" + +msgctxt "#30042" +msgid "View ID for seasons" +msgstr "ID de vista para tempadas" + +msgctxt "#30043" +msgid "View ID for episodes" +msgstr "ID de vista para episodios" + +msgctxt "#30044" +msgid "View ID for profiles" +msgstr "ID de vista para perfiles" + +msgctxt "#30045" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "Avaliación eliminada|Avaliou negativamente|Avaliou positivamente" + +msgctxt "#30046" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "Hai un problema co complemento InputStream Adaptive ou a biblioteca Widevine. Pode non estar dispoñíbel ou non habilitado; a operación será cancelada." + +msgctxt "#30047" +msgid "Library update in progress" +msgstr "Actualización da biblioteca en progreso" + +msgctxt "#30048" +msgid "Exported" +msgstr "Exportado" + +msgctxt "#30049" +msgid "Library" +msgstr "Biblioteca" + +msgctxt "#30050" +msgid "Enable Kodi library management" +msgstr "Activar a xestión da biblioteca do Kodi" + +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30052" +msgid "{} Set for library playback" +msgstr "{} Definir para reproducir dende a biblioteca" + +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30055" +msgid "{} Set at start-up" +msgstr "{} definido para o arrinque" + +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30057" +msgid "{} Remember PIN" +msgstr "{} Lembrar PIN" + +#. Unused 30058 +msgctxt "#30059" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "Coidado, está a piques de actualizar moitos títulos {}.[CR]Isto pode causar problemas do servizo ou bloquear temporalmente a conta.[CR]Desexa continuar?" + +msgctxt "#30060" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "Activar codec HEVC (4k/HDR/DolbyVision para Android)" + +msgctxt "#30061" +msgid "Update inside library" +msgstr "Actualizar dentro da biblioteca" + +msgctxt "#30062" +msgid "Parental controls" +msgstr "Controles Parentais" + +msgctxt "#30063" +msgid "An update is already in progress" +msgstr "Xa hai unha actualización en curso" + +msgctxt "#30064" +msgid "Perform auto-update" +msgstr "Realizar actualización automática" + +msgctxt "#30065" +msgid "Video library update" +msgstr "Actualización da biblioteca de vídeo" + +msgctxt "#30066" +msgid "Enable debug logging" +msgstr "Activar rexistro de depuración" + +msgctxt "#30067" +msgid "daily" +msgstr "diariamente" + +msgctxt "#30068" +msgid "every other day" +msgstr "días alternos" + +msgctxt "#30069" +msgid "every 5 days" +msgstr "cada 5 días" + +msgctxt "#30070" +msgid "weekly" +msgstr "semanalmente" + +msgctxt "#30071" +msgid "Time of Day" +msgstr "Hora do día" + +msgctxt "#30072" +msgid "Only start after 5 minutes of idle" +msgstr "Comezar após permanecer inactivo durante 5 minutos" + +msgctxt "#30073" +msgid "Always show codec information during video playback" +msgstr "Mostrar sempre a información do codec durante a reprodución de vídeo" + +msgctxt "#30074" +msgid "View for exported" +msgstr "Vista para exportados" + +msgctxt "#30075" +msgid "Ask to skip intro and recap" +msgstr "Perguntar para omitir a intro e recapitulación" + +msgctxt "#30076" +msgid "Skip intro" +msgstr "Omitir intro" + +msgctxt "#30077" +msgid "Skip recap" +msgstr "Omitir recapitulación" + +msgctxt "#30078" +msgid "Playback" +msgstr "Reprodución" + +msgctxt "#30079" +msgid "Skip automatically" +msgstr "Omitr automaticamente" + +msgctxt "#30080" +msgid "Pause when skipping" +msgstr "Pausar ao omitir" + +msgctxt "#30081" +msgid "Force HDCP version on video streams" +msgstr "Forzar a versión HDCP nos fluxos de vídeo" + +msgctxt "#30082" +msgid "Remember audio / subtitle preferences" +msgstr "Lembrar preferencias de son / subtítulos" + +msgctxt "#30083" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "Forzar a visualización de subtítulos forzados soamente coa lingua do audio configurada" + +msgctxt "#30084" +msgid "Cache objects TTL (minutes)" +msgstr "Tempo de vida útil dos elementos na caché (minutos)" + +msgctxt "#30085" +msgid "Cache metadata TTL (days)" +msgstr "Tempo de vida útil dos metadados (días)" + +msgctxt "#30086" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "Tempo de vida útil dos elementos na cache de ‘Miña Lista’ (minutos)" + +msgctxt "#30087" +msgid "Contains {} and more..." +msgstr "Contén {} e mais..." + +msgctxt "#30088" +msgid "Browse related content" +msgstr "Navegue por contidos relacionados" + +msgctxt "#30089" +msgid "Browse subgenres" +msgstr "Navegue por subxéneros" + +msgctxt "#30090" +msgid "Browse through related video lists and discover more content." +msgstr "Navegue polas listas de vídeos relacionados e descubra máis contido." + +msgctxt "#30091" +msgid "Browse and manage contents that were exported to the Kodi library." +msgstr "Navegue e xestione os contidos que foron exportados na biblioteca do Kodi." + +msgctxt "#30092" +msgid "Search for content and easily find what you want to watch." +msgstr "Procure e atope doadamente o que desexa ver." + +msgctxt "#30093" +msgid "Browse content by genre and easily discover related content." +msgstr "Navegue polo contido por xénero e descubra doadamente contido relacionado." + +msgctxt "#30094" +msgid "Browse recommendations and pick up on something new you might like." +msgstr "Navegue polas recomendacións e escolla algo novo que lle poida gustar." + +msgctxt "#30095" +msgid "All TV shows" +msgstr "Todas as series TV" + +msgctxt "#30096" +msgid "All Movies" +msgstr "Todos os Filmes" + +msgctxt "#30097" +msgid "Main Menu" +msgstr "Menú Principal" + +msgctxt "#30098" +msgid "Enable HDR10" +msgstr "Activar HDR10" + +msgctxt "#30099" +msgid "Enable Dolby Vision" +msgstr "Activar Dolby Vision" + +msgctxt "#30100" +msgid "Results for '{}'" +msgstr "Resultados para '{}'" + +msgctxt "#30101" +msgid "Netflix returned an unknown error." +msgstr "Netflix devolveu un erro descoñecido." + +msgctxt "#30102" +msgid "Netflix returned the following error:" +msgstr "Netflix devolveu o seguinte erro:" + +msgctxt "#30103" +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "Os detalles do erro escribíronse no arquivo de log do Kodi. Se a mensaxe de erro non fornecer axuda sobre como resolver o problema ou continúa recibindo este erro, notifíqueo no GitHub seguindo as instrucións do Leme." + +msgctxt "#30104" +msgid "The add-on encountered the following error:" +msgstr "O complemento atopou o seguinte erro:" + +msgctxt "#30105" +msgid "Netflix Add-on Error" +msgstr "Erro no complemento do Netflix" + +msgctxt "#30106" +msgid "Invalid PIN" +msgstr "PIN Inválido" + +msgctxt "#30107" +msgid "PIN protection is off." +msgstr "A protección por PIN está desactivada." + +msgctxt "#30108" +msgid "All content is protected by PIN." +msgstr "Todo o contido está protexido por PIN." + +msgctxt "#30109" +msgid "Login successful" +msgstr "Inicio de sesión correcto" + +#. Unused 30110 +msgctxt "#30111" +msgid "There is no contents" +msgstr "Sen contidos" + +msgctxt "#30112" +msgid "You need to be logged in to use Netflix" +msgstr "Precisa iniciar sesión para usar Netflix" + +msgctxt "#30113" +msgid "Logout successful" +msgstr "Sesión finalizada correctamente" + +#. Unused 30114, 30115 +msgctxt "#30116" +msgid "Advanced Add-on Configuration" +msgstr "Axustes avanzados" + +msgctxt "#30117" +msgid "Cache" +msgstr "Caché" + +#. Unused 30118, 30119 +msgctxt "#30120" +msgid "Cannot automatically remove a season from Kodi library, please do it manually" +msgstr "Non é posíbel eliminar automaticamente unha tempada da biblioteca do Kodi, por favor fágao manualmente" + +msgctxt "#30121" +msgid "Perform full sync now" +msgstr "Realizar unha sincronización completa agora" + +msgctxt "#30122" +msgid "Sync 'My list' to Kodi library" +msgstr "Sincronizar ‘Miña lista’ coa biblioteca do Kodi" + +msgctxt "#30123" +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "Desexa continuar?[CR]A sincronización completa borrará todos os títulos exportados anteriormente.[CR]Se hai moitos títulos, esta operación poderá demorar algún tempo." + +msgctxt "#30124" +msgid "Do you want to remove this item from the library?" +msgstr "Desexa realmente eliminar este elemento da biblioteca?" + +msgctxt "#30125" +msgid "Delete library contents" +msgstr "Borrar contidos da biblioteca" + +msgctxt "#30126" +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "Realmente desexa limpar sua biblioteca?[CR]Borraranse todos os elementos exportados." + +msgctxt "#30127" +msgid "Awarded rating of {}" +msgstr "Nota atribuída de {}" + +msgctxt "#30128" +msgid "Select a profile" +msgstr "Seleccionar un perfil" + +msgctxt "#30129" +msgid "Enable Up Next integration" +msgstr "Activar integración 'Up Next'" + +msgctxt "#30130" +msgid "Install Up Next add-on" +msgstr "Instalar o complemento 'Up Next'" + +msgctxt "#30131" +msgid "Check the logfile for detailed information" +msgstr "Comprobe o arquivo de rexistro para información detallada" + +msgctxt "#30132" +msgid "Clear in-memory cache" +msgstr "Limpar caché da memória" + +msgctxt "#30133" +msgid "Clear in-memory and on-disk cache" +msgstr "Limpar caché da memória e do disco" + +msgctxt "#30134" +msgid "Enable execution timing" +msgstr "Activar tempo de execución" + +msgctxt "#30135" +msgid "Cache cleared" +msgstr "Caché limpa" + +msgctxt "#30136" +msgid "Waiting for service start-up..." +msgstr "Agardando ao arrinque do servizo..." + +msgctxt "#30137" +msgid "Enable VP9 codec" +msgstr "Activar codec VP9" + +msgctxt "#30138" +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "Pode que os servizos de segundo plano non estean listos aínda se acaba de iniciar o Kodi ou o complemento. Nese caso, tente novamente nun intre." + +msgctxt "#30139" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "Activar IPC sobre HTTP (usar cando aparezan mensaxes de tempo excedido do complemento)" + +msgctxt "#30140" +msgid "Import existing library" +msgstr "Importar biblioteca existente" + +#. Unused 30141 to 30143 +msgctxt "#30144" +msgid "Disable WebVTT subtitle support" +msgstr "Desactivar o soporte de subtítulos WebVTT" + +msgctxt "#30145" +msgid "Recently added" +msgstr "Engadidos recentemente" + +msgctxt "#30146" +msgid "Discover the latest content added." +msgstr "Descubra o contido engadido recentemente." + +msgctxt "#30147" +msgid "Next page »" +msgstr "Páxina seguinte »" + +msgctxt "#30148" +msgid "« Previous page" +msgstr "« Páxina anterior" + +msgctxt "#30149" +msgid "Sort results by" +msgstr "Ordenar resultados por" + +msgctxt "#30150" +msgid "Alphabetical order (A-Z)" +msgstr "Orden alfabética (A-Z)" + +msgctxt "#30151" +msgid "Alphabetical order (Z-A)" +msgstr "Orden alfabética (Z-A)" + +msgctxt "#30152" +msgid "Suggestions for you" +msgstr "Suxestións para ti" + +msgctxt "#30153" +msgid "Year released" +msgstr "Ano de lanzamento" + +msgctxt "#30154" +msgid "Netflix configuration wizard" +msgstr "Asistente de configuración do Netflix" + +msgctxt "#30155" +msgid "Do you have a Netflix Premium account?" +msgstr "Ten unha conta Netflix Premium?" + +#. Unused 30156 +msgctxt "#30157" +msgid "The configuration has been completed" +msgstr "Completouse a configuración" + +msgctxt "#30158" +msgid "Restore the add-on default configuration" +msgstr "Restablecer a configuración por defecto do complemento" + +msgctxt "#30159" +msgid "View ID for main menu" +msgstr "ID de vista para o menú principal" + +msgctxt "#30160" +msgid "View ID for search" +msgstr "ID de vista para a procura" + +msgctxt "#30161" +msgid "View ID for exported" +msgstr "ID de vista para exportados" + +msgctxt "#30162" +msgid "Last used" +msgstr "Último usado" + +msgctxt "#30163" +msgid "Audio description" +msgstr "Descrición de son" + +msgctxt "#30164" +msgid "Browse contents with audio description." +msgstr "Navegar polos contidos con descrición de son." + +msgctxt "#30165" +msgid "View ID for 'My list'" +msgstr "ID de vista para 'A miña lista'" + +msgctxt "#30166" +msgid "Main menu items" +msgstr "Elementos do menú principal" + +msgctxt "#30167" +msgid "My list" +msgstr "A miña lista" + +msgctxt "#30168" +msgid "Continue watching" +msgstr "Continuar vendo" + +msgctxt "#30169" +msgid "Top picks" +msgstr "Recomendados" + +msgctxt "#30170" +msgid "New releases" +msgstr "Lanzamentos" + +msgctxt "#30171" +msgid "Trending now" +msgstr "Tendencias" + +msgctxt "#30172" +msgid "Popular on Netflix" +msgstr "Popular no Netflix" + +msgctxt "#30173" +msgid "Netflix Originals" +msgstr "Orixinais do Netflix" + +msgctxt "#30174" +msgid "TV show genres" +msgstr "Xéneros de series TV" + +msgctxt "#30175" +msgid "Movie genres" +msgstr "Xéneros de filmes" + +msgctxt "#30176" +msgid "Enable STRM resume workaround" +msgstr "Activar solución para retomar o STRM" + +msgctxt "#30177" +msgid "Ask to resume the video" +msgstr "Perguntar para retomar o vídeo" + +msgctxt "#30178" +msgid "Open Up Next add-on settings" +msgstr "Abrir axustes do complemento 'Up Next'" + +msgctxt "#30179" +msgid "Show trailers and more" +msgstr "Mostrar avance e máis" + +msgctxt "#30180" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "Houbo un problema co acceso; é posíbel que a súa conta non fose confirmada ou reactivada." + +msgctxt "#30181" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "Mostrar sempre os subtítulos cando a lingua preferida do audio non está dispoñíbel" + +msgctxt "#30182" +msgid "Export NFO files" +msgstr "Exportar arquivos NFO" + +msgctxt "#30183" +msgid "Do you want to export NFO files for the {}?" +msgstr "Desexa exportar os arquivos NFO para o {}?" + +msgctxt "#30184" +msgid "NFO Files" +msgstr "Arquivos NFO" + +msgctxt "#30185" +msgid "Enable NFO files export" +msgstr "Activar exportación de arquivos NFO" + +msgctxt "#30186" +msgid "For Movies" +msgstr "Para filmes" + +msgctxt "#30187" +msgid "For TV show" +msgstr "Para series TV" + +msgctxt "#30188" +msgid "Ask" +msgstr "Perguntar" + +msgctxt "#30189" +msgid "movie" +msgstr "filme" + +msgctxt "#30190" +msgid "TV show" +msgstr "Series TV" + +#. Unused 30191 +msgctxt "#30192" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]Estes arquivos empréganos os extractores de información para integraren a información de filmes e series TV; son útiles con episodios bonus ou edición do director" + +msgctxt "#30193" +msgid "Include tv show NFO details" +msgstr "Incluír os detalles NFO da serie tv" + +msgctxt "#30194" +msgid "Limit video stream resolution to" +msgstr "Limitar a resolución do fluxo de vídeo a" + +msgctxt "#30195" +msgid "Export new episodes" +msgstr "Exportar novos episodios" + +msgctxt "#30196" +msgid "Exclude from auto update" +msgstr "Excluír da actualización automática" + +msgctxt "#30197" +msgid "Include in auto update" +msgstr "Incluír na actualización automática" + +msgctxt "#30198" +msgid "Exporting new episodes" +msgstr "Exportando novos episódios" + +msgctxt "#30199" +msgid "Shared library (Kodi MySQL server is required)" +msgstr "Biblioteca compartida (precísase o servidor Kodi MySQL)" + +msgctxt "#30200" +msgid "Use shared library MySQL-database" +msgstr "Usar biblioteca compartida MySQL" + +msgctxt "#30201" +msgid "Username" +msgstr "Nome de usuario" + +msgctxt "#30202" +msgid "Connection to the MySQL-database was successful" +msgstr "A conexión coa base de datos MySQL estableceuse correctamente" + +msgctxt "#30203" +msgid "Host IP" +msgstr "IP do Host" + +msgctxt "#30204" +msgid "Port" +msgstr "Porto" + +msgctxt "#30205" +msgid "Test database connection" +msgstr "Probar conexión á base de datos" + +msgctxt "#30206" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" +msgstr "ERRO: A base de datos MySQL non está dispoñíbel - empregarase a base de datos local" + +msgctxt "#30207" +msgid "Set this device as main auto-updates manager" +msgstr "Definir este dispositivo como xestor principal das actualizacións automáticas" + +msgctxt "#30208" +msgid "Check if this device is the main auto-update manager" +msgstr "Comprobar se este dispositivo é o xestor principal das actualizacións automáticas" + +msgctxt "#30209" +msgid "This device now handles the auto-updates of the shared library" +msgstr "Este dispositivo encárgarase das actualizacións automáticas da biblioteca compartida a partir de agora" + +msgctxt "#30210" +msgid "This device handles the auto-updates of the shared library" +msgstr "Este dispositivo encárgase das actualizacións automáticas da biblioteca compartida" + +msgctxt "#30211" +msgid "Another device handles the auto-updates of the shared library" +msgstr "Outro dispositivo encárgase das actualizacións automáticas da biblioteca compartida" + +msgctxt "#30212" +msgid "There is no device that handles the auto-updates of the shared library" +msgstr "Non hai ningún dispositivo que se encargue das actualizacións automáticas da biblioteca compartida" + +msgctxt "#30213" +msgid "InputStream Helper settings..." +msgstr "Axustes do complemento InputStream Helper..." + +msgctxt "#30214" +msgid "Force refresh" +msgstr "Forzar actualización" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "Cor dos títulos incluídos en ‘Miña lista’" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "Cor dos títulos marcados como \"Acórdame\"" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "Desactivar notificación de sincronización finalizada" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "A biblioteca do Kodi foi actualizada" + +msgctxt "#30221" +msgid "Owner account" +msgstr "Conta principal" + +msgctxt "#30222" +msgid "Kid account" +msgstr "Conta cativa" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "Modo de actualización automática" + +msgctxt "#30225" +msgid "Manual" +msgstr "Manual" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "Axendada" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "Sincronizar a biblioteca do Kodi con ‘Miña lista’" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "Definir un perfil para a sincronización" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "No arrinque do Kodi" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "Comprobar actualizacións agora" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "Desexa comprobar agora se hai actualizacións?" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "Valoración do perfil de madurez para {}" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "Mostrar soamente os títulos con valoracións [B]{}[/B]." + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "Sincronizar co Netflix o estado da visualización dos vídeos" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "Mudar o estado da visualización (localmente)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "Marcar como visto|Marcado como non vistor|Restaurado o estado de visualización no Netflix" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Notificacións 'Up Next' (ver seguinte vídeo)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "Cor da información adicional da sinopse" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "Os servizos en segundo plano non se puideron iniciar debido a este problema:[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Open Connect CDN (o índice mais baixo é mellor)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "Os 10 mellores" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "Seleccionar o primeiro episodio non visto da serie TV" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "Borrando os arquivos exportados á biblioteca" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "Existen {} títulos que non se poden importar.[CR]Desexa borrar estes arquivos?" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "Resultados por páxina (en caso de erro por tempo excedido, diminúao)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "Desexa eliminar o estado de visualización de \"{}\"?" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "Incluír a biblioteca do Kodi (soamente enviando datos)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "Escolla o método de acceso[CR](se o acceso con Correo electrónico/Contrasinal devolve sempre \"contrasinal incorrecto\", entón empregue a chave de autenticación - instrucións no Leme do GitHub)" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "Correo electrónico/Contrasinal" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "Chave de autenticación" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "O arquivo seleccionado non é compatíbel ou está danado" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "Expirou o arquivo coa chave de autenticación" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "Insira PIN" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "Ordenar histórico por" + +msgctxt "#30400" +msgid "Search" +msgstr "Procurar" + +msgctxt "#30401" +msgid "Type of search" +msgstr "Tipo de procura" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "Procurar títulos, pessoas, gêneros" + +msgctxt "#30403" +msgid "New search..." +msgstr "Nova procura…" + +msgctxt "#30404" +msgid "Clear search history" +msgstr "Limpar histórico de procura" + +msgctxt "#30405" +msgid "Select the language" +msgstr "Seleccione a lingua" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "Desexa borrar todos os contidos?" + +msgctxt "#30407" +msgid "No matches found" +msgstr "Non se atoparon coincidencias" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "Por termo" + +msgctxt "#30411" +msgid "By audio language" +msgstr "Pola lingua do son" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "Pola lingua dos subtítulos" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "Polo ID do xénero/subxénero" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "Preferir a lingua do audio/subtítulo co código do país (se dispoñíbel)" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "Preferir pistas de audio en estéreo por defecto" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "Configuración ESN/ Widevine" + +msgctxt "#30601" +msgid "ESN:" +msgstr "ESN:" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "Mudar ESN" + +msgctxt "#30603" +msgid "Save system info" +msgstr "Gardar información do sistema" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "Widevine - Forzar nivel de seguranza" + +msgctxt "#30605" +msgid "Force {}" +msgstr "Forzar {}" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "O ESN configurouse correctamente[CR]Tenta reproducir un vídeo" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "Non se pode empregar este ESN[CR]Tenta mudar o ESN ou reinicialo.[CR][CR]Detalles do erro:[CR]{}" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "Formato incorrecto do ESN" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "Queres reiniciar todos os parámetros?" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "Empregar a encriptación do sistema para as credenciais" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "[B]Se se deshabilita provocará unha grecha de seguranza[/B]. Se o teu sistema operativo non o soporta, che saltará o control de acceso cada vez que reinicies o dispositivo, deshabilítao [B]tan só[/B] neste caso. Se mudas este parámetro che saltará o control de acceso de novo a vindeira vez que reincies." + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "Versión do manifesto MSL" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "Este contido estará dispoñíbel a partir de:[CR]{}" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "Desexas reproducir o vídeo promocional?" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "{} Acórdame" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "Novo e popular" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "Marca as series TV como vistas" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "Nótese que, ao activares esta opción, se tiveres filtros activados na configuración de aspecto, poida que non se amosen os elementos marcados como vistos." + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "Modificar a selección do tipo de fluxo" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "Modificar o axuste da selección do tipo de fluxo do complemento InputStream Adaptive, para establecer como se escollerá a calidade dos fluxos de audio e vídeo durante a reprodución." + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "Calidade adaptativa" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "Calidade de vídeo fixa" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "Preguntar a calidade de vídeo" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "Nótese que ao activar este axuste se van descartar automaticamente os fluxos fóra do rango establecido e isto pode afectar a operación do InputStream Adaptive. Se for posíbel, primeiro tenta limitar a calidade dende os axustes do InputStream Adaptive." + +msgctxt "#30721" +msgid "Size" +msgstr "Tamaño" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "Minimizar as barras negras aplicando un effecto de zoom. [ Fixo ] Configura a porcentaxe do espazo da pantalla cás barras negras poden ocupar, sendo constante para todos os vídeos. [ Relativo ] Configura a porcentaxe de redución das bandas negras, dependerá do tamaño do vídeo. [ Restablecer ] Restablece o efecto zoom aplicado previamente." + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "Esta función fai uso do efecto zoom do Kodi gardado de forma permanente na configuración de vídeo do \"Modo vista\". Se desactivas esta función no futuro, o zoom se manterá. Para restableceres o zoom selecciona o modo \"Restablecer\"." + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "Activar codec AV1" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "Engadir os cartafoles do Netflix ás fontes da biblioteca do Kodi" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "Engadíronse os cartafoles \"Netflix-Movies\" e \"Neflix-Shows\" ás fontes do Kodi, reinicia o Kodi para vérelos nos menús do Kodi Series TV e Filmes. Despois, edita os cartafoles para estableceres o tipo de contido e o extractor provedor de información axeitados." + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "[Manual] as actualizacións deben facerse manualmente empregando o menú contextual \"Exportar novos episodios\" en cada serie tv, [Programado] podes programar a hora das actualizacións, [Cando o Kodi arrinque] as actualizacións realizaranse automaticamente cand o Kodi arrinque e realizaranse unha vez ao día soamente." + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "Sincroniza a biblioteca coa Miña Lista, se A Miña Lista for modificada externamente, por exemplo cun móbil ou pola web, a actualización posporíase até a hora programada." + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "Se está activo, engade a información dos episodios ou dos filmes que axudan ao Kodi no caso que o extractor provedor de información non atope esta información. Tamén engadirá a duración dos vídeos." + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "Engadir unicamente se se emprega o extractor provedor de información \"Información local\", ou se houber algúns títulos que non se amosan la biblioteca do Kodi aínque que se engadiran." + +msgctxt "#30734" +msgid "Support" +msgstr "Axuda" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "Sobre o complemento do Netflix" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "Xera automatic novos ESNs (solución para o límite de 540p)" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "AVISO: Non empregar o ESN do dispositivo orixinal da aplicación oficial." + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "Activar a compensación de audio" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "Compensación de audio" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "Establece a compensación de audio modificando o axuste do Kodi" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "Activar VP9 Profile 2 (10/12 bit color depth)" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "Queres activar o soporte HDR10?" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "Queres activar o soporte HDR Dolby Vision?" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "O complemento xa establece a versión HDCP axeitada, pero de ser preciso podes forzar que os fluxos de vído empreguen unha versión HDCP diferente." + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "Se o teu dispositivo non soporta este codec, podes experimentar pantallas negras e fallas na imaxe. Nese caso, desactiva o codec." + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "Modo minimizar barras negras" + +msgctxt "#30747" +msgid "Fixed" +msgstr "Fixo" + +msgctxt "#30748" +msgid "Relative" +msgstr "Relativo" + +msgctxt "#30749" +msgid "Reset" +msgstr "Restablecer" diff --git a/resources/language/resource.language.he_il/strings.po b/resources/language/resource.language.he_il/strings.po index 4bbdb3b96..6f9390bbc 100644 --- a/resources/language/resource.language.he_il/strings.po +++ b/resources/language/resource.language.he_il/strings.po @@ -1,117 +1,118 @@ # Kodi Media Center language file # Addon Name: Netflix # Addon id: plugin.video.netflix -# Addon Provider: libdev + jojo + asciidisco + caphm + CastagnaIT +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait msgid "" msgstr "" "Project-Id-Version: plugin.video.netflix\n" "Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" -"POT-Creation-Date: 2017-07-24 16:15+0000\n" -"PO-Revision-Date: 2019-07-08 22:15+0000\n" -"Last-Translator: A. Dambledore\n" -"Language-Team: Eng2Heb\n" +"POT-Creation-Date: 2020-08-09 16:15+0000\n" +"PO-Revision-Date: 2020-08-09 20:40+0000\n" +"Last-Translator: Aviv Avital <>\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" -"Language: he_IL\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" - msgctxt "Addon Summary" msgid "Netflix" -msgstr "נטפליקס" +msgstr "Netflix" msgctxt "Addon Description" -msgid "Netflix VOD Services Addon" -msgstr "תוסף שירותי וי או די של נטפליקס" +msgid "Netflix VOD Services Add-on" +msgstr "תוסף VOD לשירות של Netflix" msgctxt "Addon Disclaimer" -msgid "Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing." -msgstr "חלקים שונים בתוסף זה עשויים להיות לא חוקיים באיזור שהייתך - אנא בדוק את החוקים לפני ההתקנה." +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "השימוש בתוסף זה עלול להיות לא חוקי במדינת מגוריך - אנא בדוק את החוק ברשות המקומית לפני ההתקנה" msgctxt "#30001" msgid "Recommendations" msgstr "המלצות" msgctxt "#30002" -msgid "Adult Pin" -msgstr "קוד מבוגרים" - -msgctxt "#30003" -msgid "Search term" -msgstr "חפש ביטוי" +msgid "Enter PIN to watch restricted content" +msgstr "הזן קוד PIN בכדי לצפות בתוכן" +#. Unused 30003 msgctxt "#30004" msgid "Password" -msgstr "סיסמה" +msgstr "סיסמא" msgctxt "#30005" msgid "E-mail" msgstr "אי-מייל" msgctxt "#30006" -msgid "Adult verification failed" -msgstr "אימות קוד מבוגרים נכשל" +msgid "Enter PIN to access this profile" +msgstr "הזן קוד PIN בכדי להיכנס לפרופיל זה" msgctxt "#30007" -msgid "Please Check your adult pin" -msgstr "אנא בדוק את קוד המבוגרים" +msgid "Parental Control PIN (4 digits)" +msgstr "קוד PIN עבור בקרת הורים (4 ספרות)" msgctxt "#30008" msgid "Login failed" -msgstr "הכניסה נכשלה" +msgstr "ניסיון ההתחברות נכשל" msgctxt "#30009" -msgid "Please Check your credentials" -msgstr "אנא בדוק את הגדרות ההרשאה (שם משתמש וסיסמה)" +msgid "Please check your e-mail and password" +msgstr "אנא בדוק את כתובת המייל והסיסמא שלך" msgctxt "#30010" msgid "Lists of all kinds" -msgstr "סגנונות" - -msgctxt "#30011" -msgid "Search" -msgstr "חיפוש" +msgstr "רשימת כל הסוגים" +#. Unused 30011 msgctxt "#30012" -msgid "No seasons available" -msgstr "לא נמצאו עונות" +msgid "Profiles options and parental control" +msgstr "" msgctxt "#30013" -msgid "No matches found" -msgstr "לא נמצאו התאמות" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "" msgctxt "#30014" msgid "Account" msgstr "חשבון" +msgctxt "#30015" +msgid "Other options" +msgstr "אפשרויות אחרות" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "" + msgctxt "#30017" msgid "Logout" -msgstr "ניתוק חשבון" +msgstr "התנתקות" msgctxt "#30018" msgid "Export to library" -msgstr "יצא לספרייה" +msgstr "יצוא לספרייה" msgctxt "#30019" msgid "Rate on Netflix" -msgstr "תן ציון בנטפליקס" +msgstr "דירוג דרך Netflix" msgctxt "#30020" msgid "Remove from 'My list'" -msgstr "הסר מתוך הרשימה שלי" +msgstr "להסיר מ'הרשימה שלי'" msgctxt "#30021" msgid "Add to 'My list'" -msgstr "הוסף לתוך הרשימה שלי" +msgstr "להוסיף ל'רשימה שלי'" msgctxt "#30022" -msgid "(between 0 & 10)" -msgstr "(בין 0 ו- 10)" +msgid "(between 0 and 10)" +msgstr "בין 0 ל-10" msgctxt "#30023" msgid "Expert" -msgstr "מומחה" +msgstr "הגדרות מתקדמות" msgctxt "#30024" msgid "SSL verification" @@ -123,152 +124,1133 @@ msgstr "ספרייה" msgctxt "#30026" msgid "Enable custom library folder" -msgstr "אפשר תיקייה מותאמת אישית לספרייה" +msgstr "הפעלת ספרייה מותאמת אישית" msgctxt "#30027" msgid "Custom library path" -msgstr "נתיב ספרייה מותאם אישית" +msgstr "נתיב ספריה מותאמת אישית" msgctxt "#30028" msgid "Playback error" -msgstr "תקלה בניגון" - -msgctxt "#30029" -msgid "Missing InputStream addon" -msgstr "הרחבת InputStream חסרה" +msgstr "שגיאת ניגון" +#. Unused 30029 msgctxt "#30030" msgid "Remove from library" -msgstr "הסר מהספרייה" +msgstr "להסיר מהספרייה" msgctxt "#30031" -msgid "Change library title" -msgstr "שנה כותרת ספרייה" +msgid "General" +msgstr "כללי" msgctxt "#30032" -msgid "Tracking" -msgstr "מעקב" +msgid "Codecs" +msgstr "" msgctxt "#30033" -msgid "Enable Dolby Digital Plus (DDPlus HQ and Atmos on Premium)" -msgstr "תשתמש בפורמט דולבי" - -msgctxt "#30034" -msgid "ESN (set automatically, can be changed manually)" -msgstr "רשת ESN (ברירת מחדל אוטומטי, ניתן לשנות ידנית)" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "" +#. Unused 30034 msgctxt "#30035" msgid "InputStream Adaptive settings..." -msgstr "הגדרות הרחבת InputStream..." - -msgctxt "#30036" -msgid "Always use the original title on export" -msgstr "תשתמש תמיד בכותרת המקורית לייצוא" +msgstr "הגדרות InputStream משתנה" +#. Unused 30036 msgctxt "#30037" -msgid "Views" -msgstr "תצוגות" +msgid "Skin viewtypes" +msgstr "" msgctxt "#30038" -msgid "Enable custom views" -msgstr "אפשר תצוגות מותאמות אישית" +msgid "Enable custom viewtypes" +msgstr "" msgctxt "#30039" -msgid "View for folders" -msgstr "תצוגה עבור תיקיות" +msgid "View ID for folders" +msgstr "" msgctxt "#30040" -msgid "View for movies" -msgstr "תצוגה עבור סרטים" +msgid "View ID for movies" +msgstr "" msgctxt "#30041" -msgid "View for shows" -msgstr "תצוגה עבור סדרות" +msgid "View ID for shows" +msgstr "" msgctxt "#30042" -msgid "View for seasons" -msgstr "תצוגה עבור עונות" +msgid "View ID for seasons" +msgstr "" msgctxt "#30043" -msgid "View for episodes" -msgstr "תצוגה עבור פרקים" +msgid "View ID for episodes" +msgstr "" msgctxt "#30044" -msgid "View for profiles" -msgstr "תצוגה עבור פרופילים" +msgid "View ID for profiles" +msgstr "" msgctxt "#30045" -msgid "[COLOR cyan][B]-- NEXT PAGE --[/B][/COLOR]" -msgstr "[COLOR cyan][B]-- לדף הבא --[/B][/COLOR]" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "הצבעתך הוסרה|דירגת את התוכן כשלילי|דירגת את התוכן כחיובי" msgctxt "#30046" -msgid "InputStream addon is not enabled" -msgstr "הרחבת InputStream לא מאופשרת" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "ישנה בעיה עם הגדרות הInputStream או יתכן שספריית הWidevine חסרה, הפעולה תבוטל." msgctxt "#30047" -msgid "Finally remove?" -msgstr "הוסר סוף סוף?" +msgid "Library update in progress" +msgstr "עדכון ספרייה החל" msgctxt "#30048" msgid "Exported" -msgstr "מיוצא" +msgstr "הייצוא התבצע בהצלחה" msgctxt "#30049" -msgid "Update DB" -msgstr "עדכן בסיס נתונים" +msgid "Library" +msgstr "ספרייה" msgctxt "#30050" -msgid "Update successful" -msgstr "העדכון בוצע בהצלחה" - -msgctxt "#30051" -msgid "Request Error" -msgstr "בקשה שגויה" +msgid "Enable Kodi library management" +msgstr "" +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30052" -msgid "Unable to complete the request at this time" -msgstr "אין אפשרות להשלים את הבקשה בזמן הזה" - -msgctxt "#30053" -msgid "Auto Login" -msgstr "כניסה אוטומטית" - -msgctxt "#30054" -msgid "Enable Auto Login" -msgstr "אפשר כניסה אוטומטית" +msgid "{} Set for library playback" +msgstr "אפשר ניגון מהספרייה {}" +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30055" -msgid "Profile" -msgstr "משתמש" - -msgctxt "#30056" -msgid "ID" -msgstr "מזהה" +msgid "{} Set at start-up" +msgstr "" +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30057" -msgid "Select profile in profile listing -> context menu" -msgstr "בחר שם משתמש ברשימת המשתמשים -> תפריט הקישור" - -msgctxt "#30058" -msgid "Auto Login enabled!" -msgstr "כניסה אוטומטית מופעלת!" +msgid "{} Remember PIN" +msgstr "" +#. Unused 30058 msgctxt "#30059" -msgid "Switch accounts" -msgstr "החלפת חשבונות" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "יש להיזהר, העדכון כולל מספר רב של כותרים {}.[CR]זה עלול לגרום לבעיות בשירות או להשהייה זמנית של החשבון.[CR]האם ברצונך להמשיך?" msgctxt "#30060" -msgid "Enable HEVC profiles (4k for Android/HDR/DolbyVision)" -msgstr "אפשר משתמשי HEVC ( 4k עבור אנדרואיד/HDR/DolbyVision)" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "" msgctxt "#30061" msgid "Update inside library" msgstr "עדכון בתוך הספרייה" msgctxt "#30062" -msgid "Enable/disable adult pin. Active:" -msgstr "אפשר או השבת קוד למבוגרים בלבד. פעיל:" +msgid "Parental controls" +msgstr "בקרת הורים" + +msgctxt "#30063" +msgid "An update is already in progress" +msgstr "עדכון כבר מתבצע כעת" + +msgctxt "#30064" +msgid "Perform auto-update" +msgstr "ביצוע עדכון אוטומטי" + +msgctxt "#30065" +msgid "Video library update" +msgstr "" + +msgctxt "#30066" +msgid "Enable debug logging" +msgstr "" + +msgctxt "#30067" +msgid "daily" +msgstr "יומי" + +msgctxt "#30068" +msgid "every other day" +msgstr "מספר פעמים בשבוע" + +msgctxt "#30069" +msgid "every 5 days" +msgstr "כל 5 ימים" + +msgctxt "#30070" +msgid "weekly" +msgstr "שבועי" + +msgctxt "#30071" +msgid "Time of Day" +msgstr "שעה" + +msgctxt "#30072" +msgid "Only start after 5 minutes of idle" +msgstr "התחלה רק אחרי 5 דקות של חוסר פעילות" + +msgctxt "#30073" +msgid "Always show codec information during video playback" +msgstr "תמיד להציג מידע על קידוד תוך כדי ניגון" + +msgctxt "#30074" +msgid "View for exported" +msgstr "צפייה בתכנים שיוצאו" + +msgctxt "#30075" +msgid "Ask to skip intro and recap" +msgstr "לשאול האם לדלג על הפתיח וסקירת הפרקים הקודמים" + +msgctxt "#30076" +msgid "Skip intro" +msgstr "לדלג על הפתיח" + +msgctxt "#30077" +msgid "Skip recap" +msgstr "לדלג על סקירת הפרקים הקודמים" + +msgctxt "#30078" +msgid "Playback" +msgstr "ניגון" + +msgctxt "#30079" +msgid "Skip automatically" +msgstr "לדלג אוטומטית" + +msgctxt "#30080" +msgid "Pause when skipping" +msgstr "להשהות בעת הדילוג" + +msgctxt "#30081" +msgid "Force HDCP version on video streams" +msgstr "" + +msgctxt "#30082" +msgid "Remember audio / subtitle preferences" +msgstr "לזכור העדפות כתוביות/שמע" + +msgctxt "#30083" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "" + +msgctxt "#30084" +msgid "Cache objects TTL (minutes)" +msgstr "זמן מקסימלי (דקות) לשמירת אובייקטים בזיכרון מטמון (cache)" + +msgctxt "#30085" +msgid "Cache metadata TTL (days)" +msgstr "זמן מקסימלי(ימים) לשמירת מטה-דאטה בזיכרון המטמות (cache)" + +msgctxt "#30086" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "זמן מקסימלי (דקות) לשמירת אובייקטי 'הרשימה שלי' בזיכרון המטמון (cache)" + +msgctxt "#30087" +msgid "Contains {} and more..." +msgstr "מכיל {} ועוד" + +msgctxt "#30088" +msgid "Browse related content" +msgstr "לדפדף תכנים דומים" + +msgctxt "#30089" +msgid "Browse subgenres" +msgstr "לדפדף תתי ז'אנרים" + +msgctxt "#30090" +msgid "Browse through related video lists and discover more content." +msgstr "חיפוש בתכני וידאו דומים בכדי לגלות עוד תוכן" + +msgctxt "#30091" +msgid "Browse and manage contents that were exported to the Kodi library." +msgstr "חיפוש וניהול תוכן שייוצא אל ספריית Kodi" + +msgctxt "#30092" +msgid "Search for content and easily find what you want to watch." +msgstr "לחפש תוכן ולמצוא בקלות מה שמחפשים" + +msgctxt "#30093" +msgid "Browse content by genre and easily discover related content." +msgstr "חיפוש בתכנים לפי ז'אנרים ומציאת תוכן דומה" + +msgctxt "#30094" +msgid "Browse recommendations and pick up on something new you might like." +msgstr "דפדוף בהמלצות ובחירת תכנים שיתכן ויתאימו לבחירותיך" + +msgctxt "#30095" +msgid "All TV shows" +msgstr "כל תוכניות הטלויזיה" + +msgctxt "#30096" +msgid "All Movies" +msgstr "כל הסרטים" + +msgctxt "#30097" +msgid "Main Menu" +msgstr "תפריט ראשי" + +msgctxt "#30098" +msgid "Enable HDR10" +msgstr "" + +msgctxt "#30099" +msgid "Enable Dolby Vision" +msgstr "" + +msgctxt "#30100" +msgid "Results for '{}'" +msgstr "תוצאות עבור '{}'" + +msgctxt "#30101" +msgid "Netflix returned an unknown error." +msgstr "Netflix החזירה שגיאה לא מוכרת" + +msgctxt "#30102" +msgid "Netflix returned the following error:" +msgstr "Netflix החזיר את השגיאה הבאה:" + +msgctxt "#30103" +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "פרטי השגיאה נכתבו לקבצי הלוג בKodi. במידה והודעת השגיאה לא מספקת מידע כיצד לפתור את הבעיה או שהודעת השגיאה הזו ממשיכה, יש לדווח על כך בGitHub לפי ההוראות המפורטות בדף הReadme" + +msgctxt "#30104" +msgid "The add-on encountered the following error:" +msgstr "התוסף נתקל בשגיאה הבאה:" + +msgctxt "#30105" +msgid "Netflix Add-on Error" +msgstr "שגיאה בתוסף Netflix" + +msgctxt "#30106" +msgid "Invalid PIN" +msgstr "קוד PIN לא תקין" + +msgctxt "#30107" +msgid "PIN protection is off." +msgstr "הגנות קוד PIN מבוטלות" + +msgctxt "#30108" +msgid "All content is protected by PIN." +msgstr "כל התכנים מוגנים באמצעות קוד PIN" + +msgctxt "#30109" +msgid "Login successful" +msgstr "ההתחברות הצליחה" + +#. Unused 30110 +msgctxt "#30111" +msgid "There is no contents" +msgstr "אין תוכן" + +msgctxt "#30112" +msgid "You need to be logged in to use Netflix" +msgstr "בכדי להשתמש בNetflix עליך להיות מחובר/ת" + +msgctxt "#30113" +msgid "Logout successful" +msgstr "החשבון נותק בהצלחה" + +#. Unused 30114, 30115 +msgctxt "#30116" +msgid "Advanced Add-on Configuration" +msgstr "הגדרות שימוש מתקדם" + +msgctxt "#30117" +msgid "Cache" +msgstr "זיכון מטמון" + +#. Unused 30118, 30119 +msgctxt "#30120" +msgid "Cannot automatically remove a season from Kodi library, please do it manually" +msgstr "לא ניתן להסיר אוטומטית עונה מספריית Kodi, יש להסיר אותה ידנית" + +msgctxt "#30121" +msgid "Perform full sync now" +msgstr "ביצוע סנכרון מלא כעת" + +msgctxt "#30122" +msgid "Sync 'My list' to Kodi library" +msgstr "סנכרון את 'הרשימה שלי' עם Kodi" + +msgctxt "#30123" +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "האם ברצונך להמשיך?[CR]הסנכרון המלא ימחק את כל הכותרים שייוצאו.[CR]במידה וישנם הרבה כותרים הפעולה תיקח זמן מה" + +msgctxt "#30124" +msgid "Do you want to remove this item from the library?" +msgstr "האם ברצונך להסיר את פריט זה מהספריה?" + +msgctxt "#30125" +msgid "Delete library contents" +msgstr "למחוק את תכולת הספרייה" + +msgctxt "#30126" +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "האם באמת למחוק את הספרייה שלך?[CR]כל התכנים המייוצאים מהספרייה ימחקו" + +msgctxt "#30127" +msgid "Awarded rating of {}" +msgstr "קיבל דירוג של" + +msgctxt "#30128" +msgid "Select a profile" +msgstr "בחירת פרופיל" + +msgctxt "#30129" +msgid "Enable Up Next integration" +msgstr "לאפשר אינטגרציית Up Next" + +msgctxt "#30130" +msgid "Install Up Next add-on" +msgstr "התקנת תוסף Up Next" + +msgctxt "#30131" +msgid "Check the logfile for detailed information" +msgstr "בדיקת קבצי Log עבור מידע מפורט" + +msgctxt "#30132" +msgid "Clear in-memory cache" +msgstr "ריקון זיכרון מטמון נדיף" + +msgctxt "#30133" +msgid "Clear in-memory and on-disk cache" +msgstr "ריקון זיכרון מטמון נדיף ועל הדיסק" + +msgctxt "#30134" +msgid "Enable execution timing" +msgstr "הפעלת תזמון ביצועים" + +msgctxt "#30135" +msgid "Cache cleared" +msgstr "זיכרון מטמון התרוקן" + +msgctxt "#30136" +msgid "Waiting for service start-up..." +msgstr "" + +msgctxt "#30137" +msgid "Enable VP9 codec" +msgstr "" + +msgctxt "#30138" +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "יתכן ושירותי הרקע לא זמינים במידה ורק הפעלת את Kodi. ניתן לנסות בעוד מספר רגעים" + +msgctxt "#30139" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "לאפשר IPC על גבי HTTP (ניתן להשתמש כשישנן חריגות זמן בעת שימוש בAddon Signals)" + +msgctxt "#30140" +msgid "Import existing library" +msgstr "ייבוא ספרייה קיימת" + +#. Unused 30141 to 30143 +msgctxt "#30144" +msgid "Disable WebVTT subtitle support" +msgstr "כיבוי תמיכה בתרגום WebVTT" + +msgctxt "#30145" +msgid "Recently added" +msgstr "התווסף לאחרונה" + +msgctxt "#30146" +msgid "Discover the latest content added." +msgstr "מה התווסף לאחרונה" + +msgctxt "#30147" +msgid "Next page »" +msgstr "הדף הבא" + +msgctxt "#30148" +msgid "« Previous page" +msgstr "הדף הקודם" + +msgctxt "#30149" +msgid "Sort results by" +msgstr "מיון תוצאות לפי" + +msgctxt "#30150" +msgid "Alphabetical order (A-Z)" +msgstr "סדר אלפבית עולה (א-ת)" + +msgctxt "#30151" +msgid "Alphabetical order (Z-A)" +msgstr "סדר אלפבית יורד (ת-א)" + +msgctxt "#30152" +msgid "Suggestions for you" +msgstr "הצעות בשבילך" + +msgctxt "#30153" +msgid "Year released" +msgstr "שנת שחרור" + +msgctxt "#30154" +msgid "Netflix configuration wizard" +msgstr "אשף ההגדרות של Netflix" + +msgctxt "#30155" +msgid "Do you have a Netflix Premium account?" +msgstr "האם יש ברשותך חשבון Netflix פרמיום?" + +#. Unused 30156 +msgctxt "#30157" +msgid "The configuration has been completed" +msgstr "ההגדרות נשמרו בהצלחה" + +msgctxt "#30158" +msgid "Restore the add-on default configuration" +msgstr "" + +msgctxt "#30159" +msgid "View ID for main menu" +msgstr "" + +msgctxt "#30160" +msgid "View ID for search" +msgstr "" + +msgctxt "#30161" +msgid "View ID for exported" +msgstr "" + +msgctxt "#30162" +msgid "Last used" +msgstr "היה בשימוש לאחרונה" + +msgctxt "#30163" +msgid "Audio description" +msgstr "תיאור שמע" + +msgctxt "#30164" +msgid "Browse contents with audio description." +msgstr "חיפוש תוכן עם תיאור שמע" + +msgctxt "#30165" +msgid "View ID for 'My list'" +msgstr "" + +msgctxt "#30166" +msgid "Main menu items" +msgstr "פריטי תפריט ראשי" + +msgctxt "#30167" +msgid "My list" +msgstr "הרשימה שלי" + +msgctxt "#30168" +msgid "Continue watching" +msgstr "המשך צפיה" + +msgctxt "#30169" +msgid "Top picks" +msgstr "הבחירות הכי טובות" + +msgctxt "#30170" +msgid "New releases" +msgstr "תכנים חדשים" + +msgctxt "#30171" +msgid "Trending now" +msgstr "הכי נצפה כעת" + +msgctxt "#30172" +msgid "Popular on Netflix" +msgstr "פופולרי בNetflix" + +msgctxt "#30173" +msgid "Netflix Originals" +msgstr "תכני מקור של Netflix" + +msgctxt "#30174" +msgid "TV show genres" +msgstr "ז'אנרי תוכניות טלוויזיה" + +msgctxt "#30175" +msgid "Movie genres" +msgstr "ז'אנרי סרטים" + +msgctxt "#30176" +msgid "Enable STRM resume workaround" +msgstr "הפעלת מעקף STRM עבור המשך" + +msgctxt "#30177" +msgid "Ask to resume the video" +msgstr "לשאול האם להמשיך את הוידאו" + +msgctxt "#30178" +msgid "Open Up Next add-on settings" +msgstr "פתיחת הגדרות תוסף Up Next" + +msgctxt "#30179" +msgid "Show trailers and more" +msgstr "להציג טריילרים ועוד" + +msgctxt "#30180" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "ישנה בעיה בחיבור, יתכן שהחשבון שלך לא אושר או אופשר מחדש" + +msgctxt "#30181" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "" + +msgctxt "#30182" +msgid "Export NFO files" +msgstr "ייצא קבצי NFO" + +msgctxt "#30183" +msgid "Do you want to export NFO files for the {}?" +msgstr "האם ברצונך לייצא קבצי NFO?" + +msgctxt "#30184" +msgid "NFO Files" +msgstr "קבצי NFO" + +msgctxt "#30185" +msgid "Enable NFO files export" +msgstr "לאפשר ייצוא קבצי NFO" + +msgctxt "#30186" +msgid "For Movies" +msgstr "עבור סרטים" + +msgctxt "#30187" +msgid "For TV show" +msgstr "עבור תוכניות טלויזיה" + +msgctxt "#30188" +msgid "Ask" +msgstr "לשאול" + +msgctxt "#30189" +msgid "movie" +msgstr "סרט" + +msgctxt "#30190" +msgid "TV show" +msgstr "תוכנית טלויזיה" + +#. Unused 30191 +msgctxt "#30192" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]קבצים אלו בשימוש על ידי ספקי מידע כדי לחבר מידע על סרטים ותוכניות טלויזיה, והם שימושיים בפרקי בונוס או בסרטים ב'גרסת הבמאי'" + +msgctxt "#30193" +msgid "Include tv show NFO details" +msgstr "" + +msgctxt "#30194" +msgid "Limit video stream resolution to" +msgstr "הגבלת רזולוציית הוידאו עד" + +msgctxt "#30195" +msgid "Export new episodes" +msgstr "ייצוא סדרות חדשות" + +msgctxt "#30196" +msgid "Exclude from auto update" +msgstr "לא לכלול בעדכון אוטומטי" + +msgctxt "#30197" +msgid "Include in auto update" +msgstr "לכלול בעדכון אוטומטי" + +msgctxt "#30198" +msgid "Exporting new episodes" +msgstr "סדרות חדשות מיוצאות" + +msgctxt "#30199" +msgid "Shared library (Kodi MySQL server is required)" +msgstr "ספרייה משותפת (לשם כך יש צורך בשרת Kodi MySQL)" + +msgctxt "#30200" +msgid "Use shared library MySQL-database" +msgstr "השתמש במסד נתונים של MySQL עבור הספרייה המשותפת" + +msgctxt "#30201" +msgid "Username" +msgstr "שם משתמש" + +msgctxt "#30202" +msgid "Connection to the MySQL-database was successful" +msgstr "ההתחברות למסד הנתונים של MySQL הסתיימה בהצלחה" + +msgctxt "#30203" +msgid "Host IP" +msgstr "כתובת IP מארח" + +msgctxt "#30204" +msgid "Port" +msgstr "פורט" + +msgctxt "#30205" +msgid "Test database connection" +msgstr "בדיקת חיבור למסד הנתונים" + +msgctxt "#30206" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" +msgstr "שגיאה: לא ניתן לגשת לשרת הMySQL - במקום, המערכת תשתמש במסד הנתונים המקומי " + +msgctxt "#30207" +msgid "Set this device as main auto-updates manager" +msgstr "מכשיר זה יכול לנהל את העדכון האוטומטי" + +msgctxt "#30208" +msgid "Check if this device is the main auto-update manager" +msgstr "לבדוק האם מכשיר זה הוא מנהל עדכון האוטומטי הראאשי" + +msgctxt "#30209" +msgid "This device now handles the auto-updates of the shared library" +msgstr "מכשיר זה כעת מטפל בעדכון האוטומטי של הספרייה המשותפת" + +msgctxt "#30210" +msgid "This device handles the auto-updates of the shared library" +msgstr "מכשיר זה מטפל בעדכון האוטומטי של הספרייה המשותפת" + +msgctxt "#30211" +msgid "Another device handles the auto-updates of the shared library" +msgstr "מכשיר אחר מטפל בעדכון האוטומטי של הספרייה המשותפת" + +msgctxt "#30212" +msgid "There is no device that handles the auto-updates of the shared library" +msgstr "אין מכשירים שמטפלים בעדכון האוטומטי של הספרייה המשותפת" msgctxt "#30213" msgid "InputStream Helper settings..." -msgstr "הגדרות הרחבת InputStream Helper..." +msgstr "הגדרות המסייע לInputStream " + +msgctxt "#30214" +msgid "Force refresh" +msgstr "הכרח רענון" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "צבע הכותרים הכלולים ב'הרשימה שלי'" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "השבתת הודעות 'הסנכרון הושלם'" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "ספריית Kodi עןדכנה" + +msgctxt "#30221" +msgid "Owner account" +msgstr "חשבון בעלים" + +msgctxt "#30222" +msgid "Kid account" +msgstr "חשבון ילדים" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "מצב עדכון אוטומטי" + +msgctxt "#30225" +msgid "Manual" +msgstr "ידני" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "מתוזמן" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "סנכרון ספריית Kodi עם 'הרשימה שלי'" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "הגדרת פרופיל עבור סנכרון" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "בדיקת עדכון כעת" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "האם לבדוק עדכון כעת?" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "פרופיל גיל עבור" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "להראות כותרים שמדורגים רק [B]{}[/B]" + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "סנכרון סטטוס הוידאו שנצפה עם Netflix" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "שינוי מצב צפייה (מקומית)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "סימון כנצפה|סימון כלא נצפה|שחזור סטטוס צפיה מNetflix" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "הודעות Up Next (צפיה בוידאו הבא)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "צבע אזור ה'מידע עלילתי נוסף'" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "שירותי הרקע לא יכולים להתחיל בגלל הבעיה הבאה: {}[CR]" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Open Connect CDN (האינדקס התחתון יותר טוב)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "עשרת הגדולים" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "בחירת פרק תוכנית הטלוויזיה הראשונה שלא נצפה" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "מחיקת קבצים שיוצאו לספרייה" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "ישנם {} כותרים שלא ניתן לייבא.[CR]האם ברצונך למחוק קבצים אלו?" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "מספר תוצאות בכל עמוד (במידה ותהיה שגיאה, מספר זה יופחת)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "האם ברצונך למחוק את סטטוס הצפייה של \"{}\"?" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "לכלול את ספריית Kodi (רק שליחת מידע מאופשרת)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "" + +msgctxt "#30400" +msgid "Search" +msgstr "חיפוש" + +msgctxt "#30401" +msgid "Type of search" +msgstr "סוג חיפוש" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "חיפוש של כותרים, אנשים וז'אנרים" + +msgctxt "#30403" +msgid "New search..." +msgstr "חיפוש חדש..." + +msgctxt "#30404" +msgid "Clear search history" +msgstr "ניקוי היסטוריית חיפוש" + +msgctxt "#30405" +msgid "Select the language" +msgstr "בחירת שפה" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "האם ברצונך למחוק את כל התוכן?" + +msgctxt "#30407" +msgid "No matches found" +msgstr "לא נמצאו תוצאות לחיפוש" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "לפי מונח" + +msgctxt "#30411" +msgid "By audio language" +msgstr "לפי שפת שמע" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "לפי שפת תרגום" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "" + +msgctxt "#30601" +msgid "ESN:" +msgstr "" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "" + +msgctxt "#30603" +msgid "Save system info" +msgstr "" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "" + +msgctxt "#30605" +msgid "Force {}" +msgstr "" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "" + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "" + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "" + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "" + +msgctxt "#30721" +msgid "Size" +msgstr "" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "" + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "" + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "" + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "" + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "" + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "" + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "" + +msgctxt "#30734" +msgid "Support" +msgstr "" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "" + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "" + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "" + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "" + +msgctxt "#30747" +msgid "Fixed" +msgstr "" + +msgctxt "#30748" +msgid "Relative" +msgstr "" + +msgctxt "#30749" +msgid "Reset" +msgstr "" diff --git a/resources/language/resource.language.hr_hr/strings.po b/resources/language/resource.language.hr_hr/strings.po index 13b3fc0dd..3ddf007fc 100644 --- a/resources/language/resource.language.hr_hr/strings.po +++ b/resources/language/resource.language.hr_hr/strings.po @@ -1,45 +1,43 @@ -# Croatian translation for Kodi Media Center Netfilx addon +# Kodi Media Center language file # Addon Name: Netflix # Addon id: plugin.video.netflix -# Addon Provider: libdev + jojo + asciidisco + caphm + CastagnaIT +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait msgid "" msgstr "" "Project-Id-Version: plugin.video.netflix\n" "Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" "POT-Creation-Date: 2017-07-24 16:15+0000\n" -"PO-Revision-Date: 2019-07-08 22:15+0000\n" -"Last-Translator: gogo\n" +"PO-Revision-Date: 2021-10-14 18:00+0200\n" +"Last-Translator: dsardelic\n" "Language-Team: Croatian\n" +"Language: hr_HR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: hr\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" +"X-Generator: Poedit 2.3\n" msgctxt "Addon Summary" msgid "Netflix" -msgstr "" +msgstr "Netflix" msgctxt "Addon Description" -msgid "Netflix VOD Services Addon" -msgstr "Netflix dodatak za uslugu (VOD) videa na zahtjev" +msgid "Netflix VOD Services Add-on" +msgstr "Dodatak za Netflix VOD usluge" msgctxt "Addon Disclaimer" -msgid "Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing." -msgstr "Neki dijelovi ovog dodatka možda nisu legalni u vašoj zemlji ili kraju - provjerite vaše lokalne zakone prije instalacije." +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "Korištenje ovog dodatka možda nije legalno u zemlji u kojoj boravite - prije instalacije molimo provjerite lokalne zakone." msgctxt "#30001" msgid "Recommendations" msgstr "Preporuke" msgctxt "#30002" -msgid "Adult Pin" -msgstr "Pin sadržaja za odrasle" - -msgctxt "#30003" -msgid "Search term" -msgstr "Izraz pretrage" +msgid "Enter PIN to watch restricted content" +msgstr "Unesite PIN za gledanje ograničenog sadržaja" +#. Unused 30003 msgctxt "#30004" msgid "Password" msgstr "Lozinka" @@ -49,221 +47,1211 @@ msgid "E-mail" msgstr "E-pošta" msgctxt "#30006" -msgid "Adult verification failed" -msgstr "Neuspjela ovjera sadržaja za odrasle" +msgid "Enter PIN to access this profile" +msgstr "Unesite PIN za pristup ovom profilu" msgctxt "#30007" -msgid "Please Check your adult pin" -msgstr "Provjerite svoj pin sadržaja za odrasle" +msgid "Parental Control PIN (4 digits)" +msgstr "PIN za roditeljski nadzor (4 znamenke)" msgctxt "#30008" msgid "Login failed" -msgstr "Neuspjela prijava" +msgstr "Prijava nije uspjela" msgctxt "#30009" -msgid "Please Check your credentials" -msgstr "Provjereite svoje podatke ovjere" +msgid "Please check your e-mail and password" +msgstr "Molim provjerite vašu e-poštu i lozinku" msgctxt "#30010" msgid "Lists of all kinds" -msgstr "" - -msgctxt "#30011" -msgid "Search" -msgstr "Pretraga" +msgstr "Liste svake vrste" +#. Unused 30011 msgctxt "#30012" -msgid "No seasons available" -msgstr "Nema dostupnih sezona" +msgid "Profiles options and parental control" +msgstr "Opcije profila i roditeljski nadzor" msgctxt "#30013" -msgid "No matches found" -msgstr "Nema pronađenih podudaranja" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "U listi profila otvorite kontekstni izbornik u odabranom profilu" msgctxt "#30014" msgid "Account" msgstr "Račun" +msgctxt "#30015" +msgid "Other options" +msgstr "Ostale opcije" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "" + msgctxt "#30017" msgid "Logout" msgstr "Odjava" msgctxt "#30018" msgid "Export to library" -msgstr "Izvezi u videoteku" +msgstr "Izvoz u biblioteku" msgctxt "#30019" msgid "Rate on Netflix" -msgstr "Ocjeni na Netflixu" +msgstr "Ocijenite na Netflixu" msgctxt "#30020" msgid "Remove from 'My list'" -msgstr "Ukloni s 'Moj popis'" +msgstr "Ukloni s Moje liste" msgctxt "#30021" msgid "Add to 'My list'" -msgstr "Dodaj na 'Moj popis'" +msgstr "Dodaj u Moju listu" msgctxt "#30022" -msgid "(between 0 & 10)" +msgid "(between 0 and 10)" msgstr "(između 0 i 10)" msgctxt "#30023" msgid "Expert" -msgstr "Stručno" +msgstr "Stručne" msgctxt "#30024" msgid "SSL verification" -msgstr "SSL ovjera" +msgstr "SSL verifikacija" msgctxt "#30025" msgid "Library" -msgstr "Videoteka" +msgstr "Biblioteka" msgctxt "#30026" msgid "Enable custom library folder" -msgstr "Omogući prilagođenu mapu videoteke" +msgstr "Omogući mapu za korisnički definiranu biblioteku" msgctxt "#30027" msgid "Custom library path" -msgstr "Prilagođena putanja videoteke" +msgstr "Putanja prema korisnički definiranoj biblioteci" msgctxt "#30028" msgid "Playback error" -msgstr "Greška reprodukcije" - -msgctxt "#30029" -msgid "Missing InputStream addon" -msgstr "Nedostaje dodatak ulaznog strujanja" +msgstr "Pogreška prilikom reprodukcije" +#. Unused 30029 msgctxt "#30030" msgid "Remove from library" -msgstr "Ukloni iz videoteke" +msgstr "Ukloni iz biblioteke" msgctxt "#30031" -msgid "Change library title" -msgstr "Promijeni naslov videoteke" +msgid "General" +msgstr "Opće" msgctxt "#30032" -msgid "Tracking" -msgstr "Praćenje" +msgid "Codecs" +msgstr "" -msgctxt "#30034" -msgid "ESN (set automatically, can be changed manually)" -msgstr "ESN (postavi automatski, može se promijeniti ručno)" +msgctxt "#30033" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "" +#. Unused 30034 msgctxt "#30035" msgid "InputStream Adaptive settings..." -msgstr "Postavke dodatka ulaznog strujanja..." - -msgctxt "#30036" -msgid "Always use the original title on export" -msgstr "Uvijek koristi izvorni naslov pri izvozu" +msgstr "InputStream Adaptive postavke..." +#. Unused 30036 msgctxt "#30037" -msgid "Views" -msgstr "Prikazi" +msgid "Skin viewtypes" +msgstr "Tipovi presvlaka" msgctxt "#30038" -msgid "Enable custom views" -msgstr "Omogući prilagođene prikaze" +msgid "Enable custom viewtypes" +msgstr "Omogući korisnički definirane presvlake" msgctxt "#30039" -msgid "View for folders" -msgstr "Prikaz mapa" +msgid "View ID for folders" +msgstr "ID pregleda za mape" msgctxt "#30040" -msgid "View for movies" -msgstr "Prikaz filmova" +msgid "View ID for movies" +msgstr "ID pregleda za filmove" msgctxt "#30041" -msgid "View for shows" -msgstr "Prikaz serija" +msgid "View ID for shows" +msgstr "ID pregleda za serije" msgctxt "#30042" -msgid "View for seasons" -msgstr "Prikaz sezona" +msgid "View ID for seasons" +msgstr "ID pregleda za sezone" msgctxt "#30043" -msgid "View for episodes" -msgstr "Prikaz epizoda" +msgid "View ID for episodes" +msgstr "ID pregleda za epizode" msgctxt "#30044" -msgid "View for profiles" -msgstr "Prikaz profila" +msgid "View ID for profiles" +msgstr "ID pregleda za profile" msgctxt "#30045" -msgid "[COLOR cyan][B]-- NEXT PAGE --[/B][/COLOR]" -msgstr "[COLOR cyan][B]-- SLJEDEĆA STRANICA --[/B][/COLOR]" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "Ocjena palcem uklonjena|Ocijenili ste palcem dolje|Ocijenili ste palcem gore" msgctxt "#30046" -msgid "InputStream addon is not enabled" -msgstr "Dodatak ulaznog strujanja nije omogućen" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "Postoji problem s InputStream Adaptive dodatkom ili Widevine bibliotekom. Možda nedostaje ili nije omogućena, operacija će biti otkazana." msgctxt "#30047" -msgid "Finally remove?" -msgstr "Konačno ukloni?" +msgid "Library update in progress" +msgstr "Ažuriranje biblioteke u tijeku" msgctxt "#30048" msgid "Exported" msgstr "Izvezeno" msgctxt "#30049" -msgid "Update DB" -msgstr "Nadopuni bazu podataka" +msgid "Library" +msgstr "Biblioteka" msgctxt "#30050" -msgid "Update successful" -msgstr "Nadopuna uspješna" - -msgctxt "#30051" -msgid "Request Error" -msgstr "Greška zahtjeva" +msgid "Enable Kodi library management" +msgstr "" +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30052" -msgid "Unable to complete the request at this time" -msgstr "Nemoguće je trenutno završavanje zahtjeva" - -msgctxt "#30053" -msgid "Auto Login" -msgstr "Automatska prijava" - -msgctxt "#30054" -msgid "Enable Auto Login" -msgstr "Omogući automatsku prijavu" +msgid "{} Set for library playback" +msgstr "{} Postavi za reprodukciju iz biblioteke" +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30055" -msgid "Profile" -msgstr "Profil" - -msgctxt "#30056" -msgid "ID" +msgid "{} Set at start-up" msgstr "" +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30057" -msgid "Select profile in profile listing -> context menu" -msgstr "Odaberi profil u popisu profila -> sadržajni izbornik" - -msgctxt "#30058" -msgid "Auto Login enabled!" -msgstr "Automatska prijava omogućena!" +msgid "{} Remember PIN" +msgstr "" +#. Unused 30058 msgctxt "#30059" -msgid "Switch accounts" -msgstr "Zamijeni račune" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "Oprezno, spremate se ažurirati mnogo naslova {}.[CR]Ovo može prouzrokovati probleme s uslugom ili privremenu zabranu računa.[CR]Želite li nastaviti?" msgctxt "#30060" -msgid "Enable HEVC profiles (4k for Android/HDR/DolbyVision)" -msgstr "Omogući HEVC profile (4k za Android/HDR/DolbyVision)" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "" msgctxt "#30061" msgid "Update inside library" -msgstr "Nadopuni unutar videoteke" +msgstr "Ažuriranje unutar biblioteke" msgctxt "#30062" -msgid "Enable/disable adult pin. Active:" -msgstr "Omogući/Onemogući pin sadržaja za odrasle. Aktivan:" +msgid "Parental controls" +msgstr "Roditeljske kontrole" + +msgctxt "#30063" +msgid "An update is already in progress" +msgstr "Ažuriranje je već u tijeku" + +msgctxt "#30064" +msgid "Perform auto-update" +msgstr "Izvrši automatsko ažuriranje" + +msgctxt "#30065" +msgid "Video library update" +msgstr "" + +msgctxt "#30066" +msgid "Enable debug logging" +msgstr "Uključi logiranje za otklanjanje grešaka" + +msgctxt "#30067" +msgid "daily" +msgstr "dnevno" + +msgctxt "#30068" +msgid "every other day" +msgstr "svaki drugi dan" + +msgctxt "#30069" +msgid "every 5 days" +msgstr "svakih 5 dana" + +msgctxt "#30070" +msgid "weekly" +msgstr "tjedno" + +msgctxt "#30071" +msgid "Time of Day" +msgstr "Vrijeme dana" + +msgctxt "#30072" +msgid "Only start after 5 minutes of idle" +msgstr "Počnite tek nakon 5 minuta mirovanja" + +msgctxt "#30073" +msgid "Always show codec information during video playback" +msgstr "Tijekom reprodukcije videozapisa uvijek prikaži informacije o kodeku" + +msgctxt "#30074" +msgid "View for exported" +msgstr "Prikaz za izvezeno" + +msgctxt "#30075" +msgid "Ask to skip intro and recap" +msgstr "Upitaj za preskakanje uvoda i sažetka" + +msgctxt "#30076" +msgid "Skip intro" +msgstr "Preskoči uvod" + +msgctxt "#30077" +msgid "Skip recap" +msgstr "Preskoči sažetak" + +msgctxt "#30078" +msgid "Playback" +msgstr "Reprodukcija" + +msgctxt "#30079" +msgid "Skip automatically" +msgstr "Automatski preskoči" + +msgctxt "#30080" +msgid "Pause when skipping" +msgstr "Pauziraj prilikom preskakivanja" + +msgctxt "#30081" +msgid "Force HDCP version on video streams" +msgstr "" + +msgctxt "#30082" +msgid "Remember audio / subtitle preferences" +msgstr "Zapamti postavke zvuka / titlova" + +msgctxt "#30083" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "Nametni prikaz nametnutih titlova jedino s podešenim jezikom zvuka" + +msgctxt "#30084" +msgid "Cache objects TTL (minutes)" +msgstr "Vrijeme opstanka objekata u predmemoriji (u minutama)" + +msgctxt "#30085" +msgid "Cache metadata TTL (days)" +msgstr "Vrijeme opstanka metapodataka u predmemoriji (u danima)" + +msgctxt "#30086" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "Vrijeme opstanka objekata 'Moja lista' u predmemoriji (u minutama)" + +msgctxt "#30087" +msgid "Contains {} and more..." +msgstr "Sadrži {} i još ..." + +msgctxt "#30088" +msgid "Browse related content" +msgstr "Pregledajte povezani sadržaj" + +msgctxt "#30089" +msgid "Browse subgenres" +msgstr "Pregledajte podžanrove" + +msgctxt "#30090" +msgid "Browse through related video lists and discover more content." +msgstr "Razgledajte povezane liste videozapisa i otkrijte još sadržaja." + +msgctxt "#30091" +msgid "Browse and manage contents that were exported to the Kodi library." +msgstr "Pregledajte i upravljajte sadržajem izvezenim u Kodi biblioteku." + +msgctxt "#30092" +msgid "Search for content and easily find what you want to watch." +msgstr "Potražite sadržaj i lako pronađite što želite gledati." + +msgctxt "#30093" +msgid "Browse content by genre and easily discover related content." +msgstr "Pregledajte sadržaj po žanru i lako otkrijte povezani sadržaj." + +msgctxt "#30094" +msgid "Browse recommendations and pick up on something new you might like." +msgstr "Pregledajte preporuke i pronađite nešto novo što bi vam se moglo svidjeti." + +msgctxt "#30095" +msgid "All TV shows" +msgstr "Sve TV serije" + +msgctxt "#30096" +msgid "All Movies" +msgstr "Svi filmovi" + +msgctxt "#30097" +msgid "Main Menu" +msgstr "Glavni izbornik" + +msgctxt "#30098" +msgid "Enable HDR10" +msgstr "" + +msgctxt "#30099" +msgid "Enable Dolby Vision" +msgstr "" + +msgctxt "#30100" +msgid "Results for '{}'" +msgstr "Rezultati za '{}'" + +msgctxt "#30101" +msgid "Netflix returned an unknown error." +msgstr "Netflix je vratio nepoznatu pogrešku." + +msgctxt "#30102" +msgid "Netflix returned the following error:" +msgstr "Netflix je vratio sljedeću pogrešku:" + +msgctxt "#30103" +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "Detalji o pogrešci zapisani su u kodi log datoteci. Ako poruka o pogrešci ne sadrži smjernice o tome kako riješiti problem ili ukoliko nastavite dobivati ovu pogrešku, molimo prijavite je na GitHub slijedeći upute u Readme datoteci." + +msgctxt "#30104" +msgid "The add-on encountered the following error:" +msgstr "Dodatak je naišao na sljedeću pogrešku:" + +msgctxt "#30105" +msgid "Netflix Add-on Error" +msgstr "Pogreška Netflix dodatka" + +msgctxt "#30106" +msgid "Invalid PIN" +msgstr "Neispravan PIN" + +msgctxt "#30107" +msgid "PIN protection is off." +msgstr "PIN zaštita je isključena." + +msgctxt "#30108" +msgid "All content is protected by PIN." +msgstr "Sav sadržaj zaštićen je PIN-om." + +msgctxt "#30109" +msgid "Login successful" +msgstr "Prijava uspješna" + +#. Unused 30110 +msgctxt "#30111" +msgid "There is no contents" +msgstr "Nema sadržaja" + +msgctxt "#30112" +msgid "You need to be logged in to use Netflix" +msgstr "Za korištenje Netflixa morate biti prijavljeni" + +msgctxt "#30113" +msgid "Logout successful" +msgstr "Odjava uspješna" + +#. Unused 30114, 30115 +msgctxt "#30116" +msgid "Advanced Add-on Configuration" +msgstr "Napredna konfiguracija dodatka" + +msgctxt "#30117" +msgid "Cache" +msgstr "Predmemorija" + +#. Unused 30118, 30119 +msgctxt "#30120" +msgid "Cannot automatically remove a season from Kodi library, please do it manually" +msgstr "Iz Kodi biblioteke nije moguće automatski ukloniti sezonu, molimo učinite to ručno" + +msgctxt "#30121" +msgid "Perform full sync now" +msgstr "Obavi punu sinkronizaciju sada" + +msgctxt "#30122" +msgid "Sync 'My list' to Kodi library" +msgstr "Sinkroniziraj Moju Listu s Kodi bibliotekom" + +msgctxt "#30123" +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "Želite li nastaviti?[CR]Puna sinkronizacija obrisat će sve prethodno izvezene naslove.[CR]Ukoliko ima mnogo naslova, ova operacija mogla bi potrajati." + +msgctxt "#30124" +msgid "Do you want to remove this item from the library?" +msgstr "Želite li ukloniti ovu stavku iz biblioteke?" + +msgctxt "#30125" +msgid "Delete library contents" +msgstr "Izbriši sadržaj biblioteke" + +msgctxt "#30126" +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "Želite li doista pročistiti vašu biblioteku?[CR]Sve izvezene stavke bit će izbrisane." + +msgctxt "#30127" +msgid "Awarded rating of {}" +msgstr "Dodijeljena ocjena {}" + +msgctxt "#30128" +msgid "Select a profile" +msgstr "Odaberite profil" + +msgctxt "#30129" +msgid "Enable Up Next integration" +msgstr "Omogući Up Next integraciju" + +msgctxt "#30130" +msgid "Install Up Next add-on" +msgstr "Instaliraj Up Next dodatak" + +msgctxt "#30131" +msgid "Check the logfile for detailed information" +msgstr "Provjerite log datoteku za detaljne informacije" + +msgctxt "#30132" +msgid "Clear in-memory cache" +msgstr "Oslobodi memorijsku predmemoriju" + +msgctxt "#30133" +msgid "Clear in-memory and on-disk cache" +msgstr "Oslobodi memorijsku i diskovnu predmemoriju" + +msgctxt "#30134" +msgid "Enable execution timing" +msgstr "Omogući praćenje vremena izvršenja" + +msgctxt "#30135" +msgid "Cache cleared" +msgstr "Predmemorija oslobođena" + +msgctxt "#30136" +msgid "Waiting for service start-up..." +msgstr "" + +msgctxt "#30137" +msgid "Enable VP9 codec" +msgstr "" + +msgctxt "#30138" +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "Pozadinske usluge možda još nisu dostupne ukoliko ste tek pokrenuli Kodi/dodatak. U tom slučaju pokušajte ponovo za trenutak." + +msgctxt "#30139" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "Omogući IPC preko HTTP-a (koristi se nakon pojave Addon Signals poruka vremenskog ograničenja)" + +msgctxt "#30140" +msgid "Import existing library" +msgstr "Uvezi postojeću biblioteku" + +#. Unused 30141 to 30143 +msgctxt "#30144" +msgid "Disable WebVTT subtitle support" +msgstr "Onemogući podršku za WebVTT titlove" + +msgctxt "#30145" +msgid "Recently added" +msgstr "Nedavno dodano" + +msgctxt "#30146" +msgid "Discover the latest content added." +msgstr "Otkrijte najnoviji dodani sadržaj." + +msgctxt "#30147" +msgid "Next page »" +msgstr "Sljedeća stranica »" + +msgctxt "#30148" +msgid "« Previous page" +msgstr "« Prethodna stranica" + +msgctxt "#30149" +msgid "Sort results by" +msgstr "Sortiraj rezultate prema" + +msgctxt "#30150" +msgid "Alphabetical order (A-Z)" +msgstr "Abecednom redu (A-Z)" + +msgctxt "#30151" +msgid "Alphabetical order (Z-A)" +msgstr "Abecednom redu (Z-A)" + +msgctxt "#30152" +msgid "Suggestions for you" +msgstr "Prijedlozima za vas" + +msgctxt "#30153" +msgid "Year released" +msgstr "Godini izdavanja" + +msgctxt "#30154" +msgid "Netflix configuration wizard" +msgstr "Čarobnjak za Netflix konfiguraciju" + +msgctxt "#30155" +msgid "Do you have a Netflix Premium account?" +msgstr "Imate li Netflix Premium račun?" + +#. Unused 30156 +msgctxt "#30157" +msgid "The configuration has been completed" +msgstr "Konfiguracija je dovršena" + +msgctxt "#30158" +msgid "Restore the add-on default configuration" +msgstr "" + +msgctxt "#30159" +msgid "View ID for main menu" +msgstr "ID glavnog izbornika" + +msgctxt "#30160" +msgid "View ID for search" +msgstr "ID pretrage" + +msgctxt "#30161" +msgid "View ID for exported" +msgstr "ID izvezenog" + +msgctxt "#30162" +msgid "Last used" +msgstr "Zadnje upotrijebljeno" + +msgctxt "#30163" +msgid "Audio description" +msgstr "Zvukovni opis" + +msgctxt "#30164" +msgid "Browse contents with audio description." +msgstr "Pregledajte sadržaj sa zvučnim opisom." + +msgctxt "#30165" +msgid "View ID for 'My list'" +msgstr "ID Moje liste" + +msgctxt "#30166" +msgid "Main menu items" +msgstr "Stavke glavnog izbornika" + +msgctxt "#30167" +msgid "My list" +msgstr "Moja lista" + +msgctxt "#30168" +msgid "Continue watching" +msgstr "Nastavite gledati" + +msgctxt "#30169" +msgid "Top picks" +msgstr "Top odabir" + +msgctxt "#30170" +msgid "New releases" +msgstr "Nova izdanja" + +msgctxt "#30171" +msgid "Trending now" +msgstr "Sada u trendu" + +msgctxt "#30172" +msgid "Popular on Netflix" +msgstr "Popularno na Netflixu" + +msgctxt "#30173" +msgid "Netflix Originals" +msgstr "Netflix originali" + +msgctxt "#30174" +msgid "TV show genres" +msgstr "Žanrovi TV serija" + +msgctxt "#30175" +msgid "Movie genres" +msgstr "Žanrovi filmova" + +msgctxt "#30176" +msgid "Enable STRM resume workaround" +msgstr "Omogući zaobilazno rješenje za STRM nastavak" + +msgctxt "#30177" +msgid "Ask to resume the video" +msgstr "Zatraži nastavak reprodukcije videozapisa" + +msgctxt "#30178" +msgid "Open Up Next add-on settings" +msgstr "Otvorite postavke Up Next dodatka" + +msgctxt "#30179" +msgid "Show trailers and more" +msgstr "Pokažite foršpane i još" + +msgctxt "#30180" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "Došlo je do problema s prijavom, moguće je da vaš račun nije potvrđen ili ponovno aktiviran." + +msgctxt "#30181" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "Uvijek prikaži titlove kada nije dostupan preferirani jezik zvuka" + +msgctxt "#30182" +msgid "Export NFO files" +msgstr "Izvoz NFO datoteka" + +msgctxt "#30183" +msgid "Do you want to export NFO files for the {}?" +msgstr "Želite li izvesti NFO datoteke za {}?" + +msgctxt "#30184" +msgid "NFO Files" +msgstr "NFO datoteke" + +msgctxt "#30185" +msgid "Enable NFO files export" +msgstr "Omogući izvoz NFO datoteka" + +msgctxt "#30186" +msgid "For Movies" +msgstr "Za filmove" + +msgctxt "#30187" +msgid "For TV show" +msgstr "Za TV serije" + +msgctxt "#30188" +msgid "Ask" +msgstr "Pitaj" + +msgctxt "#30189" +msgid "movie" +msgstr "film" + +msgctxt "#30190" +msgid "TV show" +msgstr "TV serija" + +#. Unused 30191 +msgctxt "#30192" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]Strugači informacija koriste ove datoteke za integraciju informacija o filmovima i TV serijama, korisno kod bonus epizoda ili režiserove verzije" + +msgctxt "#30193" +msgid "Include tv show NFO details" +msgstr "" + +msgctxt "#30194" +msgid "Limit video stream resolution to" +msgstr "Ograničite razlučivost video toka na" + +msgctxt "#30195" +msgid "Export new episodes" +msgstr "Izvezite nove epizode" + +msgctxt "#30196" +msgid "Exclude from auto update" +msgstr "Izuzmi iz automatskog ažuriranja" + +msgctxt "#30197" +msgid "Include in auto update" +msgstr "Uključi u automatsko ažuriranje" + +msgctxt "#30198" +msgid "Exporting new episodes" +msgstr "Izvoz novih epizoda" + +msgctxt "#30199" +msgid "Shared library (Kodi MySQL server is required)" +msgstr "Zajednička biblioteka (potreban je Kodi MySQL poslužitelj)" + +msgctxt "#30200" +msgid "Use shared library MySQL-database" +msgstr "Koristite MySQL bazu podataka zajedničke biblioteke" + +msgctxt "#30201" +msgid "Username" +msgstr "Korisničko ime" + +msgctxt "#30202" +msgid "Connection to the MySQL-database was successful" +msgstr "Povezivanje s MySQL bazom podataka bilo je uspješno" + +msgctxt "#30203" +msgid "Host IP" +msgstr "Poslužiteljev IP" + +msgctxt "#30204" +msgid "Port" +msgstr "Priključak" + +msgctxt "#30205" +msgid "Test database connection" +msgstr "Testiraj vezu s bazom podataka" + +msgctxt "#30206" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" +msgstr "GREŠKA: MySQL baza podataka nije dostupna - bit će korištena lokalna baza podataka" + +msgctxt "#30207" +msgid "Set this device as main auto-updates manager" +msgstr "Postavi ovaj uređaj za glavnog upravitelja automatskih ažuriranja" + +msgctxt "#30208" +msgid "Check if this device is the main auto-update manager" +msgstr "Provjeri je li ovaj uređaj glavni upravitelj automatskog ažuriranja" + +msgctxt "#30209" +msgid "This device now handles the auto-updates of the shared library" +msgstr "Ovaj uređaj sada rukuje automatskim ažuriranjima dijeljene biblioteke" + +msgctxt "#30210" +msgid "This device handles the auto-updates of the shared library" +msgstr "Ovaj uređaj rukuje automatskim ažuriranjima dijeljene biblioteke" + +msgctxt "#30211" +msgid "Another device handles the auto-updates of the shared library" +msgstr "Drugi uređaj rukuje automatskim ažuriranjima dijeljene biblioteke" + +msgctxt "#30212" +msgid "There is no device that handles the auto-updates of the shared library" +msgstr "Nijedan uređaj ne rukuje automatskim ažuriranjima dijeljene biblioteke" msgctxt "#30213" msgid "InputStream Helper settings..." +msgstr "InputStream Helper postavke..." + +msgctxt "#30214" +msgid "Force refresh" +msgstr "Prisilno osvježi" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "Boja naslova iz Moje liste" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "Onemogući obavijest o završetku sinkronizacije" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "Kodi biblioteka je ažurirana" + +msgctxt "#30221" +msgid "Owner account" +msgstr "Račun vlasnika" + +msgctxt "#30222" +msgid "Kid account" +msgstr "Dječji račun" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "Način rada s automatskim ažuriranjem" + +msgctxt "#30225" +msgid "Manual" +msgstr "Ručno" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "Zakazano" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "Sinkroniziraj Kodi biblioteku s Mojom listom" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "Postavi profil za sinkronizaciju" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "Provjerite ima li sad ažuriranja" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "Želite li provjeriti ima li sad ažuriranja?" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "Ocjena zrelosti profila od {}" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "Prikaži samo naslove ocijenjene [B]{}[/B]." + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "S Netflixom sinkroniziraj status odgledanosti videozapisa" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "Promijeni status odgledanosti (lokalno)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "Označeno kao odgledano|Označeno kao neodgledano|Vraćen Netflixov status odgledanosti" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Up Next obavijesti (gledajte sljedeći videozapis)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "Boja dodatnih informacija o radnji" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "Pozadinske servise nije moguće pokrenuti uslijed ovog problema:[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Otvori Connect CDN (niži indeks je bolji)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "Top 10" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "Odaberi prvu neodgledanu epizodu TV serije" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "Brisanje datoteka izvezenih u biblioteku" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "{} naslov(a) ne može se uvesti.[CR]Želite li obrisati ove datoteke?" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "Rezultata po stranici (smanjite u slučaju pogreške s istekom vremena)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "Želite li ukloniti status odgledanosti za \"{}\"?" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "Uključi Kodi biblioteku (samo slanje podataka)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "Odaberite metodu prijave[CR](ukoliko prijava s e-poštom/lozinkom uvijek vraća \"Neispravna lozinka\", upotrijebite autentikacijski ključ, upute u GitHub ReadMe datoteci)" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "E-pošta/Lozinka" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "Autentikacijski ključ" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "Odabrana datoteka nije kompatibilna ili je korumpirana" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "Autentikacijski ključ je istekao" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "Unesite PIN" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "Sortiraj povijest prema" + +msgctxt "#30400" +msgid "Search" +msgstr "Traži" + +msgctxt "#30401" +msgid "Type of search" +msgstr "Tip pretrage" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "Pretraži naslove, ljude, žanrove" + +msgctxt "#30403" +msgid "New search..." +msgstr "Nova pretraga..." + +msgctxt "#30404" +msgid "Clear search history" +msgstr "Obriši povijest pretraga" + +msgctxt "#30405" +msgid "Select the language" +msgstr "Odaberi jezik" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "Želite li obrisati čitav sadržaj?" + +msgctxt "#30407" +msgid "No matches found" +msgstr "Nema rezultata" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "Prema pojmu" + +msgctxt "#30411" +msgid "By audio language" +msgstr "Prema jeziku zvuka" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "Prema jeziku titlova" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "Prema ID-u žanra/podžanra" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "Preferiraj jezik zvuka/titlova s kodom države (ukoliko je dostupan)" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "Zadano preferiraj stereo zvučne zapise" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "ESN / Widevine postavke" + +msgctxt "#30601" +msgid "ESN:" +msgstr "ESN:" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "Promijeni ESN" + +msgctxt "#30603" +msgid "Save system info" +msgstr "Spremi informacije o sustavu" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "Widevine - Forsirana razina sigurnosti" + +msgctxt "#30605" +msgid "Force {}" +msgstr "Forsiraj {}" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "ESN uspješno postavljen[CR]Pokušajte reproducirati neki video zapis" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "Ovaj ESN ne može se koristiti[CR]Pokušajte promijeniti ESN ili mu obnoviti postavke.[CR][CR]Detalji o pogrešci:[CR]{}" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "Pogrešni format ESN-a" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "Želite li obnoviti sve postavke?" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "" + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "Ovaj naslov bit će dostupan od:[CR]{}" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "Želite li reproducirati promotivni foršpan?" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "{} Podsjeti me" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "Novo i popularno" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "" + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "" + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "" + +msgctxt "#30721" +msgid "Size" +msgstr "" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "" + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "" + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "" + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "" + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "" + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "" + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "" + +msgctxt "#30734" +msgid "Support" +msgstr "" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "" + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "" + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "" + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "" + +msgctxt "#30747" +msgid "Fixed" +msgstr "" + +msgctxt "#30748" +msgid "Relative" +msgstr "" + +msgctxt "#30749" +msgid "Reset" msgstr "" diff --git a/resources/language/resource.language.hu_hu/strings.po b/resources/language/resource.language.hu_hu/strings.po new file mode 100644 index 000000000..fc48f8871 --- /dev/null +++ b/resources/language/resource.language.hu_hu/strings.po @@ -0,0 +1,1256 @@ +# Kodi Media Center language file +# Addon Name: Netflix +# Addon id: plugin.video.netflix +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait +msgid "" +msgstr "" +"Project-Id-Version: plugin.video.netflix\n" +"Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" +"POT-Creation-Date: 2017-07-24 16:15+0000\n" +"PO-Revision-Date: 2023-04-04 16:40+0000\n" +"Last-Translator: frodo19\n" +"Language-Team: Hungarian\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 "Netflix" +msgstr "Netflix" + +msgctxt "Addon Description" +msgid "Netflix VOD Services Add-on" +msgstr "Netflix video kiegészítő" + +msgctxt "Addon Disclaimer" +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "Előfordulhat, hogy a kiegészítő használata nem törvényes az Ön országában - telepítés előtt tájékozódjon a helyi törvényekről" + +msgctxt "#30001" +msgid "Recommendations" +msgstr "Ajánlások" + +msgctxt "#30002" +msgid "Enter PIN to watch restricted content" +msgstr "Kód megadás a felnőtt tartalomhoz" + +#. Unused 30003 +msgctxt "#30004" +msgid "Password" +msgstr "Jelszó" + +msgctxt "#30005" +msgid "E-mail" +msgstr "E-mail" + +msgctxt "#30006" +msgid "Enter PIN to access this profile" +msgstr "PIN megadása a profilhoz" + +msgctxt "#30007" +msgid "Parental Control PIN (4 digits)" +msgstr "Szülői kontroll kód (4 számjegy)" + +msgctxt "#30008" +msgid "Login failed" +msgstr "Bejelentkezés sikertelen" + +msgctxt "#30009" +msgid "Please check your e-mail and password" +msgstr "Kérjük, ellenőrizze hitelesítő adatait" + +msgctxt "#30010" +msgid "Lists of all kinds" +msgstr "Vegyes lista" + +#. Unused 30011 +msgctxt "#30012" +msgid "Profiles options and parental control" +msgstr "Profil opciók és szülői kontroll" + +msgctxt "#30013" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "A profillistában nyissa meg a helyi menüt a kiválasztott profilnál" + +msgctxt "#30014" +msgid "Account" +msgstr "Fiók" + +msgctxt "#30015" +msgid "Other options" +msgstr "Opciók" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "Ha kijelentkezik, minden fiókadat törlődik, de a Kodi-médiatárban lévő, exportált videofájlok megmaradnak.[CR]Folytatja?" + +msgctxt "#30017" +msgid "Logout" +msgstr "Kijelentkezés" + +msgctxt "#30018" +msgid "Export to library" +msgstr "Exportálás a médiatárba" + +msgctxt "#30019" +msgid "Rate on Netflix" +msgstr "Értékelés a Netflixen" + +msgctxt "#30020" +msgid "Remove from 'My list'" +msgstr "Törlés a saját listámból" + +msgctxt "#30021" +msgid "Add to 'My list'" +msgstr "Hozzáadás a saját listámhoz" + +msgctxt "#30022" +msgid "(between 0 and 10)" +msgstr "0 és 10 között" + +msgctxt "#30023" +msgid "Expert" +msgstr "Haladó" + +msgctxt "#30024" +msgid "SSL verification" +msgstr "SSL ellenőrzés" + +msgctxt "#30025" +msgid "Library" +msgstr "Médiatár" + +msgctxt "#30026" +msgid "Enable custom library folder" +msgstr "Saját médiatár mappa engedélyezése" + +msgctxt "#30027" +msgid "Custom library path" +msgstr "Saját médiatár elérése" + +msgctxt "#30028" +msgid "Playback error" +msgstr "Lejátszási hiba" + +#. Unused 30029 +msgctxt "#30030" +msgid "Remove from library" +msgstr "Törlés a médiatárból" + +msgctxt "#30031" +msgid "General" +msgstr "Általános" + +msgctxt "#30032" +msgid "Codecs" +msgstr "Kodekek" + +msgctxt "#30033" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "Dolby Digital Plus kodek engedélyezése (Atmos Prémium fiókban)" + +#. Unused 30034 +msgctxt "#30035" +msgid "InputStream Adaptive settings..." +msgstr "InputStream Adaptive beállítások..." + +#. Unused 30036 +msgctxt "#30037" +msgid "Skin viewtypes" +msgstr "Felszín nézettípusok" + +msgctxt "#30038" +msgid "Enable custom viewtypes" +msgstr "Saját nézettípusok engedélyetése" + +msgctxt "#30039" +msgid "View ID for folders" +msgstr "Nézettípus azonosító mappákhoz" + +msgctxt "#30040" +msgid "View ID for movies" +msgstr "Nézettípus azonosító filmekhez" + +msgctxt "#30041" +msgid "View ID for shows" +msgstr "Nézettípus azonosító sorozatokhoz" + +msgctxt "#30042" +msgid "View ID for seasons" +msgstr "Nézettípus azonosító szezonokhoz" + +msgctxt "#30043" +msgid "View ID for episodes" +msgstr "Nézettípus azonosító epizódokhoz" + +msgctxt "#30044" +msgid "View ID for profiles" +msgstr "Nézettípus azonosító profilhoz" + +msgctxt "#30045" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "Értékelés visszavonva|Saját értékelés le|Saját értékelés fel" + +msgctxt "#30046" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "Probléma van az Inputstream kiegészítővel, vagy a Widevine könyvtárral. Lehet hogy hiányzik, vagy nincs engedélyezve, a művelet törölve." + +msgctxt "#30047" +msgid "Library update in progress" +msgstr "A médiatár frissítése folyamatban van" + +msgctxt "#30048" +msgid "Exported" +msgstr "Exportált" + +msgctxt "#30049" +msgid "Library" +msgstr "Médiatár" + +msgctxt "#30050" +msgid "Enable Kodi library management" +msgstr "Kodi médiatár kezelés engedélyezve" + +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30052" +msgid "{} Set for library playback" +msgstr "{} Beállítva a médiatár lejátszásához" + +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30055" +msgid "{} Set at start-up" +msgstr "{} Beállítva intításkor" + +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30057" +msgid "{} Remember PIN" +msgstr "{} Ne feledje a PIN-kódot" + +#. Unused 30058 +msgctxt "#30059" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "Légy óvatos, amikor sok címet frissítesz egyszerre {}.[CR]Ez okozhat szolgáltatási problémákat vagy ideiglenes fiók tiltást is.[CR]Kívánod folytatni?" + +msgctxt "#30060" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "HEVC kodek engedélyezése (4k/HDR/DolbyVision Androidnál)" + +msgctxt "#30061" +msgid "Update inside library" +msgstr "Belső médiatár frissítése" + +msgctxt "#30062" +msgid "Parental controls" +msgstr "Szülői felügyelet" + +msgctxt "#30063" +msgid "An update is already in progress" +msgstr "A frissítés már folyamatban van" + +msgctxt "#30064" +msgid "Perform auto-update" +msgstr "Automatikus frissítés teljesítése" + +msgctxt "#30065" +msgid "Video library update" +msgstr "Médiatár frissítése" + +msgctxt "#30066" +msgid "Enable debug logging" +msgstr "Hibakeresési naplózás engedélyezése" + +msgctxt "#30067" +msgid "daily" +msgstr "napi" + +msgctxt "#30068" +msgid "every other day" +msgstr "másnaponta" + +msgctxt "#30069" +msgid "every 5 days" +msgstr "5 naponta" + +msgctxt "#30070" +msgid "weekly" +msgstr "heti" + +msgctxt "#30071" +msgid "Time of Day" +msgstr "Adott időpontban" + +msgctxt "#30072" +msgid "Only start after 5 minutes of idle" +msgstr "Indítás ha 5 percig készenlétben van" + +msgctxt "#30073" +msgid "Always show codec information during video playback" +msgstr "Kodek infó mutatása folyamatosan a videólejátszás közben" + +msgctxt "#30074" +msgid "View for exported" +msgstr "Nézet az exportálthoz" + +msgctxt "#30075" +msgid "Ask to skip intro and recap" +msgstr "Intró átugrásának kérése" + +msgctxt "#30076" +msgid "Skip intro" +msgstr "Intró átugrása" + +msgctxt "#30077" +msgid "Skip recap" +msgstr "Átugrás" + +msgctxt "#30078" +msgid "Playback" +msgstr "Lejátszás" + +msgctxt "#30079" +msgid "Skip automatically" +msgstr "Automatikus átugrás" + +msgctxt "#30080" +msgid "Pause when skipping" +msgstr "Pillanat állj átugrásnál" + +msgctxt "#30081" +msgid "Force HDCP version on video streams" +msgstr "HDCP-verzió kényszerítése a videófolyamokon" + +msgctxt "#30082" +msgid "Remember audio / subtitle preferences" +msgstr "Hang/felirat beállítások megjegyzése" + +msgctxt "#30083" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "Kényszeríteni a kényszerített feliratok megjelenítését csak a beállított nyelvhez" + +msgctxt "#30084" +msgid "Cache objects TTL (minutes)" +msgstr "Gyorsítótár-elem megőrzése (perc)" + +msgctxt "#30085" +msgid "Cache metadata TTL (days)" +msgstr "Gyorsítótár-elem metaadatok megőrzése (nap)" + +msgctxt "#30086" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "Gyorsítótár-elem élettartama a saját listámban (perc)" + +msgctxt "#30087" +msgid "Contains {} and more..." +msgstr "Tartalom {} és több..." + +msgctxt "#30088" +msgid "Browse related content" +msgstr "Kapcsolódó tartalom böngészése" + +msgctxt "#30089" +msgid "Browse subgenres" +msgstr "Műfaj típusok böngészése" + +msgctxt "#30090" +msgid "Browse through related video lists and discover more content." +msgstr "Böngéssz a kapcsolódó video listákon és fedezz fel további tartalmat" + +msgctxt "#30091" +msgid "Browse and manage contents that were exported to the Kodi library." +msgstr "Böngéssz és rendezd a tartalmat ami a Kodi médiatárba exportálásra került" + +msgctxt "#30092" +msgid "Search for content and easily find what you want to watch." +msgstr "Egyszerű, tartalom szerinti keresés" + +msgctxt "#30093" +msgid "Browse content by genre and easily discover related content." +msgstr "Böngészés műfaj szerint, és kapcsolódó tartalom szerint" + +msgctxt "#30094" +msgid "Browse recommendations and pick up on something new you might like." +msgstr "Böngészés ajánlás szerint, találj valami kedvedre valót" + +msgctxt "#30095" +msgid "All TV shows" +msgstr "Minden sorozat" + +msgctxt "#30096" +msgid "All Movies" +msgstr "Minden film" + +msgctxt "#30097" +msgid "Main Menu" +msgstr "Főmenü" + +msgctxt "#30098" +msgid "Enable HDR10" +msgstr "HDR10 engedélyezése" + +msgctxt "#30099" +msgid "Enable Dolby Vision" +msgstr "Dolby Vision endélyezése" + +msgctxt "#30100" +msgid "Results for '{}'" +msgstr "Találat '{}'" + +msgctxt "#30101" +msgid "Netflix returned an unknown error." +msgstr "A Netflix leállt ismeretlen hiba miatt." + +msgctxt "#30102" +msgid "Netflix returned the following error:" +msgstr "A Netflix leállt a következő hiba miatt:" + +msgctxt "#30103" +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "A hiba részleteit a Kodi a naplóba mentette. Ha a hibaüzenet nem ad útmutatást a probléma megoldására, vagy továbbra is jelentkezik a hiba, kérjük, jelentse a GitHubon keresztül, a Readme utasításainak megfelelően." + +msgctxt "#30104" +msgid "The add-on encountered the following error:" +msgstr "A kiegészítő leállt a következő hibával:" + +msgctxt "#30105" +msgid "Netflix Add-on Error" +msgstr "Netflix kiegészítő hiba" + +msgctxt "#30106" +msgid "Invalid PIN" +msgstr "Hibás pinkód" + +msgctxt "#30107" +msgid "PIN protection is off." +msgstr "Pinkód védelem kikapcsolva." + +msgctxt "#30108" +msgid "All content is protected by PIN." +msgstr "Minden tartalom pinkóddal védett." + +msgctxt "#30109" +msgid "Login successful" +msgstr "Bejelentkezés sikeres" + +#. Unused 30110 +msgctxt "#30111" +msgid "There is no contents" +msgstr "Nincs tartalom ehhez" + +msgctxt "#30112" +msgid "You need to be logged in to use Netflix" +msgstr "Be kell jelentkezned a Netflix használatához" + +msgctxt "#30113" +msgid "Logout successful" +msgstr "Bejelentkezés sikeres" + +#. Unused 30114, 30115 +msgctxt "#30116" +msgid "Advanced Add-on Configuration" +msgstr "Haladó beállítások" + +msgctxt "#30117" +msgid "Cache" +msgstr "Gyorsítótár" + +#. Unused 30118, 30119 +msgctxt "#30120" +msgid "Cannot automatically remove a season from Kodi library, please do it manually" +msgstr "A Kodi médiatárból nem törölhető automatikusan az évad, töröld kézzel." + +msgctxt "#30121" +msgid "Perform full sync now" +msgstr "Teljes szinkronizálás indítása most" + +msgctxt "#30122" +msgid "Sync 'My list' to Kodi library" +msgstr "Saját lista szinkronizálása a Kodi médiatárral" + +msgctxt "#30123" +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "Folytatni akarod?[CR]A teljes szinkronizálás törli az összes korábban exportált címet.[CR]Ha sok cím van, ez a művelet több időt vehet igénybe." + +msgctxt "#30124" +msgid "Do you want to remove this item from the library?" +msgstr "Valóban törlöd ezt az elemet?" + +msgctxt "#30125" +msgid "Delete library contents" +msgstr "Médiatár tartalom tisztítása" + +msgctxt "#30126" +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "Valóban tisztítani szertnéd a médiatárat?[CR]Az összes exportált elem törlődik." + +msgctxt "#30127" +msgid "Awarded rating of {}" +msgstr "Értékelés {}" + +msgctxt "#30128" +msgid "Select a profile" +msgstr "Profil választás" + +msgctxt "#30129" +msgid "Enable Up Next integration" +msgstr "UP Next kiegészítő használata" + +msgctxt "#30130" +msgid "Install Up Next add-on" +msgstr "Up Next kiegészítő telepítése" + +msgctxt "#30131" +msgid "Check the logfile for detailed information" +msgstr "A hibanaplót nézd meg, a részletes infóért" + +msgctxt "#30132" +msgid "Clear in-memory cache" +msgstr "Memória gyorsítótár törlése" + +msgctxt "#30133" +msgid "Clear in-memory and on-disk cache" +msgstr "Lemez és memória gyorsítótár törlése" + +msgctxt "#30134" +msgid "Enable execution timing" +msgstr "A végrehajtás ütemezésének engedélyezése" + +msgctxt "#30135" +msgid "Cache cleared" +msgstr "Gyorsítótár törölve" + +msgctxt "#30136" +msgid "Waiting for service start-up..." +msgstr "Várakozás a kiszolgáló indítására..." + +msgctxt "#30137" +msgid "Enable VP9 codec" +msgstr "VP9 kodek engedélyezése" + +msgctxt "#30138" +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "Lehet, hogy a háttérszolgáltatások még nem érhetők el, vagy éppen elindította a Kodi/a kiegészítőt. Ebben az esetben próbálkozzon újra egy később" + +msgctxt "#30139" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "IPC engedélyezése HTTP-n keresztül (az Addon Signals időtúllépéseinek használata esetén)" + +msgctxt "#30140" +msgid "Import existing library" +msgstr "Meglévő médiatár importálása" + +#. Unused 30141 to 30143 +msgctxt "#30144" +msgid "Disable WebVTT subtitle support" +msgstr "WebVTT felirat támogatás tiltása" + +msgctxt "#30145" +msgid "Recently added" +msgstr "Utoljára hozzáadva" + +msgctxt "#30146" +msgid "Discover the latest content added." +msgstr "Fedezze fel a frissen hozzáadott tartalmat." + +msgctxt "#30147" +msgid "Next page »" +msgstr "Következő oldal »" + +msgctxt "#30148" +msgid "« Previous page" +msgstr "« Előző oldal" + +msgctxt "#30149" +msgid "Sort results by" +msgstr "Tartalom rendezése" + +msgctxt "#30150" +msgid "Alphabetical order (A-Z)" +msgstr "Betűrendben (A-Z)" + +msgctxt "#30151" +msgid "Alphabetical order (Z-A)" +msgstr "Betűrendben (Z-A)" + +msgctxt "#30152" +msgid "Suggestions for you" +msgstr "Javaslatok neked" + +msgctxt "#30153" +msgid "Year released" +msgstr "Bemutató éve" + +msgctxt "#30154" +msgid "Netflix configuration wizard" +msgstr "Netflix kiegészítő beállítása" + +msgctxt "#30155" +msgid "Do you have a Netflix Premium account?" +msgstr "Van Netflix prémium hozzáférésed?" + +#. Unused 30156 +msgctxt "#30157" +msgid "The configuration has been completed" +msgstr "Beállítás kész" + +msgctxt "#30158" +msgid "Restore the add-on default configuration" +msgstr "Állítsa vissza a kiegészítő alapértelmezett konfigurációját" + +msgctxt "#30159" +msgid "View ID for main menu" +msgstr "Nézettípus azonosító a főmenühöz" + +msgctxt "#30160" +msgid "View ID for search" +msgstr "Nézettípus azonosító a kereséshez" + +msgctxt "#30161" +msgid "View ID for exported" +msgstr "Nézettípus azonosító exportálthoz" + +msgctxt "#30162" +msgid "Last used" +msgstr "Utoljára használt" + +msgctxt "#30163" +msgid "Audio description" +msgstr "Hang narrátor" + +msgctxt "#30164" +msgid "Browse contents with audio description." +msgstr "Böngészés hang narrátor alaplán " + +msgctxt "#30165" +msgid "View ID for 'My list'" +msgstr "Nézettípus azonosító a saját listámhoz" + +msgctxt "#30166" +msgid "Main menu items" +msgstr "Főmenü elemek" + +msgctxt "#30167" +msgid "My list" +msgstr "Saját lista" + +msgctxt "#30168" +msgid "Continue watching" +msgstr "Folyamatban levő" + +msgctxt "#30169" +msgid "Top picks" +msgstr "Legjobb válogatások" + +msgctxt "#30170" +msgid "New releases" +msgstr "Új megjelenések" + +msgctxt "#30171" +msgid "Trending now" +msgstr "Trendi most" + +msgctxt "#30172" +msgid "Popular on Netflix" +msgstr "Népszerű a Netflixen" + +msgctxt "#30173" +msgid "Netflix Originals" +msgstr "Eredeti Netflix gyártás" + +msgctxt "#30174" +msgid "TV show genres" +msgstr "Sorozat műfajok" + +msgctxt "#30175" +msgid "Movie genres" +msgstr "Film műfajok" + +msgctxt "#30176" +msgid "Enable STRM resume workaround" +msgstr "Engedélyezze az STRM folytatódó munkafolyamatot" + +msgctxt "#30177" +msgid "Ask to resume the video" +msgstr "Kérdés hogy folytassa-e a videót" + +msgctxt "#30178" +msgid "Open Up Next add-on settings" +msgstr "Up Next kiegészítő beállítások megnyitása" + +msgctxt "#30179" +msgid "Show trailers and more" +msgstr "Bemutatók és egyebek mutatása" + +msgctxt "#30180" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "Probléma merült fel a bejelentkezés során, lehetséges, hogy fiókját nem erősítették meg vagy nem aktiválták" + +msgctxt "#30181" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "Mindig mutassa a feliratot, ha a preferált audio nyelv nem érhető el" + +msgctxt "#30182" +msgid "Export NFO files" +msgstr "NFO fájlok exportálása" + +msgctxt "#30183" +msgid "Do you want to export NFO files for the {}?" +msgstr "Szeretnéd az NFO fájlokat exportálni {}?" + +msgctxt "#30184" +msgid "NFO Files" +msgstr "NFO fájlok" + +msgctxt "#30185" +msgid "Enable NFO files export" +msgstr "NFO fájlok exportálásának engedélyezése" + +msgctxt "#30186" +msgid "For Movies" +msgstr "Filmekhez" + +msgctxt "#30187" +msgid "For TV show" +msgstr "Sorozatokhoz" + +msgctxt "#30188" +msgid "Ask" +msgstr "Kérdez" + +msgctxt "#30189" +msgid "movie" +msgstr "film" + +msgctxt "#30190" +msgid "TV show" +msgstr "sorozat" + +#. Unused 30191 +msgctxt "#30192" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]Ezeket a fájlokat a szolgáltatói információs scrapperek használják a filmek és a sorozatok információinak integrálására, hasznosak a bónusz epizódok vagy a rendezői vágások során." + +msgctxt "#30193" +msgid "Include tv show NFO details" +msgstr "Tartalmazza a Sorozat NFO részleteit" + +msgctxt "#30194" +msgid "Limit video stream resolution to" +msgstr "Videostream felbontási limit" + +msgctxt "#30195" +msgid "Export new episodes" +msgstr "Új sorozatok exportálása" + +msgctxt "#30196" +msgid "Exclude from auto update" +msgstr "Kivétel az auto frissítésből" + +msgctxt "#30197" +msgid "Include in auto update" +msgstr "Belefoglalva az auto frissítésbe" + +msgctxt "#30198" +msgid "Exporting new episodes" +msgstr "Új epizódok exportálása" + +msgctxt "#30199" +msgid "Shared library (Kodi MySQL server is required)" +msgstr "Megosztott könyvtár (Kodi MySQL szerver kell hozzá)" + +msgctxt "#30200" +msgid "Use shared library MySQL-database" +msgstr "MySQL szerver használata a média adatbázishoz" + +msgctxt "#30201" +msgid "Username" +msgstr "Felhasználónév" + +msgctxt "#30202" +msgid "Connection to the MySQL-database was successful" +msgstr "MySQL adatbázis kapcsolat sikeres" + +msgctxt "#30203" +msgid "Host IP" +msgstr "Host IP" + +msgctxt "#30204" +msgid "Port" +msgstr "Port" + +msgctxt "#30205" +msgid "Test database connection" +msgstr "Adatbázis csatlakozási teszt" + +msgctxt "#30206" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" +msgstr "HIBA: A MySQL adatbázis nem érhető el - a helyi adatbázist fogjuk használni" + +msgctxt "#30207" +msgid "Set this device as main auto-updates manager" +msgstr "Eszköz beállítása automatikus frissítéskezelőként" + +msgctxt "#30208" +msgid "Check if this device is the main auto-update manager" +msgstr "Megnézi hogy ez az eszköz, automatikus frissítéskezelő-e" + +msgctxt "#30209" +msgid "This device now handles the auto-updates of the shared library" +msgstr "Eszköz most automatikus frissítéskezelőként beállítva" + +msgctxt "#30210" +msgid "This device handles the auto-updates of the shared library" +msgstr "Eszköz beállítása automatikus frissítéskezelőként" + +msgctxt "#30211" +msgid "Another device handles the auto-updates of the shared library" +msgstr "Másik eszköz beállítása automatikus frissítéskezelőként" + +msgctxt "#30212" +msgid "There is no device that handles the auto-updates of the shared library" +msgstr "Nincs eszköz beállítva automatikus frissítéskezelőként" + +msgctxt "#30213" +msgid "InputStream Helper settings..." +msgstr "InputStream Helper beállítások..." + +msgctxt "#30214" +msgid "Force refresh" +msgstr "Kényszerített frissítés" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "Saját listám elemeinek színe" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "Az emlékeztetőre felvett címek színe" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "A Szinkronizálás kész, infó elrejtése" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "Kodi médiatár frissítve" + +msgctxt "#30221" +msgid "Owner account" +msgstr "Felhasználói fiók " + +msgctxt "#30222" +msgid "Kid account" +msgstr "Gyermek fiók" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "Automata frissítési mód" + +msgctxt "#30225" +msgid "Manual" +msgstr "Kézi" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "Ütemezett" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "Kodi médiatár szinronizálása a Saját listámmal" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "Profil választás a szinkronizáláshoz" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "Kodi indításakor" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "Frissítés keresése most" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "Akarsz most keresni frissítést?" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "Életkor szerinti értékelés {}" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "Csak az értékelt címeket mutatja [B]{}[/B]" + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "Nézett státusz szinkronizálása a Netflix-el" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "Nézett státusz változtatása (helyileg)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "Nézettként jelöl|Nem látottként jelöl|Netflix nézett státusz visszaállítása" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Up Next jelzés (következő videó nézése)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "A kiegészítő információ színe" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "A háttér folyamat nem indult el a következő hiba miatt:[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Open Connect CDN (alacsonyabb index jobb)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "Top 10" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "Első nem látott sorozatrész kiválasztása" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "A médiatárba exportált fájlok törlése" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "Vannak még {} címek amiket nem lehet importálni.[CR]Akarod törölni ezeket?" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "Eredmények oldalanként (időtúllépés esetén csökkenti azt)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "Törlöd a nézett állapotot \"{}\"?" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "Tartalmazza a Kodi médiatárat (csak adatok küldése)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "Válassza ki a bejelentkezési módot[CR](ha a bejelentkezés E-Mail/Jelszóval mindig hibával visszatér \"hibás jelszó\", akkor használja a Hitelesítési kulcsot, a GitHub ReadMe-ben található leírás szerint)" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "E-Mail/Jelszó" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "Hitelesítési kulcs" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "A kiválasztott fájl nem kompatibilis, vagy sérült" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "A hitelesítési kulcs fájl lejárt" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "PIN megadása" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "Rendezés időrend szerint" + +msgctxt "#30400" +msgid "Search" +msgstr "Keresés" + +msgctxt "#30401" +msgid "Type of search" +msgstr "Keresési típus" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "Címek, emberek, műfajok keresése" + +msgctxt "#30403" +msgid "New search..." +msgstr "Új keresés" + +msgctxt "#30404" +msgid "Clear search history" +msgstr "Keresési előzmények törlése" + +msgctxt "#30405" +msgid "Select the language" +msgstr "Nyelv választás" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "Törlöd a tartalmat?" + +msgctxt "#30407" +msgid "No matches found" +msgstr "Nincs találat" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "Kifejezés szerint" + +msgctxt "#30411" +msgid "By audio language" +msgstr "Szinkronhang nyelv szerint" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "Feliratnyelv szerint" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "Műfaj/alműfaj azonosító szerint" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "Előnyben részesíti a hang/felirat nyelvét országkóddal (ha van)" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "Alapértelmezés szerint előnyben részesíti a sztereó hangsávokat" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "ESN / Widevine beállítások" + +msgctxt "#30601" +msgid "ESN:" +msgstr "ESN" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "ESN változtatása" + +msgctxt "#30603" +msgid "Save system info" +msgstr "Rendszerinfó mentése" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "Widevine - kényszerített biztonsági szint" + +msgctxt "#30605" +msgid "Force {}" +msgstr "Kényszerített" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "ESN teljesen beállítva[CR]Próbálj elindítani egy filmet" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "Ez az ESN nem használható[CR]Próbálj ESN-t váltani vagy resetelni az ESN-t.[CR][CR]Hiba részletek:[CR]{}" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "Rossz ESN formátum" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "Akarod az összes beállítást visszaállítani?" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "Rendszer-alapú titkosítást használj a hitelesítő adatokhoz" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "[B]Ha letiltja, biztonsági rést okoz[/B]. Ha az operációs rendszer nem támogatja, akkor minden alkalommal, amikor újraindítja az eszközt, a rendszer felszólítja, hogy jelentkezzen be, [B]csak[/B] ebben az esetben . Ha módosítja ezt a beállítást, a rendszer a következő újraindításkor újra bejelentkezést fog kérni." + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "MSL manifest verziója" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "Ez a cím elérhető lesz ettől az időponttól:[CR]{}" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "Akarod az előzetes bemutatót lejátszani" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "{} Emlékeztető" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "Új és népszerű" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "Az elkezdett sorozatokat jelöld nézettként" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "Kérjük, vegye figyelembe, hogy ha engedélyezi ezt a beállítást, és a szűrők engedélyezve vannak a felszín beállításaiban, előfordulhat, hogy a megtekintettként megjelölt elemek nem jelennek meg." + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "Az adatfolyam kiválasztási típusának felülbírálása" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "Az InputStream Adaptive bővítmény adatfolyam típus beállításának felülbírálása annak beállításához, hogy lejátszás közben választhassuk ki az audio/video folyam minőségét." + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "Alkalmazkodó minőség" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "Rögzített videó minőség" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "Kérdezz rá a videó minőségre" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "Kérjük, vegye figyelembe, hogy ennek a beállításnak az engedélyezése kényszeríti a beállított tartományon kívüli adatfolyamok eltávolítását, és ez hatással lehet az InputStream Adaptive működésére. Ha lehetséges, próbálja meg először korlátozni a minőséget az InputStream Adaptive beállításaiban." + +msgctxt "#30721" +msgid "Size" +msgstr "Méret" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "Minimálisra csökkenti a fekete sávokat zoom effektus alkalmazásával. [ Rögzített ] Beállítja, hogy a fekete sávok mekkora képernyőterületet foglalhatnak el, minden videónál állandó lesz. [ Relatív ] Beállítja a fekete sáv minimalizálásának százalékos arányát, az egyes videóméretektől függően. [ Reset ] Állítsa vissza a korábban alkalmazott nagyítási effektust" + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "Ez a funkció a Kodi zoom effektust használja, amely állandóan tárolva van a \"Nézet mód\" videóbeállításban. Ha a jövőben letiltja ezt a funkciót, a zoom megmarad, így visszaállítja a nagyítást a \"Reset\" beállítási módra." + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "AV1 kodek engedélyezése" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "A Netflix mappák hozzáadása a Kodi médiatár forrásokhoz" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "A \"Netflix-Filmek\" és \"Netflix-sorozatok\" mappák hozzáadva a Kodi forrásokhoz, Kodi újraindítás szükséges, hogy megjelenjenek a Kodi [Sorozatok] / [Filmek] menükben. Ezután szerkeszthetőek a mappák a megfelelő tartalomtípus és az információs szolgáltató beállításával." + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "[Kézi] A frissítéseket manuálisan kell elvégezni az \"Export új epizódok\" helyi menü használatával az egyes sorozatoknál, [Ütemezett] Az automatikus frissítéseket az ütemezett időpontokban hozza létre, [amikor Kodi elindul] ez naponta egyszer fog megtörténni" + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "Szinkronizálja a médiatárat a saját listámmal, ha a listámat kívülről módosították, pl. Mobilalkalmazás vagy webhely, a frissítést az ütemezett időre elhalasztja." + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "Ha engedélyezve van, hozzáadhat információkat az epizódokhoz vagy filmekhez. Ez akkor lehet hasznos, ha a KODI Információs Szolgáltató Scraper nem szolgáltat megfelelő adatokat. Hozzáadja a videók időtartamát is." + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "Csak akkor engedélyezze, ha a \"helyi információk\" információszolgáltató nem használható, vagy ha bizonyos címek nem jelennek meg a Kodi könyvtárban, annak ellenére, hogy hozzáadta." + +msgctxt "#30734" +msgid "Support" +msgstr "Támogatás" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "A Netflix kiegészítőről" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "Automatikusan új ESN-eket generál (kerülő megoldás az 540p-s korláthoz)" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "FIGYELMEZTETÉS: Ne használja a hivatalos alkalmazás eredeti ESN-jét." + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "Hangeltolás engedélyezése" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "Hangeltolás" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "Beállítja a hangeltolást a Kodi-beállítás felülbírálásával" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "VP9 Profile 2 engedélyezése (10/12 bites színmélység)" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "Szeretné engedélyezni a HDR10 támogatást?" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "Szeretné engedélyezni a HDR Dolby Vision támogatást?" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "A kiegészítő beállítja a megfelelő HDCP-verziót, de ha szükséges, kényszerítheti a videofolyamok kérését egy másik HDCP-verzióhoz." + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "Ha az eszköz nem támogatja ezt a kodeket, előfordulhat, hogy fekete képernyőt lát, nincs kép vagy sérült a kép. Ha igen, kapcsolja ki ezt a kodeket." + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "A fekete sávok minimalizálási módja" + +msgctxt "#30747" +msgid "Fixed" +msgstr "Rögzített" + +msgctxt "#30748" +msgid "Relative" +msgstr "Képarányfüggő" + +msgctxt "#30749" +msgid "Reset" +msgstr "Visszaállítás" diff --git a/resources/language/resource.language.it_it/strings.po b/resources/language/resource.language.it_it/strings.po index f67404589..562145bdb 100644 --- a/resources/language/resource.language.it_it/strings.po +++ b/resources/language/resource.language.it_it/strings.po @@ -1,88 +1,90 @@ # Kodi Media Center language file # Addon Name: Netflix # Addon id: plugin.video.netflix -# Addon Provider: libdev + jojo + asciidisco + caphm + CastagnaIT +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait msgid "" msgstr "" "Project-Id-Version: plugin.video.netflix\n" "Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" -"POT-Creation-Date: 2017-07-24 16:15+0000\n" -"PO-Revision-Date: 2019-08-19 20:40+0000\n" -"Last-Translator: Stefano Gottardo <>\n" +"POT-Creation-Date: 2017-07-24 00:00+0000\n" +"PO-Revision-Date: 2023-01-14 00:00+0000\n" +"Last-Translator: Stefano Gottardo\n" "Language-Team: Italian\n" +"Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: it\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" msgctxt "Addon Summary" msgid "Netflix" -msgstr "" +msgstr "Netflix" msgctxt "Addon Description" -msgid "Netflix VOD Services Addon" -msgstr "Addon per i servizi VOD di Netflix" +msgid "Netflix VOD Services Add-on" +msgstr "Add-on per i servizi VOD di Netflix" msgctxt "Addon Disclaimer" -msgid "Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing." -msgstr "Alcune parti di questa addon potrebbero non essere legali nel tuo paese - verifica le leggi in vigore prima di installare." +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "L'utilizzo di questo add-on potrebbe non essere legale nel vostro paese di residenza - si prega di verificare le leggi in vigore prima dell'installazione." msgctxt "#30001" msgid "Recommendations" msgstr "Consigliati" msgctxt "#30002" -msgid "Adult Pin" -msgstr "Pin per il controllo parentale" - -msgctxt "#30003" -msgid "Search term" -msgstr "Ricerca per titoli, persone, generi" +msgid "Enter PIN to watch restricted content" +msgstr "Inserisci il PIN per guardare i contenuti con accesso limitato" +#. Unused 30003 msgctxt "#30004" msgid "Password" -msgstr "" +msgstr "Password" msgctxt "#30005" msgid "E-mail" -msgstr "" +msgstr "E-mail" msgctxt "#30006" -msgid "Adult verification failed" -msgstr "Controllo parentale non riuscito" +msgid "Enter PIN to access this profile" +msgstr "Inserisci PIN per accedere a questo profilo" msgctxt "#30007" -msgid "Please Check your adult pin" -msgstr "Verifica che il pin di controllo parentale sia corretto" +msgid "Parental Control PIN (4 digits)" +msgstr "PIN filtro famiglia (4 cifre)" msgctxt "#30008" msgid "Login failed" msgstr "Accesso non riuscito" msgctxt "#30009" -msgid "Please Check your credentials" -msgstr "Verifica le tue credenziali" +msgid "Please check your e-mail and password" +msgstr "Controlla la tua e-mail e password" msgctxt "#30010" msgid "Lists of all kinds" msgstr "Liste di ogni tipo" -msgctxt "#30011" -msgid "Search" -msgstr "Ricerca" - +#. Unused 30011 msgctxt "#30012" -msgid "No seasons available" -msgstr "Nessuna stagione disponibile" +msgid "Profiles options and parental control" +msgstr "Opzioni profili e filtro famiglia" msgctxt "#30013" -msgid "No matches found" -msgstr "Nessuna corrispondenza trovata" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "Nell'elenco dei profili, aprire il menu contestuale nel profilo scelto" msgctxt "#30014" msgid "Account" -msgstr "" +msgstr "Account" + +msgctxt "#30015" +msgid "Other options" +msgstr "Altre opzioni" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "Se si effettua la disconnessione, tutti i dati dell'account saranno rimossi, ma i file dei video esportati nella libreria Kodi saranno conservati.[CR]Vuoi continuare?" msgctxt "#30017" msgid "Logout" @@ -105,7 +107,7 @@ msgid "Add to 'My list'" msgstr "Aggiungi a 'La mia lista'" msgctxt "#30022" -msgid "(between 0 & 10)" +msgid "(between 0 and 10)" msgstr "(tra 0 e 10)" msgctxt "#30023" @@ -132,157 +134,135 @@ msgctxt "#30028" msgid "Playback error" msgstr "Errore di riproduzione" -msgctxt "#30029" -msgid "Missing InputStream addon" -msgstr "Addon InputStream mancante" - +#. Unused 30029 msgctxt "#30030" msgid "Remove from library" msgstr "Rimuovi dalla libreria" msgctxt "#30031" -msgid "Change library title" -msgstr "Modifica il titolo della libreria" +msgid "General" +msgstr "Generale" msgctxt "#30032" -msgid "Tracking" -msgstr "" +msgid "Codecs" +msgstr "Codec" msgctxt "#30033" -msgid "Enable Dolby Digital Plus (DDPlus HQ and Atmos on Premium)" -msgstr "Abilita Dolby Digital Plus (DDPlus HQ e Atmos su Premium)" - -msgctxt "#30034" -msgid "ESN (set automatically, can be changed manually)" -msgstr "ESN" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "Abilita codec Dolby Digital Plus (Atmos su account Premium)" +#. Unused 30034 msgctxt "#30035" msgid "InputStream Adaptive settings..." msgstr "Configurazione InputStream Adaptive..." -msgctxt "#30036" -msgid "Always use the original title on export" -msgstr "Utilizza sempre il titolo originale" - +#. Unused 30036 msgctxt "#30037" -msgid "Views" -msgstr "Viste" +msgid "Skin viewtypes" +msgstr "Tipi di vista Skin" msgctxt "#30038" -msgid "Enable custom views" -msgstr "Abilita viste personalizzate" +msgid "Enable custom viewtypes" +msgstr "Abilita tipi di vista personalizzati" msgctxt "#30039" -msgid "View for folders" -msgstr "Vista per cartelle" +msgid "View ID for folders" +msgstr "ID vista per cartelle" msgctxt "#30040" -msgid "View for movies" -msgstr "Vista per film" +msgid "View ID for movies" +msgstr "ID vista per film" msgctxt "#30041" -msgid "View for shows" -msgstr "Vista per serie" +msgid "View ID for shows" +msgstr "ID vista per serie tv" msgctxt "#30042" -msgid "View for seasons" -msgstr "Vista per stagioni" +msgid "View ID for seasons" +msgstr "ID vista per stagioni" msgctxt "#30043" -msgid "View for episodes" -msgstr "Vista per episodi" +msgid "View ID for episodes" +msgstr "ID vista per episodi" msgctxt "#30044" -msgid "View for profiles" -msgstr "Vista per profili" +msgid "View ID for profiles" +msgstr "ID vista per profili" msgctxt "#30045" -msgid "[COLOR cyan][B]-- NEXT PAGE --[/B][/COLOR]" -msgstr "[COLOR cyan][B]-- PAGINA SUCCESSIVA --[/B][/COLOR]" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "Valutazione rimossa|Hai valutato con un non mi piace|Hai valutato con un mi piace" msgctxt "#30046" -msgid "InputStream addon is not enabled" -msgstr "L'addon InputStream Adaptive non è abilitato" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "Si è verificato un problema con InputStream Adaptive add-on o con la libreria Widevine. Potrebbe essere mancante o non abilitata, l'operazione è stata annullata." msgctxt "#30047" -msgid "Finally remove?" -msgstr "Rimuovere in modo permanente?" +msgid "Library update in progress" +msgstr "Aggiornamento della libreria in corso" msgctxt "#30048" msgid "Exported" msgstr "Esportati" msgctxt "#30049" -msgid "Update DB" -msgstr "Aggiorna DB" +msgid "Library" +msgstr "Libreria" msgctxt "#30050" -msgid "Update successful" -msgstr "Aggiornamento avvenuto con successo" - -msgctxt "#30051" -msgid "Request Error" -msgstr "Richiesta fallita" +msgid "Enable Kodi library management" +msgstr "Abilita la gestione della libreria Kodi" +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30052" -msgid "Unable to complete the request at this time" -msgstr "Non è stato possibile completare la richiesta" - -msgctxt "#30053" -msgid "Auto Login" -msgstr "Login Automatico" - -msgctxt "#30054" -msgid "Enable Auto Login" -msgstr "Abilita l'accesso automatico" +msgid "{} Set for library playback" +msgstr "{} Imposta per riproduzione dalla libreria" +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30055" -msgid "Profile" -msgstr "Profilo" - -msgctxt "#30056" -msgid "ID" -msgstr "" +msgid "{} Set at start-up" +msgstr "{} Imposta all'avvio" +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30057" -msgid "Select profile in profile listing -> context menu" -msgstr "Seleziona il profilo dalla lista profili -> menu contestuale" - -msgctxt "#30058" -msgid "Auto Login enabled!" -msgstr "Accesso automatico abilitato!" +msgid "{} Remember PIN" +msgstr "{} Memorizza PIN" +#. Unused 30058 msgctxt "#30059" -msgid "Switch accounts" -msgstr "Cambia account" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "Fare attenzione, stai per aggiornare molti titoli {}.[CR]Questo potrebbe causare problemi di servizio o il temporaneo blocco dell'account.[CR]Vuoi continuare?" msgctxt "#30060" -msgid "Enable HEVC profiles (4k for Android/HDR/DolbyVision)" -msgstr "Abilita HEVC (4k per Android/HDR/DolbyVision)" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "Abilita codec HEVC (4k/HDR/Dolby Vision su Android)" msgctxt "#30061" msgid "Update inside library" msgstr "Aggiorna nella libreria" msgctxt "#30062" -msgid "Enable/disable adult pin. Active:" -msgstr "Abilita/disabilita controllo parentale. Attivo:" +msgid "Parental controls" +msgstr "Filtro famiglia" msgctxt "#30063" -msgid "new episodes added to library" -msgstr "nuovi episodi aggiunti alla libreria" +msgid "An update is already in progress" +msgstr "Un aggiornamento è già in corso" msgctxt "#30064" -msgid "Export new episodes" -msgstr "Esporta nuovi episodi" +msgid "Perform auto-update" +msgstr "Programma l'aggiornamento" msgctxt "#30065" -msgid "Auto-update" -msgstr "Aggiornamento automatico" +msgid "Video library update" +msgstr "Aggiornamento della videoteca" msgctxt "#30066" -msgid "never" -msgstr "mai" +msgid "Enable debug logging" +msgstr "Abilita log di debug" msgctxt "#30067" msgid "daily" @@ -309,8 +289,8 @@ msgid "Only start after 5 minutes of idle" msgstr "Avvia solo dopo 5 minuti di inattività" msgctxt "#30073" -msgid "Check every X minutes if update scheduled" -msgstr "Controlla ogni X minuti se l'aggiornamento è schedulato" +msgid "Always show codec information during video playback" +msgstr "Visualizza sempre le info di codifica durante la riproduzione" msgctxt "#30074" msgid "View for exported" @@ -341,28 +321,28 @@ msgid "Pause when skipping" msgstr "Dopo il salto metti in pausa" msgctxt "#30081" -msgid "Force support of HDCP 2.2 (enable 4K content)" -msgstr "Forza il supporto per HDCP 2.2 (abilita contenuti 4K)" +msgid "Force HDCP version on video streams" +msgstr "Forza versione HDCP sui flussi video" msgctxt "#30082" msgid "Remember audio / subtitle preferences" msgstr "Ricorda preferenze audio/sottotitoli" msgctxt "#30083" -msgid "Save bookmarks for library items / mark as watched" -msgstr "Salva segnalibri per le voci della libreria / contrassegnate già viste" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "Forzare la visualizzazione dei sottotitoli forzati solo con la lingua audio impostata" msgctxt "#30084" -msgid "Cache item time-to-live (minutes)" -msgstr "Mantieni oggetti in Cache per (minuti)" +msgid "Cache objects TTL (minutes)" +msgstr "Cache oggetti TTL (minuti)" msgctxt "#30085" -msgid "Cache item time-to-live for metadata (days)" -msgstr "Mantieni metadati in Cache per (giorni)" +msgid "Cache metadata TTL (days)" +msgstr "Cache metadati TTL (giorni)" msgctxt "#30086" -msgid "Invalidate entire cache on modification of My List" -msgstr "Invalida l'intera cache quando 'La mia lista' viene modificata" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "Cache oggetti 'La mia lista' TTL (minuti)" msgctxt "#30087" msgid "Contains {} and more..." @@ -397,7 +377,7 @@ msgid "Browse recommendations and pick up on something new you might like." msgstr "Sfoglia i consigli proposti e scopri qualcosa di nuovo che potrebbe piacerti." msgctxt "#30095" -msgid "All TV Shows" +msgid "All TV shows" msgstr "Tutte le Serie TV" msgctxt "#30096" @@ -409,16 +389,16 @@ msgid "Main Menu" msgstr "Menu Principale" msgctxt "#30098" -msgid "Enable HDR profiles" -msgstr "Abilita profili HDR" +msgid "Enable HDR10" +msgstr "Abilita HDR10" msgctxt "#30099" -msgid "Enable DolbyVision profiles" -msgstr "Abilita profili DolbyVision" +msgid "Enable Dolby Vision" +msgstr "Abilita Dolby Vision" msgctxt "#30100" -msgid "Results for \"{}\"" -msgstr "Risultati per \"{}\"" +msgid "Results for '{}'" +msgstr "Risultati per '{}'" msgctxt "#30101" msgid "Netflix returned an unknown error." @@ -429,37 +409,34 @@ msgid "Netflix returned the following error:" msgstr "Netflix ha restituito il seguente errore:" msgctxt "#30103" -msgid "Error details have been written to the Kodi logfile. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it via GitHub. Please provide a full debug log with your error report (enable \"Debug Logging\" in Kodi settings)." -msgstr "I dettagli dell'errore sono stati scritti nel file di log di Kodi. Se il messaggio di errore non fornisce indicazioni su come risolvere il problema o se l'errore continua a presentarsi, si prega di segnalarlo tramite GitHub. Nella segnalazione fornire un report dell'errore completo di log di debug (Attiva \"Logging di debug\" dalle impostazioni di Kodi)." +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "I dettagli dell'errore sono stati scritti nel file di log di Kodi. Se il messaggio di errore non fornisce indicazioni su come risolvere il problema o se l'errore continua a presentarsi, si prega di segnalarlo tramite GitHub seguendo le istruzioni nel Readme." msgctxt "#30104" -msgid "The addon encountered to following error:" -msgstr "L'Addon ha riscontrato il seguente errore:" +msgid "The add-on encountered the following error:" +msgstr "L'Add-on ha riscontrato il seguente errore:" msgctxt "#30105" -msgid "Netflix Addon Error" -msgstr "Errore Netflix Addon" +msgid "Netflix Add-on Error" +msgstr "Errore Netflix Add-on" msgctxt "#30106" msgid "Invalid PIN" msgstr "PIN non valido" msgctxt "#30107" -msgid "Disabled PIN verfication" -msgstr "Verifica del PIN disabilitata" +msgid "PIN protection is off." +msgstr "La protezione PIN è disattivata." msgctxt "#30108" -msgid "Enabled PIN verfication" -msgstr "Verifica del PIN abilitata" +msgid "All content is protected by PIN." +msgstr "Tutti i contenuti sono protetti da PIN." msgctxt "#30109" msgid "Login successful" -msgstr "Login riuscito" - -msgctxt "#30110" -msgid "Background services started" -msgstr "Servizi in background avviati" +msgstr "Accesso riuscito" +#. Unused 30110 msgctxt "#30111" msgid "There is no contents" msgstr "Non ci sono contenuti" @@ -472,85 +449,71 @@ msgctxt "#30113" msgid "Logout successful" msgstr "Disconnessione riuscita" -msgctxt "#30114" -msgid "Keep My List and Kodi Library in sync" -msgstr "Mantieni 'La mia lista' e la libreria Kodi sincronizzate" - -msgctxt "#30115" -msgid "Content Profiles" -msgstr "Profili per contenuti" - +#. Unused 30114, 30115 msgctxt "#30116" -msgid "Advanced Addon Configuration" -msgstr "Configurazione avanzata Addon" +msgid "Advanced Add-on Configuration" +msgstr "Configurazione avanzata dell'Add-on" msgctxt "#30117" msgid "Cache" msgstr "Gestione Cache" -msgctxt "#30118" -msgid "API operation failed: {}" -msgstr "API operazione fallita: {}" - -msgctxt "#30119" -msgid "My List operation successful" -msgstr "'La mia lista' operazione riuscita" - +#. Unused 30118, 30119 msgctxt "#30120" msgid "Cannot automatically remove a season from Kodi library, please do it manually" msgstr "Non è possibile rimuovere automaticamente una stagione dalla libreria Kodi, si prega di farlo manualmente." msgctxt "#30121" msgid "Perform full sync now" -msgstr "Esegui sincronizzazione adesso" +msgstr "Esegui adesso la sincronizzazione completa" msgctxt "#30122" -msgid "Sync My List to Kodi library" +msgid "Sync 'My list' to Kodi library" msgstr "Sincronizza 'La mia lista' con la libreria Kodi" msgctxt "#30123" -msgid "Do you really want to proceed?\nThis will delete all previously exported items and export all items from My List for the currently active profile.\nThis operation may take some time..." -msgstr "Vuoi davvero proseguire?\nQuesta operazione eliminerà tutti gli oggetti esportati in precedenza ed esporterà tutti gli oggetti contenuti in 'La mia lista' per il profilo attualmente attivo.\nQuesta operazione richiederà un pò di tempo..." +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "Vuoi continuare?[CR]La sincronizzazione completa eliminerà tutti i file esportati in precedenza.[CR]Se sono presenti molti titoli questa operazione potrebbe richiedere un pò di tempo." msgctxt "#30124" -msgid "Do you really want to remove this item?" -msgstr "Vuoi realmente rimuovere quest'oggetto?" +msgid "Do you want to remove this item from the library?" +msgstr "Vuoi rimuovere l'oggetto dalla libreria?" msgctxt "#30125" -msgid "Purge library" -msgstr "Svuota libreria" +msgid "Delete library contents" +msgstr "Elimina i contenuti della libreria" msgctxt "#30126" -msgid "Do you really want to purge your library?\nAll exported items will be deleted." -msgstr "Vuoi realmente svuotare la tua libreria?\nTutte le esportazioni saranno eliminate." +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "Vuoi realmente svuotare la tua libreria?[CR]Tutte le esportazioni saranno eliminate." msgctxt "#30127" msgid "Awarded rating of {}" msgstr "Punteggio assegnato di {}" msgctxt "#30128" -msgid "No contained titles yet..." -msgstr "Per adesso non ci sono ancora dei titoli contenuti..." +msgid "Select a profile" +msgstr "Seleziona un profilo" msgctxt "#30129" msgid "Enable Up Next integration" -msgstr "Abilita integrazione 'Prossimi video'" +msgstr "Abilita integrazione con Up Next" msgctxt "#30130" -msgid "Show notification instead of modal dialog for errors" -msgstr "Mostra una notifica invece della finestra modale per gli errori" +msgid "Install Up Next add-on" +msgstr "Installa Up Next add-on" msgctxt "#30131" msgid "Check the logfile for detailed information" msgstr "Controlla il file di log per informazioni dettagliate" msgctxt "#30132" -msgid "Purge in-memory cache" -msgstr "Svuota cache in memoria" +msgid "Clear in-memory cache" +msgstr "Elimina cache in memoria" msgctxt "#30133" -msgid "Purge in-memory and on-disk cache" -msgstr "Svuota cache in memoria e su disco" +msgid "Clear in-memory and on-disk cache" +msgstr "Elimina cache in memoria e su disco" msgctxt "#30134" msgid "Enable execution timing" @@ -558,40 +521,29 @@ msgstr "Abilita tempi di esecuzione" msgctxt "#30135" msgid "Cache cleared" -msgstr "Cache ripulita" +msgstr "Cache eliminata" msgctxt "#30136" -msgid "Enable 1080p Unlock (not recommended for Android)" -msgstr "Abilita sblocco 1080p (non raccomandato per Android)" +msgid "Waiting for service start-up..." +msgstr "In attesa dell'avvio del servizio..." msgctxt "#30137" -msgid "Enable VP9 profiles (disable if it causes artifacts)" -msgstr "Abilita profili VP9 (disabilitare se causa artefatti)" +msgid "Enable VP9 codec" +msgstr "Abilita codec VP9" msgctxt "#30138" -msgid "The requested action timed out. The background services may not yet be available if you just started Kodi/the addon. In this case, try again in a moment." -msgstr "L'azione richiesta è scaduta. Se hai appena avviato Addon/Kodi i servizi in background potrebbero non essere ancora disponibili. In questo caso, riprova tra un momento." +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "Se hai appena avviato Kodi o l'add-on i servizi protrebbero non essere ancora disponibili. In questo caso, riprova tra un momento." msgctxt "#30139" -msgid "Enable IPC over HTTP (use when experiencing AddonSignals timeouts)" -msgstr "Abilita IPC su HTTP (da utilizzare se si verifica timeout in AddonSignals)" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "Abilita IPC su HTTP (utilizzare quando si verificano Addon Signals timeout)" msgctxt "#30140" -msgid "Migrate Library to new format" -msgstr "Migra la libreria nel nuovo formato" - -msgctxt "#30141" -msgid "Disable startup notification" -msgstr "Disabilita notifica all'avvio" - -msgctxt "#30142" -msgid "Custom" -msgstr "Personalizzata" - -msgctxt "#30143" -msgid "Custom view ID" -msgstr "ID Vista personalizzata" +msgid "Import existing library" +msgstr "Importa libreria esistente" +#. Unused 30141 to 30143 msgctxt "#30144" msgid "Disable WebVTT subtitle support" msgstr "Disabilita supporto ai sottotitoli WebVTT" @@ -605,12 +557,12 @@ msgid "Discover the latest content added." msgstr "Scopri gli ultimi contenuti aggiunti." msgctxt "#30147" -msgid "Next page >>" -msgstr "Pagina seguente >>" +msgid "Next page »" +msgstr "Pagina seguente »" msgctxt "#30148" -msgid "<< Previous page" -msgstr "<< Pagina precedente" +msgid "« Previous page" +msgstr "« Pagina precedente" msgctxt "#30149" msgid "Sort results by" @@ -633,36 +585,33 @@ msgid "Year released" msgstr "Anno di uscita" msgctxt "#30154" -msgid "Netflix addon configuration" -msgstr "Configurazione Netflix addon" +msgid "Netflix configuration wizard" +msgstr "Configurazione guidata Netflix" msgctxt "#30155" msgid "Do you have a Netflix Premium account?" msgstr "Hai un account Netflix Premium?" -msgctxt "#30156" -msgid "Does your device support the 4K standard?\r\nThe device must:\r\nBe Netflix certified, support for Widevine Security level L1 and have a 4K display HDCP 2.2" -msgstr "Il dispositivo supporta lo standard 4K?\r\nIl dispositivo deve:\r\nEssere certificato Netflix, supportare Widevine Security level L1 ed avere un display 4K HDCP 2.2" - +#. Unused 30156 msgctxt "#30157" -msgid "The InputStream Adaptive settings panel will now be opened, so you have to change this option \"Override HDCP status\" to ON" -msgstr "Sarà ora aperto il pannello di InputStream Adaptive, dove è necessario ABILITARE l'opzione \"Override HDCP status\"" +msgid "The configuration has been completed" +msgstr "La configurazione è stata completata" msgctxt "#30158" -msgid "Show configuration screen at addon startup" -msgstr "Mostra schermata per la configurazione all'avvio dell'addon" +msgid "Restore the add-on default configuration" +msgstr "Ripristina la configurazione di default dell'add-on" msgctxt "#30159" -msgid "View for main menu" -msgstr "Vista per il menu principale" +msgid "View ID for main menu" +msgstr "ID vista per menu principale" msgctxt "#30160" -msgid "View for search" -msgstr "Vista per la ricerca" +msgid "View ID for search" +msgstr "ID vista per ricerca" msgctxt "#30161" -msgid "View for exported" -msgstr "Vista per esportati" +msgid "View ID for exported" +msgstr "ID vista per esportati" msgctxt "#30162" msgid "Last used" @@ -677,8 +626,8 @@ msgid "Browse contents with audio description." msgstr "Sfoglia i contenuti con audiodescrizione." msgctxt "#30165" -msgid "View for my list" -msgstr "Vista per la mia lista" +msgid "View ID for 'My list'" +msgstr "ID vista per 'La mia lista'" msgctxt "#30166" msgid "Main menu items" @@ -713,7 +662,7 @@ msgid "Netflix Originals" msgstr "Originali Netflix" msgctxt "#30174" -msgid "Tv Show genres" +msgid "TV show genres" msgstr "Generi di Serie Tv" msgctxt "#30175" @@ -721,28 +670,28 @@ msgid "Movie genres" msgstr "Generi di Film" msgctxt "#30176" -msgid "Enable STRM resume workaround (only library)" -msgstr "Abilita STRM workaround 'riprendi da' (solo libreria)" +msgid "Enable STRM resume workaround" +msgstr "Abilita STRM workaround 'riprendi da'" msgctxt "#30177" msgid "Ask to resume the video" msgstr "Chiedi per riprendere un video" msgctxt "#30178" -msgid "Open UpNext Addon settings" -msgstr "Apri impostazioni UpNext Addon" +msgid "Open Up Next add-on settings" +msgstr "Apri le impostazioni Up Next add-on" msgctxt "#30179" -msgid "Show the trailers" -msgstr "Mostra i trailers" +msgid "Show trailers and more" +msgstr "Mostra trailer e altro" msgctxt "#30180" -msgid "No trailers available" -msgstr "Nessun trailer disponibile" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "Si è verificato un problema con l'accesso, è possibile che il vostro account non sia stato confermato oppure riattivato." msgctxt "#30181" -msgid "Disable subtitles if there are no forced subtitles streams (Works only if Kodi Player is set to \"Only forced\" subtitles)" -msgstr "Disabilita i sottotitoli quando non ci sono tracce forzate (Funziona se il Player Kodi viene impostato con sottotitoli \"Solo forzati\")" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "Mostra sempre i sottotitoli quando non è disponibile la lingua audio preferita" msgctxt "#30182" msgid "Export NFO files" @@ -765,7 +714,7 @@ msgid "For Movies" msgstr "Per i film" msgctxt "#30187" -msgid "For TV Show" +msgid "For TV show" msgstr "Per le Serie TV" msgctxt "#30188" @@ -780,17 +729,14 @@ msgctxt "#30190" msgid "TV show" msgstr "serie tv" -msgctxt "#30191" -msgid "Do you want to export NFO files for the {} added to 'My List'?" -msgstr "Vuoi esportare i file NFO per {} aggiunti alla 'Mia lista'?" - +#. Unused 30191 msgctxt "#30192" -msgid "\r\nThese files are used by the provider information scrapers to integrate movies and tv shows info, useful with bonus episodes or director's cut" -msgstr "\r\nQuesti file sono utilizzati dai provider informazioni scrapers per integrare info dei film e serie tv, utili per episodi bonus o director's cut" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]Questi file sono utilizzati dai provider informazioni scrapers per integrare info dei film e serie tv, utili per episodi bonus o director's cut" msgctxt "#30193" -msgid "Include all information in NFO files (use only with 'Local Information' scraper)" -msgstr "Includi tutte le informazioni nei file NFO (usare solo con scraper 'Local Information')" +msgid "Include tv show NFO details" +msgstr "Includi nei NFO i dettagli delle serie tv" msgctxt "#30194" msgid "Limit video stream resolution to" @@ -817,7 +763,7 @@ msgid "Shared library (Kodi MySQL server is required)" msgstr "Libreria condivisa (E' richiesto Kodi MySQL server)" msgctxt "#30200" -msgid "Use MySQL shared library database" +msgid "Use shared library MySQL-database" msgstr "Usa MySQL per condividere la libreria" msgctxt "#30201" @@ -825,7 +771,7 @@ msgid "Username" msgstr "Nome utente" msgctxt "#30202" -msgid "Connection to the MySQL database was successful" +msgid "Connection to the MySQL-database was successful" msgstr "Connessione al database MySQL effettuata con successo" msgctxt "#30203" @@ -841,7 +787,7 @@ msgid "Test database connection" msgstr "Prova di connessione al database" msgctxt "#30206" -msgid "ERROR: The MySQL database is not reachable - the local database will be used" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" msgstr "ERRORE: Il database MySQL non è raggiungibile - sarà utilizzato il database locale" msgctxt "#30207" @@ -853,21 +799,458 @@ msgid "Check if this device is the main auto-update manager" msgstr "Verifica se questo dispositivo gestisce gli aggiornamenti automatici" msgctxt "#30209" -msgid "This device now handles the auto-updates of shared-library" +msgid "This device now handles the auto-updates of the shared library" msgstr "Questo dispositivo ora gestisce gli aggiornamenti automatici alla libreria" msgctxt "#30210" -msgid "This device handles the auto-updates of shared-library" +msgid "This device handles the auto-updates of the shared library" msgstr "Questo dispositivo gestisce gli aggiornamenti automatici alla libreria" msgctxt "#30211" -msgid "Another device handles the auto-updates of shared-library" +msgid "Another device handles the auto-updates of the shared library" msgstr "Un'altro dispositivo sta gestendo gli aggiornamenti automatici alla libreria" msgctxt "#30212" -msgid "There is no device that handles the auto-updates of shared-library" +msgid "There is no device that handles the auto-updates of the shared library" msgstr "Nessun dispositivo sta gestendo gli aggiornamenti automatici alla libreria" msgctxt "#30213" msgid "InputStream Helper settings..." msgstr "Configurazione InputStream Helper..." + +msgctxt "#30214" +msgid "Force refresh" +msgstr "Forza l'aggiornamento" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "Colore dei titoli inclusi su \"La mia lista\"" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "Colore dei titoli contrassegnati come \"Ricordamelo\"" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "Disabilita notifica per sincronizzazione completata" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "La libreria di Kodi è stata aggiornata" + +msgctxt "#30221" +msgid "Owner account" +msgstr "Account principale" + +msgctxt "#30222" +msgid "Kid account" +msgstr "Account bambini" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "Modalità di auto aggiornamento" + +msgctxt "#30225" +msgid "Manual" +msgstr "Manuale" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "Programmata" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "Sincronizza la libreria Kodi con 'La mia lista'" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "Imposta un profilo per la sincronizzazione" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "All'avvio di Kodi" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "Esegui adesso gli aggiornamenti" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "Vuoi verificare gli aggiornamenti ora?" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "Classificazione per età per il profilo {}" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "Mostra solo titoli con classificazione [B]{}[/B]." + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "Sincronizza lo stato guardato dei video con Netflix" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "Modifica lo stato guardato (localmente)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "Segnato come visto|Segnato come non visto|Ripristinato con lo stato di Netflix" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Notifiche Up Next (guarda prossimo video)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "Colore delle info supplementari nella trama" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "I servizi in background non sono stati avviati a causa di questo problema:[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Open Connect CDN (l'indice più basso è il migliore)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "Top 10" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "Seleziona il primo episodio serie TV non visto" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "Eliminazione dei file esportati nella libreria" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "Ci sono {} titoli che non possono essere importati.[CR]Vuoi eliminare questi files?" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "Risultati per pagina (in caso di errore timeout ridurre)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "Vuoi rimuovere lo stato guardato di \"{}\"?" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "Includi libreria Kodi (solo per invio dati)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "Scegli il metodo di accesso[CR](se l'accesso con E-Mail/Password restituisce sempre \"password non corretta\", utilizzare la Chiave di autenticazione, istruzioni nel ReadMe su GitHub)" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "E-Mail/Password" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "Chiave di autenticazione" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "Il file selezionato non è compatibile o è corrotto" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "La chiave di autenticazione è scaduta" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "Inserire PIN" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "Ordina la cronologia per" + +msgctxt "#30400" +msgid "Search" +msgstr "Cerca" + +msgctxt "#30401" +msgid "Type of search" +msgstr "Tipo di ricerca" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "Cerca titoli, persone, generi" + +msgctxt "#30403" +msgid "New search..." +msgstr "Nuova ricerca..." + +msgctxt "#30404" +msgid "Clear search history" +msgstr "Elimina cronologia di ricerca" + +msgctxt "#30405" +msgid "Select the language" +msgstr "Seleziona la lingua" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "Vuoi eliminare l'intero contenuto?" + +msgctxt "#30407" +msgid "No matches found" +msgstr "Nessuna corrispondenza trovata" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "Per termine" + +msgctxt "#30411" +msgid "By audio language" +msgstr "Per lingua audio" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "Per lingua sottotitoli" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "Per ID genere/sottogenere" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "Preferire la lingua audio/sottotitoli con il codice paese (se disponibile)" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "Preferire come predefinite le tracce audio stereo" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "Impostazioni ESN / Widevine" + +msgctxt "#30601" +msgid "ESN:" +msgstr "ESN:" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "Cambia ESN" + +msgctxt "#30603" +msgid "Save system info" +msgstr "Salva info di sistema" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "Widevine - Forza livello di sicurezza" + +msgctxt "#30605" +msgid "Force {}" +msgstr "Forza {}" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "ESN impostato con successo[CR]Prova a riprodurre un video" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "Questo ESN non può essere utilizzato[CR]Prova a cambiare ESN oppure fare reset.[CR][CR]Dettagli errore:[CR]{}" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "Il formato ESN non è corretto" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "Vuoi effettuare il reset di tutte le impostazioni?" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "Usa crittografia di sistema per proteggere le credenziali" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "[B]Se disabilitata causerà una falla di sicurezza[/B]. Se il tuo sistema non supporta questa protezione, l'add-on chiederà il login ad ogni riavvio del dispositivo, [B]solo[/B] in questo caso disabilita la protezione. Se questa impostazione viene modificata al riavvio dovrai fare nuovamente il login." + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "Versione MSL-manifest" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "Questo titolo sarà disponibile dal:[CR]{}" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "Vuoi riprodurre il trailer promozionale?" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "{} Ricordamelo" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "Nuovi e popolari" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "Contrassegna le serie tv iniziate come viste" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "Da notare che attivando questa impostazione, se i filtri nelle impostazioni della Skin sono abilitati, gli elementi contrassegnati come visti potrebbero non essere visibili." + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "Sovrascrivere il tipo di selezione del flusso" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "Sostituisce l'impostazione del tipo di selezione del flusso di InputStream Adaptive add-on, per impostare come viene scelta la qualità dei flussi audio / video durante la riproduzione." + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "Qualità adattiva" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "Qualità video fissa" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "Chiedi qualità video" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "Si noti che l'attivazione di questa impostazione forzerà la rimozione dei flussi che non rientrano nell'intervallo impostato e ciò può influire sul funzionamento di InputStream Adaptive. Se possibile, provare prima a limitare la qualità dalle impostazioni di InputStream Adaptive." + +msgctxt "#30721" +msgid "Size" +msgstr "Dimensione" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "Riduce le barre nere applicando l'effetto zoom. [ Fissa ] Imposta la percentuale di schermo che le barre nere possono occupare, sarà costante per tutti i video. [ Relativa ] Imposta la percentuale di riduzione, dipenderà dalla dimensione di ogni video. [ Resetta ] Resetta l'effetto zoom precedentemente applicato." + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "Questa funzionalità fa uso dell'effetto zoom di Kodi che viene permanentemente salvato nell'impostazione video \"Modalità di visualizzazione\", se in futuro si disabilita questa funzionalità lo zoom sarà preservato, per resettare lo zoom impostare la modalità \"Resetta\"." + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "Abilita codec AV1" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "Aggiungi le cartelle di Netflix alle sorgenti della libreria di Kodi" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "Le cartelle \"Netflix-Movies\" e \"Netflix-Shows\" sono state aggiunte alle sorgenti di Kodi, riavviare Kodi per visualizzarle nei menu Kodi [Programmi TV] / [Film]. Quindi modificare le cartelle per impostare il tipo di contenuto appropriato e il provider di informazioni scraper." + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "[Manuale] gli aggiornamenti devono essere eseguiti manualmente utilizzando il menu contestuale \"Esporta nuovi episodi\" su ogni serie tv, [Programmata] si può stabilire aggiornamenti automatici as orari prestabiliti, [All'avvio di Kodi] gli aggiornamenti automatici iniziano all'avvio di Kodi e verranno eseguiti solo una volta al giorno." + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "Sincronizza la libreria con La mia lista, se la mia lista è stata modificata esternamente, ad esempio con l'app mobile o il sito web, l'aggiornamento sarà posticipato come programmato." + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "Se abilitato, aggiunge ulteriori informazioni agli episodi o ai film, utile quando lo scraper del provider di informazioni di Kodi non fornisce dati. Verrà aggiunta anche la durata dei video." + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "Da attivare solo se si utilizza lo scraper del provider di informazioni \"Local Information\" o se alcuni titoli non vengono visualizzati nella libreria di Kodi nonostante siano stati aggiunti." + +msgctxt "#30734" +msgid "Support" +msgstr "Supporto" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "Informazioni su Netflix add-on" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "Genera automaticamente nuovi ESN (workaround per limite 540p)" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "ATTENZIONE: Non usare l'ESN originale dell'app del dispositivo." + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "Abilita sfasamento audio" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "Sfasamento audio" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "Imposta sfasamento della traccia audio sovrascrivendo l'impostazione di Kodi" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "Abilita VP9 Profile 2 (profondità colore 10/12 bit)" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "Vuoi abilitare il supporto per HDR10?" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "Vuoi abilitare il supporto per HDR Dolby Vision?" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "L'add-on imposta già la versione HDCP appropriata, ma se richiesto si può forzare la richiesta dei flussi video per una versione HDCP diversa." + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "Se il vostro dispositivo non supporta questo codec, è possibile riscontrare schermo nero, assenza di immagini o immagini danneggiate. In tal caso, disabilitare il codec." + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "Modalità di riduzione barre nere" + +msgctxt "#30747" +msgid "Fixed" +msgstr "Fissa" + +msgctxt "#30748" +msgid "Relative" +msgstr "Relativa" + +msgctxt "#30749" +msgid "Reset" +msgstr "Ripristina" diff --git a/resources/language/resource.language.ja_jp/strings.po b/resources/language/resource.language.ja_jp/strings.po new file mode 100644 index 000000000..4a4dd8368 --- /dev/null +++ b/resources/language/resource.language.ja_jp/strings.po @@ -0,0 +1,1256 @@ +# Kodi Media Center language file +# Addon Name: Netflix +# Addon id: plugin.video.netflix +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait +msgid "" +msgstr "" +"Project-Id-Version: plugin.video.netflix\n" +"Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" +"POT-Creation-Date: 2017-07-24 16:15+0000\n" +"PO-Revision-Date: 2020-07-02 18:20+0900\n" +"Last-Translator: Allen Choi<37539914+Thunderbird2086@users.noreply.github.com>\n" +"Language-Team: Japanese\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=2; plural=(n != 1);\n" + +msgctxt "Addon Summary" +msgid "Netflix" +msgstr "Netflix" + +msgctxt "Addon Description" +msgid "Netflix VOD Services Add-on" +msgstr "Netflixアドオン" + +msgctxt "Addon Disclaimer" +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "一部の国では、このアドオンを使用するのが不法でありますので、設置する前に必ず関連法律を確認してください" + +msgctxt "#30001" +msgid "Recommendations" +msgstr "オススメ" + +msgctxt "#30002" +msgid "Enter PIN to watch restricted content" +msgstr "制限コンテンツを見るのにはPINを入力してください。" + +#. Unused 30003 +msgctxt "#30004" +msgid "Password" +msgstr "暗証番号" + +msgctxt "#30005" +msgid "E-mail" +msgstr "メールアドレス" + +msgctxt "#30006" +msgid "Enter PIN to access this profile" +msgstr "このプロフィールを使うにはPINを入力してください。" + +msgctxt "#30007" +msgid "Parental Control PIN (4 digits)" +msgstr "視聴制限PIN(4桁)" + +msgctxt "#30008" +msgid "Login failed" +msgstr "ログイン失敗" + +msgctxt "#30009" +msgid "Please check your e-mail and password" +msgstr "ログインIDや暗証番号を確認してください" + +msgctxt "#30010" +msgid "Lists of all kinds" +msgstr "ジャンル" + +#. Unused 30011 +msgctxt "#30012" +msgid "Profiles options and parental control" +msgstr "プロフィールオプションと視聴制限" + +msgctxt "#30013" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "プロフィールリストから選び、コンテキストメニューを開きましょう。" + +msgctxt "#30014" +msgid "Account" +msgstr "アカウント" + +msgctxt "#30015" +msgid "Other options" +msgstr "その他" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "" + +msgctxt "#30017" +msgid "Logout" +msgstr "ログアウト" + +msgctxt "#30018" +msgid "Export to library" +msgstr "ライブラリに追加" + +msgctxt "#30019" +msgid "Rate on Netflix" +msgstr "評価" + +msgctxt "#30020" +msgid "Remove from 'My list'" +msgstr "マイリストから削除" + +msgctxt "#30021" +msgid "Add to 'My list'" +msgstr "マイリストに追加" + +msgctxt "#30022" +msgid "(between 0 and 10)" +msgstr "(0-10)" + +msgctxt "#30023" +msgid "Expert" +msgstr "エキスパート" + +msgctxt "#30024" +msgid "SSL verification" +msgstr "SSL認証" + +msgctxt "#30025" +msgid "Library" +msgstr "ライブラリ" + +msgctxt "#30026" +msgid "Enable custom library folder" +msgstr "カスタムライブラリフォルダを使用" + +msgctxt "#30027" +msgid "Custom library path" +msgstr "カスタムライブラリパース" + +msgctxt "#30028" +msgid "Playback error" +msgstr "再生エラー" + +#. Unused 30029 +msgctxt "#30030" +msgid "Remove from library" +msgstr "ライブラリから削除" + +msgctxt "#30031" +msgid "General" +msgstr "一般設定" + +msgctxt "#30032" +msgid "Codecs" +msgstr "" + +msgctxt "#30033" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "" + +#. Unused 30034 +msgctxt "#30035" +msgid "InputStream Adaptive settings..." +msgstr "インプットストリームアドオン設定" + +#. Unused 30036 +msgctxt "#30037" +msgid "Skin viewtypes" +msgstr "スキンビュータイプ" + +msgctxt "#30038" +msgid "Enable custom viewtypes" +msgstr "カスタム・ビュー使用" + +msgctxt "#30039" +msgid "View ID for folders" +msgstr "フォルダーのビューID" + +msgctxt "#30040" +msgid "View ID for movies" +msgstr "映画のビューID" + +msgctxt "#30041" +msgid "View ID for shows" +msgstr "TV番組のビューID" + +msgctxt "#30042" +msgid "View ID for seasons" +msgstr "シーズンのビューID" + +msgctxt "#30043" +msgid "View ID for episodes" +msgstr "エピソードのビューID" + +msgctxt "#30044" +msgid "View ID for profiles" +msgstr "プロフィールのビューID" + +msgctxt "#30045" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "親指評価なし|サムズアップ|サムズダウン" + +msgctxt "#30046" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "InputStream Adaptive又はWidevineに問題があるようです。インストールされなかったり、無効になっておりますので、起動をキャンセルします。" + +msgctxt "#30047" +msgid "Library update in progress" +msgstr "ライブラリ更新中" + +msgctxt "#30048" +msgid "Exported" +msgstr "エクスポートされた項目" + +msgctxt "#30049" +msgid "Library" +msgstr "ライブラリ" + +msgctxt "#30050" +msgid "Enable Kodi library management" +msgstr "" + +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30052" +msgid "{} Set for library playback" +msgstr "{} ライブラリから再生を設定" + +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30055" +msgid "{} Set at start-up" +msgstr "" + +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30057" +msgid "{} Remember PIN" +msgstr "" + +#. Unused 30058 +msgctxt "#30059" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "警告!多め({}個)のタイトルを更新しようとします。[CR]これは大変な問題を起こすか、アカウントが一時的にブロックされる可能性があります。[CR]宜しいですか。" + +msgctxt "#30060" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "" + +msgctxt "#30061" +msgid "Update inside library" +msgstr "ライブラリ更新" + +msgctxt "#30062" +msgid "Parental controls" +msgstr "ペアレンタルコントロール" + +msgctxt "#30063" +msgid "An update is already in progress" +msgstr "更新中です。" + +msgctxt "#30064" +msgid "Perform auto-update" +msgstr "更新タイミング" + +msgctxt "#30065" +msgid "Video library update" +msgstr "" + +msgctxt "#30066" +msgid "Enable debug logging" +msgstr "デバグロギング" + +msgctxt "#30067" +msgid "daily" +msgstr "毎日" + +msgctxt "#30068" +msgid "every other day" +msgstr "二日に一回" + +msgctxt "#30069" +msgid "every 5 days" +msgstr "五日に一回" + +msgctxt "#30070" +msgid "weekly" +msgstr "毎週" + +msgctxt "#30071" +msgid "Time of Day" +msgstr "実行時刻" + +msgctxt "#30072" +msgid "Only start after 5 minutes of idle" +msgstr "5分以上何もしなかったら" + +msgctxt "#30073" +msgid "Always show codec information during video playback" +msgstr "コーデック情報を常に表示" + +msgctxt "#30074" +msgid "View for exported" +msgstr "エクスポート" + +msgctxt "#30075" +msgid "Ask to skip intro and recap" +msgstr "スキップを確認" + +msgctxt "#30076" +msgid "Skip intro" +msgstr "イントロをスキップ" + +msgctxt "#30077" +msgid "Skip recap" +msgstr "本編へスキップ" + +msgctxt "#30078" +msgid "Playback" +msgstr "再生" + +msgctxt "#30079" +msgid "Skip automatically" +msgstr "オートスキップ" + +msgctxt "#30080" +msgid "Pause when skipping" +msgstr "スキップする際に停止" + +msgctxt "#30081" +msgid "Force HDCP version on video streams" +msgstr "" + +msgctxt "#30082" +msgid "Remember audio / subtitle preferences" +msgstr "音声・字幕設定を保存" + +msgctxt "#30083" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "音声に含まれた強制字幕は無条件に表示" + +msgctxt "#30084" +msgid "Cache objects TTL (minutes)" +msgstr "キャッシュ保存時間(分)" + +msgctxt "#30085" +msgid "Cache metadata TTL (days)" +msgstr "メタデータのキャッシュ保存期間(日)" + +msgctxt "#30086" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "'マイリスト'のキャッシュ保存期間(分)" + +msgctxt "#30087" +msgid "Contains {} and more..." +msgstr "{}に関連する項目" + +msgctxt "#30088" +msgid "Browse related content" +msgstr "こちらもオススメ" + +msgctxt "#30089" +msgid "Browse subgenres" +msgstr "ジャンル" + +msgctxt "#30090" +msgid "Browse through related video lists and discover more content." +msgstr "関連メディアリストを検索し、他のコンテンツを見つかる。" + +msgctxt "#30091" +msgid "Browse and manage contents that were exported to the Kodi library." +msgstr "Kodiライブラリにエクスポートされた項目を管理する。" + +msgctxt "#30092" +msgid "Search for content and easily find what you want to watch." +msgstr "コンテンツを検索し、見たいものを見つかる。" + +msgctxt "#30093" +msgid "Browse content by genre and easily discover related content." +msgstr "ジャンル毎にコンテンツをみて、より簡単にオススメを見つかる。" + +msgctxt "#30094" +msgid "Browse recommendations and pick up on something new you might like." +msgstr "オススメを検索し、もしもの作品を選んでみる。" + +msgctxt "#30095" +msgid "All TV shows" +msgstr "全てのTV番組・ドラマ" + +msgctxt "#30096" +msgid "All Movies" +msgstr "全ての映画" + +msgctxt "#30097" +msgid "Main Menu" +msgstr "メインメニュー" + +msgctxt "#30098" +msgid "Enable HDR10" +msgstr "" + +msgctxt "#30099" +msgid "Enable Dolby Vision" +msgstr "" + +msgctxt "#30100" +msgid "Results for '{}'" +msgstr "'{}'の検索結果" + +msgctxt "#30101" +msgid "Netflix returned an unknown error." +msgstr "Netflixからのアンノーンエラー" + +msgctxt "#30102" +msgid "Netflix returned the following error:" +msgstr "Netflixからのエラーメッセージ:" + +msgctxt "#30103" +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "エラーの詳細はログファイルに書いてあります。解決方法が見つからなかったり同じエラー続くと、GithubのReadmeの指示通り報告してください。" + +msgctxt "#30104" +msgid "The add-on encountered the following error:" +msgstr "エラーが発生しました:" + +msgctxt "#30105" +msgid "Netflix Add-on Error" +msgstr "Netflixアドオンエラー" + +msgctxt "#30106" +msgid "Invalid PIN" +msgstr "PIN誤り" + +msgctxt "#30107" +msgid "PIN protection is off." +msgstr "PINによる制限をしない。" + +msgctxt "#30108" +msgid "All content is protected by PIN." +msgstr "PINによるコンテンツ制限" + +msgctxt "#30109" +msgid "Login successful" +msgstr "ログイン成功" + +#. Unused 30110 +msgctxt "#30111" +msgid "There is no contents" +msgstr "コンテンツなし!" + +msgctxt "#30112" +msgid "You need to be logged in to use Netflix" +msgstr "Netflixを見るのには、ログインしてください" + +msgctxt "#30113" +msgid "Logout successful" +msgstr "ログアウト成功" + +#. Unused 30114, 30115 +msgctxt "#30116" +msgid "Advanced Add-on Configuration" +msgstr "上級者向け設定" + +msgctxt "#30117" +msgid "Cache" +msgstr "キャッシュ" + +#. Unused 30118, 30119 +msgctxt "#30120" +msgid "Cannot automatically remove a season from Kodi library, please do it manually" +msgstr "Kodiライブラリからシーズンを削除しませんでしたので、手動で削除してください" + +msgctxt "#30121" +msgid "Perform full sync now" +msgstr "これから全ての同期化を実行します。" + +msgctxt "#30122" +msgid "Sync 'My list' to Kodi library" +msgstr "マイリストとKodiライブラリの同期化" + +msgctxt "#30123" +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "全ての同期化を実行することによって以前エクスポートしたタイトルが削除されます。[CR]タイトルが多い場合は時間が掛かります。[CR]宜しいですか。" + +msgctxt "#30124" +msgid "Do you want to remove this item from the library?" +msgstr "この項目を削除しますか。" + +msgctxt "#30125" +msgid "Delete library contents" +msgstr "ライブラリ削除" + +msgctxt "#30126" +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "ライブラリを削除しますか。[CR]エクスポートをされた項目を全て削除します。" + +msgctxt "#30127" +msgid "Awarded rating of {}" +msgstr "{}の評価により授与しました。" + +msgctxt "#30128" +msgid "Select a profile" +msgstr "プロフィールを選ぶ" + +msgctxt "#30129" +msgid "Enable Up Next integration" +msgstr "Up Next 機能を使う" + +msgctxt "#30130" +msgid "Install Up Next add-on" +msgstr "Up Nextアドオンインストール" + +msgctxt "#30131" +msgid "Check the logfile for detailed information" +msgstr "詳細はログファイルを確認してください。" + +msgctxt "#30132" +msgid "Clear in-memory cache" +msgstr "メモリキャッシュを削除" + +msgctxt "#30133" +msgid "Clear in-memory and on-disk cache" +msgstr "メモリとディスクキャッシュを削除" + +msgctxt "#30134" +msgid "Enable execution timing" +msgstr "実行タイミング" + +msgctxt "#30135" +msgid "Cache cleared" +msgstr "キャッシュを削除しました。" + +msgctxt "#30136" +msgid "Waiting for service start-up..." +msgstr "" + +msgctxt "#30137" +msgid "Enable VP9 codec" +msgstr "" + +msgctxt "#30138" +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "Kodiやアドオンを起動したばかりの場合、バックグラウンドサービスの準備が出来てない可能性があります。しばらく経ってからもう一度。" + +msgctxt "#30139" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "HTTP経由IPCを使う(アドオンタイムアウトが発生したら試してください)" + +msgctxt "#30140" +msgid "Import existing library" +msgstr "既存のライブラリをインポート" + +#. Unused 30141 to 30143 +msgctxt "#30144" +msgid "Disable WebVTT subtitle support" +msgstr "WebVTT字幕を使わない" + +msgctxt "#30145" +msgid "Recently added" +msgstr "新着" + +msgctxt "#30146" +msgid "Discover the latest content added." +msgstr "最近追加された新作を探す。" + +msgctxt "#30147" +msgid "Next page »" +msgstr "次へ »" + +msgctxt "#30148" +msgid "« Previous page" +msgstr "« 前へ" + +msgctxt "#30149" +msgid "Sort results by" +msgstr "並び替え" + +msgctxt "#30150" +msgid "Alphabetical order (A-Z)" +msgstr "昇順(作品名)" + +msgctxt "#30151" +msgid "Alphabetical order (Z-A)" +msgstr "降順(作品名)" + +msgctxt "#30152" +msgid "Suggestions for you" +msgstr "あなたにオススメ" + +msgctxt "#30153" +msgid "Year released" +msgstr "公開年" + +msgctxt "#30154" +msgid "Netflix configuration wizard" +msgstr "Netflixアドオン設定" + +msgctxt "#30155" +msgid "Do you have a Netflix Premium account?" +msgstr "Netflixプレミアムアカウントをお持ちですか。" + +#. Unused 30156 +msgctxt "#30157" +msgid "The configuration has been completed" +msgstr "設定を完了しました。" + +msgctxt "#30158" +msgid "Restore the add-on default configuration" +msgstr "" + +msgctxt "#30159" +msgid "View ID for main menu" +msgstr "メインメニューのビューID" + +msgctxt "#30160" +msgid "View ID for search" +msgstr "検索のビューID" + +msgctxt "#30161" +msgid "View ID for exported" +msgstr "エクスポートのビューID" + +msgctxt "#30162" +msgid "Last used" +msgstr "最近使った設定" + +msgctxt "#30163" +msgid "Audio description" +msgstr "副音声・音声ガイド付き" + +msgctxt "#30164" +msgid "Browse contents with audio description." +msgstr "副音声・音声ガイド付き" + +msgctxt "#30165" +msgid "View ID for 'My list'" +msgstr "マイリストのビューID" + +msgctxt "#30166" +msgid "Main menu items" +msgstr "メインメニュー" + +msgctxt "#30167" +msgid "My list" +msgstr "マイリスト" + +msgctxt "#30168" +msgid "Continue watching" +msgstr "視聴中コンテンツ" + +msgctxt "#30169" +msgid "Top picks" +msgstr "あなたにイチオシ!" + +msgctxt "#30170" +msgid "New releases" +msgstr "新作・準新作" + +msgctxt "#30171" +msgid "Trending now" +msgstr "人気急上昇中の作品" + +msgctxt "#30172" +msgid "Popular on Netflix" +msgstr "Netflixでの人気の作品" + +msgctxt "#30173" +msgid "Netflix Originals" +msgstr "Netflixオリジナル作品" + +msgctxt "#30174" +msgid "TV show genres" +msgstr "TV番組・ドラマ" + +msgctxt "#30175" +msgid "Movie genres" +msgstr "映画" + +msgctxt "#30176" +msgid "Enable STRM resume workaround" +msgstr "中断した所から続いて観る" + +msgctxt "#30177" +msgid "Ask to resume the video" +msgstr "中断した所から観るか聞く" + +msgctxt "#30178" +msgid "Open Up Next add-on settings" +msgstr "Up Next アドオン設定画面" + +msgctxt "#30179" +msgid "Show trailers and more" +msgstr "予告編など" + +msgctxt "#30180" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "ログイン失敗、アカウント確認がまだ取れてないか再入会が出来なかった可能性があります。" + +msgctxt "#30181" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "希望の音声がない場合、常に字幕を表示" + +msgctxt "#30182" +msgid "Export NFO files" +msgstr "NFOファイルエクスポート" + +msgctxt "#30183" +msgid "Do you want to export NFO files for the {}?" +msgstr "{}のNFOファイルをエクスポートしますか。" + +msgctxt "#30184" +msgid "NFO Files" +msgstr "NFOファイル" + +msgctxt "#30185" +msgid "Enable NFO files export" +msgstr "NFOファイルをエクスポートする" + +msgctxt "#30186" +msgid "For Movies" +msgstr "映画" + +msgctxt "#30187" +msgid "For TV show" +msgstr "TV番組・ドラマ" + +msgctxt "#30188" +msgid "Ask" +msgstr "聞く" + +msgctxt "#30189" +msgid "movie" +msgstr "映画" + +msgctxt "#30190" +msgid "TV show" +msgstr "TV番組・ドラマ" + +#. Unused 30191 +msgctxt "#30192" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]このファイルは、映画やTV番組の情報、ボーナスエピソードや監督版などの情報を収集する情報提供者により使われます。" + +msgctxt "#30193" +msgid "Include tv show NFO details" +msgstr "" + +msgctxt "#30194" +msgid "Limit video stream resolution to" +msgstr "解像度を制限" + +msgctxt "#30195" +msgid "Export new episodes" +msgstr "新エピソードをエクスポート" + +msgctxt "#30196" +msgid "Exclude from auto update" +msgstr "自動更新を除く" + +msgctxt "#30197" +msgid "Include in auto update" +msgstr "自動更新を含む" + +msgctxt "#30198" +msgid "Exporting new episodes" +msgstr "新エピソードをエクスポート中" + +msgctxt "#30199" +msgid "Shared library (Kodi MySQL server is required)" +msgstr "共有ライブラリ(Kodi MySQLサーバーが必要)" + +msgctxt "#30200" +msgid "Use shared library MySQL-database" +msgstr "MySQL共有ライブラリデータベースを使う。" + +msgctxt "#30201" +msgid "Username" +msgstr "ユーザー名" + +msgctxt "#30202" +msgid "Connection to the MySQL-database was successful" +msgstr "MySQLデータベースと接続出来ました。" + +msgctxt "#30203" +msgid "Host IP" +msgstr "ホストIP" + +msgctxt "#30204" +msgid "Port" +msgstr "ポート" + +msgctxt "#30205" +msgid "Test database connection" +msgstr "データベース接続テスト" + +msgctxt "#30206" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" +msgstr "エラー: MySQLデータベースに繋がらない - ローカルデータベースを使う" + +msgctxt "#30207" +msgid "Set this device as main auto-updates manager" +msgstr "このデバイスを自動更新マネージャーにする。" + +msgctxt "#30208" +msgid "Check if this device is the main auto-update manager" +msgstr "このデバイスが自動更新マネージャーかをチェック" + +msgctxt "#30209" +msgid "This device now handles the auto-updates of the shared library" +msgstr "このデバイスは共有ライブラリを自動更新します。" + +msgctxt "#30210" +msgid "This device handles the auto-updates of the shared library" +msgstr "このデバイスが共有ライブラリを自動更新します。" + +msgctxt "#30211" +msgid "Another device handles the auto-updates of the shared library" +msgstr "別のデバイスが共有ライブラリを自動更新します。" + +msgctxt "#30212" +msgid "There is no device that handles the auto-updates of the shared library" +msgstr "共有ライブラリを自動更新するデバイスがありません。" + +msgctxt "#30213" +msgid "InputStream Helper settings..." +msgstr "インプットストリームヘルパーアドオン設定" + +msgctxt "#30214" +msgid "Force refresh" +msgstr "強制更新する" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "マイリストにあるタイトルの色" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "同期化完了の通知を使わない。" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "Kodiライブラリを更新しました。" + +msgctxt "#30221" +msgid "Owner account" +msgstr "オーナーアカウント" + +msgctxt "#30222" +msgid "Kid account" +msgstr "お子様アカウント" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "自動更新" + +msgctxt "#30225" +msgid "Manual" +msgstr "マニュアル" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "自動" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "マイリストとKodiライブラリの同期化" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "同期化用プロフィール設定" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "今、更新内容を確認" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "更新内容を確認しましょうか。" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "{}さんの年齢制限" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "[B]{}[/B]の作品のみ視聴できます。" + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "視聴状態をNetflixと同期する。" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "視聴状態を変更(ローカルのみ)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "視聴済みにする|未視聴にする|視聴状態をNetflixから持って来る" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Up Next知らせ(続きをみる)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "追加情報の色" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "バッググラウンドサービスが起動しなかった理由は:[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Open Connect CDNサーバー設定(低いほど良い)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "トップ10" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "最初の未視聴のエピソードを選択" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "ライブラリにエクスポートしたファイルを削除中" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "{}個のタイトルはインポート出来ません。[CR]削除しましょうか。" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "1頁の検索結果(タイムアウトの場合は減らしましょ)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "{}の視聴状態を削除しますか。" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "Kodiライブラリに追加(送ったデータのみ)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "ログイン方法を選択してください。[CR](Eメール/パスワードで\"パスワード誤り\"が続出すると、認証キーを試してください。詳細はGitHubのReadMeまで)" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "Eメール/パスワード" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "認証キー" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "交換性のないか壊れたファイルを選択しました。" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "認証キーが無効です。" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "PINを入力してください。" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "履歴の並び順" + +msgctxt "#30400" +msgid "Search" +msgstr "検索" + +msgctxt "#30401" +msgid "Type of search" +msgstr "検索方法" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "タイトル、人物、ジャンル" + +msgctxt "#30403" +msgid "New search..." +msgstr "新たな検索" + +msgctxt "#30404" +msgid "Clear search history" +msgstr "検索履歴削除" + +msgctxt "#30405" +msgid "Select the language" +msgstr "言語" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "全てを削除しますか。" + +msgctxt "#30407" +msgid "No matches found" +msgstr "一致する項目はありませんでした。" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "項目" + +msgctxt "#30411" +msgid "By audio language" +msgstr "音声" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "字幕" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "ジャンル/サーブジャンルID" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "(利用可能であれば)国名コード付きの音声・字幕を優先" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "デフォルトでステレオ音声を優先" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "ESN/Widevine設定" + +msgctxt "#30601" +msgid "ESN:" +msgstr "ESN:" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "ESN更新" + +msgctxt "#30603" +msgid "Save system info" +msgstr "システム情報保存" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "Widevine - 強制セキュリティレベル" + +msgctxt "#30605" +msgid "Force {}" +msgstr "強制{}" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "ESN設定しました。[CR]ビデオを再生して見てね。" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "使えないESNです。[CR]ESNを更新するかリセットしてください。[CR][CR]エラー詳細:[CR]{}" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "ESNフォーマット誤り" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "全ての設定をリセットしましょうか。" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "システムベースの暗号化使用" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "[B]無効にするとセキュリティ漏洩の可能性があります。[/B]OSが[B]サーポートしない場合のみ[/B]無効にしてください。この場合は再起動する度にログインしないと行けません。この設定を変えると次回の再起動時に再ログインすることになります。" + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "MSLマニファストバージョン" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "{}[CR]配信開始" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "予告編を再生しましょうか。" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "リマインドする {}" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "新作&人気作" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "見始めたTV番組・ドラマを視聴済みにする。" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "有効化するとスキン設定によって視聴済みとなったアイテムが表示されない場合があります。" + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "ストリーム選択方法を無効化" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "InputStream Adaptiveアドオンの設定を無効化し、再生中に音声や解像度を選択する。" + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "解像度自動設定" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "解像度固定" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "解像度選択" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "有効化すると設定範囲外のストリームを削除することでInputStream Adaptiveの動作に影響を与える可能性があります。可能であればInputStream Adaptiveの設定から解像度制限を行ってください。" + +msgctxt "#30721" +msgid "Size" +msgstr "" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "" + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "" + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "" + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "" + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "" + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "" + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "" + +msgctxt "#30734" +msgid "Support" +msgstr "" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "" + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "" + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "" + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "" + +msgctxt "#30747" +msgid "Fixed" +msgstr "" + +msgctxt "#30748" +msgid "Relative" +msgstr "" + +msgctxt "#30749" +msgid "Reset" +msgstr "" diff --git a/resources/language/resource.language.ko_kr/strings.po b/resources/language/resource.language.ko_kr/strings.po index 99201b71c..43f6699cb 100644 --- a/resources/language/resource.language.ko_kr/strings.po +++ b/resources/language/resource.language.ko_kr/strings.po @@ -1,89 +1,91 @@ # Kodi Media Center language file # Addon Name: Netflix # Addon id: plugin.video.netflix -# Addon Provider: libdev + jojo + asciidisco + caphm + CastagnaIT +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait msgid "" msgstr "" "Project-Id-Version: plugin.video.netflix\n" "Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" "POT-Creation-Date: 2017-07-24 16:15+0000\n" -"PO-Revision-Date: 2019-07-08 22:15+0000\n" +"PO-Revision-Date: 2020-07-02 18:20+0900\n" "Last-Translator: Allen Choi<37539914+Thunderbird2086@users.noreply.github.com>\n" "Language-Team: Korean\n" +"Language: ko\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 "Netflix" -msgstr "" +msgstr "Netflix" msgctxt "Addon Description" -msgid "Netflix VOD Services Addon" +msgid "Netflix VOD Services Add-on" msgstr "Netflix VOD 애드온" msgctxt "Addon Disclaimer" -msgid "Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing." -msgstr "일부 국가에서는 이 애드온의 일부가 불법일 수도 있으니, 설치하기전 관련 법을 확인하기 바랍니다." +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "일부 국가에서는 이 애드온을 사용하는 것이 불법일 수도 있으니, 설치하기전 관련 법을 확인하기 바랍니다." msgctxt "#30001" msgid "Recommendations" -msgstr "추천" +msgstr "추천콘텐츠" msgctxt "#30002" -msgid "Adult Pin" -msgstr "성인인증코드" - -msgctxt "#30003" -msgid "Search term" -msgstr "검색어" +msgid "Enter PIN to watch restricted content" +msgstr "제한된 콘텐츠를 보려면 PIN을 입력해야 합니다." +#. Unused 30003 msgctxt "#30004" msgid "Password" msgstr "암호" msgctxt "#30005" msgid "E-mail" -msgstr "" +msgstr "E-mail" msgctxt "#30006" -msgid "Adult verification failed" -msgstr "성인인증 실패" +msgid "Enter PIN to access this profile" +msgstr "이 프로파일을 이용하려면 PIN을 입력하십시오." msgctxt "#30007" -msgid "Please Check your adult pin" -msgstr "성인인증코드를 확인하십시오." +msgid "Parental Control PIN (4 digits)" +msgstr "자녀보호PIN(4자리숫자)" msgctxt "#30008" msgid "Login failed" msgstr "로그인 실패" msgctxt "#30009" -msgid "Please Check your credentials" +msgid "Please check your e-mail and password" msgstr "로그인 암호를 확인하십시오." msgctxt "#30010" msgid "Lists of all kinds" msgstr "장르" -msgctxt "#30011" -msgid "Search" -msgstr "검색" - +#. Unused 30011 msgctxt "#30012" -msgid "No seasons available" -msgstr "볼 수 있는 시즌이 없습니다." +msgid "Profiles options and parental control" +msgstr "프로파일 설정 및 시청제한" msgctxt "#30013" -msgid "No matches found" -msgstr "일치하는 것이 없습니다." +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "프로파일 목록에서 선택한 후, 바로가기 메뉴를 이용하십시오." msgctxt "#30014" msgid "Account" msgstr "계정" +msgctxt "#30015" +msgid "Other options" +msgstr "기타" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "" + msgctxt "#30017" msgid "Logout" msgstr "로그아웃" @@ -105,7 +107,7 @@ msgid "Add to 'My list'" msgstr "찜한 것들에 추가" msgctxt "#30022" -msgid "(between 0 & 10)" +msgid "(between 0 and 10)" msgstr "(0-10 사이)" msgctxt "#30023" @@ -132,157 +134,135 @@ msgctxt "#30028" msgid "Playback error" msgstr "재생 오류" -msgctxt "#30029" -msgid "Missing InputStream addon" -msgstr "InputStream 애드온이 없군요." - +#. Unused 30029 msgctxt "#30030" msgid "Remove from library" msgstr "라이브러리에서 지우기" msgctxt "#30031" -msgid "Change library title" -msgstr "라이브러리 이름 바꾸기" +msgid "General" +msgstr "일반설정" msgctxt "#30032" -msgid "Tracking" -msgstr "추적" +msgid "Codecs" +msgstr "" msgctxt "#30033" -msgid "Use Dolby Sound" -msgstr "돌비사운드 사용" - -msgctxt "#30034" -msgid "ESN (set automatically, can be changed manually)" -msgstr "ESN(자동 설정, 수동변경 가능)" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "" +#. Unused 30034 msgctxt "#30035" msgid "InputStream Adaptive settings..." msgstr "InputStream 애드온 설정" -msgctxt "#30036" -msgid "Always use the original title on export" -msgstr "내보낼 때 항상 원래 제목 사용하기" - +#. Unused 30036 msgctxt "#30037" -msgid "Views" -msgstr "보기" +msgid "Skin viewtypes" +msgstr "스킨뷰 타입" msgctxt "#30038" -msgid "Enable custom views" -msgstr "사용자 설정" +msgid "Enable custom viewtypes" +msgstr "커스텀뷰 사용" msgctxt "#30039" -msgid "View for folders" -msgstr "" +msgid "View ID for folders" +msgstr "폴더의 뷰아이디" msgctxt "#30040" -msgid "View for movies" -msgstr "" +msgid "View ID for movies" +msgstr "영화의 뷰아이디" msgctxt "#30041" -msgid "View for shows" -msgstr "" +msgid "View ID for shows" +msgstr "TV프로그램의 뷰아이디" msgctxt "#30042" -msgid "View for seasons" -msgstr "" +msgid "View ID for seasons" +msgstr "시즌의 뷰아이디" msgctxt "#30043" -msgid "View for episodes" -msgstr "" +msgid "View ID for episodes" +msgstr "에피소드의 뷰아이디" msgctxt "#30044" -msgid "View for profiles" -msgstr "" +msgid "View ID for profiles" +msgstr "프로파일의 뷰아이디" msgctxt "#30045" -msgid "[COLOR cyan][B]-- NEXT PAGE --[/B][/COLOR]" -msgstr "[COLOR cyan][B]-- 다음 --[/B][/COLOR]" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "엄지평점 없음|별로에요|좋아요" msgctxt "#30046" -msgid "InputStream addon is not enabled" -msgstr "InputStream addon을 설치 혹은 켜십시오." +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "InputStream Adaptive 혹은 Widevine에 문제가 있습니다. 설치되지 않았거나 사용하지 않음으로 되어 있어 동작을 취소합니다." msgctxt "#30047" -msgid "Finally remove?" -msgstr "정말로 지울까요?" +msgid "Library update in progress" +msgstr "라이브러를 새로 고치는 중" msgctxt "#30048" msgid "Exported" msgstr "내보낸 것들" msgctxt "#30049" -msgid "Update DB" -msgstr "DB 새로고침" +msgid "Library" +msgstr "라이브러리" msgctxt "#30050" -msgid "Update successful" -msgstr "새로고침 성공" - -msgctxt "#30051" -msgid "Request Error" -msgstr "요청오류" +msgid "Enable Kodi library management" +msgstr "" +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30052" -msgid "Unable to complete the request at this time" -msgstr "실패했습니다." - -msgctxt "#30053" -msgid "Auto Login" -msgstr "자동로그인" - -msgctxt "#30054" -msgid "Enable Auto Login" -msgstr "자동로그인 켬" +msgid "{} Set for library playback" +msgstr "{} 라이브러리로부터 재생 설정" +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30055" -msgid "Profile" -msgstr "프로파일" - -msgctxt "#30056" -msgid "ID" +msgid "{} Set at start-up" msgstr "" +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30057" -msgid "Select profile in profile listing -> context menu" +msgid "{} Remember PIN" msgstr "" -msgctxt "#30058" -msgid "Auto Login enabled!" -msgstr "자동 로그인이 켜졌습니다!" - +#. Unused 30058 msgctxt "#30059" -msgid "Switch accounts" -msgstr "다른 계정으로" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "상당히 많은 수({})를 새로 고치려고하는 군요.[CR]심각한 문제를 일으키거나 잠시 계정을 사용하지 못할 수도 있으니 주의하십시오.[CR]계속 하시렵니까?" msgctxt "#30060" -msgid "Enable HEVC profiles (4k for Android/HDR/DolbyVision)" -msgstr "HEVC프로파일 활성화(안드로이드의 4k 등)" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "" msgctxt "#30061" msgid "Update inside library" msgstr "라이브러리 새로 고침" msgctxt "#30062" -msgid "Enable/disable adult pin. Active:" -msgstr "성인인증코드 사용/사용안함" +msgid "Parental controls" +msgstr "부모의 통제" msgctxt "#30063" -msgid "new episodes added to library" -msgstr "새 에피소드가 라이브러리에 추가되었습니다." +msgid "An update is already in progress" +msgstr "새로 고치고 있는 중입니다." msgctxt "#30064" -msgid "Export new episodes" -msgstr "새 에피소드를 내보냅니다." +msgid "Perform auto-update" +msgstr "언제?" msgctxt "#30065" -msgid "Auto-update" -msgstr "자동으로 새로 고칩니다." +msgid "Video library update" +msgstr "" msgctxt "#30066" -msgid "never" -msgstr "안함" +msgid "Enable debug logging" +msgstr "디버그로그 사용" msgctxt "#30067" msgid "daily" @@ -309,13 +289,968 @@ msgid "Only start after 5 minutes of idle" msgstr "5분이상 빈둥거리면 시작합니다." msgctxt "#30073" -msgid "Check every X minutes if update scheduled" -msgstr "새로 고칠 때 몇분 마다 확인할까요?" +msgid "Always show codec information during video playback" +msgstr "코덱정보를 항상 표시" msgctxt "#30074" msgid "View for exported" -msgstr "내보낸 것들 보기" +msgstr "내보낸 것들" + +msgctxt "#30075" +msgid "Ask to skip intro and recap" +msgstr "건너뛸 때 물어보기" + +msgctxt "#30076" +msgid "Skip intro" +msgstr "오프닝 건너뛰기" + +msgctxt "#30077" +msgid "Skip recap" +msgstr "줄거리 건너뛰기" + +msgctxt "#30078" +msgid "Playback" +msgstr "재생" + +msgctxt "#30079" +msgid "Skip automatically" +msgstr "자동으로 건너뛰기" + +msgctxt "#30080" +msgid "Pause when skipping" +msgstr "건너뛸 때 멈추기" + +msgctxt "#30081" +msgid "Force HDCP version on video streams" +msgstr "" + +msgctxt "#30082" +msgid "Remember audio / subtitle preferences" +msgstr "음성/자막 설정 기억하기" + +msgctxt "#30083" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "음성에 포함된 강제자막은 무조건 표시" + +msgctxt "#30084" +msgid "Cache objects TTL (minutes)" +msgstr "캐쉬보존시간(분단위)" + +msgctxt "#30085" +msgid "Cache metadata TTL (days)" +msgstr "메타데이타의 캐쉬보존기간(일단위)" + +msgctxt "#30086" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "'내가 찜한것'의 캐쉬보존기간(분단위)" + +msgctxt "#30087" +msgid "Contains {} and more..." +msgstr "{}을(를) 포함한 것들..." + +msgctxt "#30088" +msgid "Browse related content" +msgstr "비슷한 콘텐츠" + +msgctxt "#30089" +msgid "Browse subgenres" +msgstr "쟝르" + +msgctxt "#30090" +msgid "Browse through related video lists and discover more content." +msgstr "비슷한 비디오리스트를 검색하여 더 많은 컨텐츠를 찾습니다." + +msgctxt "#30091" +msgid "Browse and manage contents that were exported to the Kodi library." +msgstr "Kodi라이브러리로 내보낸 컨텐츠를 관리합니다." + +msgctxt "#30092" +msgid "Search for content and easily find what you want to watch." +msgstr "컨텐츠를 검색하여 보고 싶은 것을 쉽게 찾습니다." + +msgctxt "#30093" +msgid "Browse content by genre and easily discover related content." +msgstr "콘텐츠를 검색하여 비슷한 것을 찾습니다." + +msgctxt "#30094" +msgid "Browse recommendations and pick up on something new you might like." +msgstr "추천목록을 검색하여 혹시 좋아할 지도 모를 컨텐츠를 골라봅니다." + +msgctxt "#30095" +msgid "All TV shows" +msgstr "TV 프로그램 전체" + +msgctxt "#30096" +msgid "All Movies" +msgstr "영화 전체" + +msgctxt "#30097" +msgid "Main Menu" +msgstr "홈" + +msgctxt "#30098" +msgid "Enable HDR10" +msgstr "" + +msgctxt "#30099" +msgid "Enable Dolby Vision" +msgstr "" + +msgctxt "#30100" +msgid "Results for '{}'" +msgstr "'{}'의 검색 결과" + +msgctxt "#30101" +msgid "Netflix returned an unknown error." +msgstr "Netflix로부터 알수 없는 오류." + +msgctxt "#30102" +msgid "Netflix returned the following error:" +msgstr "Netflix로부터의 오류내용:" + +msgctxt "#30103" +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "자세한 내용은 Kodi 로그파일을 참고하십시오. 로그에 해결 방법이 나와 았지 않거나 계속 이 내용을 보게 되면, Github의 Readme에 나온 대로 우리에게 알려 주십시오." + +msgctxt "#30104" +msgid "The add-on encountered the following error:" +msgstr "오류가 생겼습니다:" + +msgctxt "#30105" +msgid "Netflix Add-on Error" +msgstr "Netflix애드온 오류" + +msgctxt "#30106" +msgid "Invalid PIN" +msgstr "PIN이 틀렸습니다." + +msgctxt "#30107" +msgid "PIN protection is off." +msgstr "PIN보호 사용안함" + +msgctxt "#30108" +msgid "All content is protected by PIN." +msgstr "모든 콘텐츠에 PIN을 사용합니다." + +msgctxt "#30109" +msgid "Login successful" +msgstr "로그인 성공" + +#. Unused 30110 +msgctxt "#30111" +msgid "There is no contents" +msgstr "없습니다." + +msgctxt "#30112" +msgid "You need to be logged in to use Netflix" +msgstr "Netflix를 보려면 먼저 로그인 하십시오." + +msgctxt "#30113" +msgid "Logout successful" +msgstr "로그인 하였습니다." + +#. Unused 30114, 30115 +msgctxt "#30116" +msgid "Advanced Add-on Configuration" +msgstr "고급설정" + +msgctxt "#30117" +msgid "Cache" +msgstr "캐쉬" + +#. Unused 30118, 30119 +msgctxt "#30120" +msgid "Cannot automatically remove a season from Kodi library, please do it manually" +msgstr "Kodi라이브러리에서 시진을 지우지 못 했습니다. 수동으로 삭제해 주십시오." + +msgctxt "#30121" +msgid "Perform full sync now" +msgstr "전체 동기를 시작합니다." + +msgctxt "#30122" +msgid "Sync 'My list' to Kodi library" +msgstr "찜한 것들과 Kodi 라이브러리를 동기화" + +msgctxt "#30123" +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "전체 동기를 하면 전에 내보냈던 것들이 모두 지워집니다.[CR]타이틀이 많은 경우에는 시간이 걸립니다.[CR]계속 하시렵니까?" + +msgctxt "#30124" +msgid "Do you want to remove this item from the library?" +msgstr "정말로 지우시렵니까?" + +msgctxt "#30125" +msgid "Delete library contents" +msgstr "라이브러리를 지웁니다." + +msgctxt "#30126" +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "라이브러리를 삭제하시렵니까?[CR]내보낸 것들을 모두 지웁니다." + +msgctxt "#30127" +msgid "Awarded rating of {}" +msgstr "{}의 평가로 상을 받았습니다." + +msgctxt "#30128" +msgid "Select a profile" +msgstr "프로파일 선택" + +msgctxt "#30129" +msgid "Enable Up Next integration" +msgstr "Up Next 기능 사용" + +msgctxt "#30130" +msgid "Install Up Next add-on" +msgstr "Up Next 애드온 설치" + +msgctxt "#30131" +msgid "Check the logfile for detailed information" +msgstr "자세한 내용은 로그파일을 확인하십시오." + +msgctxt "#30132" +msgid "Clear in-memory cache" +msgstr "메모리캐쉬 삭제" + +msgctxt "#30133" +msgid "Clear in-memory and on-disk cache" +msgstr "메모리/디스크 캐쉬 삭제" + +msgctxt "#30134" +msgid "Enable execution timing" +msgstr "실행시간" + +msgctxt "#30135" +msgid "Cache cleared" +msgstr "캐쉬를 지웠습니다." + +msgctxt "#30136" +msgid "Waiting for service start-up..." +msgstr "" + +msgctxt "#30137" +msgid "Enable VP9 codec" +msgstr "" + +msgctxt "#30138" +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "이제 막 Kodi 혹은 애드온을 실행했으면, 백그라운드 서비스가 준비 되지 않았을 수 있으니 잠시후 다시 해 보십시오." + +msgctxt "#30139" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "HTTP경유의 IPC사용(애드온 타임아웃이 생기는 경우 사용해 보십시오)" + +msgctxt "#30140" +msgid "Import existing library" +msgstr "라이브러리에서 가져오기" + +#. Unused 30141 to 30143 +msgctxt "#30144" +msgid "Disable WebVTT subtitle support" +msgstr "WebVTT 자막 지원 안함" + +msgctxt "#30145" +msgid "Recently added" +msgstr "최신등록콘텐츠" + +msgctxt "#30146" +msgid "Discover the latest content added." +msgstr "추가된 새로운 컨텐츠를 찾아봅니다." + +msgctxt "#30147" +msgid "Next page »" +msgstr "다음 »" + +msgctxt "#30148" +msgid "« Previous page" +msgstr "« 이전" + +msgctxt "#30149" +msgid "Sort results by" +msgstr "정렬" + +msgctxt "#30150" +msgid "Alphabetical order (A-Z)" +msgstr "오름차순(ㄱ-Z)" + +msgctxt "#30151" +msgid "Alphabetical order (Z-A)" +msgstr "내림차순(Z-ㄱ)" + +msgctxt "#30152" +msgid "Suggestions for you" +msgstr "추천콘텐츠" + +msgctxt "#30153" +msgid "Year released" +msgstr "출시일순" + +msgctxt "#30154" +msgid "Netflix configuration wizard" +msgstr "Netflix애드온 설정" + +msgctxt "#30155" +msgid "Do you have a Netflix Premium account?" +msgstr "Netflix프리미엄계정을 가지고 있나요?" + +#. Unused 30156 +msgctxt "#30157" +msgid "The configuration has been completed" +msgstr "설정을 완료하였습니다." + +msgctxt "#30158" +msgid "Restore the add-on default configuration" +msgstr "" + +msgctxt "#30159" +msgid "View ID for main menu" +msgstr "첫 화면의 뷰아이디" + +msgctxt "#30160" +msgid "View ID for search" +msgstr "검색의 뷰아이디" + +msgctxt "#30161" +msgid "View ID for exported" +msgstr "내보낸 것들의 뷰아이디" + +msgctxt "#30162" +msgid "Last used" +msgstr "마지막으로 사용한 설정" + +msgctxt "#30163" +msgid "Audio description" +msgstr "음성지원" + +msgctxt "#30164" +msgid "Browse contents with audio description." +msgstr "음성지원" + +msgctxt "#30165" +msgid "View ID for 'My list'" +msgstr "'내가 찜한 콘텐츠'의 뷰아이디" + +msgctxt "#30166" +msgid "Main menu items" +msgstr "첫 화면" + +msgctxt "#30167" +msgid "My list" +msgstr "내가 찜한 콘텐츠" + +msgctxt "#30168" +msgid "Continue watching" +msgstr "시청 중인 콘텐츠" + +msgctxt "#30169" +msgid "Top picks" +msgstr "취향 저격 베스트 콘텐츠" + +msgctxt "#30170" +msgid "New releases" +msgstr "신규콘텐츠" + +msgctxt "#30171" +msgid "Trending now" +msgstr "지금 뜨는 콘텐츠" + +msgctxt "#30172" +msgid "Popular on Netflix" +msgstr "Netflix 인기 콘텐츠" + +msgctxt "#30173" +msgid "Netflix Originals" +msgstr "Netflix 오리지널" + +msgctxt "#30174" +msgid "TV show genres" +msgstr "TV 프로그램" + +msgctxt "#30175" +msgid "Movie genres" +msgstr "영화" + +msgctxt "#30176" +msgid "Enable STRM resume workaround" +msgstr "보던 곳 부터 보기기능 사용" + +msgctxt "#30177" +msgid "Ask to resume the video" +msgstr "보던 곳부터 볼 것인지 물어 봅니다." + +msgctxt "#30178" +msgid "Open Up Next add-on settings" +msgstr "Up Next애드온 설정화면" + +msgctxt "#30179" +msgid "Show trailers and more" +msgstr "예고편 및 다른 영상" + +msgctxt "#30180" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "로그인에 문제가 있습니다. 계정이 아직 처리 중이거나 활성화 되지 않았을 수 있습니다." + +msgctxt "#30181" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "원하는 음성이 없으면, 항상 자막을 보여줍니다." + +msgctxt "#30182" +msgid "Export NFO files" +msgstr "NFO파일 내보내기" + +msgctxt "#30183" +msgid "Do you want to export NFO files for the {}?" +msgstr "{}의 NFO파일을 내보낼까요?" + +msgctxt "#30184" +msgid "NFO Files" +msgstr "NFO파일" + +msgctxt "#30185" +msgid "Enable NFO files export" +msgstr "NFO파일을 내보냅니다" + +msgctxt "#30186" +msgid "For Movies" +msgstr "영화" + +msgctxt "#30187" +msgid "For TV show" +msgstr "TV프로그램" + +msgctxt "#30188" +msgid "Ask" +msgstr "물어봄" + +msgctxt "#30189" +msgid "movie" +msgstr "영화" + +msgctxt "#30190" +msgid "TV show" +msgstr "TV프로그램" + +#. Unused 30191 +msgctxt "#30192" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]영화나 TV프로그램의 정보, 보너스 에피소드 또는 감독판등의 정보를 수집하는 정보제공자가 이 파일을 사용합니다." + +msgctxt "#30193" +msgid "Include tv show NFO details" +msgstr "" + +msgctxt "#30194" +msgid "Limit video stream resolution to" +msgstr "해상도 제한" + +msgctxt "#30195" +msgid "Export new episodes" +msgstr "새로운 에피소드를 내보냅니다." + +msgctxt "#30196" +msgid "Exclude from auto update" +msgstr "자동 업데이트는 제외" + +msgctxt "#30197" +msgid "Include in auto update" +msgstr "자동 업데이트 포함" + +msgctxt "#30198" +msgid "Exporting new episodes" +msgstr "새로운 에피소드를 내보내는 중" + +msgctxt "#30199" +msgid "Shared library (Kodi MySQL server is required)" +msgstr "공유라이브러리(Kodi MySQL서버가 필요합니다)" + +msgctxt "#30200" +msgid "Use shared library MySQL-database" +msgstr "MySQL 공유라이브러리 데이타베이스를 사용합니다." + +msgctxt "#30201" +msgid "Username" +msgstr "사용자이름" + +msgctxt "#30202" +msgid "Connection to the MySQL-database was successful" +msgstr "MySQL과 접속하였습니다." + +msgctxt "#30203" +msgid "Host IP" +msgstr "IP" + +msgctxt "#30204" +msgid "Port" +msgstr "포트" + +msgctxt "#30205" +msgid "Test database connection" +msgstr "데이타베이스 시험접속" + +msgctxt "#30206" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" +msgstr "ERROR: MySQL 데이터베이스와 연결되지 않아, 로컬 데이타베이스를 사용합니다." + +msgctxt "#30207" +msgid "Set this device as main auto-updates manager" +msgstr "이 장치를 자동 업데이트 매니저로 사용합니다." + +msgctxt "#30208" +msgid "Check if this device is the main auto-update manager" +msgstr "이 장치가 주 매니저인지 확인합니다." + +msgctxt "#30209" +msgid "This device now handles the auto-updates of the shared library" +msgstr "이 장치가 공유라이브러리의 자동 업데이트를 지금 합니다." + +msgctxt "#30210" +msgid "This device handles the auto-updates of the shared library" +msgstr "이 장치가 공유라이브러리의 자동 업데이트를 합니다." + +msgctxt "#30211" +msgid "Another device handles the auto-updates of the shared library" +msgstr "다른 장치가 공유라이브러리의 자동 업데이트를 합니다." + +msgctxt "#30212" +msgid "There is no device that handles the auto-updates of the shared library" +msgstr "공유라이브러리를 자동업데이트할 장치가 없습니다." msgctxt "#30213" msgid "InputStream Helper settings..." msgstr "InputStream Helper 애드온 설정" + +msgctxt "#30214" +msgid "Force refresh" +msgstr "강제로 새로 고칩니다" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "찜한 것에 있는 타이틀을 색으로 강조" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "동기화완료 알림을 사용하지 않습니다." + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "Kodi라이브러리를 새로 고쳤습니다." + +msgctxt "#30221" +msgid "Owner account" +msgstr "주인장" + +msgctxt "#30222" +msgid "Kid account" +msgstr "키즈" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "자동으로 고치기" + +msgctxt "#30225" +msgid "Manual" +msgstr "수동" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "자동" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "Kodi라이브러리와 내가 찜한 것들을 동기화" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "동기화할 프로파일을 설정합니다." + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "업데이트를 지금 확인합니다" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "업데이트를 확인하시렵니까?" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "{} 프로파일의 관람등급" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "[B]{}[/B]의 콘텐츠만 보여줍니다." + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "시청상태를 Netflix와 동기화합니다." + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "시청상태를 변경(Kodi에서만)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "본것으로|안본것으로|Netflix로부터" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Up Next알림(다음것 보기)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "추가정보에 사용할 색" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "백그라운드 서비스를 시작하지 못한 이유는:[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Open Connect CDN서버 설정(낮을 수록 좋다)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "상위10" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "보지않은 첫 에피소드를 선택" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "라이브러리로 내보낸 것들을 삭제 중" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "{}개의 타이틀을 가져올 수 없습니다.[CR]삭제하시렵니까?" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "한페이지에 보여줄 검색결과(타임아웃이 생기면 줄이시오)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "{}의 시청상태를 지울까요?" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "Kodi라이브러리에 포함시킵니다(보낸 데이터만)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "로그인방법을 고르시지요[CR](E-Mail/암호에서 계속 \"암호가 틀렸다\"고 하면, 인증키를 사용해 보십시오. GitHub의 ReadMe에 설명이 있습니다." + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "E-Mail/암호" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "인증키" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "호환성이 없거나 망가진 화일을 골랐습니다." + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "인증키의 유효기간이 지났습니다." + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "PIN을 입력하시오" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "검색기록 정렬" + +msgctxt "#30400" +msgid "Search" +msgstr "검색" + +msgctxt "#30401" +msgid "Type of search" +msgstr "검색 방법" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "타이틀, 인물, 장르" + +msgctxt "#30403" +msgid "New search..." +msgstr "새로운 검색" + +msgctxt "#30404" +msgid "Clear search history" +msgstr "검색 결과 삭제" + +msgctxt "#30405" +msgid "Select the language" +msgstr "언어" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "모두 다 지울까요?" + +msgctxt "#30407" +msgid "No matches found" +msgstr "일치하는 것이 없습니다." + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "항목" + +msgctxt "#30411" +msgid "By audio language" +msgstr "음성" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "자막" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "장르/서브장르ID" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "(사용할 수 있으면)국가코드가 있는 음성/자막을 먼저 선택합니다." + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "스테레오 음성을 먼저 선택합니다." + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "ESN/Widevine 설정" + +msgctxt "#30601" +msgid "ESN:" +msgstr "ESN:" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "ESN변경" + +msgctxt "#30603" +msgid "Save system info" +msgstr "시스템 정보 저장" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "Widebine - 강제보안레벨" + +msgctxt "#30605" +msgid "Force {}" +msgstr "강제 {}" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "ESN설정을 바꾸었습니다[CR]영상을 재생해 보십시오" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "사용할 수 없는 ESN입니다[CR]ESN을 바꾸거나 다시 설정하십시오.[CR][CR]자세한 내용은:[CR]{}" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "ESN형식이 이상합니다." + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "전부 다시 설정할까요?" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "시스템에서 제공하는 암호화 사용" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "[B]이 기능을 끄면, 보안이 약해질 수 있습니다.[/B] OS에서 [B]지원하지 않을 경우에만[/B] 끄고, 재기동할때마다 로그인을 해야합니다. 설정을 바꾸면, 다음번에 재기동할 때 다시 로그인을 해야합니다." + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "MSL버전" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "{}[CR]공개" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "예고편을 재생할까요?" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "알림받기 {}" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "NEW! 요즘 대세 콘텐츠" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "보기 시작한 TV시리즈를 본 것으로 설정" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "이 기능을 사용하면, 스킨설정에 따라 본 것으로 설정한 것들이 나타나지 않을 수도 있습니다." + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "스트림설정 방법을 무시" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "InputStream 애드온의 설정을 무시하고, 재생 중에 음성과 화질을 선택할 수 있습니다." + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "해상도 자동조정" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "화질고정" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "해상도 선택" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "이 기능을 사용하면 설정 범위 밖의 스트림을 삭제함으로서 InputStream Adaptive의 동작에 영향을 줄 수 있습니다. 가능하면 InputStream Adaptive의 설정에서 화질을 제한하는 것이 좋습니다." + +msgctxt "#30721" +msgid "Size" +msgstr "" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "" + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "" + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "" + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "" + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "" + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "" + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "" + +msgctxt "#30734" +msgid "Support" +msgstr "" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "" + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "" + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "" + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "" + +msgctxt "#30747" +msgid "Fixed" +msgstr "" + +msgctxt "#30748" +msgid "Relative" +msgstr "" + +msgctxt "#30749" +msgid "Reset" +msgstr "" diff --git a/resources/language/resource.language.nl_nl/strings.po b/resources/language/resource.language.nl_nl/strings.po index f7e5317bf..485866771 100644 --- a/resources/language/resource.language.nl_nl/strings.po +++ b/resources/language/resource.language.nl_nl/strings.po @@ -1,7 +1,7 @@ # Kodi Media Center language file # Addon Name: Netflix # Addon id: plugin.video.netflix -# Addon Provider: libdev + jojo + asciidisco + caphm + CastagnaIT +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait msgid "" msgstr "" "Project-Id-Version: plugin.video.netflix\n" @@ -10,10 +10,10 @@ msgstr "" "PO-Revision-Date: 2019-09-11 02:42+0000\n" "Last-Translator: Dag Wieers \n" "Language-Team: Dutch\n" +"Language: nl\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" @@ -21,11 +21,11 @@ msgid "Netflix" msgstr "Netflix" msgctxt "Addon Description" -msgid "Netflix VOD Services Addon" -msgstr "Bekijk Netflix films en tvprogramma's in Kodi." +msgid "Netflix VOD Services Add-on" +msgstr "Netflix films en tv-programma's" msgctxt "Addon Disclaimer" -msgid "Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing." +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." msgstr "Sommige delen van deze add-on zijn mogelijk onwettig in jouw land - raadpleeg de lokale wetgeving alvorens deze add-on te installeren." msgctxt "#30001" @@ -33,57 +33,59 @@ msgid "Recommendations" msgstr "Aanbevelingen" msgctxt "#30002" -msgid "Adult Pin" -msgstr "Pincode" - -msgctxt "#30003" -msgid "Search term" -msgstr "Zoekterm" +msgid "Enter PIN to watch restricted content" +msgstr "Voer uw PIN in voor beschermde toegang" +#. Unused 30003 msgctxt "#30004" msgid "Password" -msgstr "Paswoord" +msgstr "Wachtwoord" msgctxt "#30005" msgid "E-mail" msgstr "E-mail" msgctxt "#30006" -msgid "Adult verification failed" -msgstr "Code kinderslot incorrect" +msgid "Enter PIN to access this profile" +msgstr "Voer uw PIN in voor toegang tot dit profiel" msgctxt "#30007" -msgid "Please Check your adult pin" -msgstr "Controleer de code van het kinderslot" +msgid "Parental Control PIN (4 digits)" +msgstr "Ouderlijk toezicht PIN (4 cijfers)" msgctxt "#30008" msgid "Login failed" msgstr "Inloggen mislukt" msgctxt "#30009" -msgid "Please Check your credentials" +msgid "Please check your e-mail and password" msgstr "Controleer uw inloggegevens" msgctxt "#30010" msgid "Lists of all kinds" msgstr "Alle soorten lijsten" -msgctxt "#30011" -msgid "Search" -msgstr "Zoeken" - +#. Unused 30011 msgctxt "#30012" -msgid "No seasons available" -msgstr "Geen seizoenen beschikbaar" +msgid "Profiles options and parental control" +msgstr "" msgctxt "#30013" -msgid "No matches found" -msgstr "Geen overeenkomsten gevonden" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "" msgctxt "#30014" msgid "Account" msgstr "Account" +msgctxt "#30015" +msgid "Other options" +msgstr "Andere opties" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "" + msgctxt "#30017" msgid "Logout" msgstr "Uitloggen" @@ -98,15 +100,15 @@ msgstr "Op Netflix beoordelen" msgctxt "#30020" msgid "Remove from 'My list'" -msgstr "Van 'Mijn lijst’ verwijderen" +msgstr "Van 'Mijn lijst' verwijderen" msgctxt "#30021" msgid "Add to 'My list'" -msgstr "Aan 'Mijn lijst’ toevoegen" +msgstr "Aan 'Mijn lijst' toevoegen" msgctxt "#30022" -msgid "(between 0 & 10)" -msgstr "(tussen 0 & 10)" +msgid "(between 0 and 10)" +msgstr "(tussen 0 en 10)" msgctxt "#30023" msgid "Expert" @@ -132,157 +134,135 @@ msgctxt "#30028" msgid "Playback error" msgstr "Afspeelfout" -msgctxt "#30029" -msgid "Missing InputStream addon" -msgstr "InputStream niet gevonden" - +#. Unused 30029 msgctxt "#30030" msgid "Remove from library" msgstr "Uit de bibliotheek verwijderen" msgctxt "#30031" -msgid "Change library title" -msgstr "Titel bibliotheek aanpassen" +msgid "General" +msgstr "Algemeen" msgctxt "#30032" -msgid "Tracking" -msgstr "Tracking" +msgid "Codecs" +msgstr "" msgctxt "#30033" -msgid "Enable Dolby Digital Plus (DDPlus HQ and Atmos on Premium)" -msgstr "Dolby Digital Plus inschakelen (DDPlus HQ en Atmos op Netflix Premium)" - -msgctxt "#30034" -msgid "ESN (set automatically, can be changed manually)" -msgstr "ESN (automatisch, kan handmatig aangepast worden)" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "" +#. Unused 30034 msgctxt "#30035" -msgid "Inputstream Addon Settings..." -msgstr "Inputstream Addon instellingen…" - -msgctxt "#30036" -msgid "Always use the original title on export" -msgstr "Gebruik altijd de originele titel bij het exporteren" +msgid "InputStream Adaptive settings..." +msgstr "InputStream Adaptive instellingen…" +#. Unused 30036 msgctxt "#30037" -msgid "Views" -msgstr "Views" +msgid "Skin viewtypes" +msgstr "" msgctxt "#30038" -msgid "Enable custom views" -msgstr "Activeer aangepaste views" +msgid "Enable custom viewtypes" +msgstr "" msgctxt "#30039" -msgid "View for folders" -msgstr "View voor folders" +msgid "View ID for folders" +msgstr "" msgctxt "#30040" -msgid "View for movies" -msgstr "View voor films" +msgid "View ID for movies" +msgstr "" msgctxt "#30041" -msgid "View for shows" -msgstr "View voor tvprogramma's" +msgid "View ID for shows" +msgstr "" msgctxt "#30042" -msgid "View for seasons" -msgstr "View voor seizoenen" +msgid "View ID for seasons" +msgstr "" msgctxt "#30043" -msgid "View for episodes" -msgstr "View voor afleveringen" +msgid "View ID for episodes" +msgstr "" msgctxt "#30044" -msgid "View for profiles" -msgstr "View voor profielen" +msgid "View ID for profiles" +msgstr "" msgctxt "#30045" -msgid "[COLOR cyan][B]-- NEXT PAGE --[/B][/COLOR]" -msgstr "[COLOR cyan][B]-- VOLGENDE PAGINA --[/B][/COLOR]" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "Beoordeling verwijderen|Je raadt niet meer aan|Je raadt aan" msgctxt "#30046" -msgid "Inputstream addon is not enabled" -msgstr "Inputstream add-on is niet ingeschakeld" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "Er is een probleem met het afspelen van DRM. De InputStream Adaptive add-on is mogelijk niet geactiveerd, of de Widevine CDM library ontbreekt. Het afspelen wordt afgebroken." msgctxt "#30047" -msgid "Finally remove?" -msgstr "Definitief verwijderen?" +msgid "Library update in progress" +msgstr "Bezig met bijwerken van de bibliotheek" msgctxt "#30048" msgid "Exported" msgstr "Geëxporteerd" msgctxt "#30049" -msgid "Update DB" -msgstr "Update database" +msgid "Library" +msgstr "Bibliotheek" msgctxt "#30050" -msgid "Update successful" -msgstr "Succesvol geüpdate" - -msgctxt "#30051" -msgid "Request Error" -msgstr "Request fout" +msgid "Enable Kodi library management" +msgstr "" +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30052" -msgid "Unable to complete the request at this time" -msgstr "Het is niet mogelijk om het request uit te voeren op dit moment" - -msgctxt "#30053" -msgid "Auto Login" -msgstr "Automatisch aanmelden" - -msgctxt "#30054" -msgid "Enable Auto Login" -msgstr "Automatisch aanmelden inschakelen" +msgid "{} Set for library playback" +msgstr "{} Instellen voor afspelen vanuit de bibliotheek" +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30055" -msgid "Profile" -msgstr "Profiel" - -msgctxt "#30056" -msgid "ID" -msgstr "Id" +msgid "{} Set at start-up" +msgstr "" +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30057" -msgid "Select profile in profile listing -> context menu" -msgstr "Selecteer het profiel in de profiellijst -> context menu" - -msgctxt "#30058" -msgid "Auto Login enabled!" -msgstr "Automatisch inloggen ingeschakeld!" +msgid "{} Remember PIN" +msgstr "" +#. Unused 30058 msgctxt "#30059" -msgid "Switch accounts" -msgstr "Verwissel van account" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "Pas op! U bent van plan om meerdere titles bij te werken {}.[CR]Het is mogelijk dat er problemen met Netflix ontstaan of uw account tijdelijk geblokkeerd wordt.[CR] Wilt u doorgaan?" msgctxt "#30060" -msgid "Enable HEVC profiles (4k for Android/HDR/DolbyVision)" -msgstr "Activeer HEVC profielen (4K voor Android/HDR/DolbyVision)" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "" msgctxt "#30061" msgid "Update inside library" -msgstr "Updaten in de bibliotheek" +msgstr "Bijwerken in de bibliotheek" msgctxt "#30062" -msgid "Enable/disable adult pin. Active:" -msgstr "Adult PIN Inschakelen/uitschakelen. Ingeschakeld:" +msgid "Parental controls" +msgstr "Ouderlijk toezicht" msgctxt "#30063" -msgid "new episodes added to library" -msgstr "niewe afleveringen werden toegevoegd in de bibliotheek" +msgid "An update is already in progress" +msgstr "Het bijwerken is al gestart" msgctxt "#30064" -msgid "Export new episodes" -msgstr "Exporteer nieuwe afleveringen" +msgid "Perform auto-update" +msgstr "Voer automatisch bijwerken uit" msgctxt "#30065" -msgid "Auto-update" -msgstr "Automatisch updaten" +msgid "Video library update" +msgstr "" msgctxt "#30066" -msgid "never" -msgstr "nooit" +msgid "Enable debug logging" +msgstr "" msgctxt "#30067" msgid "daily" @@ -290,7 +270,7 @@ msgstr "dagelijks" msgctxt "#30068" msgid "every other day" -msgstr "iedere andere dag" +msgstr "om de dag" msgctxt "#30069" msgid "every 5 days" @@ -302,19 +282,19 @@ msgstr "wekelijks" msgctxt "#30071" msgid "Time of Day" -msgstr "Tijdstip" +msgstr "Tijd v.d. dag" msgctxt "#30072" msgid "Only start after 5 minutes of idle" msgstr "Begin pas na 5 minuten inactiviteit" msgctxt "#30073" -msgid "Check every X minutes if update scheduled" -msgstr "Controleer elke X minuten als update is gepland" +msgid "Always show codec information during video playback" +msgstr "Toon altijd de codec informatie bij het afspelen van video's" msgctxt "#30074" msgid "View for exported" -msgstr "View voor exported" +msgstr "Weergave voor geëxporteerd" msgctxt "#30075" msgid "Ask to skip intro and recap" @@ -326,7 +306,7 @@ msgstr "Sla de intro over" msgctxt "#30077" msgid "Skip recap" -msgstr "Sla de korte samenvatting over" +msgstr "Korte samenvatting overslaan" msgctxt "#30078" msgid "Playback" @@ -341,28 +321,28 @@ msgid "Pause when skipping" msgstr "Pauzeer tijdens overslaan" msgctxt "#30081" -msgid "Force support of HDCP 2.2 (enable 4K content)" -msgstr "Forceer ondersteuning van HDCP 2.2 (schakel 4K kwaliteit in)" +msgid "Force HDCP version on video streams" +msgstr "" msgctxt "#30082" msgid "Remember audio / subtitle preferences" msgstr "Onthoud geluid / ondertiteling voorkeuren" msgctxt "#30083" -msgid "Save bookmarks for library items / mark as watched" -msgstr "Bewaar favorieten voor de library inhoud / markeer als bekeken" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "" msgctxt "#30084" -msgid "Cache item time-to-live (minutes)" +msgid "Cache objects TTL (minutes)" msgstr "Cache time-to-live (minuten)" msgctxt "#30085" -msgid "Cache item time-to-live for metadata (days)" -msgstr "Cache time-to-live voor metadata (dagen)" +msgid "Cache metadata TTL (days)" +msgstr "Metadata cache time-to-live (dagen)" msgctxt "#30086" -msgid "Invalidate entire cache on modification of My List" -msgstr "Verwijder volledige cache bij wijzigingen van Mijn Lijst" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "'Mijn lijst' cache time-to-live (minuten)" msgctxt "#30087" msgid "Contains {} and more..." @@ -370,74 +350,74 @@ msgstr "Bevat {} en meer..." msgctxt "#30088" msgid "Browse related content" -msgstr "Doorblader gerelateerde videos" +msgstr "Blader door gerelateerde video's" msgctxt "#30089" msgid "Browse subgenres" -msgstr "Doorblader subgenres" +msgstr "Blader door subgenres" msgctxt "#30090" msgid "Browse through related video lists and discover more content." -msgstr "Doorblader gerelateerde videos en ontdek meer inhoud." +msgstr "Blader door gerelateerde video's en ontdek meer inhoud." msgctxt "#30091" msgid "Browse and manage contents that were exported to the Kodi library." -msgstr "Doorblader en beheer geëxporteerde videos in de Kodi bibliotheek" +msgstr "Blader door en beheer geëxporteerde video's in de Kodi-bibliotheek." msgctxt "#30092" msgid "Search for content and easily find what you want to watch." -msgstr "Zoek naar videos en vind met gemak wat je zocht." +msgstr "Zoek naar video's en vind met gemak wat u zocht." msgctxt "#30093" msgid "Browse content by genre and easily discover related content." -msgstr "Doorblader videos op basis van genre en ontdek met gemak gerelateerde inhoud." +msgstr "Blader door video's gebaseerd op genre en ontdek meer gerelateerde inhoud." msgctxt "#30094" msgid "Browse recommendations and pick up on something new you might like." -msgstr "Doorblader aangeprezen videos en kies iets dat je bevalt." +msgstr "Blader door aangeprezen video's en kies iets dat je leuk lijkt." msgctxt "#30095" -msgid "All TV Shows" -msgstr "Alle tvprogramma's" +msgid "All TV shows" +msgstr "Alle tv-programma's" msgctxt "#30096" msgid "All Movies" -msgstr "Alle films" +msgstr "Alle Films" msgctxt "#30097" msgid "Main Menu" msgstr "Hoofdmenu" msgctxt "#30098" -msgid "Enable HDR profiles" -msgstr "Activeer HDR profielen" +msgid "Enable HDR10" +msgstr "" msgctxt "#30099" -msgid "Enable DolbyVision profiles" -msgstr "Activeer DolbyVision profielen" +msgid "Enable Dolby Vision" +msgstr "" msgctxt "#30100" -msgid "Results for \"{}\"" -msgstr "Resultaten voor \"{}\"" +msgid "Results for '{}'" +msgstr "Resultaten voor '{}'" msgctxt "#30101" msgid "Netflix returned an unknown error." -msgstr "Netflix had een onbekende fout." +msgstr "Netflix geeft een onbekende foutmelding." msgctxt "#30102" msgid "Netflix returned the following error:" -msgstr "Netflix had the volgende foutmelding:" +msgstr "Netflix geeft de volgende foutmelding:" msgctxt "#30103" -msgid "Error details have been written to the Kodi logfile. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it via GitHub. Please provide a full debug log with your error report (enable \"Debug Logging\" in Kodi settings)." -msgstr "Een gedetaileerde foutmelding werd weggeschreven in het Kodi logboek. Als de foutmelding niet helpt bij het oplossen van het probleem, of the probleem blijft zich voordoen, gelieve deze fout te melden op GitHub. Voeg de volledige debug log toe aan je probleemrapport (schakel \"Debug logboek\" in in de Kodi instellingen)." +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "Details van de foutmelding zijn weggeschreven naar Kodi's logbestand. Als de foutmelding geen duidelijke oorzaak weergeeft of je blijft dezelfde foutmelding krijgen, wil ik u verzoeken dit te rapporteren op GitHub door de Readme instructies te volgen." msgctxt "#30104" -msgid "The addon encountered to following error:" +msgid "The add-on encountered the following error:" msgstr "Deze add-on ondervond de volgende fout:" msgctxt "#30105" -msgid "Netflix Addon Error" +msgid "Netflix Add-on Error" msgstr "Netflix add-on fout" msgctxt "#30106" @@ -445,21 +425,18 @@ msgid "Invalid PIN" msgstr "Foutieve PIN" msgctxt "#30107" -msgid "Disabled PIN verfication" -msgstr "PIN verificatie uitgeschakeld" +msgid "PIN protection is off." +msgstr "PIN beveiliging uit." msgctxt "#30108" -msgid "Enabled PIN verfication" -msgstr "Activeer PIN verificatie" +msgid "All content is protected by PIN." +msgstr "Alle content is beveiligd met de PIN" msgctxt "#30109" msgid "Login successful" msgstr "Succesvol aangemeld" -msgctxt "#30110" -msgid "Background services started" -msgstr "Achtergronddiensten werden gestart" - +#. Unused 30110 msgctxt "#30111" msgid "There is no contents" msgstr "Er is geen inhoud" @@ -472,129 +449,104 @@ msgctxt "#30113" msgid "Logout successful" msgstr "Succesvol afgemeld" -msgctxt "#30114" -msgid "Keep My List and Kodi Library in sync" -msgstr "Houd Mijn Lijst en de Kodi bibliotheek gesynchroniseerd" - -msgctxt "#30115" -msgid "Content Profiles" -msgstr "Inhoudsprofielen" - +#. Unused 30114, 30115 msgctxt "#30116" -msgid "Advanced Addon Configuration" +msgid "Advanced Add-on Configuration" msgstr "Geavanceerde add-on configuratie" msgctxt "#30117" msgid "Cache" msgstr "Cache" -msgctxt "#30118" -msgid "API operation failed: {}" -msgstr "API operatie mislukt: {}" - -msgctxt "#30119" -msgid "My List operation successful" -msgstr "Mijn Lijst operatie was succesvol" - +#. Unused 30118, 30119 msgctxt "#30120" msgid "Cannot automatically remove a season from Kodi library, please do it manually" -msgstr "Het is onmogelijk om automatisch een seizoen te verwijderen uit de Kodi bibliotheek, gelieve dit manueel te doen" +msgstr "Het is onmogelijk om automatisch een seizoen te verwijderen uit de Kodi-bibliotheek, gelieve dit handmatig uit te voeren" msgctxt "#30121" msgid "Perform full sync now" msgstr "Voer een volledige synchronisatie uit" msgctxt "#30122" -msgid "Sync My List to Kodi library" -msgstr "Synchroniseer Mijn Lijst met de Kodi bibliotheek" +msgid "Sync 'My list' to Kodi library" +msgstr "Synchroniseer 'Mijn lijst' met de Kodi-bibliotheek" msgctxt "#30123" -msgid "Do you really want to proceed?\nThis will delete all previously exported items and export all items from My List for the currently active profile.\nThis operation may take some time..." -msgstr "Wil je écht verder gaan?\nDit zal al je vorige geëxporteerde items verwijderen en all items van Mijn Lijst voor het huidige actieve profiel exporteren.\nDit zal een tijdje duren..." +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "Wilt u doorgaan?[CR]Het volledig bijwerken van de bibliotheek zal resulteren in het verwijderen van eerder geëxporteerde titels.[CR]Het volledig bijwerken van de bibliotheek kan daardoor enige tijd duren." msgctxt "#30124" -msgid "Do you really want to remove this item?" -msgstr "Wil je dit verwijderen?" +msgid "Do you want to remove this item from the library?" +msgstr "Wilt u dit item verwijderen uit de bibliotheek?" msgctxt "#30125" -msgid "Purge library" -msgstr "Bibliotheek leegmaken" +msgid "Delete library contents" +msgstr "Inhoud bibliotheek verwijderen" msgctxt "#30126" -msgid "Do you really want to purge your library?\nAll exported items will be deleted." -msgstr "Wil je echt de bibliotheek leegmaken?\nAlle geêxporteerde items worden verwijderd." +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "Wilt u echt alles uit de bibliotheek verwijderen?[CR]Alle geëxporteerde items worden verwijderd." msgctxt "#30127" msgid "Awarded rating of {}" msgstr "Toegekende beoordeling van {}" msgctxt "#30128" -msgid "No contained titles yet..." -msgstr "Nog geen titles..." +msgid "Select a profile" +msgstr "Selecteer een profiel" msgctxt "#30129" msgid "Enable Up Next integration" msgstr "Activeer Up Next integratie" msgctxt "#30130" -msgid "Show notification instead of modal dialog for errors" -msgstr "Toon een melding in plaats van een venster bij fouten" +msgid "Install Up Next add-on" +msgstr "Installeer de Up Next add-on" msgctxt "#30131" msgid "Check the logfile for detailed information" msgstr "Controleer het logboek voor gedetaileerde informatie" msgctxt "#30132" -msgid "Purge in-memory cache" +msgid "Clear in-memory cache" msgstr "In-memory cache leegmaken" msgctxt "#30133" -msgid "Purge in-memory and on-disk cache" +msgid "Clear in-memory and on-disk cache" msgstr "In-memory en on-disk cache leegmaken" msgctxt "#30134" msgid "Enable execution timing" -msgstr "Timing van uitvoering inschakelen" +msgstr "Tijdstip van uitvoering inschakelen" msgctxt "#30135" msgid "Cache cleared" msgstr "Cache leegmaken" msgctxt "#30136" -msgid "Enable 1080p Unlock (not recommended for Android)" -msgstr "1080p Unlock inschakelen (niet aan te raden op Android)" +msgid "Waiting for service start-up..." +msgstr "" msgctxt "#30137" -msgid "Enable VP9 profiles (disable if it causes artifacts)" -msgstr "VP9 profielen inschakelen (schakel dit uit bij schermartifacten)" +msgid "Enable VP9 codec" +msgstr "" msgctxt "#30138" -msgid "The requested action timed out. The background services may not yet be available if you just started Kodi/the addon. In this case, try again in a moment." -msgstr "The gevraagde actie duurde te lang. De achtergronddiensten zijn mogelijk nog niet beschikbaar als je net Kodi of de add-on hebt gestart. In dat geval, probeer zo meteen opnieuw." +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "De achtergrondservices zijn mogelijk nog niet gestart als je net Kodi of deze add-on hebt gestart. Probeer het zo meteen opnieuw." msgctxt "#30139" -msgid "Enable IPC over HTTP (use when experiencing AddonSignals timeouts)" -msgstr "IPC over HTTP inschakelen (gebruik it wanneer je AddonSignals timeouts ondervindt)" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "IPC over HTTP inschakelen (gebruik dit wanneer Addon Signals timeouts ondervindt)" msgctxt "#30140" -msgid "Migrate Library to new format" -msgstr "Migreer de bibliotheek naar het nieuwe formaat" - -msgctxt "#30141" -msgid "Disable startup notification" -msgstr "Startmelding uitschakelen" - -msgctxt "#30142" -msgid "Custom" -msgstr "Aangepast" - -msgctxt "#30143" -msgid "Custom view ID" -msgstr "Aangepaste view Id" +msgid "Import existing library" +msgstr "Importeer bestaande bibliotheek" +#. Unused 30141 to 30143 msgctxt "#30144" msgid "Disable WebVTT subtitle support" -msgstr "WebVTT ondertitels uitschakelen" +msgstr "Schakel ondersteuning voor WebVTT ondertiteling uit" msgctxt "#30145" msgid "Recently added" @@ -605,12 +557,12 @@ msgid "Discover the latest content added." msgstr "Ontdek de laatste toegevoegde inhoud." msgctxt "#30147" -msgid "Next page >>" -msgstr "Volgende pagina >>" +msgid "Next page »" +msgstr "Volgende pagina »" msgctxt "#30148" -msgid "<< Previous page" -msgstr "<< Vorige pagina" +msgid "« Previous page" +msgstr "« Vorige pagina" msgctxt "#30149" msgid "Sort results by" @@ -618,51 +570,48 @@ msgstr "Sorteer resultaten op" msgctxt "#30150" msgid "Alphabetical order (A-Z)" -msgstr "Alfabetische lijst (A-Z)" +msgstr "Alfabetische volgorde (A-Z)" msgctxt "#30151" msgid "Alphabetical order (Z-A)" -msgstr "Alfabetische lijst (Z-A)" +msgstr "Alfabetische volgorde (Z-A)" msgctxt "#30152" msgid "Suggestions for you" -msgstr "Suggesties voor jou" +msgstr "Aanbevolen voor jou" msgctxt "#30153" msgid "Year released" -msgstr "Jaar van vrijgave" +msgstr "Jaar van uitgave" msgctxt "#30154" -msgid "Netflix addon configuration" +msgid "Netflix configuration wizard" msgstr "Netflix add-on configuratie" msgctxt "#30155" msgid "Do you have a Netflix Premium account?" -msgstr "Heb je een Netflix Premium account?" - -msgctxt "#30156" -msgid "Does your device support the 4K standard?\r\nThe device must:\r\nBe Netflix certified, support for Widevine Security level L1 and have a 4K display HDCP 2.2" -msgstr "Ondersteunt jouw toestel 4K?\nDan moet je toestel:\nNetflix gecertifieerd zijn, Widevine Security level L1 ondersteunen én een 4K display met HDCP 2.2 hebben" +msgstr "Heeft u een Netflix Premium account?" +#. Unused 30156 msgctxt "#30157" -msgid "The InputStream Adaptive settings panel will now be opened, so you have to change this option \"Override HDCP status\" to ON" -msgstr "The InputStream Adaptive instellingen worden geopend zodat je optie \"Override HDCP status\" kan inschakelen" +msgid "The configuration has been completed" +msgstr "De configuratie is geslaagd" msgctxt "#30158" -msgid "Show configuration screen at addon startup" -msgstr "Toon schermcofiguratie tijdens add-on start" +msgid "Restore the add-on default configuration" +msgstr "" msgctxt "#30159" -msgid "View for main menu" -msgstr "View voor hoofdmenu" +msgid "View ID for main menu" +msgstr "" msgctxt "#30160" -msgid "View for search" -msgstr "View voor zoekopdrachten" +msgid "View ID for search" +msgstr "" msgctxt "#30161" -msgid "View for exported" -msgstr "View voor geëxporteerd" +msgid "View ID for exported" +msgstr "" msgctxt "#30162" msgid "Last used" @@ -670,15 +619,15 @@ msgstr "Laatst bekeken" msgctxt "#30163" msgid "Audio description" -msgstr "Audio descriptie" +msgstr "Audiobeschrijving" msgctxt "#30164" msgid "Browse contents with audio description." -msgstr "Doorblader inhoud met audio descriptie" +msgstr "Blader door inhoud met audiobeschrijving" msgctxt "#30165" -msgid "View for my list" -msgstr "View voor Mijn Lijst" +msgid "View ID for 'My list'" +msgstr "" msgctxt "#30166" msgid "Main menu items" @@ -686,7 +635,7 @@ msgstr "Hoofdmenu items" msgctxt "#30167" msgid "My list" -msgstr "Mijn Lijst" +msgstr "Mijn lijst" msgctxt "#30168" msgid "Continue watching" @@ -698,7 +647,7 @@ msgstr "Top keuzes" msgctxt "#30170" msgid "New releases" -msgstr "Nieuwe uitgaves" +msgstr "Nieuwe uitgaven" msgctxt "#30171" msgid "Trending now" @@ -713,36 +662,36 @@ msgid "Netflix Originals" msgstr "Netflix Originals" msgctxt "#30174" -msgid "Tv Show genres" -msgstr "Tvprogramma genres" +msgid "TV show genres" +msgstr "Tv-programma genres" msgctxt "#30175" msgid "Movie genres" msgstr "Film genres" msgctxt "#30176" -msgid "Enable STRM resume workaround (only library)" -msgstr "STRM hervatting inschakelen (enkel bibliotheek)" +msgid "Enable STRM resume workaround" +msgstr "STRM hervatting inschakelen" msgctxt "#30177" msgid "Ask to resume the video" msgstr "Vraag om het afspelen te hervatten" msgctxt "#30178" -msgid "Open UpNext Addon settings" +msgid "Open Up Next add-on settings" msgstr "Open de Up Next add-on instellingen" msgctxt "#30179" -msgid "Show the trailers" -msgstr "Toon voorfilmpjes" +msgid "Show trailers and more" +msgstr "Toon trailers & meer" msgctxt "#30180" -msgid "No trailers available" -msgstr "Geen voorfilmpjes beschikbaar" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "Er is een probleem opgetreden met het inloggen. Het is mogelijk dat je account nog niet bevestigd of geheractiveerd is." msgctxt "#30181" -msgid "Disable subtitles if there are no forced subtitles streams (Works only if Kodi Player is set to \"Only forced\" subtitles)" -msgstr "Ondertitels uitschakelen als er geen geforceerde ondertitels worden meegeleverd (Werkt enkel als Kodi Player \"Alleen geforceerde\" ondertitles heeft ingeschakeld)" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "" msgctxt "#30182" msgid "Export NFO files" @@ -750,7 +699,7 @@ msgstr "NFO bestanden exporteren" msgctxt "#30183" msgid "Do you want to export NFO files for the {}?" -msgstr "Wil je NFO bestanden exporteren voor {}" +msgstr "Wil je NFO bestanden exporteren voor {}?" msgctxt "#30184" msgid "NFO Files" @@ -765,12 +714,12 @@ msgid "For Movies" msgstr "Voor films" msgctxt "#30187" -msgid "For TV Show" -msgstr "Voor tvprogramma" +msgid "For TV show" +msgstr "Voor tv-programma" msgctxt "#30188" msgid "Ask" -msgstr "Vraag" +msgstr "Vragen" msgctxt "#30189" msgid "movie" @@ -778,23 +727,20 @@ msgstr "film" msgctxt "#30190" msgid "TV show" -msgstr "tvprogramma" - -msgctxt "#30191" -msgid "Do you want to export NFO files for the {} added to 'My List'?" -msgstr "Wil je de NFO bestanden exporteren voor {} toegevoegd aan 'Mijn Lijst'?" +msgstr "Tv-programma" +#. Unused 30191 msgctxt "#30192" -msgid "\r\nThese files are used by the provider information scrapers to integrate movies and tv shows info, useful with bonus episodes or director's cut" -msgstr "\r\nDeze bestanden worden door de 'provider information scrapers' gebruikt om films en tvprogrammas informatie te integreren, handig voor extra's en director's cut" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]Deze bestanden worden door 'provider information scrapers' gebruikt om informatie over films en tv-programma's toe te voegen, handig voor extra's en director's cut" msgctxt "#30193" -msgid "Include all information in NFO files (use only with 'Local Information' scraper)" -msgstr "Gebruik all informatie uit NFO bestanden (enkel te gebruiken met 'Local Information' scraper)" +msgid "Include tv show NFO details" +msgstr "" msgctxt "#30194" msgid "Limit video stream resolution to" -msgstr "Beperk de video stream kwaliteit tot" +msgstr "Beperk de resolutie van de videostream tot" msgctxt "#30195" msgid "Export new episodes" @@ -814,18 +760,18 @@ msgstr "Exporteer nieuwe afleveringen" msgctxt "#30199" msgid "Shared library (Kodi MySQL server is required)" -msgstr "Gedeelde-bibliotheek (Kodi MySQL server is noodzakelijk)" +msgstr "Gedeelde bibliotheek (Kodi MySQL server is noodzakelijk)" msgctxt "#30200" -msgid "Use MySQL shared library database" -msgstr "Gebruik de MySQL gedeelde-bibliotheek database" +msgid "Use shared library MySQL-database" +msgstr "Gebruik de gedeelde MySQL bibliotheek database" msgctxt "#30201" msgid "Username" msgstr "Gebruikersnaam" msgctxt "#30202" -msgid "Connection to the MySQL database was successful" +msgid "Connection to the MySQL-database was successful" msgstr "Succesvol verbonden met de MySQL database" msgctxt "#30203" @@ -841,33 +787,470 @@ msgid "Test database connection" msgstr "Test de database verbinding" msgctxt "#30206" -msgid "ERROR: The MySQL database is not reachable - the local database will be used" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" msgstr "FOUT: De MySQL database is niet bereikbaar - de lokale database wordt gebruikt" msgctxt "#30207" msgid "Set this device as main auto-updates manager" -msgstr "Stel dit device in als hoofdbeheerde van automatische updates" +msgstr "Stel dit apparaat in als hoofdbeheerder van automatische updates" msgctxt "#30208" msgid "Check if this device is the main auto-update manager" -msgstr "Controleer of dit device de hoofd beheerder is van automatische updates" +msgstr "Controleer of dit apparaat de hoofdbeheerder is van automatische updates" msgctxt "#30209" -msgid "This device now handles the auto-updates of shared-library" -msgstr "Dit device handelt nu de automatische updates van de gedeelde-bibliotheek af" +msgid "This device now handles the auto-updates of the shared library" +msgstr "Dit apparaat handelt nu de automatische updates van de gedeelde bibliotheek af" msgctxt "#30210" -msgid "This device handles the auto-updates of shared-library" -msgstr "Dit device handelt de automatische updates van de gedeelde-bibliotheek af" +msgid "This device handles the auto-updates of the shared library" +msgstr "Dit apparaat handelt de automatische updates van de gedeelde bibliotheek af" msgctxt "#30211" -msgid "Another device handles the auto-updates of shared-library" -msgstr "Een ander device handelt de automatische updates van de gedeelde-bibliotheek af" +msgid "Another device handles the auto-updates of the shared library" +msgstr "Een ander apparaat handelt de automatische updates van de gedeelde bibliotheek af" msgctxt "#30212" -msgid "There is no device that handles the auto-updates of shared-library" -msgstr "Er is geen device dat automatische updates van de gedeelde-bibliotheek kan afhandelen" +msgid "There is no device that handles the auto-updates of the shared library" +msgstr "Er is geen apparaat dat automatische updates van de gedeelde bibliotheek afhandelt" msgctxt "#30213" msgid "InputStream Helper settings..." -msgstr "InputStream Helper instellingen…" +msgstr "InputStream Helper instellingen..." + +msgctxt "#30214" +msgid "Force refresh" +msgstr "Forceer vernieuwen" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "Benadruk titels uit \"Mijn lijst\" met een kleur" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "Melding voor 'Synchronisatie voltooid' uitschakelen" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "De Kodi-bibliotheek is bijgewerkt" + +msgctxt "#30221" +msgid "Owner account" +msgstr "Eigenaarsaccount" + +msgctxt "#30222" +msgid "Kid account" +msgstr "Kinderaccount" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "Automatisch bijwerken modus" + +msgctxt "#30225" +msgid "Manual" +msgstr "Handmatig" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "Ingepland" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "Synchroniseer Kodi-bibliotheek met 'Mijn lijst'" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "Stel profiel in voor synchronisatie" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "Controleer nu voor updates" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "Wil je nu controleren voor een update?" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "leeftijdsaanduiding voor profiel {}" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "Toon enkel video's met waardering [B]{}[/B]" + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "Synchroniseer de 'bekeken' status van video's met Netflix" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "Verander de 'bekeken' status (lokaal)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "Markeer als bekeken|Markeer als onbekeken|Herstel Netflix bekeken status" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Up Next meldingen (bekijk volgende video)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "Kleur van bijkomende plot informatie" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "De achtergrondservices kunnen niet worden gestart door het volgende probleem:[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Open Connect CDN (een lagere index is beter)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "Top 10" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "Selecteer de eerste onbekeken aflevering van een tv-programma" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "Bezig met verwijderen van geëxporteerde bestanden" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "Er zijn {} titels die niet geimporteerd kunnen worden.[CR]Wilt u deze bestanden verwijderen?" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "Resultaten per pagina (als er timeouts optreden, verlaag dan deze waarde)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "Wilt u de bekeken status van {} verwijderen?" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "Kodi-Bibliotheek inbegrepen (alleen data versturen)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "Kies de inlogmethode[CR](als het inloggen met E-Mail/Wachtwoord altijd \"ongeldig wachtwoord\" geeft, gebruik dan een authenticatiesleutel, instructies in de ReadMe op GitHub)" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "E-mail/Wachtwoord" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "Authenticatiesleutel" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "Het geselecteerde bestand is niet compatibel of beschadigd" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "Het authenticatiesleutelbestand is vervallen" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "Voer uw PIN in" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "Sorteer geschiedenis op" + +msgctxt "#30400" +msgid "Search" +msgstr "Zoeken" + +msgctxt "#30401" +msgid "Type of search" +msgstr "Type zoekopdracht" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "Zoek titels, mensen, genres" + +msgctxt "#30403" +msgid "New search..." +msgstr "Nieuwe zoekopdracht..." + +msgctxt "#30404" +msgid "Clear search history" +msgstr "Ruim zoekgeschiedenis op" + +msgctxt "#30405" +msgid "Select the language" +msgstr "Selecteer de taal" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "Wilt u alles verwijderen?" + +msgctxt "#30407" +msgid "No matches found" +msgstr "Geen overeenkomsten gevonden" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "Op zoekterm" + +msgctxt "#30411" +msgid "By audio language" +msgstr "Op geluidstaal" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "Op ondertitelingstaal" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "Op genre/subgenre ID" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "" + +msgctxt "#30601" +msgid "ESN:" +msgstr "" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "" + +msgctxt "#30603" +msgid "Save system info" +msgstr "" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "" + +msgctxt "#30605" +msgid "Force {}" +msgstr "" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "" + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "" + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "" + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "" + +msgctxt "#30721" +msgid "Size" +msgstr "" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "" + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "" + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "" + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "" + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "" + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "" + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "" + +msgctxt "#30734" +msgid "Support" +msgstr "" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "" + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "" + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "" + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "" + +msgctxt "#30747" +msgid "Fixed" +msgstr "" + +msgctxt "#30748" +msgid "Relative" +msgstr "" + +msgctxt "#30749" +msgid "Reset" +msgstr "" diff --git a/resources/language/resource.language.pl_pl/strings.po b/resources/language/resource.language.pl_pl/strings.po index e25e28567..74865a1d9 100644 --- a/resources/language/resource.language.pl_pl/strings.po +++ b/resources/language/resource.language.pl_pl/strings.po @@ -1,19 +1,19 @@ # Kodi Media Center language file # Addon Name: Netflix # Addon id: plugin.video.netflix -# Addon Provider: libdev + jojo + asciidisco + caphm + CastagnaIT +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait msgid "" msgstr "" "Project-Id-Version: plugin.video.netflix\n" "Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" "POT-Creation-Date: 2017-07-24 16:15+0000\n" -"PO-Revision-Date: 2019-07-08 22:15+0000\n" +"PO-Revision-Date: 2021-08-13 16:34+0000\n" "Last-Translator: Marcin W. + notoco\n" "Language-Team: Polish\n" +"Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: pl\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" msgctxt "Addon Summary" @@ -21,25 +21,22 @@ msgid "Netflix" msgstr "Netflix" msgctxt "Addon Description" -msgid "Netflix VOD Services Addon" +msgid "Netflix VOD Services Add-on" msgstr "Wtyczka dla usług VOD serwisu Netflix" msgctxt "Addon Disclaimer" -msgid "Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing." -msgstr "Niektóre części tego dodatku mogą być nielegalne w Twoim kraju - sprawdź przed instalacją zgodność z lokalnym prawem." +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "Korzystanie z tego dodatku może być niezgodne z prawem w twoim kraju zamieszkania - przed zainstalowaniem zapoznaj się z lokalnymi przepisami." msgctxt "#30001" msgid "Recommendations" msgstr "Polecane" msgctxt "#30002" -msgid "Adult Pin" -msgstr "Kod kontroli rodzicielskiej" - -msgctxt "#30003" -msgid "Search term" -msgstr "Szukane wyrażenie" +msgid "Enter PIN to watch restricted content" +msgstr "Wprowadź PIN żeby oglądać zabezpieczoną zawartość" +#. Unused 30003 msgctxt "#30004" msgid "Password" msgstr "Hasło" @@ -49,41 +46,46 @@ msgid "E-mail" msgstr "Adres e-mail" msgctxt "#30006" -msgid "Adult verification failed" -msgstr "Nieudana weryfikacja kontroli rodzicielskiej" +msgid "Enter PIN to access this profile" +msgstr "Podaj PIN do tego profilu" msgctxt "#30007" -msgid "Please Check your adult pin" -msgstr "Zweryfikuj poprawność kodu kontroli rodzicielskiej" +msgid "Parental Control PIN (4 digits)" +msgstr "PIN kontroli rodzicielskiej (4 cyfry)" msgctxt "#30008" msgid "Login failed" msgstr "Nieudane logowanie" msgctxt "#30009" -msgid "Please Check your credentials" -msgstr "Zweryfikuj poprawność wprowadzonych danych" +msgid "Please check your e-mail and password" +msgstr "Sprawdź swój e-mail i hasło" msgctxt "#30010" msgid "Lists of all kinds" -msgstr "Listy wszelkiego rodzaju" - -msgctxt "#30011" -msgid "Search" -msgstr "Szukaj" +msgstr "Wszystkie listy" +#. Unused 30011 msgctxt "#30012" -msgid "No seasons available" -msgstr "Brak dostępnych sezonów" +msgid "Profiles options and parental control" +msgstr "Opcje profili i kontroli rodzicielskiej" msgctxt "#30013" -msgid "No matches found" -msgstr "Brak pasujących wyników" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "Na liście profili otwórz menu kontekstowe w wybranym profilu" msgctxt "#30014" msgid "Account" msgstr "Konto" +msgctxt "#30015" +msgid "Other options" +msgstr "Pozostałe opcje" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "Jeśli się wylogujesz, wszystkie dane konta zostaną usunięte, ale wyeksportowane pliki wideo w bibliotece Kodi zostaną zachowane.[CR]Czy chcesz kontynuować?" + msgctxt "#30017" msgid "Logout" msgstr "Wyloguj" @@ -105,7 +107,7 @@ msgid "Add to 'My list'" msgstr "Dodaj do 'Moja lista'" msgctxt "#30022" -msgid "(between 0 & 10)" +msgid "(between 0 and 10)" msgstr "(między 0 a 10)" msgctxt "#30023" @@ -126,163 +128,141 @@ msgstr "Używaj niestandardowego folderu biblioteki" msgctxt "#30027" msgid "Custom library path" -msgstr "Folder niestandardowy" +msgstr "Własna lokalizacja biblioteki" msgctxt "#30028" msgid "Playback error" msgstr "Błąd odtwarzania" -msgctxt "#30029" -msgid "Missing InputStream addon" -msgstr "Brakuje wtyczki strumieniowej InputStream Adaptive" - +#. Unused 30029 msgctxt "#30030" msgid "Remove from library" msgstr "Usuń z biblioteki" msgctxt "#30031" -msgid "Change library title" -msgstr "Zmień tytuł" +msgid "General" +msgstr "Ogólne" msgctxt "#30032" -msgid "Tracking" -msgstr "Śledzenie" +msgid "Codecs" +msgstr "Kodeki" msgctxt "#30033" -msgid "Enable Dolby Digital Plus (DDPlus HQ and Atmos on Premium)" -msgstr "Aktywuj Dolby Digital Plus (DDPlus HQ i Atmos na Premium)" - -msgctxt "#30034" -msgid "ESN (set automatically, can be changed manually)" -msgstr "ESN (ustawiany automatycznie, z możliwością zmiany)" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "Włącz kodek Dolby Digital Plus (Atmos na koncie Premium)" +#. Unused 30034 msgctxt "#30035" msgid "InputStream Adaptive settings..." msgstr "Ustawienia wtyczki strumieniowej..." -msgctxt "#30036" -msgid "Always use the original title on export" -msgstr "Używaj oryginalnego tytułu podczas eksportu" - +#. Unused 30036 msgctxt "#30037" -msgid "Views" -msgstr "Widoki" +msgid "Skin viewtypes" +msgstr "Widoki skóry" msgctxt "#30038" -msgid "Enable custom views" -msgstr "Aktywuj niestandardowe widoki" +msgid "Enable custom viewtypes" +msgstr "Włącz własne typy widoków" msgctxt "#30039" -msgid "View for folders" -msgstr "Widok folderów" +msgid "View ID for folders" +msgstr "ID widoku dla folderów" msgctxt "#30040" -msgid "View for movies" -msgstr "Widok filmów" +msgid "View ID for movies" +msgstr "ID widoku dla filmów" msgctxt "#30041" -msgid "View for shows" -msgstr "Widok seriali" +msgid "View ID for shows" +msgstr "ID widoku dla seriali" msgctxt "#30042" -msgid "View for seasons" -msgstr "Widok sezonów" +msgid "View ID for seasons" +msgstr "ID widoku dla sezonów" msgctxt "#30043" -msgid "View for episodes" -msgstr "Widok odcinków" +msgid "View ID for episodes" +msgstr "ID widoku dla odcinków" msgctxt "#30044" -msgid "View for profiles" -msgstr "Widok profili" +msgid "View ID for profiles" +msgstr "ID widoku dla profili" msgctxt "#30045" -msgid "[COLOR cyan][B]-- NEXT PAGE --[/B][/COLOR]" -msgstr "[COLOR cyan][B]-- NASTĘPNA STRONA --[/B][/COLOR]" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "Usunięto ocenę|Oceniłeś kciuk w dół|Oceniłeś kciuk w górę" msgctxt "#30046" -msgid "InputStream addon is not enabled" -msgstr "Wtyczka strumieniowa nie jest aktywna" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "Wystąpił problem z dodatkiem InputStream Adaptive lub biblioteką Widevine. Może ich brakować lub mogą nie być włączone, operacja zostanie anulowana." msgctxt "#30047" -msgid "Finally remove?" -msgstr "Czy usunąć definitywnie?" +msgid "Library update in progress" +msgstr "Aktualizacja biblioteki w toku" msgctxt "#30048" msgid "Exported" msgstr "Wyeksportowane" msgctxt "#30049" -msgid "Update DB" -msgstr "Aktualizuj bazę" +msgid "Library" +msgstr "Bibilioteka" msgctxt "#30050" -msgid "Update successful" -msgstr "Aktualizacja zakończona powodzeniem" - -msgctxt "#30051" -msgid "Request Error" -msgstr "Błąd żądania" +msgid "Enable Kodi library management" +msgstr "Włącz zarządzanie biblioteką Kodi" +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30052" -msgid "Unable to complete the request at this time" -msgstr "Żądanie nie może teraz zostać wykonane" - -msgctxt "#30053" -msgid "Auto Login" -msgstr "Logowanie automatyczne" - -msgctxt "#30054" -msgid "Enable Auto Login" -msgstr "Aktywuj logowanie automatyczne" +msgid "{} Set for library playback" +msgstr "{} Ustaw do odtwarzania z biblioteki" +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30055" -msgid "Profile" -msgstr "Profil" - -msgctxt "#30056" -msgid "ID" -msgstr "Identyfikator" +msgid "{} Set at start-up" +msgstr "{} Ustaw przy uruchomieniu" +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30057" -msgid "Select profile in profile listing -> context menu" -msgstr "Wybierz profil z listy profili -> menu kontekstowe" - -msgctxt "#30058" -msgid "Auto Login enabled!" -msgstr "Logowanie automatyczne zostało aktywowane!" +msgid "{} Remember PIN" +msgstr "{} Zapamiętaj PIN" +#. Unused 30058 msgctxt "#30059" -msgid "Switch accounts" -msgstr "Zmień konto" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "Uważaj, masz zamiar zaktualizować wiele tytułów {}.[CR]Może to spowodować problemy z usługą lub tymczasową blokadę konta.[CR]Czy chcesz kontynuować?" msgctxt "#30060" -msgid "Enable HEVC profiles (4k for Android/HDR/DolbyVision)" -msgstr "Aktywuj profile HEVC (4K dla Androida/HDR/DolbyVision)" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "Włącz kodek HEVC (4k/HDR/DolbyVision dla Androida)" msgctxt "#30061" msgid "Update inside library" msgstr "Aktualizuj wewnątrz biblioteki" msgctxt "#30062" -msgid "Enable/disable adult pin. Active:" -msgstr "Włączanie/wyłączanie PIN kontroli rodzicielskiej. Aktywne:" +msgid "Parental controls" +msgstr "Kontrola rodzicielska" msgctxt "#30063" -msgid "new episodes added to library" -msgstr "nowe odcinki dodane do biblioteki" +msgid "An update is already in progress" +msgstr "Aktualizacja jest w toku" msgctxt "#30064" -msgid "Export new episodes" -msgstr "Eksportuj nowe odcinki" +msgid "Perform auto-update" +msgstr "Wykonaj aktualizację automatycznie" msgctxt "#30065" -msgid "Auto-update" -msgstr "Automatyczna aktualizacja" +msgid "Video library update" +msgstr "Aktualizacja biblioteki wideo" msgctxt "#30066" -msgid "never" -msgstr "nigdy" +msgid "Enable debug logging" +msgstr "Włącz rejestrowanie debugowania" msgctxt "#30067" msgid "daily" @@ -309,8 +289,8 @@ msgid "Only start after 5 minutes of idle" msgstr "Uruchom tylko po 5 minutach bezczynności" msgctxt "#30073" -msgid "Check every X minutes if update scheduled" -msgstr "Sprawdzaj co X minut jeśli zaplanowano aktualizację" +msgid "Always show codec information during video playback" +msgstr "Zawsze pokazuj informacje o kodekach podczas odtwarzania" msgctxt "#30074" msgid "View for exported" @@ -318,7 +298,7 @@ msgstr "Zobacz wyeksportowane" msgctxt "#30075" msgid "Ask to skip intro and recap" -msgstr "Pytaj o pominiecie czołówki i listy płac" +msgstr "Pytaj o pominięcie czołówki i podsumowania" msgctxt "#30076" msgid "Skip intro" @@ -326,7 +306,7 @@ msgstr "Pomiń czołówkę" msgctxt "#30077" msgid "Skip recap" -msgstr "Pomiń listę" +msgstr "Pomiń podsumowanie" msgctxt "#30078" msgid "Playback" @@ -341,28 +321,28 @@ msgid "Pause when skipping" msgstr "Pauza podczas pomijania" msgctxt "#30081" -msgid "Force support of HDCP 2.2 (enable 4K content)" -msgstr "Wymuś wsparcie HDCP 2.2 (uaktywnia treści 4K)" +msgid "Force HDCP version on video streams" +msgstr "Wymuś wersję HDCP dla strumieni wideo" msgctxt "#30082" msgid "Remember audio / subtitle preferences" -msgstr "Zachowuj ustawienia dźwięku i napisów" +msgstr "Pamiętaj ustawienia dźwięku i napisów" msgctxt "#30083" -msgid "Save bookmarks for library items / mark as watched" -msgstr "Zachowaj zakładki dla pozycji biblioteki / oznacz jako obejrzane" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "Wymuś wyświetlanie wymuszonych napisów tylko przy ustawionym języku audio" msgctxt "#30084" -msgid "Cache item time-to-live (minutes)" -msgstr "Utrzymuj pamięć podręczną przez (minuty)" +msgid "Cache objects TTL (minutes)" +msgstr "Utrzymuj w pamięci podręcznej przez (minuty)" msgctxt "#30085" -msgid "Cache item time-to-live for metadata (days)" +msgid "Cache metadata TTL (days)" msgstr "Utrzymuj pamięć podręczną metadanych przez (dni)" msgctxt "#30086" -msgid "Invalidate entire cache on modification of My List" -msgstr "Unieważnij całą pamięć podręczną przy modyfikacji Mojej listy" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "Czas przechowywania w pamięci podręcznej mojej listy (minuty)" msgctxt "#30087" msgid "Contains {} and more..." @@ -397,7 +377,7 @@ msgid "Browse recommendations and pick up on something new you might like." msgstr "Przeglądaj rekomendacje i wybierz coś, co może Ci się spodobać." msgctxt "#30095" -msgid "All TV Shows" +msgid "All TV shows" msgstr "Wszystkie seriale" msgctxt "#30096" @@ -409,35 +389,35 @@ msgid "Main Menu" msgstr "Główne menu" msgctxt "#30098" -msgid "Enable HDR profiles" -msgstr "Włącz profile HDR" +msgid "Enable HDR10" +msgstr "Włącz HDR10" msgctxt "#30099" -msgid "Enable DolbyVision profiles" -msgstr "Włącz profile DolbyVision" +msgid "Enable Dolby Vision" +msgstr "Włącz Dolby Vision" msgctxt "#30100" -msgid "Results for \"{}\"" -msgstr "Wyniki dla \"{}\"" +msgid "Results for '{}'" +msgstr "Wyniki dla '{}'" msgctxt "#30101" msgid "Netflix returned an unknown error." -msgstr "Netflix zwrócił nieznanu błąd" +msgstr "Netflix zwrócił nieznany błąd" msgctxt "#30102" msgid "Netflix returned the following error:" msgstr "Netflix zwrócił poniższy błąd" msgctxt "#30103" -msgid "Error details have been written to the Kodi logfile. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it via GitHub. Please provide a full debug log with your error report (enable \"Debug Logging\" in Kodi settings)." -msgstr "Szczegóły błędów zostały zapisane w pliku logu Kodi. Jeśli komunikat o błędzie nie zawiera wskazówek, jak rozwiązać problem lub nadal występuje ten błąd, zgłoś go za pośrednictwem GitHub. Podaj pełny dziennik debugowania wraz z raportem o błędzie (włącz \"Dziennikowanie debugowania\" w ustawieniach Kodi)" +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "Szczegóły błędów zostały zapisane w pliku logu Kodi. Jeśli komunikat o błędzie nie zawiera wskazówek, jak rozwiązać problem lub nadal występuje ten błąd, zgłoś go za pośrednictwem GitHub korzystając z instrukcji Readme" msgctxt "#30104" -msgid "The addon encountered to following error:" +msgid "The add-on encountered the following error:" msgstr "Dodatek napotkał następujący błąd:" msgctxt "#30105" -msgid "Netflix Addon Error" +msgid "Netflix Add-on Error" msgstr "Błąd dodatku Netflix" msgctxt "#30106" @@ -445,24 +425,21 @@ msgid "Invalid PIN" msgstr "Nieprawidłowy PIN" msgctxt "#30107" -msgid "Disabled PIN verfication" -msgstr "Wyłączona kontrola PIN" +msgid "PIN protection is off." +msgstr "Zabezpieczenie PIN jest wyłączone." msgctxt "#30108" -msgid "Enabled PIN verfication" -msgstr "Włączona kontrola PIN" +msgid "All content is protected by PIN." +msgstr "Cała zawartość jest chroniona przez PIN" msgctxt "#30109" msgid "Login successful" msgstr "Zalogowano pomyślnie" -msgctxt "#30110" -msgid "Background services started" -msgstr "Uruchomiono usługi w tle" - +#. Unused 30110 msgctxt "#30111" msgid "There is no contents" -msgstr "Brak zawartości" +msgstr "Nie znaleziono treści" msgctxt "#30112" msgid "You need to be logged in to use Netflix" @@ -470,32 +447,18 @@ msgstr "Musisz być zalogowany żeby używać Netflix" msgctxt "#30113" msgid "Logout successful" -msgstr "Wylogowano pomysłnie" - -msgctxt "#30114" -msgid "Keep My List and Kodi Library in sync" -msgstr "Utrzymaj synchronizację Mojej Listy i biblioteki Kodi" - -msgctxt "#30115" -msgid "Content Profiles" -msgstr "Profile zawartości" +msgstr "Wylogowano pomyślnie" +#. Unused 30114, 30115 msgctxt "#30116" -msgid "Advanced Addon Configuration" +msgid "Advanced Add-on Configuration" msgstr "Zaawansowana konfiguracja dodatku" msgctxt "#30117" msgid "Cache" msgstr "Pamięć podręczna" -msgctxt "#30118" -msgid "API operation failed: {}" -msgstr "Operacja API nie powiodła się: {}" - -msgctxt "#30119" -msgid "My List operation successful" -msgstr "Działanie na Mojej liście zakończyły się powodzeniem" - +#. Unused 30118, 30119 msgctxt "#30120" msgid "Cannot automatically remove a season from Kodi library, please do it manually" msgstr "Nie można automatycznie usunąć sezonu z biblioteki Kodi, zrób to ręcznie" @@ -505,52 +468,52 @@ msgid "Perform full sync now" msgstr "Wykonaj teraz pełną synchronizację" msgctxt "#30122" -msgid "Sync My List to Kodi library" -msgstr "Synchronizuj Moją Listę z biblioteką Kodi" +msgid "Sync 'My list' to Kodi library" +msgstr "Synchronizuj Moją Listę do biblioteki Kodi" msgctxt "#30123" -msgid "Do you really want to proceed?\nThis will delete all previously exported items and export all items from My List for the currently active profile.\nThis operation may take some time..." -msgstr "Czy na pewno chcesz kontynuować? \nSpowoduje to usunięcie wszystkich wcześniej wyeksportowanych pozycji i wyeksportowanie wszystkich pozycji z Mojej listy dla aktualnie aktywnego profilu. \nTa operacja może trochę potrwać ..." +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "Czy chcesz kontynuować?[CR]Pełna synchronizacja spowoduje usunięcie wszystkich wcześniej wyeksportowanych tytułów.[CR]Jeśli istnieje wiele tytułów, operacja ta może zająć trochę czasu." msgctxt "#30124" -msgid "Do you really want to remove this item?" -msgstr "Czy na pewno chcesz usunąć ę pozycję?" +msgid "Do you want to remove this item from the library?" +msgstr "Czy na pewno chcesz usunąć tę pozycję?" msgctxt "#30125" -msgid "Purge library" +msgid "Delete library contents" msgstr "Wyczyść bibliotekę" msgctxt "#30126" -msgid "Do you really want to purge your library?\nAll exported items will be deleted." -msgstr "Czy na pewno chcesz wyczyścić bibliotekę? \nWszystkie wyeksportowane pozycje zostaną usunięte." +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "Czy na pewno chcesz wyczyścić bibliotekę? [CR]Wszystkie wyeksportowane pozycje zostaną usunięte." msgctxt "#30127" msgid "Awarded rating of {}" msgstr "Przyznana ocena {}" msgctxt "#30128" -msgid "No contained titles yet..." -msgstr "Brak zawartych tytułów ..." +msgid "Select a profile" +msgstr "Wybierz profil" msgctxt "#30129" msgid "Enable Up Next integration" msgstr "Włącz integrację z Up Next" msgctxt "#30130" -msgid "Show notification instead of modal dialog for errors" -msgstr "Pokaż powiadomienia zamiast okna dialogowego dla błędów" +msgid "Install Up Next add-on" +msgstr "Zainstaluj dodatek Up Next" msgctxt "#30131" msgid "Check the logfile for detailed information" msgstr "Sprawdź plik dziennika, aby uzyskać szczegółowe informacje" msgctxt "#30132" -msgid "Purge in-memory cache" -msgstr "Wyczyść pamięć podręczną w pamięci" +msgid "Clear in-memory cache" +msgstr "Wyczyść pamięć podręczną" msgctxt "#30133" -msgid "Purge in-memory and on-disk cache" -msgstr "Wyczyść pamięć podręczną i pamięć podręczną dysku" +msgid "Clear in-memory and on-disk cache" +msgstr "Wyczyść pamięć podręczną i pamięć podręczną na dysku" msgctxt "#30134" msgid "Enable execution timing" @@ -561,37 +524,26 @@ msgid "Cache cleared" msgstr "Pamięć podręczna wyczyszczona" msgctxt "#30136" -msgid "Enable 1080p Unlock (not recommended for Android)" -msgstr "Włącz odblokowywanie 1080p (niezalecane dla Androida)" +msgid "Waiting for service start-up..." +msgstr "Czekaj na uruchomienie usługi..." msgctxt "#30137" -msgid "Enable VP9 profiles (disable if it causes artifacts)" -msgstr "Włącz profile VP9 (wyłącz jeśli powoduje artefakty)" +msgid "Enable VP9 codec" +msgstr "Włącz kodek VP9" msgctxt "#30138" -msgid "The requested action timed out. The background services may not yet be available if you just started Kodi/the addon. In this case, try again in a moment." -msgstr "Żądana akcja wygasła. Usługi w tle mogą nie być jeszcze dostępne, jeśli właśnie uruchomiłeś Kodi/dodatek. W takim przypadku spróbuj ponownie za chwilę." +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "Usługi w tle mogą nie być jeszcze dostępne, jeśli właśnie uruchomiłeś Kodi/dodatek. W takim przypadku spróbuj ponownie za chwilę." msgctxt "#30139" -msgid "Enable IPC over HTTP (use when experiencing AddonSignals timeouts)" -msgstr "Włącz IPC przez HTTP (użyj, gdy występują przekroczenia czasu AddonSignals)" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "Włącz IPC przez HTTP (użyj, gdy występują przekroczenia czasu Addon Signals)" msgctxt "#30140" -msgid "Migrate Library to new format" -msgstr "Migracja biblioteki do nowego formatu" - -msgctxt "#30141" -msgid "Disable startup notification" -msgstr "Wyłącz powiadomienia o starcie" - -msgctxt "#30142" -msgid "Custom" -msgstr "Własny" - -msgctxt "#30143" -msgid "Custom view ID" -msgstr "ID własnego widoku" +msgid "Import existing library" +msgstr "Importuj istniejącą bibliotekę" +#. Unused 30141 to 30143 msgctxt "#30144" msgid "Disable WebVTT subtitle support" msgstr "Wyłącz obsługę napisów WebVTT" @@ -605,12 +557,12 @@ msgid "Discover the latest content added." msgstr "Odkryj ostatnio dodane materiały." msgctxt "#30147" -msgid "Next page >>" -msgstr "Następna strona >>" +msgid "Next page »" +msgstr "Następna strona »" msgctxt "#30148" -msgid "<< Previous page" -msgstr "<< Poprzednia strona" +msgid "« Previous page" +msgstr "« Poprzednia strona" msgctxt "#30149" msgid "Sort results by" @@ -633,36 +585,33 @@ msgid "Year released" msgstr "Rok wydania" msgctxt "#30154" -msgid "Netflix addon configuration" +msgid "Netflix configuration wizard" msgstr "Konfiguracja dodatku Netflix" msgctxt "#30155" msgid "Do you have a Netflix Premium account?" msgstr "Czy masz konto Netflix Premium?" -msgctxt "#30156" -msgid "Does your device support the 4K standard?\r\nThe device must:\r\nBe Netflix certified, support for Widevine Security level L1 and have a 4K display HDCP 2.2" -msgstr "Czy twoje urządzenie obsługuje standard 4k?\r\nUrządzenie musi:\r\n Być certyfikowane przez Netflix, wspierać Widevine Security poziom L1 i posiadać ekran 4K HDCP 2.2" - +#. Unused 30156 msgctxt "#30157" -msgid "The InputStream Adaptive settings panel will now be opened, so you have to change this option \"Override HDCP status\" to ON" -msgstr "Zostanie otworzone okno ustawień dodatku InputStream Adaptive, żebyś mógł włączyć opcję \"Override HDCP status\"" +msgid "The configuration has been completed" +msgstr "Konfiguracja została zakończona" msgctxt "#30158" -msgid "Show configuration screen at addon startup" -msgstr "Pokaż ekran konfiguracji przy starcie dodatku" +msgid "Restore the add-on default configuration" +msgstr "Przywróć domyślną konfigurację dodatku" msgctxt "#30159" -msgid "View for main menu" -msgstr "Widok dla głównego menu" +msgid "View ID for main menu" +msgstr "ID widoku dla głównego menu" msgctxt "#30160" -msgid "View for search" -msgstr "Widok dla wyszukiwania" +msgid "View ID for search" +msgstr "ID widoku dla wyszukiwania" msgctxt "#30161" -msgid "View for exported" -msgstr "Widok dla wyeksportowanych" +msgid "View ID for exported" +msgstr "ID widoku dla eksportowanych" msgctxt "#30162" msgid "Last used" @@ -677,8 +626,8 @@ msgid "Browse contents with audio description." msgstr "Przeglądaj zawartość z deskrypcją audio" msgctxt "#30165" -msgid "View for my list" -msgstr "Widok dla mojej listy" +msgid "View ID for 'My list'" +msgstr "ID widoku dla 'Moja lista'" msgctxt "#30166" msgid "Main menu items" @@ -694,7 +643,7 @@ msgstr "Oglądaj dalej" msgctxt "#30169" msgid "Top picks" -msgstr "Wybrane" +msgstr "Najczęściej wybierane" msgctxt "#30170" msgid "New releases" @@ -702,7 +651,7 @@ msgstr "Nowości" msgctxt "#30171" msgid "Trending now" -msgstr "Popularne teraz" +msgstr "Aktualne trendy" msgctxt "#30172" msgid "Popular on Netflix" @@ -713,7 +662,7 @@ msgid "Netflix Originals" msgstr "Netflix Originals" msgctxt "#30174" -msgid "Tv Show genres" +msgid "TV show genres" msgstr "Gatunki seriali" msgctxt "#30175" @@ -721,28 +670,28 @@ msgid "Movie genres" msgstr "Gatunki filmów" msgctxt "#30176" -msgid "Enable STRM resume workaround (only library)" -msgstr "Uaktywnij obejście wznawiania STRM (tylko biblioteka)" +msgid "Enable STRM resume workaround" +msgstr "Uaktywnij obejście wznawiania STRM" msgctxt "#30177" msgid "Ask to resume the video" msgstr "Pytaj o wznowienie odtwarzania" msgctxt "#30178" -msgid "Open UpNext Addon settings" -msgstr "Otwórz ustawienia dodatku UpNext" +msgid "Open Up Next add-on settings" +msgstr "Otwórz ustawienia dodatku Up Next" msgctxt "#30179" -msgid "Show the trailers" -msgstr "Pokaż zwiastuny" +msgid "Show trailers and more" +msgstr "Pokaż zwiastuny i więcej" msgctxt "#30180" -msgid "No trailers available" -msgstr "Zwiastuny niedostępne" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "Wystąpił problem z logowaniem, możliwe, że Twoje konto nie zostało potwierdzone lub reaktywowane" msgctxt "#30181" -msgid "Disable subtitles if there are no forced subtitles streams (Works only if Kodi Player is set to \"Only forced\" subtitles)" -msgstr "Wyłącz napisy, jeśli nie ma wymuszonych strumieni napisów (Działa tylko, jeśli odtwarzacz Kodi jest ustawiony na \"Tylko wymuszone\" napisy)" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "Zawsze pokazuj napisy, gdy preferowany język dźwięku nie jest dostępny" msgctxt "#30182" msgid "Export NFO files" @@ -765,7 +714,7 @@ msgid "For Movies" msgstr "Dla filmów" msgctxt "#30187" -msgid "For TV Show" +msgid "For TV show" msgstr "Dla seriali" msgctxt "#30188" @@ -780,21 +729,18 @@ msgctxt "#30190" msgid "TV show" msgstr "serial" -msgctxt "#30191" -msgid "Do you want to export NFO files for the {} added to 'My List'?" -msgstr "Czy chcesz wyeksportować pliki NFO dla {} dodanego do 'Mojej listy'?" - +#. Unused 30191 msgctxt "#30192" -msgid "\r\nThese files are used by the provider information scrapers to integrate movies and tv shows info, useful with bonus episodes or director's cut" -msgstr "\r\nPliki te są wykorzystywane przez scrapery informacji do integracji informacji o filmach i serialach, wraz z dodatkowymi odcinkami lub wersjami reżyserskimi" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]Pliki te są wykorzystywane przez scrapery informacji do integracji informacji o filmach i serialach, wraz z dodatkowymi odcinkami lub wersjami reżyserskimi" msgctxt "#30193" -msgid "Include all information in NFO files (use only with 'Local Information' scraper)" -msgstr "Uwzględnij wszystkie informacje w plikach NFO (używaj tylko ze scraperem 'Informacje lokalne')" +msgid "Include tv show NFO details" +msgstr "Uwzględnij szczegóły serialu NFO" msgctxt "#30194" msgid "Limit video stream resolution to" -msgstr "Ograniczaj jakość strumienia do" +msgstr "Ograniczaj rozdzielczość strumieniowania do" msgctxt "#30195" msgid "Export new episodes" @@ -802,7 +748,7 @@ msgstr "Eksportuj nowe odcinki" msgctxt "#30196" msgid "Exclude from auto update" -msgstr "Wyłącz z autoaktualizacji" +msgstr "Wyklucz z autoaktualizacji" msgctxt "#30197" msgid "Include in auto update" @@ -817,7 +763,7 @@ msgid "Shared library (Kodi MySQL server is required)" msgstr "Współdzielona bilioteka (wymaga serwera Kodi MySQL)" msgctxt "#30200" -msgid "Use MySQL shared library database" +msgid "Use shared library MySQL-database" msgstr "Używaj współdzielonej bazy danych MySQL dla bilioteki" msgctxt "#30201" @@ -825,7 +771,7 @@ msgid "Username" msgstr "Użytkownik" msgctxt "#30202" -msgid "Connection to the MySQL database was successful" +msgid "Connection to the MySQL-database was successful" msgstr "Połączono z bazą danych MySQL" msgctxt "#30203" @@ -841,33 +787,470 @@ msgid "Test database connection" msgstr "Sprawdź połączenie z bazą" msgctxt "#30206" -msgid "ERROR: The MySQL database is not reachable - the local database will be used" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" msgstr "Błąd - baza danych MySQL jest niedostępna - będzie używana lokalna baza danych" msgctxt "#30207" msgid "Set this device as main auto-updates manager" -msgstr "Ustaw to urządzenie jako główne w autoaktualizacji" +msgstr "Ustaw to urządzenie jako główny manager autoaktualizacji" msgctxt "#30208" msgid "Check if this device is the main auto-update manager" -msgstr "Sprawdź czy to urządzenie jest głównym w autoaktulizacji" +msgstr "Sprawdź czy to urządzenie jest głównym managerem autoaktulizacji" msgctxt "#30209" -msgid "This device now handles the auto-updates of shared-library" +msgid "This device now handles the auto-updates of the shared library" msgstr "To urządzenie obsługuje teraz automatyczne aktualizacje biblioteki współużytkowanej" msgctxt "#30210" -msgid "This device handles the auto-updates of shared-library" +msgid "This device handles the auto-updates of the shared library" msgstr "To urządzenie obsługuje automatyczne aktualizacje biblioteki współużytkowanej" msgctxt "#30211" -msgid "Another device handles the auto-updates of shared-library" +msgid "Another device handles the auto-updates of the shared library" msgstr "Inne urządzenie obsługuje automatyczne aktualizacje biblioteki współużytkowanej" msgctxt "#30212" -msgid "There is no device that handles the auto-updates of shared-library" +msgid "There is no device that handles the auto-updates of the shared library" msgstr "Nie ma urządzenia obsługującego automatyczne aktualizacje biblioteki współużytkowanej" msgctxt "#30213" msgid "InputStream Helper settings..." -msgstr "" +msgstr "Ustawienia InputStream Helper..." + +msgctxt "#30214" +msgid "Force refresh" +msgstr "Wymuś odświeżenie" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "Podświetl tytuły znajdujące się na Mojej liście kolorem" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "Kolor tytułów oznaczonych jako \"Przypomnij mi\"" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "Wyłącz powiadomienie o zakończeniu synchronizacji" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "Biblioteka Kodi została zaktualizowana" + +msgctxt "#30221" +msgid "Owner account" +msgstr "Profil właściciela" + +msgctxt "#30222" +msgid "Kid account" +msgstr "Profil dziecka" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "Tryb automatycznej aktualizacji" + +msgctxt "#30225" +msgid "Manual" +msgstr "Ręcznie" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "Zaplanowana" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "Synchronizuj bibliotekę Kodi z Moją Listą" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "Ustaw profil dla synchronizacji" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "Przy starcie Kodi" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "Sprawdź aktualizacje teraz" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "Czy chcesz teraz sprawdzić aktualizację?" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "Ograniczenia wiekowe dla {}" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "Pokaż tylko tytuły ocenione [B]{}[/B]." + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "Zsynchronizuj status oglądanych filmów z Netflix" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "Zmień status oglądania (lokalnie)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "Oznacz jako obejrzane|Oznacz jako nieobejrzane|Przywróć stan obejrzenia z Netflix" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Powiadomienia Up Next (oglądaj następne wideo)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "Kolor dodatkowych informacji fabuły" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "usługi w tle nie mogą być uruchomione z powodu:[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Open Connect CDN (niższy indeks jest lepszy)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "Top 10" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "Wybierz pierwszy nieoglądany odcinek serialu" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "Usuwanie plików wyeksportowanych do biblioteki" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "Istnieje {} tytułów, których nie można zaimportować.[CR]Czy chcesz usunąć te pliki?" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "Wyników na stronę (w przypadku przekroczenia limitu czasu zmniejsz go)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "Czy chcesz usunąć status oglądania \"{}\"?" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "Uwzględnij bibliotekę Kodi (tylko wysyłanie danych)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "Wybierz metodę logowania[CR](jeśli logowanie przy użyciu adresu e-mail/hasła zawsze zwraca \"nieprawidłowe hasło\", użyj klucza uwierzytelniania, instrukcje w pliku ReadMe GitHub)" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "e-mail/hasło" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "Klucz uwierzytelniający" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "Wybrany plik jest niekomompatybilny lub uszkodzony" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "Plik klucza uwierzytelniającego wygasł" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "Podaj PIN" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "Sortuj historię według" + +msgctxt "#30400" +msgid "Search" +msgstr "Szukaj" + +msgctxt "#30401" +msgid "Type of search" +msgstr "Typ wyszukiwania" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "Szukaj tytułu, postaci, gatunków" + +msgctxt "#30403" +msgid "New search..." +msgstr "Nowe wyszukiwanie" + +msgctxt "#30404" +msgid "Clear search history" +msgstr "Wyczyść historię wyszukiwania" + +msgctxt "#30405" +msgid "Select the language" +msgstr "Wybierz język" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "Czy chcesz usunąć całą zawartość?" + +msgctxt "#30407" +msgid "No matches found" +msgstr "Brak pasujących wyników" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "Wg słowa" + +msgctxt "#30411" +msgid "By audio language" +msgstr "Wg języka audio" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "Wg języka napisów" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "Wg ID gatunku/podgatunku" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "Preferuj język dźwięku/napisów z kodem kraju (jeśli jest dostępny)" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "Domyślnie preferuj ścieżki audio stereo" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "Ustawienia ESN / Widevine" + +msgctxt "#30601" +msgid "ESN:" +msgstr "ESN:" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "Zmień ESN" + +msgctxt "#30603" +msgid "Save system info" +msgstr "Zapisz informacje systemowe" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "Widevine - Wymuś poziom bezpieczeństwa" + +msgctxt "#30605" +msgid "Force {}" +msgstr "Wymuś {}" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "ESN ustawiono pomyślnie[CR]Spróbuj odtworzyć video" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "Ten ESN nie może być użyty[CR]Spróbuj zmienić lub zresetować ESN[CR]Szczegóły błędu:[CR]{}" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "Nieprawidłowy format ESN" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "Czy chcesz zresetować wszystkie ustawienia?" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "Używaj szyfrowania opartego na systemie dla poświadczeń" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "[B]W przypadku wyłączenia spowoduje to wyciek zabezpieczeń[/B]. Jeśli Twój system operacyjny tego nie obsługuje, będziesz proszony o zalogowanie się za każdym razem, gdy ponownie uruchomisz urządzenie, [B]tylko[/B] w tym przypadku wyłącz je. Jeśli zmienisz to ustawienie, zostaniesz poproszony o ponowne zalogowanie się przy następnym uruchomieniu." + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "Wersja manifestu MSL" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "Ten tytuł będzie dostępny od:[CR]{}" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "Czy chcesz odtworzyć trailer promocyjny?" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "{} Przypomnij mi" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "Nowe i popularne" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "Oznacz rozpoczęte seriale jako obejrzane" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "Pamiętaj, że włączając to ustawienie, jeśli filtry są włączone w ustawieniach skórek, elementy oznaczone jako obserwowane mogą nie być widoczne." + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "Zastąp typ wyboru strumienia" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "Zastąp ustawienie Typ wyboru strumienia w dodatku InputStream Adaptive, aby określić sposób wyboru jakości strumieni audio/wideo podczas odtwarzania." + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "Jakość dostosowana" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "Stała jakość wideo" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "Pytaj o jakość wideo" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "Należy pamiętać, że włączenie tego ustawienia wymusi usunięcie strumieni, które nie mieszczą się w ustawionym zakresie, co może wpłynąć na działanie InputStream Adaptive. Jeśli to możliwe, spróbuj najpierw ograniczyć jakość w ustawieniach InputStream Adaptive." + +msgctxt "#30721" +msgid "Size" +msgstr "Rozmiar" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "Minimalizuje czarne paski poprzez zastosowanie efektu powiększenia. [ Stały ] Ustawia procent przestrzeni ekranu, którą mogą zajmować czarne paski, będzie stała dla wszystkich filmów. [ Relatywny ] Ustawia procent minimalizacji czarnych pasów, będzie zależny od każdego rozmiaru wideo. [ Reset ] Resetuje poprzednio zastosowany efekt powiększenia." + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "Ta funkcja wykorzystuje efekt zoomu Kodi, który jest na stałe zapisany w ustawieniach wideo, jeśli wyłączysz tę funkcję w przyszłości, zoom zostanie zachowany, aby zresetować zoom zmień tryb ustawień na \"Zresetuj\"." + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "Włącz kodek AV1" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "Dodaj foldery Netflix do źródeł biblioteki Kodi" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "Foldery \"Netflix-Movies\" i \"Netflix-Shows\" zostały dodane do źródeł Kodi, uruchom ponownie Kodi, aby wyświetlić je w menu Kodi [Seriale] / [Filmy]. Następnie edytuj foldery, aby ustawić odpowiedni typ zawartości i dostawcę informacji." + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "[Ręcznie] aktualizacje muszą być wykonywane ręcznie za pomocą menu kontekstowego \"Eksportuj nowe odcinki\" w każdym serialu, [Zaplanowane] możesz ustawić automatyczne aktualizacje w zaplanowanych godzinach, [Kiedy Kodi uruchamia się] automatyczne aktualizacje rozpoczą się po uruchomieniu Kodi i będą robione tylko raz dziennie." + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "Synchronizuje bibliotekę z Moją listą, jeśli moja lista została zmodyfikowana zewnętrznie, np. w aplikacji mobilnej lub stronie internetowej, aktualizacja zostanie przesunięta na zaplanowany czas." + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "Jeśli ta opcja jest włączona, dodaje informacje do odcinków lub filmów, które są przydatne, gdy dostawca informacji Kodi nie dostarcza danych. Spowoduje to również dodanie czasu trwania wideo." + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "Włącz tylko wtedy, gdy używany jest dostawca informacji \"Informacje lokalne\" lub jeśli niektóre tytuły nie są wyświetlane w bibliotece Kodi, mimo że zostały dodane." + +msgctxt "#30734" +msgid "Support" +msgstr "Wsparcie" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "O dodatku Netflix" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "Automatyczne generowanie nowych numerów ESN (obejście dla limitu 540p)" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "OSTRZEŻENIE: Nie używaj oryginalnego ESN urządzenia z oficjalnej aplikacji." + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "Włącz przesunięcie dźwięku" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "Przesunięcie dźwięku" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "Ustawia przesunięcie dźwięku, zastępując ustawienie Kodi" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "Włącz VP9 Profil 2 (10/12-bitowa głębia kolorów)" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "Czy chcesz włączyć obsługę HDR10?" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "Czy chcesz włączyć obsługę HDR Dolby Vision?" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "Dodatek już ustawia odpowiednią wersję HDCP, ale w razie potrzeby możesz wymusić żądanie strumieni wideo dla innej wersji HDCP." + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "Jeśli Twoje urządzenie nie obsługuje tego kodeka, może wystąpić czarny ekran, brak obrazu lub artefakty. Jeśli tak, wyłącz kodek." + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "Tryb minimalizacji czarnych pasów" + +msgctxt "#30747" +msgid "Fixed" +msgstr "Stały" + +msgctxt "#30748" +msgid "Relative" +msgstr "Względny" + +msgctxt "#30749" +msgid "Reset" +msgstr "Zresetuj" diff --git a/resources/language/resource.language.pt_br/strings.po b/resources/language/resource.language.pt_br/strings.po index 5b6b1f3ae..d7f440399 100644 --- a/resources/language/resource.language.pt_br/strings.po +++ b/resources/language/resource.language.pt_br/strings.po @@ -1,7 +1,7 @@ # Kodi Media Center language file # Addon Name: Netflix # Addon id: plugin.video.netflix -# Addon Provider: libdev + jojo + asciidisco + caphm + CastagnaIT +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait msgid "" msgstr "" "Project-Id-Version: plugin.video.netflix\n" @@ -10,10 +10,10 @@ msgstr "" "PO-Revision-Date: 2019-07-08 22:15+0000\n" "Last-Translator: MediaBrasil\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" -"Language: pt_BR\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" msgctxt "Addon Summary" @@ -21,25 +21,22 @@ msgid "Netflix" msgstr "Netflix" msgctxt "Addon Description" -msgid "Netflix VOD Services Addon" -msgstr "Addon do Netflix, um serviço de Video On Demand" +msgid "Netflix VOD Services Add-on" +msgstr "Addon da Netflix, um serviço de Video Sob Demanda" msgctxt "Addon Disclaimer" -msgid "Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing." -msgstr "Algumas partes deste addon poderão não ser legais no seu país de residência. Por favor, verifique as suas leis locais antes de instalar." +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "O uso deste addon pode não ser legal no seu país de residência - por favor, verifique as suas leis locais antes de instalar." msgctxt "#30001" msgid "Recommendations" msgstr "Recomendações" msgctxt "#30002" -msgid "Adult Pin" -msgstr "Pin de controle parental" - -msgctxt "#30003" -msgid "Search term" -msgstr "Termo de pesquisa" +msgid "Enter PIN to watch restricted content" +msgstr "Digite o PIN para assistir ao conteúdo restrito" +#. Unused 30003 msgctxt "#30004" msgid "Password" msgstr "Senha" @@ -49,41 +46,46 @@ msgid "E-mail" msgstr "Email" msgctxt "#30006" -msgid "Adult verification failed" -msgstr "A validação de controle parental falhou" +msgid "Enter PIN to access this profile" +msgstr "Entre com o PIN para acessar este perfil" msgctxt "#30007" -msgid "Please Check your adult pin" -msgstr "Por favor, verifique se o pin de controle parental está correto" +msgid "Parental Control PIN (4 digits)" +msgstr "PIN Controle Parental (4 dígitos)" msgctxt "#30008" msgid "Login failed" -msgstr "Erro ao inicar sessão" +msgstr "Erro ao iniciar sessão" msgctxt "#30009" -msgid "Please Check your credentials" +msgid "Please check your e-mail and password" msgstr "Por favor, verifique as suas credenciais de acesso" msgctxt "#30010" msgid "Lists of all kinds" msgstr "Listar todos os tipos" -msgctxt "#30011" -msgid "Search" -msgstr "Procurar" - +#. Unused 30011 msgctxt "#30012" -msgid "No seasons available" -msgstr "Sem temporadas disponíveis" +msgid "Profiles options and parental control" +msgstr "Opções de perfis e controle parental" msgctxt "#30013" -msgid "No matches found" -msgstr "Não foram encontrados resultados" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "Na lista de perfis, abra o menu de contexto no perfil escolhido" msgctxt "#30014" msgid "Account" msgstr "Conta" +msgctxt "#30015" +msgid "Other options" +msgstr "Outras opções" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "Se você sair, todos os dados da conta serão excluídos, mas os arquivos de vídeo exportados na biblioteca Kodi serão preservados.[CR]Deseja continuar?" + msgctxt "#30017" msgid "Logout" msgstr "Terminar sessão" @@ -94,7 +96,7 @@ msgstr "Exportar para a coleção" msgctxt "#30019" msgid "Rate on Netflix" -msgstr "Avaliar no Netflix" +msgstr "Avaliar na Netflix" msgctxt "#30020" msgid "Remove from 'My list'" @@ -105,7 +107,7 @@ msgid "Add to 'My list'" msgstr "Adicionar à 'Minha Lista'" msgctxt "#30022" -msgid "(between 0 & 10)" +msgid "(between 0 and 10)" msgstr "(Entre 0 & 10)" msgctxt "#30023" @@ -132,157 +134,135 @@ msgctxt "#30028" msgid "Playback error" msgstr "Erro de reprodução" -msgctxt "#30029" -msgid "Missing InputStream addon" -msgstr "Addon InputStream ausente" - +#. Unused 30029 msgctxt "#30030" msgid "Remove from library" msgstr "Remover da coleção" msgctxt "#30031" -msgid "Change library title" -msgstr "Alterar título" +msgid "General" +msgstr "Geral" msgctxt "#30032" -msgid "Tracking" -msgstr "Rastreio" +msgid "Codecs" +msgstr "" msgctxt "#30033" -msgid "Enable Dolby Digital Plus (DDPlus HQ and Atmos on Premium)" -msgstr "Ativar Dolby Digital Plus (DDPlus HQ e Atmos on Premium)" - -msgctxt "#30034" -msgid "ESN (set automatically, can be changed manually)" -msgstr "ESN (definido automaticamente, pode ser alterado manualmente)" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "" +#. Unused 30034 msgctxt "#30035" msgid "InputStream Adaptive settings..." -msgstr "Ajustes do addon InputStream" - -msgctxt "#30036" -msgid "Always use the original title on export" -msgstr "Utilizar sempre o título original para exportar" +msgstr "Ajustes do addon InputStream..." +#. Unused 30036 msgctxt "#30037" -msgid "Views" -msgstr "Modos de Vistas" +msgid "Skin viewtypes" +msgstr "Tipos de vista skin" msgctxt "#30038" -msgid "Enable custom views" -msgstr "Ativar vistas personalizadas" +msgid "Enable custom viewtypes" +msgstr "Ativar tipos de vistas customizadas" msgctxt "#30039" -msgid "View for folders" -msgstr "Vista para as pastas" +msgid "View ID for folders" +msgstr "ID visualização para pastas" msgctxt "#30040" -msgid "View for movies" -msgstr "Vista para os filmes" +msgid "View ID for movies" +msgstr "ID visualização para filmes" msgctxt "#30041" -msgid "View for shows" -msgstr "Vista para as séries" +msgid "View ID for shows" +msgstr "ID visualização para seriados" msgctxt "#30042" -msgid "View for seasons" -msgstr "Vista para as temporadas" +msgid "View ID for seasons" +msgstr "ID visualização para temporadas" msgctxt "#30043" -msgid "View for episodes" -msgstr "Vista para os episódios" +msgid "View ID for episodes" +msgstr "ID visualização para episódios" msgctxt "#30044" -msgid "View for profiles" -msgstr "Vista para os perfis" +msgid "View ID for profiles" +msgstr "ID visualização para perfis" msgctxt "#30045" -msgid "[COLOR cyan][B]-- NEXT PAGE --[/B][/COLOR]" -msgstr "[COLOR cyan][B]-- PÁGINA SEGUINTE --[/B][/COLOR]" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "Avaliaçao removida|Você avaliou negativamente|Você avaliou positivamente" msgctxt "#30046" -msgid "InputStream addon is not enabled" -msgstr "O addon InputStream não está ativo" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "Há um problema com o complemento InputStream Adaptive ou a biblioteca Widevine. Pode estar ausente ou não habilitado, a operação será cancelada." msgctxt "#30047" -msgid "Finally remove?" -msgstr "Deseja eliminar?" +msgid "Library update in progress" +msgstr "Atualização da coleção em progresso" msgctxt "#30048" msgid "Exported" msgstr "Exportado" msgctxt "#30049" -msgid "Update DB" -msgstr "Atualizar base de dados" +msgid "Library" +msgstr "Coleção" msgctxt "#30050" -msgid "Update successful" -msgstr "Atualização concluída com sucesso" - -msgctxt "#30051" -msgid "Request Error" -msgstr "Erro no pedido" +msgid "Enable Kodi library management" +msgstr "Ativar gerenciamento de coleção no Kodi" +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30052" -msgid "Unable to complete the request at this time" -msgstr "De momento, não foi possível concluír o pedido" - -msgctxt "#30053" -msgid "Auto Login" -msgstr "Inicio de sessão automático" - -msgctxt "#30054" -msgid "Enable Auto Login" -msgstr "Ativar o início de sessão automático" +msgid "{} Set for library playback" +msgstr "{} Definir para reprodução na coleção" +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30055" -msgid "Profile" -msgstr "Perfil" - -msgctxt "#30056" -msgid "ID" -msgstr "ID" +msgid "{} Set at start-up" +msgstr "{} Setar ao inicializar" +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active msgctxt "#30057" -msgid "Select profile in profile listing -> context menu" -msgstr "Selecionar perfil na lista de perfis -> menu de contexto" - -msgctxt "#30058" -msgid "Auto Login enabled!" -msgstr "Início de sessão automático ativado!" +msgid "{} Remember PIN" +msgstr "{} Lembrar PIN" +#. Unused 30058 msgctxt "#30059" -msgid "Switch accounts" -msgstr "Mudar de conta" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "Cuidado, você está prestes a atualizar muitos títulos {}.[CR]Isso pode causar problemas de serviço ou banir temporariamente a conta.[CR]Deseja continuar?" msgctxt "#30060" -msgid "Enable HEVC profiles (4k for Android/HDR/DolbyVision)" -msgstr "Ativar perfis HEVC (4k para Android/HDR/DolbyVision)" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "" msgctxt "#30061" msgid "Update inside library" msgstr "Atualizar dentro da coleção" msgctxt "#30062" -msgid "Enable/disable adult pin. Active:" -msgstr "Ativar / desativar o pin de controle parental. Ativo: " +msgid "Parental controls" +msgstr "Controles Parentais" msgctxt "#30063" -msgid "new episodes added to library" -msgstr "novos episódios adicionados à coleção" +msgid "An update is already in progress" +msgstr "Uma atualização já está em andamento" msgctxt "#30064" -msgid "Export new episodes" -msgstr "Exportar novos episódios" +msgid "Perform auto-update" +msgstr "Realizar atualização automática" msgctxt "#30065" -msgid "Auto-update" -msgstr "Atualizar automaticamente" +msgid "Video library update" +msgstr "Atualizar coleção de vídeo" msgctxt "#30066" -msgid "never" -msgstr "nunca" +msgid "Enable debug logging" +msgstr "Ativar log de depuração" msgctxt "#30067" msgid "daily" @@ -309,16 +289,16 @@ msgid "Only start after 5 minutes of idle" msgstr "Começar apenas depois de 5 minutos de inatividade" msgctxt "#30073" -msgid "Check every X minutes if update scheduled" -msgstr "Verificar a cada X minutos se uma atualização está programada" +msgid "Always show codec information during video playback" +msgstr "Sempre mostrar informação do codec durante a reprodução de vídeo" msgctxt "#30074" msgid "View for exported" -msgstr "Vista para exportados" +msgstr "Visualização para exportados" msgctxt "#30075" msgid "Ask to skip intro and recap" -msgstr "Perguntar por escapar da intro e recap" +msgstr "Perguntar para pular a intro e recapitulação" msgctxt "#30076" msgid "Skip intro" @@ -326,7 +306,7 @@ msgstr "Pular intro" msgctxt "#30077" msgid "Skip recap" -msgstr "Pular recap" +msgstr "Pular recapitulação" msgctxt "#30078" msgid "Playback" @@ -341,40 +321,40 @@ msgid "Pause when skipping" msgstr "Pausar quando pular" msgctxt "#30081" -msgid "Force support of HDCP 2.2 (enable 4K content)" -msgstr "Forçar suporte para HDCP 2.2 (habilita conteúdo 4K)" +msgid "Force HDCP version on video streams" +msgstr "" msgctxt "#30082" msgid "Remember audio / subtitle preferences" -msgstr "Lembrar preferências para áudio / legendas" +msgstr "Lembrar preferências de áudio / legendas" msgctxt "#30083" -msgid "Save bookmarks for library items / mark as watched" -msgstr "Salvar marcadores para itens na coleção / marcar como assistido" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "Forçe a exibição de legendas forçadas apenas com o idioma de áudio definido" msgctxt "#30084" -msgid "Cache item time-to-live (minutes)" -msgstr "Cache de item time-to-live (minutos)" +msgid "Cache objects TTL (minutes)" +msgstr "Tempo de vida útil do item de cache (minutos)" msgctxt "#30085" -msgid "Cache item time-to-live for metadata (days)" -msgstr "Tempo de vida dos metadados para itens em cache (dias)" +msgid "Cache metadata TTL (days)" +msgstr "Tempo de vida útil do item de cache para metadados (dias)" msgctxt "#30086" -msgid "Invalidate entire cache on modification of My List" -msgstr "Invalidar o cache inteiro na modificação da Minha Lista" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "Tempo de vida útil do item de cache da minha lista (minutos)" msgctxt "#30087" msgid "Contains {} and more..." -msgstr "Contém {} e muito mais..." +msgstr "Contém {} e mais..." msgctxt "#30088" msgid "Browse related content" -msgstr "Navegar em conteúdos relacionados" +msgstr "Procurar conteúdos relacionados" msgctxt "#30089" msgid "Browse subgenres" -msgstr "Navegar em subgêneros" +msgstr "Procurar subgêneros" msgctxt "#30090" msgid "Browse through related video lists and discover more content." @@ -382,11 +362,11 @@ msgstr "Navegue pelas listas de vídeos relacionados e descubra mais conteúdo." msgctxt "#30091" msgid "Browse and manage contents that were exported to the Kodi library." -msgstr "Navegue e gerencie os conteúdos que foram exportados para a biblioteca Kodi." +msgstr "Navegue e gerencie os conteúdos que foram exportados para a coleção no Kodi." msgctxt "#30092" msgid "Search for content and easily find what you want to watch." -msgstr "Pesquise conteúdo e encontre facilmente o que você deseja assistir." +msgstr "Pesquise e encontre facilmente o que você deseja assistir." msgctxt "#30093" msgid "Browse content by genre and easily discover related content." @@ -394,10 +374,10 @@ msgstr "Navegue por conteúdo por gênero e descubra facilmente conteúdo relaci msgctxt "#30094" msgid "Browse recommendations and pick up on something new you might like." -msgstr "Navegue em recomendações e escolha algo novo que possas gostar." +msgstr "Navegue em recomendações e escolha algo novo que você possa gostar." msgctxt "#30095" -msgid "All TV Shows" +msgid "All TV shows" msgstr "Todos os Seriados" msgctxt "#30096" @@ -409,16 +389,16 @@ msgid "Main Menu" msgstr "Menu Principal" msgctxt "#30098" -msgid "Enable HDR profiles" -msgstr "Ativar perfis HDR" +msgid "Enable HDR10" +msgstr "" msgctxt "#30099" -msgid "Enable DolbyVision profiles" -msgstr "Ativar perfis DolbyVision" +msgid "Enable Dolby Vision" +msgstr "" msgctxt "#30100" -msgid "Results for \"{}\"" -msgstr "Resultados para \"{}\"" +msgid "Results for '{}'" +msgstr "Resultados para '{}'" msgctxt "#30101" msgid "Netflix returned an unknown error." @@ -429,15 +409,15 @@ msgid "Netflix returned the following error:" msgstr "Netflix retornou o seguinte erro:" msgctxt "#30103" -msgid "Error details have been written to the Kodi logfile. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it via GitHub. Please provide a full debug log with your error report (enable \"Debug Logging\" in Kodi settings)." -msgstr "Detalhes do erro foram gravados no arquivo de log do Kodi. Se a mensagem de erro não fornecer orientação sobre como resolver o problema ou se você continuar recebendo esse erro, informe-o via GitHub. Por favor, forneça um registro de depuração completo com o seu relatório de erro (ative \"Debug Logging\" nas configurações do Kodi)." +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "Os detalhes do erro foram gravados no arquivo de log do Kodi. Se a mensagem de erro não fornecer orientações sobre como resolver o problema ou você continuar recebendo esse erro, informe-o no GitHub seguindo as instruções do Leia-me." msgctxt "#30104" -msgid "The addon encountered to following error:" +msgid "The add-on encountered the following error:" msgstr "O add-on encontrou o seguinte erro:" msgctxt "#30105" -msgid "Netflix Addon Error" +msgid "Netflix Add-on Error" msgstr "Erro no Add-on Netflix" msgctxt "#30106" @@ -445,21 +425,18 @@ msgid "Invalid PIN" msgstr "PIN Inválido" msgctxt "#30107" -msgid "Disabled PIN verfication" -msgstr "Desabilitar verificação do PIN" +msgid "PIN protection is off." +msgstr "Proteção PIN está desligado." msgctxt "#30108" -msgid "Enabled PIN verfication" -msgstr "Ativar verificação do PIN" +msgid "All content is protected by PIN." +msgstr "Todos os conteúdos estão protegidos pelo PIN." msgctxt "#30109" msgid "Login successful" msgstr "Logado com sucesso" -msgctxt "#30110" -msgid "Background services started" -msgstr "Serviços em background inicializados" - +#. Unused 30110 msgctxt "#30111" msgid "There is no contents" msgstr "Sem conteúdos" @@ -472,85 +449,71 @@ msgctxt "#30113" msgid "Logout successful" msgstr "Deslogado com sucesso" -msgctxt "#30114" -msgid "Keep My List and Kodi Library in sync" -msgstr "Mantenha minha lista e coleção no Kodi em sincronia" - -msgctxt "#30115" -msgid "Content Profiles" -msgstr "Conteúdos por Perfis" - +#. Unused 30114, 30115 msgctxt "#30116" -msgid "Advanced Addon Configuration" +msgid "Advanced Add-on Configuration" msgstr "Ajustes avançados" msgctxt "#30117" msgid "Cache" msgstr "Cache" -msgctxt "#30118" -msgid "API operation failed: {}" -msgstr "API operação falhou: {}" - -msgctxt "#30119" -msgid "My List operation successful" -msgstr "Minha lista operou com sucesso" - +#. Unused 30118, 30119 msgctxt "#30120" msgid "Cannot automatically remove a season from Kodi library, please do it manually" -msgstr "Não posso remover uma temporada automaticamente da coleção do Kodi, proceda com isto manualmente" +msgstr "Não é possível remover automaticamente uma temporada da coleção do Kodi, faça-o manualmente" msgctxt "#30121" msgid "Perform full sync now" -msgstr "Performar sincronização total agora" +msgstr "Executar sincronização completa agora" msgctxt "#30122" -msgid "Sync My List to Kodi library" +msgid "Sync 'My list' to Kodi library" msgstr "Sincronizar Minha Lista para a coleção do Kodi" msgctxt "#30123" -msgid "Do you really want to proceed?\nThis will delete all previously exported items and export all items from My List for the currently active profile.\nThis operation may take some time..." -msgstr "Você realmente deseja prosseguir?\nIsso excluirá todos os itens exportados anteriormente e exportará todos os itens da Minha Lista para o perfil ativo no momento.\nEsta operação pode levar algum tempo..." +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "Deseja continuar?[CR]A sincronização completa excluirá todos os títulos exportados anteriormente.[CR]Se houver muitos títulos, esta operação poderá demorar algum tempo." msgctxt "#30124" -msgid "Do you really want to remove this item?" +msgid "Do you want to remove this item from the library?" msgstr "Deseja realmente remover este item?" msgctxt "#30125" -msgid "Purge library" -msgstr "Limpar coleção" +msgid "Delete library contents" +msgstr "Limpar conteúdos da coleção" msgctxt "#30126" -msgid "Do you really want to purge your library?\nAll exported items will be deleted." -msgstr "Realmente deseja limpar sua coleção?\nTodos os itens exportados serão excluídos." +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "Realmente deseja limpar sua coleção?[CR]Todos os itens exportados serão excluídos." msgctxt "#30127" msgid "Awarded rating of {}" msgstr "Nota atribuída de {}" msgctxt "#30128" -msgid "No contained titles yet..." -msgstr "Sem títulos disponíveis ainda..." +msgid "Select a profile" +msgstr "Selecionar um perfil" msgctxt "#30129" msgid "Enable Up Next integration" -msgstr "Ativar notificação a seguir" +msgstr "Ativar integração Up Next" msgctxt "#30130" -msgid "Show notification instead of modal dialog for errors" -msgstr "Mostrar notificação ao invés de uma caixa de diálogo para erros" +msgid "Install Up Next add-on" +msgstr "Instalar add-on Up Next" msgctxt "#30131" msgid "Check the logfile for detailed information" msgstr "Verifique o arquivo de log para informações detalhadas" msgctxt "#30132" -msgid "Purge in-memory cache" -msgstr "Expurgar cache na memória" +msgid "Clear in-memory cache" +msgstr "Limpar cache na memória" msgctxt "#30133" -msgid "Purge in-memory and on-disk cache" -msgstr "Expurgar cache na memória e no disco" +msgid "Clear in-memory and on-disk cache" +msgstr "Limpar a memória e o cache do disco" msgctxt "#30134" msgid "Enable execution timing" @@ -561,40 +524,29 @@ msgid "Cache cleared" msgstr "Cache limpo" msgctxt "#30136" -msgid "Enable 1080p Unlock (not recommended for Android)" -msgstr "Ativar desbloqueio 1080p (não recomendado para Android)" +msgid "Waiting for service start-up..." +msgstr "Aguardando inicialização do serviço..." msgctxt "#30137" -msgid "Enable VP9 profiles (disable if it causes artifacts)" -msgstr "Ativar perfis VP9 (desabilite se causar artefatos)" +msgid "Enable VP9 codec" +msgstr "" msgctxt "#30138" -msgid "The requested action timed out. The background services may not yet be available if you just started Kodi/the addon. In this case, try again in a moment." -msgstr "A ação solicitada expirou. Os serviços de segundo plano podem ainda não estar disponíveis se você acabou de iniciar o Kodi/addon. Neste caso, tente novamente daqui a pouco." +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "Os serviços de segundo plano podem ainda não estar disponíveis se você acabou de iniciar o Kodi/addon. Neste caso, tente novamente daqui a pouco." msgctxt "#30139" -msgid "Enable IPC over HTTP (use when experiencing AddonSignals timeouts)" -msgstr "Ativar IPC sobre HTTP (usar quando tiver problemas de tempo expirado no AddonSignals)" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "Ativar IPC sobre HTTP (usar quando tiver problemas de tempo expirado no Addon Signals)" msgctxt "#30140" -msgid "Migrate Library to new format" -msgstr "Migrar coleção para novo formato" - -msgctxt "#30141" -msgid "Disable startup notification" -msgstr "Desativar notificação de inicialização" - -msgctxt "#30142" -msgid "Custom" -msgstr "Customizado" - -msgctxt "#30143" -msgid "Custom view ID" -msgstr "Visualizar Custom ID" +msgid "Import existing library" +msgstr "Importar coleção existente" +#. Unused 30141 to 30143 msgctxt "#30144" msgid "Disable WebVTT subtitle support" -msgstr "Desabilitar suporte legendas WebVTT" +msgstr "Desativar o suporte a legendas WebVTT" msgctxt "#30145" msgid "Recently added" @@ -605,12 +557,12 @@ msgid "Discover the latest content added." msgstr "Descubra os conteúdos mais recentes." msgctxt "#30147" -msgid "Next page >>" -msgstr "Próxima página >>" +msgid "Next page »" +msgstr "Próxima página »" msgctxt "#30148" -msgid "<< Previous page" -msgstr "<< Página anterior" +msgid "« Previous page" +msgstr "« Página anterior" msgctxt "#30149" msgid "Sort results by" @@ -618,7 +570,7 @@ msgstr "Ordenar resultados por" msgctxt "#30150" msgid "Alphabetical order (A-Z)" -msgstr "Ordem alfabética A-Z)" +msgstr "Ordem alfabética (A-Z)" msgctxt "#30151" msgid "Alphabetical order (Z-A)" @@ -633,52 +585,49 @@ msgid "Year released" msgstr "Ano de lançamento" msgctxt "#30154" -msgid "Netflix addon configuration" +msgid "Netflix configuration wizard" msgstr "Configuração do add-on do Netflix" msgctxt "#30155" msgid "Do you have a Netflix Premium account?" -msgstr "Tens uma conta Netflix Premium?" - -msgctxt "#30156" -msgid "Does your device support the 4K standard?\r\nThe device must:\r\nBe Netflix certified, support for Widevine Security level L1 and have a 4K display HDCP 2.2" -msgstr "Tens um dispositivo com suporte para o padrão 4K?\r\nO dispositivo deve ter:\r\nSer certificado pelo Netflix, suportar o nível L1 de segurança Widevine e ter uma tela 4k como HDCP 2.2" +msgstr "Você tem uma conta Netflix Premium?" +#. Unused 30156 msgctxt "#30157" -msgid "The InputStream Adaptive settings panel will now be opened, so you have to change this option \"Override HDCP status\" to ON" -msgstr "O painel de ajustes do Inputstream Adaptive será agora aberto, podes então alterar a opção \"Sobreponha estatus HDCP\" para Ligado" +msgid "The configuration has been completed" +msgstr "A configuração foi concluída" msgctxt "#30158" -msgid "Show configuration screen at addon startup" -msgstr "Mostrar tela de ajustes ao iniciar o add-on" +msgid "Restore the add-on default configuration" +msgstr "" msgctxt "#30159" -msgid "View for main menu" -msgstr "Visualização para o menu principal" +msgid "View ID for main menu" +msgstr "Visualizar ID para menu principal" msgctxt "#30160" -msgid "View for search" -msgstr "Visualização para procura" +msgid "View ID for search" +msgstr "ID Visualização para procura" msgctxt "#30161" -msgid "View for exported" -msgstr "Visualização para exportar" +msgid "View ID for exported" +msgstr "ID Visualização para exportado" msgctxt "#30162" msgid "Last used" -msgstr "Último usado" +msgstr "Usado por último" msgctxt "#30163" msgid "Audio description" -msgstr "Audio descrição" +msgstr "Descrição de áudio" msgctxt "#30164" msgid "Browse contents with audio description." msgstr "Navegar nos conteúdos com descrição por áudio" msgctxt "#30165" -msgid "View for my list" -msgstr "Visualização para minha lista" +msgid "View ID for 'My list'" +msgstr "ID Visualização para 'Minha lista'" msgctxt "#30166" msgid "Main menu items" @@ -706,14 +655,14 @@ msgstr "Tendências" msgctxt "#30172" msgid "Popular on Netflix" -msgstr "Popular no Netflix" +msgstr "Popular na Netflix" msgctxt "#30173" -msgid "NETFLIX ORIGINALS" -msgstr "Produções Originais" +msgid "Netflix Originals" +msgstr "Originais da Netflix" msgctxt "#30174" -msgid "Tv Show genres" +msgid "TV show genres" msgstr "Gêneros de seriados" msgctxt "#30175" @@ -721,28 +670,28 @@ msgid "Movie genres" msgstr "Gêneros de filmes" msgctxt "#30176" -msgid "Enable STRM resume workaround (only library)" -msgstr "Ativar solução alternativa de retomada do STRM (somente coleção)" +msgid "Enable STRM resume workaround" +msgstr "Ativar solução alternativa para retomada o STRM" msgctxt "#30177" msgid "Ask to resume the video" msgstr "Perguntar para retomar o vídeo" msgctxt "#30178" -msgid "Open UpNext Addon settings" -msgstr "Abrir ajustes do add-on UpNext" +msgid "Open Up Next add-on settings" +msgstr "Abrir ajustes do add-on Up Next" msgctxt "#30179" -msgid "Show the trailers" -msgstr "Mostrar os trailers" +msgid "Show trailers and more" +msgstr "Mostrar trailers & mais" msgctxt "#30180" -msgid "No trailers available" -msgstr "Trailers indisponíveis" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "Houve um problema com o login, é possível que sua conta não tenha sido confirmada ou reativada." msgctxt "#30181" -msgid "Disable subtitles if there are no forced subtitles streams (Works only if Kodi Player is set to \"Only forced\" subtitles)" -msgstr "Desativar legendas se não houver fluxos de legendas forçadas (Funciona somente se o Kodi Player estiver configurado para legendas \"Somente forçadas\")" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "Sempre mostre as legendas quando o idioma de áudio preferido não estiver disponível" msgctxt "#30182" msgid "Export NFO files" @@ -758,14 +707,14 @@ msgstr "Arquivos NFO" msgctxt "#30185" msgid "Enable NFO files export" -msgstr "Ativar exportar arquivos NFO" +msgstr "Ativar exportação de arquivos NFO" msgctxt "#30186" msgid "For Movies" msgstr "Para filmes" msgctxt "#30187" -msgid "For TV Show" +msgid "For TV show" msgstr "Para seriados" msgctxt "#30188" @@ -780,17 +729,14 @@ msgctxt "#30190" msgid "TV show" msgstr "seriado" -msgctxt "#30191" -msgid "Do you want to export NFO files for the {} added to 'My List'?" -msgstr "Deseja exportar arquivos NFO para o {} adicionado em 'Minha Lista'?" - +#. Unused 30191 msgctxt "#30192" -msgid "\r\nThese files are used by the provider information scrapers to integrate movies and tv shows info, useful with bonus episodes or director's cut" -msgstr "\r\nEsses arquivos são usados ​​pelos raspadores de informações para integrar informações sobre filmes e programas de TV, úteis para episódios de bônus ou para cortes do diretor" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]Esses arquivos são usados pelos buscadores de informações do provedor para integrar informações de filmes e programas de TV, úteis com episódios de bônus ou cortes do diretor." msgctxt "#30193" -msgid "Include all information in NFO files (use only with 'Local Information' scraper)" -msgstr "Incluir todas as informações nos arquivos NFO (Usado somente com o raspador 'Local Information Only')" +msgid "Include tv show NFO details" +msgstr "Incluir detalhes NFO de seriados" msgctxt "#30194" msgid "Limit video stream resolution to" @@ -802,11 +748,11 @@ msgstr "Exportar novos episódios" msgctxt "#30196" msgid "Exclude from auto update" -msgstr "Excluir da auto atualização" +msgstr "Excluir da atualização automática" msgctxt "#30197" msgid "Include in auto update" -msgstr "Incluir na atualiazação automática" +msgstr "Incluir na atualização automática" msgctxt "#30198" msgid "Exporting new episodes" @@ -814,19 +760,19 @@ msgstr "Exportando novos episódios" msgctxt "#30199" msgid "Shared library (Kodi MySQL server is required)" -msgstr "Compartilhar coleção (Kodi MySQL é requerido)" +msgstr "Biblioteca compartilhada (é necessário o servidor Kodi MySQL)" msgctxt "#30200" -msgid "Use MySQL shared library database" -msgstr "Usar banco de dados da coleção compartilhada MYSQL" +msgid "Use shared library MySQL-database" +msgstr "Usar banco de dados de biblioteca compartilhada MySQL" msgctxt "#30201" msgid "Username" msgstr "Usuário" msgctxt "#30202" -msgid "Connection to the MySQL database was successful" -msgstr "Conexão para o banco de dados do MySQL efetuado com sucesso" +msgid "Connection to the MySQL-database was successful" +msgstr "A conexão com o banco de dados MySQL foi bem-sucedida" msgctxt "#30203" msgid "Host IP" @@ -841,7 +787,7 @@ msgid "Test database connection" msgstr "Testar conexão do banco de dados" msgctxt "#30206" -msgid "ERROR: The MySQL database is not reachable - the local database will be used" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" msgstr "ERRO: O banco de dados MySQL não está acessível - o banco de dados local será usado" msgctxt "#30207" @@ -853,21 +799,458 @@ msgid "Check if this device is the main auto-update manager" msgstr "Verificar se este dispositivo é o gerenciador principal das atualizações automáticas" msgctxt "#30209" -msgid "This device now handles the auto-updates of shared-library" +msgid "This device now handles the auto-updates of the shared library" msgstr "Este dispositivo agora lida com as atualizações automáticas da biblioteca compartilhada" msgctxt "#30210" -msgid "This device handles the auto-updates of shared-library" +msgid "This device handles the auto-updates of the shared library" msgstr "Este dispositivo lida com as atualizações automáticas da biblioteca compartilhada" msgctxt "#30211" -msgid "Another device handles the auto-updates of shared-library" +msgid "Another device handles the auto-updates of the shared library" msgstr "Outro dispositivo lida com as atualizações automáticas da biblioteca compartilhada" msgctxt "#30212" -msgid "There is no device that handles the auto-updates of shared-library" +msgid "There is no device that handles the auto-updates of the shared library" msgstr "Nenhum dispositivo lida com as atualizações automáticas da biblioteca compartilhada" msgctxt "#30213" msgid "InputStream Helper settings..." msgstr "Ajustes do addon InputStream Helper..." + +msgctxt "#30214" +msgid "Force refresh" +msgstr "Forçar atualização" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "Destacar os títulos incluídos em minha lista com uma cor" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "Cor dos títulos marcados como \"Lembre-me\"" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "Desativar notificação para sincronização finalizada" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "A coleção do Kodi foi atualizada" + +msgctxt "#30221" +msgid "Owner account" +msgstr "Conta principal" + +msgctxt "#30222" +msgid "Kid account" +msgstr "Conta infantil" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "Modo de atualização automática" + +msgctxt "#30225" +msgid "Manual" +msgstr "Manual" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "Agendada" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "Sincroniza coleção do Kodi com minha lista" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "Definir um perfil para sincronização" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "Quanto o Kodi inicializar" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "Verificar por atualizações agora" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "Deseja verificar por atualizações agora?" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "Perfil maturidade da classificação indicativa para {}" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "Mostrar apenas os títulos com classificação indicativa [B]{}[/B]." + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "Sincronize o estatus assistido dos vídeos com o Netflix" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "Alterar o estatus assistido (localmente)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "Marcar como assistido|Marcado como por assistir|Restaurado estatus de assistido via Netflix" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Notificações Up Next (assistir próximo vídeo)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "Cor das informações suplementares do enredo" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "Os serviços em segundo plano não podem ser iniciados devido a este problema:[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Open Connect CDN (o índice mais baixo é melhor)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "Top 10" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "Selecionar o primeiro episódio de seriado não assistido" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "Deletando arquivos exportados para a coleção" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "Existem {} títulos que não puderam ser importados.[CR]Deseha deletar estes arquivos?" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "Resultados por página (em caso de erro de tempo limite, diminua-o)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "Deseja remover o status de assistido de \"{}\"?" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "Incluir na coleção do Kodi (somente enviando dado)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "Escolha o método de login[CR](se o login com E-Mail/Senha retorna sempre \"senha incorreta\", então use a chave de autenticação, instruções no Leiame do GitHub)" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "E-mail/Senha" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "Chave de autenticação" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "O arquivo selecione não é compatível ou está corrompido" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "O arquivo da chave de autenticação expirou" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "Entre com o PIN" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "Classificar histórico por" + +msgctxt "#30400" +msgid "Search" +msgstr "Buscar" + +msgctxt "#30401" +msgid "Type of search" +msgstr "Tipo de pesquisa" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "Procurar títulos, pessoas, gêneros" + +msgctxt "#30403" +msgid "New search..." +msgstr "Nova pesquisa..." + +msgctxt "#30404" +msgid "Clear search history" +msgstr "Limpar histórico de pesquisa" + +msgctxt "#30405" +msgid "Select the language" +msgstr "Selecione o idioma" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "Deseja deletar todo o conteúdo?" + +msgctxt "#30407" +msgid "No matches found" +msgstr "Não foram encontrados resultados" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "Pelo termo" + +msgctxt "#30411" +msgid "By audio language" +msgstr "Pelo idioma de áudio" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "Pelo idioma da legenda" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "Pelo ID do gênero/subgênero" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "Preferir o idioma de áudio/legenda com código do país (se disponível)" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "Prefere tracks de áudio estéreo por padrão" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "ESN / Ajustes Widevine" + +msgctxt "#30601" +msgid "ESN:" +msgstr "ESN:" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "Alterar ESN" + +msgctxt "#30603" +msgid "Save system info" +msgstr "Salvar info sistema" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "Widevine - Forçar nível segurança" + +msgctxt "#30605" +msgid "Force {}" +msgstr "Forçar {}" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "ESN definido com sucesso[CR]Tente reproduzir um vídeo" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "Este ESN não pode ser usado[CR]Tente alterar o ESN ou resetar o ESN.[CR][CR]Detalhes do erro:[CR]{}" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "ESN formato com erro" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "Deseja resetar todos os ajustes?" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "Use sistema baseado em encriptação para credenciais" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "[B]Se desativado, causará um problema de segurança[/B]. Se o seu sistema operacional não suportar, será solicitado que você faça o login toda vez que reiniciar o dispositivo, [B]apenas[/B] neste caso, desative-o. Se alterar esta configuração, você será solicitado a fazer login novamente na próxima vez que reiniciar." + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "MSL versão do manifesto" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "Este título estará disponível em:[CR]{}" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "Você quer reproduzir o trailer promocional?" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "{} Lembre-me" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "Novo e popular" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "Marque como assistido seriados iniciados" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "Observe que, ao ativar essa configuração, se os filtros estiverem ativados nas configurações de skin, os itens marcados como assistidos podem não ficar visíveis." + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "Substituir o tipo de seleção de stream" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "Substitua a configuração do tipo de seleção de fluxo do complemento InputStream Adaptive para definir como a qualidade dos fluxos de áudio/vídeo será escolhida durante a reprodução." + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "Qualidade adaptável" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "Qualidade do vídeo fixa" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "Perguntar pela qualidade do vídeo" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "Observe que habilitar essa configuração forçará a remoção de fluxos que não estão dentro do intervalo definido e isso pode afetar a operação do InputStream Adaptive. Se possível, tente limitar a qualidade das configurações adaptativas do InputStream primeiro." + +msgctxt "#30721" +msgid "Size" +msgstr "" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "" + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "" + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "Adicione pastas Netflix às fontes da biblioteca Kodi" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "As pastas \"Netflix-Movies\" e \"Netflix-Shows\" foram adicionadas às fontes Kodi, reinicie o Kodi para visualizá-las nos menus Kodi [Programas de TV] / [Filmes]. Em seguida, edite as pastas para definir o tipo de conteúdo apropriado e o scraper do provedor de informações." + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "As atualizações [manuais] devem ser feitas manualmente usando o menu de contexto \"Exportar novos episódios\" em cada programa de TV, [Agendado] você pode estabelecer atualizações automáticas em horários programados, [Quando o Kodi inicia] as atualizações automáticas começam na inicialização do Kodi e será feito apenas uma vez por dia." + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "Sincroniza a biblioteca com Minha lista, se minha lista foi modificada externamente, por ex. aplicativo móvel ou site, a atualização será adiada para o horário agendado." + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "Se ativado, adiciona informações a episódios ou filmes que são úteis quando o scraper do provedor de informações Kodi não fornece dados. Isso também adicionará a duração dos vídeos" + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "Ative apenas se o raspador do provedor de informações \"Informações locais\" for usado ou se determinados títulos não forem exibidos na biblioteca Kodi, apesar de terem sido adicionados." + +msgctxt "#30734" +msgid "Support" +msgstr "Suporte" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "Sobre o add-on Netflix" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "Gerar automaticamente novos ESNs (solução alternativa para limite de 540p)" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "AVISO: Não use o dispositivo original ESN do aplicativo oficial" + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "" + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "" + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "" + +msgctxt "#30747" +msgid "Fixed" +msgstr "" + +msgctxt "#30748" +msgid "Relative" +msgstr "" + +msgctxt "#30749" +msgid "Reset" +msgstr "" diff --git a/resources/language/resource.language.pt_pt/strings.po b/resources/language/resource.language.pt_pt/strings.po deleted file mode 100644 index f6f03a724..000000000 --- a/resources/language/resource.language.pt_pt/strings.po +++ /dev/null @@ -1,321 +0,0 @@ -# Kodi Media Center language file -# Addon Name: Netflix -# Addon id: plugin.video.netflix -# Addon Provider: libdev + jojo + asciidisco + caphm + CastagnaIT -msgid "" -msgstr "" -"Project-Id-Version: plugin.video.netflix\n" -"Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" -"POT-Creation-Date: 2017-07-24 16:15+0000\n" -"PO-Revision-Date: 2019-07-08 22:15+0000\n" -"Last-Translator: Gustavo Silva \n" -"Language-Team: Portuguese\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" - -msgctxt "Addon Summary" -msgid "Netflix" -msgstr "" - -msgctxt "Addon Description" -msgid "Netflix VOD Services Addon" -msgstr "Addon do Netflix, um serviço de Video On Demand" - -msgctxt "Addon Disclaimer" -msgid "Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing." -msgstr "Algumas partes deste addon poderão não ser legais no seu país de residência. Por favor, verifique as suas leis locais antes de instalar." - -msgctxt "#30001" -msgid "Recommendations" -msgstr "Recomendações" - -msgctxt "#30002" -msgid "Adult Pin" -msgstr "Pin de controlo parental" - -msgctxt "#30003" -msgid "Search term" -msgstr "Termo de pesquisa" - -msgctxt "#30004" -msgid "Password" -msgstr "Senha" - -msgctxt "#30005" -msgid "E-mail" -msgstr "" - -msgctxt "#30006" -msgid "Adult verification failed" -msgstr "A validação de controlo parental falhou" - -msgctxt "#30007" -msgid "Please Check your adult pin" -msgstr "Por favor, verifique se o pin de controlo parental está correcto" - -msgctxt "#30008" -msgid "Login failed" -msgstr "Erro ao inicar sessão" - -msgctxt "#30009" -msgid "Please Check your credentials" -msgstr "Por favor, verifique as suas credenciais de acesso" - -msgctxt "#30010" -msgid "Lists of all kinds" -msgstr "" - -msgctxt "#30011" -msgid "Search" -msgstr "Procurar" - -msgctxt "#30012" -msgid "No seasons available" -msgstr "Sem temporadas disponíveis" - -msgctxt "#30013" -msgid "No matches found" -msgstr "Não foram encontrados resultados" - -msgctxt "#30014" -msgid "Account" -msgstr "Conta" - -msgctxt "#30017" -msgid "Logout" -msgstr "Terminar sessão" - -msgctxt "#30018" -msgid "Export to library" -msgstr "Exportar para a biblioteca" - -msgctxt "#30019" -msgid "Rate on Netflix" -msgstr "Avaliar no Netflix" - -msgctxt "#30020" -msgid "Remove from 'My list'" -msgstr "Remover da 'Minha Lista'" - -msgctxt "#30021" -msgid "Add to 'My list'" -msgstr "Adicionar à 'Minha Lista'" - -msgctxt "#30022" -msgid "(between 0 & 10)" -msgstr "(Entre 0 e 10)" - -msgctxt "#30023" -msgid "Expert" -msgstr "" - -msgctxt "#30024" -msgid "SSL verification" -msgstr "Verificação SSL" - -msgctxt "#30025" -msgid "Library" -msgstr "Bibilioteca" - -msgctxt "#30026" -msgid "Enable custom library folder" -msgstr "Activar pasta de biblioteca personalizada" - -msgctxt "#30027" -msgid "Custom library path" -msgstr "Caminho para a pasta de bibilioteca personalizada" - -msgctxt "#30028" -msgid "Playback error" -msgstr "Erro de reprodução" - -msgctxt "#30029" -msgid "Missing InputStream addon" -msgstr "O addon InputStream está em falta" - -msgctxt "#30030" -msgid "Remove from library" -msgstr "Remover da biblioteca" - -msgctxt "#30031" -msgid "Change library title" -msgstr "Alterar título" - -msgctxt "#30032" -msgid "Tracking" -msgstr "Rastreio" - -msgctxt "#30033" -msgid "Enable Dolby Digital Plus (DDPlus HQ and Atmos on Premium)" -msgstr "Ativar Dolby Digital Plus (DDPlus HQ e Atmos on Premium)" - -msgctxt "#30034" -msgid "ESN (set automatically, can be changed manually)" -msgstr "ESN (definido automaticamente, pode ser alterado manualmente)" - -msgctxt "#30035" -msgid "InputStream Adaptive settings..." -msgstr "Configurações do addon InputStream" - -msgctxt "#30036" -msgid "Always use the original title on export" -msgstr "Utilizar sempre o título original para exportar" - -msgctxt "#30037" -msgid "Views" -msgstr "Reproduções" - -msgctxt "#30038" -msgid "Enable custom views" -msgstr "Activar vistas personalizadas" - -msgctxt "#30039" -msgid "View for folders" -msgstr "Vista para as pastas" - -msgctxt "#30040" -msgid "View for movies" -msgstr "Vista para os filmes" - -msgctxt "#30041" -msgid "View for shows" -msgstr "Vista para as séries" - -msgctxt "#30042" -msgid "View for seasons" -msgstr "Vista para as temporadas" - -msgctxt "#30043" -msgid "View for episodes" -msgstr "Vista para os episódios" - -msgctxt "#30044" -msgid "View for profiles" -msgstr "Vista para os perfis" - -msgctxt "#30045" -msgid "[COLOR cyan][B]-- NEXT PAGE --[/B][/COLOR]" -msgstr "[COLOR cyan][B]-- PÁGINA SEGUINTE --[/B][/COLOR]" - -msgctxt "#30046" -msgid "InputStream addon is not enabled" -msgstr "O addon InputStream não está activado" - -msgctxt "#30047" -msgid "Finally remove?" -msgstr "Deseja eliminar?" - -msgctxt "#30048" -msgid "Exported" -msgstr "Exportado" - -msgctxt "#30049" -msgid "Update DB" -msgstr "Actualizar base de dados" - -msgctxt "#30050" -msgid "Update successful" -msgstr "Actualização concluída com sucesso" - -msgctxt "#30051" -msgid "Request Error" -msgstr "Erro no pedido" - -msgctxt "#30052" -msgid "Unable to complete the request at this time" -msgstr "De momento, não foi possível concluír o pedido" - -msgctxt "#30053" -msgid "Auto Login" -msgstr "Inicio de sessão automático" - -msgctxt "#30054" -msgid "Enable Auto Login" -msgstr "Activar o início de sessão automático" - -msgctxt "#30055" -msgid "Profile" -msgstr "Perfil" - -msgctxt "#30056" -msgid "ID" -msgstr "" - -msgctxt "#30057" -msgid "Select profile in profile listing -> context menu" -msgstr "Seleccionar perfil na lista de perfis -> menu de contexto" - -msgctxt "#30058" -msgid "Auto Login enabled!" -msgstr "Início de sessão automático activado!" - -msgctxt "#30059" -msgid "Switch accounts" -msgstr "Mudar de conta" - -msgctxt "#30060" -msgid "Enable HEVC profiles (4k for Android/HDR/DolbyVision)" -msgstr "Activar perfis HEVC (4k para Android/HDR/DolbyVision)" - -msgctxt "#30061" -msgid "Update inside library" -msgstr "Actualizar dentro da biblioteca" - -msgctxt "#30062" -msgid "Enable/disable adult pin. Active:" -msgstr "Activar / desactivar o pin de controlo parental. Activo: " - -msgctxt "#30063" -msgid "new episodes added to library" -msgstr "novos episódios adicionados à biblioteca" - -msgctxt "#30064" -msgid "Export new episodes" -msgstr "Exportar novos episódios" - -msgctxt "#30065" -msgid "Auto-update" -msgstr "Actualizar automaticamente" - -msgctxt "#30066" -msgid "never" -msgstr "nunca" - -msgctxt "#30067" -msgid "daily" -msgstr "diáriamente" - -msgctxt "#30068" -msgid "every other day" -msgstr "dias alternados" - -msgctxt "#30069" -msgid "every 5 days" -msgstr "cada 5 dias" - -msgctxt "#30070" -msgid "weekly" -msgstr "semanalmente" - -msgctxt "#30071" -msgid "Time of Day" -msgstr "Hora do dia" - -msgctxt "#30072" -msgid "Only start after 5 minutes of idle" -msgstr "Começar apenas depois de 5 minutos de inactividade" - -msgctxt "#30073" -msgid "Check every X minutes if update scheduled" -msgstr "Verificar a cada X minutos se uma actualização está programada" - -msgctxt "#30074" -msgid "View for exported" -msgstr "Vista para exportados" - -msgctxt "#30213" -msgid "InputStream Helper settings..." -msgstr "Configurações do addon InputStream Helper..." diff --git a/resources/language/resource.language.ro_ro/strings.po b/resources/language/resource.language.ro_ro/strings.po new file mode 100644 index 000000000..620b0477c --- /dev/null +++ b/resources/language/resource.language.ro_ro/strings.po @@ -0,0 +1,1256 @@ +# Kodi Media Center language file +# Addon Name: Netflix +# Addon id: plugin.video.netflix +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait +msgid "" +msgstr "" +"Project-Id-Version: plugin.video.netflix\n" +"Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" +"POT-Creation-Date: 2020-04-13 19:54+0000\n" +"PO-Revision-Date: 2022-06-15 22:25+0000\n" +"Last-Translator: tmihai20 <>\n" +"Language-Team: Romanian (Romania)\n" +"Language: ro_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 "Netflix" +msgstr "Netflix" + +msgctxt "Addon Description" +msgid "Netflix VOD Services Add-on" +msgstr "Add-on pentru servicii video la cerere Netflix" + +msgctxt "Addon Disclaimer" +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "Utilizarea acestui add-on ar putea fi ilegală în țara în care locuiți - vă rugăm să verificați legile locale înainte de instalare." + +msgctxt "#30001" +msgid "Recommendations" +msgstr "Recomandări" + +msgctxt "#30002" +msgid "Enter PIN to watch restricted content" +msgstr "Introduceți cod pentru a vedea conținutul restricționat" + +#. Unused 30003 +msgctxt "#30004" +msgid "Password" +msgstr "Parolă" + +msgctxt "#30005" +msgid "E-mail" +msgstr "Adresă email" + +msgctxt "#30006" +msgid "Enter PIN to access this profile" +msgstr "Introduceți cod pentru a accesa profilul" + +msgctxt "#30007" +msgid "Parental Control PIN (4 digits)" +msgstr "Cod pentru control parental (4 cifre)" + +msgctxt "#30008" +msgid "Login failed" +msgstr "Conectare eșuată" + +msgctxt "#30009" +msgid "Please check your e-mail and password" +msgstr "Vă rugăm să verificați adresa email și parola" + +msgctxt "#30010" +msgid "Lists of all kinds" +msgstr "Listă cu toate tipurile" + +#. Unused 30011 +msgctxt "#30012" +msgid "Profiles options and parental control" +msgstr "Profiluri și control parental" + +msgctxt "#30013" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "În lista de profiluri deschide meniul contextual pentru profilul ales" + +msgctxt "#30014" +msgid "Account" +msgstr "Cont" + +msgctxt "#30015" +msgid "Other options" +msgstr "Alte opțiuni" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "Dacă vă deconectați, datele contului vor fi șterse, dar fișierele video exportate în librăria Kodi vor fi păstrate.[CR]Doriți să continuați?" + +msgctxt "#30017" +msgid "Logout" +msgstr "Deconectare" + +msgctxt "#30018" +msgid "Export to library" +msgstr "Exportă în bibliotecă" + +msgctxt "#30019" +msgid "Rate on Netflix" +msgstr "Evaluează pe Netflix" + +msgctxt "#30020" +msgid "Remove from 'My list'" +msgstr "Șterge din 'Lista mea'" + +msgctxt "#30021" +msgid "Add to 'My list'" +msgstr "Adaugă în 'Lista mea'" + +msgctxt "#30022" +msgid "(between 0 and 10)" +msgstr "(între 0 și 10)" + +msgctxt "#30023" +msgid "Expert" +msgstr "Avansat" + +msgctxt "#30024" +msgid "SSL verification" +msgstr "Verificare SSL" + +msgctxt "#30025" +msgid "Library" +msgstr "Bibliotecă" + +msgctxt "#30026" +msgid "Enable custom library folder" +msgstr "Activează director personalizat în bibliotecă" + +msgctxt "#30027" +msgid "Custom library path" +msgstr "Cale personalizată pentru bibliotecă" + +msgctxt "#30028" +msgid "Playback error" +msgstr "Eroare de redare" + +#. Unused 30029 +msgctxt "#30030" +msgid "Remove from library" +msgstr "Șterge din bibliotecă" + +msgctxt "#30031" +msgid "General" +msgstr "General" + +msgctxt "#30032" +msgid "Codecs" +msgstr "Codecuri" + +msgctxt "#30033" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "Activează codecul Dolby Digital Plus (numit Atmos în contul premium)" + +#. Unused 30034 +msgctxt "#30035" +msgid "InputStream Adaptive settings..." +msgstr "Setări pentru InputStream Adaptive..." + +#. Unused 30036 +msgctxt "#30037" +msgid "Skin viewtypes" +msgstr "Modalități de vizualizare pentru skin" + +msgctxt "#30038" +msgid "Enable custom viewtypes" +msgstr "Activează modalități de vizualizare personalizate" + +msgctxt "#30039" +msgid "View ID for folders" +msgstr "ID de vizualizare pentru directoare" + +msgctxt "#30040" +msgid "View ID for movies" +msgstr "ID vizualizare pentru filme" + +msgctxt "#30041" +msgid "View ID for shows" +msgstr "ID vizualizare pentru seriale" + +msgctxt "#30042" +msgid "View ID for seasons" +msgstr "ID vizualizare pentru sezoane" + +msgctxt "#30043" +msgid "View ID for episodes" +msgstr "ID vizualizare pentru episoade" + +msgctxt "#30044" +msgid "View ID for profiles" +msgstr "ID vizualizare pentru profile" + +msgctxt "#30045" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "Evaluare ștearsă|Evaluare negativă|Evaluare pozitivă" + +msgctxt "#30046" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "A apărut o problemă cu add-on-ul InputStream Adaptive sau cu biblioteca Widevine. Ar putea lipsi sau nu este activ, operațiunea va fi anulată." + +msgctxt "#30047" +msgid "Library update in progress" +msgstr "Actualizare bibliotecă în curs" + +msgctxt "#30048" +msgid "Exported" +msgstr "Exportat" + +msgctxt "#30049" +msgid "Library" +msgstr "Bibliotecă" + +msgctxt "#30050" +msgid "Enable Kodi library management" +msgstr "Activează administrarea bibliotecii Kodi" + +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30052" +msgid "{} Set for library playback" +msgstr "{} Ales pentru redarea bibliotecii" + +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30055" +msgid "{} Set at start-up" +msgstr "Configurat la pornire {}" + +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30057" +msgid "{} Remember PIN" +msgstr "PIN memorat {}" + +#. Unused 30058 +msgctxt "#30059" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "Atenție, urmează să actualizați multe titluri {}.[CR]Aceasta poate cauza probleme cu serviciul sau banare temporară a contului.[CR]Doriți să continuați?" + +msgctxt "#30060" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "Activează codecul HEVC (4K/HDR/DolbyVision în Android)" + +msgctxt "#30061" +msgid "Update inside library" +msgstr "Actualizează în interiorul bibliotecii" + +msgctxt "#30062" +msgid "Parental controls" +msgstr "Control parental" + +msgctxt "#30063" +msgid "An update is already in progress" +msgstr "O actualizare este deja în desfășurare" + +msgctxt "#30064" +msgid "Perform auto-update" +msgstr "Execută auto-actualizare" + +msgctxt "#30065" +msgid "Video library update" +msgstr "Actualizează biblioteca video" + +msgctxt "#30066" +msgid "Enable debug logging" +msgstr "Activează loguri pentru depanare" + +msgctxt "#30067" +msgid "daily" +msgstr "zilnic" + +msgctxt "#30068" +msgid "every other day" +msgstr "o dată la câteva zile" + +msgctxt "#30069" +msgid "every 5 days" +msgstr "la fiecare 5 zile" + +msgctxt "#30070" +msgid "weekly" +msgstr "săptămânal" + +msgctxt "#30071" +msgid "Time of Day" +msgstr "ora din zi" + +msgctxt "#30072" +msgid "Only start after 5 minutes of idle" +msgstr "Pornește doar după 5 minute de inactivitate" + +msgctxt "#30073" +msgid "Always show codec information during video playback" +msgstr "Arată mereu informații despre codec în timpul redării" + +msgctxt "#30074" +msgid "View for exported" +msgstr "Mod vizualizare pentru exporturi" + +msgctxt "#30075" +msgid "Ask to skip intro and recap" +msgstr "Întreabă dacă se dorește să se sară peste intro și recapitulare" + +msgctxt "#30076" +msgid "Skip intro" +msgstr "Derulează peste intro" + +msgctxt "#30077" +msgid "Skip recap" +msgstr "Derulează peste recapitulare" + +msgctxt "#30078" +msgid "Playback" +msgstr "Redare" + +msgctxt "#30079" +msgid "Skip automatically" +msgstr "Derulează în mod automat" + +msgctxt "#30080" +msgid "Pause when skipping" +msgstr "Pauză la derulare" + +msgctxt "#30081" +msgid "Force HDCP version on video streams" +msgstr "Forțează o anumită versiune HDCP în fluxurile video" + +msgctxt "#30082" +msgid "Remember audio / subtitle preferences" +msgstr "Memorează preferințele audio / pentru subtitrare" + +msgctxt "#30083" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "Forțează afișarea de titrări forțate doar când este configurată limba pentru audio" + +msgctxt "#30084" +msgid "Cache objects TTL (minutes)" +msgstr "Păstrează obiecte în cache pentru (minute)" + +msgctxt "#30085" +msgid "Cache metadata TTL (days)" +msgstr "Păstrează metadate în cache pentru (zile)" + +msgctxt "#30086" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "Păstrează 'Lista mea' în cache pentru (minute)" + +msgctxt "#30087" +msgid "Contains {} and more..." +msgstr "Conține {} și mai multe..." + +msgctxt "#30088" +msgid "Browse related content" +msgstr "Navighează conținut similar" + +msgctxt "#30089" +msgid "Browse subgenres" +msgstr "Navighează subgenuri" + +msgctxt "#30090" +msgid "Browse through related video lists and discover more content." +msgstr "Navighează liste cu conținut similar și descoperă conținut suplimentar." + +msgctxt "#30091" +msgid "Browse and manage contents that were exported to the Kodi library." +msgstr "Navighează și gestionează conținut care a fost exportat în biblioteca Kodi" + +msgctxt "#30092" +msgid "Search for content and easily find what you want to watch." +msgstr "Căută conținut și descoperă ușor ce dorești să vezi." + +msgctxt "#30093" +msgid "Browse content by genre and easily discover related content." +msgstr "Navighează conținut după gen și descoperiți ușor conținut similar." + +msgctxt "#30094" +msgid "Browse recommendations and pick up on something new you might like." +msgstr "Navighează recomandări și alegeți ceva nou de urmărit." + +msgctxt "#30095" +msgid "All TV shows" +msgstr "Toate serialele TV" + +msgctxt "#30096" +msgid "All Movies" +msgstr "Toate filmele" + +msgctxt "#30097" +msgid "Main Menu" +msgstr "meniu principal" + +msgctxt "#30098" +msgid "Enable HDR10" +msgstr "Activează HDR10" + +msgctxt "#30099" +msgid "Enable Dolby Vision" +msgstr "Activează Dolby Vision" + +msgctxt "#30100" +msgid "Results for '{}'" +msgstr "Rezultate pentru '{}'" + +msgctxt "#30101" +msgid "Netflix returned an unknown error." +msgstr "Netflix a întâlnit o eroare necunoscută." + +msgctxt "#30102" +msgid "Netflix returned the following error:" +msgstr "Netflix a dat de următoarea eroare:" + +msgctxt "#30103" +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "Detalii despre eroare au fost scrise în jurnalul Kodi. Dacă mesajul de eroare nu ajută la rezolvarea problemei sau dacă continuați să primiți această eroare, vă rugăm să raportați pe GitHub folosind instrucțiunile din Readme." + +msgctxt "#30104" +msgid "The add-on encountered the following error:" +msgstr "Add-on-ul a întâlnit următoarea eroare:" + +msgctxt "#30105" +msgid "Netflix Add-on Error" +msgstr "Eroare add-on Netflix" + +msgctxt "#30106" +msgid "Invalid PIN" +msgstr "Cod invalid" + +msgctxt "#30107" +msgid "PIN protection is off." +msgstr "Protecția cu cod este dezactivată." + +msgctxt "#30108" +msgid "All content is protected by PIN." +msgstr "Tot conținutul este protejat cu cod." + +msgctxt "#30109" +msgid "Login successful" +msgstr "Conectare reușită" + +#. Unused 30110 +msgctxt "#30111" +msgid "There is no contents" +msgstr "Nu există conținut" + +msgctxt "#30112" +msgid "You need to be logged in to use Netflix" +msgstr "Trebuie să fiți autentificat pentru a folosi Netflix" + +msgctxt "#30113" +msgid "Logout successful" +msgstr "Deconectare reușită" + +#. Unused 30114, 30115 +msgctxt "#30116" +msgid "Advanced Add-on Configuration" +msgstr "Configurare avansată pentru add-on" + +msgctxt "#30117" +msgid "Cache" +msgstr "Gestionare cache" + +#. Unused 30118, 30119 +msgctxt "#30120" +msgid "Cannot automatically remove a season from Kodi library, please do it manually" +msgstr "Nu se poate șterge automat un sezon din biblioteca Kodi, vă rugăm s-o faceți manual" + +msgctxt "#30121" +msgid "Perform full sync now" +msgstr "Efectuează acum o sincronizare completă" + +msgctxt "#30122" +msgid "Sync 'My list' to Kodi library" +msgstr "Sincronizează 'Lista mea' cu biblioteca Kodi" + +msgctxt "#30123" +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "Doriți să continuați?[CR]Sincronizarea completă va șterge toate titlurile exportate anterior.[CR]Dacă sunt multe tituri operațiunea ar putea dura considerabil." + +msgctxt "#30124" +msgid "Do you want to remove this item from the library?" +msgstr "Doriți să ștergeți acest element din bibliotecă?" + +msgctxt "#30125" +msgid "Delete library contents" +msgstr "Golește biblioteca" + +msgctxt "#30126" +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "Doriți cu adevărat să goliți biblioteca dumneavoastră?[CR]Toate elementele exportate vor fi șterse." + +msgctxt "#30127" +msgid "Awarded rating of {}" +msgstr "Ați evaluat cu {}" + +msgctxt "#30128" +msgid "Select a profile" +msgstr "Alegeți un profil" + +msgctxt "#30129" +msgid "Enable Up Next integration" +msgstr "Activează integrarea cu 'Up Next'" + +msgctxt "#30130" +msgid "Install Up Next add-on" +msgstr "Instalaează add-on-ul Up Next" + +msgctxt "#30131" +msgid "Check the logfile for detailed information" +msgstr "Verifică jurnalul de erori pentru informații detaliate" + +msgctxt "#30132" +msgid "Clear in-memory cache" +msgstr "Șterge memoria cache" + +msgctxt "#30133" +msgid "Clear in-memory and on-disk cache" +msgstr "Șterge cache din memorie și de pe disc" + +msgctxt "#30134" +msgid "Enable execution timing" +msgstr "Activează timpul de execuție" + +msgctxt "#30135" +msgid "Cache cleared" +msgstr "Memoria cache golită" + +msgctxt "#30136" +msgid "Waiting for service start-up..." +msgstr "Se așteaptă pornirea serviciului..." + +msgctxt "#30137" +msgid "Enable VP9 codec" +msgstr "Activează codecul VP9" + +msgctxt "#30138" +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "Serviciile de fundal ar putea să nu fie disponibile dacă tocmai ați pornit Kodi/add-on-ul. În acest caz, încercați din nou mai târziu." + +msgctxt "#30139" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "Activează IPC prin HTTP (folosiți-l atunci când apar mesaje legate de Addon Signals)" + +msgctxt "#30140" +msgid "Import existing library" +msgstr "Importă bibliotecă existentă" + +#. Unused 30141 to 30143 +msgctxt "#30144" +msgid "Disable WebVTT subtitle support" +msgstr "Dezactivează suportul WebVTT pentru subtitrări" + +msgctxt "#30145" +msgid "Recently added" +msgstr "Adăugate recent" + +msgctxt "#30146" +msgid "Discover the latest content added." +msgstr "Descoperă conținut adăugat recent." + +msgctxt "#30147" +msgid "Next page »" +msgstr "Pagina următoare »" + +msgctxt "#30148" +msgid "« Previous page" +msgstr "« Pagina anterioară" + +msgctxt "#30149" +msgid "Sort results by" +msgstr "Sortează rezultatele" + +msgctxt "#30150" +msgid "Alphabetical order (A-Z)" +msgstr "În ordine alfabetică (A-Z)" + +msgctxt "#30151" +msgid "Alphabetical order (Z-A)" +msgstr "În ordine inversă alfabetică (Z-A)" + +msgctxt "#30152" +msgid "Suggestions for you" +msgstr "Sugestii pentru dumneavoastră" + +msgctxt "#30153" +msgid "Year released" +msgstr "Anul lansării" + +msgctxt "#30154" +msgid "Netflix configuration wizard" +msgstr "Asistent configurare Netflix" + +msgctxt "#30155" +msgid "Do you have a Netflix Premium account?" +msgstr "Aveți un cont premium Netflix?" + +#. Unused 30156 +msgctxt "#30157" +msgid "The configuration has been completed" +msgstr "Configurarea s-a terminat" + +msgctxt "#30158" +msgid "Restore the add-on default configuration" +msgstr "Restabilește configurația implicită a addon-ului" + +msgctxt "#30159" +msgid "View ID for main menu" +msgstr "ID de vizualizare pentru meniul principal" + +msgctxt "#30160" +msgid "View ID for search" +msgstr "ID de vizualizare pentru căutare" + +msgctxt "#30161" +msgid "View ID for exported" +msgstr "ID de vizualizare pentru exporturi" + +msgctxt "#30162" +msgid "Last used" +msgstr "Folosit ultima dată" + +msgctxt "#30163" +msgid "Audio description" +msgstr "Descriere audio" + +msgctxt "#30164" +msgid "Browse contents with audio description." +msgstr "Navighează conținut cu descriere audio." + +msgctxt "#30165" +msgid "View ID for 'My list'" +msgstr "ID de vizualizare pentru 'Lista mea'" + +msgctxt "#30166" +msgid "Main menu items" +msgstr "Elemente meniu principal" + +msgctxt "#30167" +msgid "My list" +msgstr "Lista mea" + +msgctxt "#30168" +msgid "Continue watching" +msgstr "Continuă vizionarea" + +msgctxt "#30169" +msgid "Top picks" +msgstr "Alegeri de top" + +msgctxt "#30170" +msgid "New releases" +msgstr "Noutăți" + +msgctxt "#30171" +msgid "Trending now" +msgstr "Titlurile momentului" + +msgctxt "#30172" +msgid "Popular on Netflix" +msgstr "Popular pe Netflix" + +msgctxt "#30173" +msgid "Netflix Originals" +msgstr "Programe originale Netflix" + +msgctxt "#30174" +msgid "TV show genres" +msgstr "Genuri seriale TV" + +msgctxt "#30175" +msgid "Movie genres" +msgstr "Genuri filme" + +msgctxt "#30176" +msgid "Enable STRM resume workaround" +msgstr "Activează truc STRM" + +msgctxt "#30177" +msgid "Ask to resume the video" +msgstr "Cere ca redarea să fie reluată" + +msgctxt "#30178" +msgid "Open Up Next add-on settings" +msgstr "Deschide setările add-on-ului Up Next" + +msgctxt "#30179" +msgid "Show trailers and more" +msgstr "Arată trailere și altele" + +msgctxt "#30180" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "A apărut o problemă cu conectarea, este posibil ca contul să nu fi fost confirmat sau reactivat" + +msgctxt "#30181" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "Arată mereu subtitrări când limba preferată pentru audio nu este disponibilă" + +msgctxt "#30182" +msgid "Export NFO files" +msgstr "Exportă fișierele cu informații" + +msgctxt "#30183" +msgid "Do you want to export NFO files for the {}?" +msgstr "Doriți să exportați fișierele cu informații pentru {}?" + +msgctxt "#30184" +msgid "NFO Files" +msgstr "Fișiere cu informații" + +msgctxt "#30185" +msgid "Enable NFO files export" +msgstr "Activează exportul fișierelor cu informații" + +msgctxt "#30186" +msgid "For Movies" +msgstr "Pentru filme" + +msgctxt "#30187" +msgid "For TV show" +msgstr "Pentru seriale TV" + +msgctxt "#30188" +msgid "Ask" +msgstr "Întreabă" + +msgctxt "#30189" +msgid "movie" +msgstr "film" + +msgctxt "#30190" +msgid "TV show" +msgstr "serial TV" + +#. Unused 30191 +msgctxt "#30192" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]Aceste fișiere sunt utilizare de furnizorii de informații pentru a integra informații despre seriale TV și filme, folositoare pentru conținut bonus sau versiunea regizorului" + +msgctxt "#30193" +msgid "Include tv show NFO details" +msgstr "Include detalii despre serial TV din NFO" + +msgctxt "#30194" +msgid "Limit video stream resolution to" +msgstr "Limitează rezoluția fluxului video la" + +msgctxt "#30195" +msgid "Export new episodes" +msgstr "Exportă noile episoade" + +msgctxt "#30196" +msgid "Exclude from auto update" +msgstr "Exclude din actualizarea automată" + +msgctxt "#30197" +msgid "Include in auto update" +msgstr "Include în actualizarea automată" + +msgctxt "#30198" +msgid "Exporting new episodes" +msgstr "Se exportă noile episoade" + +msgctxt "#30199" +msgid "Shared library (Kodi MySQL server is required)" +msgstr "Bibliotecă partajată (este nevoie de server MySQL Kodi)" + +msgctxt "#30200" +msgid "Use shared library MySQL-database" +msgstr "Folosește baza de date MySQL pentru biblioteca partajată" + +msgctxt "#30201" +msgid "Username" +msgstr "Nume utilizator" + +msgctxt "#30202" +msgid "Connection to the MySQL-database was successful" +msgstr "Conectarea la baza de date MySQL a reușit" + +msgctxt "#30203" +msgid "Host IP" +msgstr "IP gazdă" + +msgctxt "#30204" +msgid "Port" +msgstr "Port" + +msgctxt "#30205" +msgid "Test database connection" +msgstr "Testează conexiunea cu baza de date" + +msgctxt "#30206" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" +msgstr "EROARE: Baza de date MySQL n-a putut fi contactată - va fi utilizată baza de date locală" + +msgctxt "#30207" +msgid "Set this device as main auto-updates manager" +msgstr "Configurează acest dispozitiv pentru gestiunea actualizărilor automate" + +msgctxt "#30208" +msgid "Check if this device is the main auto-update manager" +msgstr "Verifică dacă acest dispozitiv gestionează actualizările automate" + +msgctxt "#30209" +msgid "This device now handles the auto-updates of the shared library" +msgstr "Acest dispozitiv gestionează acum actualizările automate ale bibliotecii partajate" + +msgctxt "#30210" +msgid "This device handles the auto-updates of the shared library" +msgstr "Acest dispozitiv gestionează actualizările automate ale bibliotecii partajate" + +msgctxt "#30211" +msgid "Another device handles the auto-updates of the shared library" +msgstr "Alt dispozitiv gestionează actualizările automate ale bibliotecii partajate" + +msgctxt "#30212" +msgid "There is no device that handles the auto-updates of the shared library" +msgstr "Nu există dispozitiv care să gestioneze actualizările automate ale bibliotecii partajate" + +msgctxt "#30213" +msgid "InputStream Helper settings..." +msgstr "Setări add-on InputStream Helper..." + +msgctxt "#30214" +msgid "Force refresh" +msgstr "Forțează actualizarea" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "Culoarea titlurilor incluse în \"Lista mea\"" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "Culoare a titlurilor marcate ca \"Memento\"" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "Dezactivează notificarea pentru completarea sincronizării" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "Biblioteca Kodi a fost actualizată" + +msgctxt "#30221" +msgid "Owner account" +msgstr "Cont principal" + +msgctxt "#30222" +msgid "Kid account" +msgstr "Cont copil" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "Mod auto actualizare" + +msgctxt "#30225" +msgid "Manual" +msgstr "Manual" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "Programat" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "Sincronizează biblioteca Kodi cu 'Lista mea'" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "Alege un profil pentru sincronizare" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "La pornirea Kodi" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "Verifică acum dacă există actualizări" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "Doriți să verificați acum dacă există actualizări?" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "Clasificarea maturității pentru profilul {}" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "Arată doar titluri cu clasificarea [B]{}[/B]." + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "Sincronizează statutul conținutului urmărit cu Netflix" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "Schimbă statutul conținutului urmărit (doar local)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "Marchează ca văzut|Marchează ca nevăzut|Statutul conținutul restabilit din Netflix" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Notificări Up Next (redă următorul video)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "Culoare pentru informațiile suplimentare legate de intrigă" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "Serviciile nu pot fi pornite în fundal din cauza acestei probleme:[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Open Connect CDN (indicele mai mic este mai bun)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "Top 10" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "Selectează primul episod neurmărit" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "Se șterg fișiere exportate în bibliotecă" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "Există {} titluri care nu au putut fi importate.[CR]Doriți să ștergeți aceste fișiere?" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "Rezultate pe pagină (în caz de eroare micșorați numărul de rezultate)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "Doriți să ștergeți \"{}\" din lista de vizualizare?" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "Include biblioteca Kodi (doar trimite date)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "Alegeți metoda de conectare[CR](dacă conectarea cu adresă email/parolă returnează mereu \"parolă incorectă\", atunci folosiți cheia de autentificare, instrucțiunile se găsesc în ReadMe pe GitHub)" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "Adresă email/parolă" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "Cheie de autentificare" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "Fișierul selectat este incompatibil sau este corupt" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "Fișierul cu cheia de autentificare a expirat" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "Introduceți cod" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "Sortează istoricul după" + +msgctxt "#30400" +msgid "Search" +msgstr "Căutare" + +msgctxt "#30401" +msgid "Type of search" +msgstr "Tip de căutare" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "Caută titluri, oameni, genuri" + +msgctxt "#30403" +msgid "New search..." +msgstr "Căutare nouă..." + +msgctxt "#30404" +msgid "Clear search history" +msgstr "Șterge istoricul de căutare" + +msgctxt "#30405" +msgid "Select the language" +msgstr "Alege limba" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "Doriți să ștergeți întreg conținutul?" + +msgctxt "#30407" +msgid "No matches found" +msgstr "Nu s-a găsit nimic" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "După termen" + +msgctxt "#30411" +msgid "By audio language" +msgstr "După limba vorbită" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "După limba subtitrării" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "După gen/subgen" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "Preferă limba pentru audio/subtitări cu codul de țară (dacă este disponibil)" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "Alege în mod implicit sunet stereo" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "Setări ESN / Widevine" + +msgctxt "#30601" +msgid "ESN:" +msgstr "ESN:" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "Schimbă ESN" + +msgctxt "#30603" +msgid "Save system info" +msgstr "Salvează informații despre sistem" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "Forțează nivel de securitate Widevine" + +msgctxt "#30605" +msgid "Force {}" +msgstr "Forțează {}" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "ESN configurat cu succes[CR]Încearcă să redai video" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "Acest ESN nu poate fi utilizat[CR]Încearcă să schimbi sau să resetezi ESN.[CR][CR]Detalii eroare:[CR]{}" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "Format greșit pentru ESN" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "Doriți să resetați toate setările?" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "Folosește încriptare de sistem pentru identificatori" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "[B]Dacă este dezactivat va cauza o breșă de securitate[/B]. Dacă sistemul dumneavoastră de operare nu oferă suport, vi se va cere să vă logați de fiecare dată când reporniți dispozitivul, dezactivați opțiunea [B]doar[/B] în acest caz. Dacă schimbați această setare vi se va cere să vă logați din nou la următoarea repornire." + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "Versiune manifest MSL" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "Acest titlu va fi disponibil începând cu:[CR]{}" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "Doriți să redați trailerul promoțional?" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "{} Adăugă un memento" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "Nou și popular" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "Marchează serialele TV începute ca văzute" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "Vă rugăm să luați aminte că activând această setare în filtrele din setările temei, elementele marcate ca văzute ar putea să nu fie vizibile." + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "Ignorați tipul de selecției a fluxului" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "Ignorați configurarea tipului de selecție a fluxului a addon-ului InputStream Adaptive, pentru a configura modul în care va fi aleasă calitatea fluxurilor audio și video în timpul redării." + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "Calitate adaptivă" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "Calitate fixă pentru video" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "Întreabă de calitatea video" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "Vă rugăm să luați aminte că activarea acestei setări va forța eliminarea fluxurilor care nu se încadrează în intervalul stabilit și aceasta poate afecta funcționarea InputStream Adaptive. Dacă este posibil încercați mai întâi limitarea calității din setările InputStream Adaptive." + +msgctxt "#30721" +msgid "Size" +msgstr "Dimensiune" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "Micșorează barele negre prin aplicarea unui efect de apropiere. [ Fix ] Setează procentajul din ecran pe care îl pot ocupa benzile negre, care va fi același pentru toate video-urile. [ Relativ ] Setează procentajul de micșorare a benzilor negre, care va depinde de dimensiunea fiecărui video. [ Reset ] Resetează efectul de apropiere aplicat anterior." + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "Această caracteristică folosește efectul de apropiere al Kodi care este stocat permanent în setarea video \"Mod vizualizare\", dacă dezactivați această caracteristică în viitor efectul de apropiere va fi păstrat, pentru a reseta efectul de apropiere schimbați setarea pe \"Reset\"." + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "Activează codecul AV1" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "Adaugă directoarele Netflix la sursele bibliotecii Kodi" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "Directoarele \"Filme-Netflix\" și \"Seriale-Netflix\" au fost adăugate la sursele Kodi, reporniți Kodi pentru a le vedea în meniurile Kodi [Seriale] / [Filme]. Editați directoarele pentru a configura corespunzător tipul de conținut și furnizorii de informații." + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "[Manual] Actualizările trebuie să fie făcute manual folosing meniul de context \"Exportă noile episoade\" pentru fiecare serial TV, [Programat] puteți configura actualizări automate în momente programate, [La pornirea Kodi] actualizările automate se fac la pornirea Kodi și doar o dată pe zi." + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "Sincronizează biblioteca cu 'Lista mea'. Dacă lista a fost modificată din exterior de către o aplicație mobilă sau un site web, actualizarea va fi amânată până în momentul programat." + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "Dacă este activat, adaugă informații utile pentru episoade sau filme atunci când furnizorii de informații Kodi nu au date. Se va adăuga și durata videourilor." + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "Activează doar dacă este folosit furnizorul de informații \"Informații locale\" sau dacă anumite titluri nu sunt afișate în biblioteca Kodi deși ele au fost adăugate." + +msgctxt "#30734" +msgid "Support" +msgstr "Asistență" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "Despre addon-ul Netflix" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "Generează automat noi ESN-uri ( soluție alternativă pentru limitarea la 540p)" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "AVERTISMENT: Nu folosiți ESN-ul original al dispozitivului și aplicației oficiale." + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "Activează decalajul audio" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "Decalaj audio" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "Setează decalajul audio prin suprascrierea setării din Kodi" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "Activează profilul 2 VP9 (adâncime de culoare 10/12 bit)" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "Doriți să activați suportul pentru HDR10?" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "Doriți să activați suportul pentru HDR Dolby Vision?" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "Addon-ul configurează deja versiunea potrivită HDCP, dar la cerere puteți forța cererea de fluxuri video pentru o versiune HDCP diferită." + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "Dacă dispozitivul dumneavoastră nu suportă acest codec, ați putea vedea ecran negru, nici un fel de imagine sau imagine coruptă. Dacă este așa dezactivați codecul. " + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "Mod de micșorare a benzilor negre" + +msgctxt "#30747" +msgid "Fixed" +msgstr "Fix" + +msgctxt "#30748" +msgid "Relative" +msgstr "Relativ" + +msgctxt "#30749" +msgid "Reset" +msgstr "Reset" diff --git a/resources/language/resource.language.sk_sk/strings.po b/resources/language/resource.language.sk_sk/strings.po deleted file mode 100644 index 1cd5b1907..000000000 --- a/resources/language/resource.language.sk_sk/strings.po +++ /dev/null @@ -1,273 +0,0 @@ -# Kodi Media Center language file -# Addon Name: Netflix -# Addon id: plugin.video.netflix -# Addon Provider: libdev + jojo + asciidisco + caphm + CastagnaIT -msgid "" -msgstr "" -"Project-Id-Version: plugin.video.netflix\n" -"Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" -"POT-Creation-Date: 2017-07-24 16:15+0000\n" -"PO-Revision-Date: 2019-07-08 22:15+0000\n" -"Last-Translator: Matej Moško \n" -"Language-Team: Slovak\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=2; plural=(n != 1);\n" - -msgctxt "Addon Summary" -msgid "Netflix" -msgstr "Netflix" - -msgctxt "Addon Description" -msgid "Netflix VOD Services Addon" -msgstr "Doplnok na prehrávanie obsahu služby Netflix" - -msgctxt "Addon Disclaimer" -msgid "Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing." -msgstr "Niektoré súčasti tohto doplnku môžu byť v krajine vášho pobytu nelegálne. Pred inštaláciou prosím overte, či neporušujete miestne zákony." - -msgctxt "#30001" -msgid "Recommendations" -msgstr "Odporúčania" - -msgctxt "#30002" -msgid "Adult Pin" -msgstr "Rodičovský zámok" - -msgctxt "#30003" -msgid "Search term" -msgstr "Vyhľadávací výraz" - -msgctxt "#30004" -msgid "Password" -msgstr "Heslo" - -msgctxt "#30005" -msgid "E-mail" -msgstr "E-mail" - -msgctxt "#30006" -msgid "Adult verification failed" -msgstr "Zlyhalo odomknutie rodičovského zámku" - -msgctxt "#30007" -msgid "Please Check your adult pin" -msgstr "Prosím skontrolujte heslo k rodičovskému zámku" - -msgctxt "#30008" -msgid "Login failed" -msgstr "Prihlásenie sa nepodarilo" - -msgctxt "#30009" -msgid "Please Check your credentials" -msgstr "Prosím skontrolujte prihlasovacie údaje" - -msgctxt "#30010" -msgid "Lists of all kinds" -msgstr "" - -msgctxt "#30011" -msgid "Search" -msgstr "Vyhľadávanie" - -msgctxt "#30012" -msgid "No seasons available" -msgstr "Nenašli sa žiadne série" - -msgctxt "#30013" -msgid "No matches found" -msgstr "Žiadna zhoda vo vyhľadávaní" - -msgctxt "#30014" -msgid "Account" -msgstr "Účet" - -msgctxt "#30017" -msgid "Logout" -msgstr "Odhlásiť sa" - -msgctxt "#30018" -msgid "Export to library" -msgstr "Exportovať do knižnice" - -msgctxt "#30019" -msgid "Rate on Netflix" -msgstr "Ohodnotiť na Netflixe" - -msgctxt "#30020" -msgid "Remove from 'My list'" -msgstr "Odstrániť z 'Môjho zoznamu'" - -msgctxt "#30021" -msgid "Add to 'My list'" -msgstr "Pridať do 'Môjho zoznamu'" - -msgctxt "#30022" -msgid "(between 0 & 10)" -msgstr "(od 0 do 10)" - -msgctxt "#30023" -msgid "Expert" -msgstr "Rozšírené" - -msgctxt "#30024" -msgid "SSL verification" -msgstr "SSL overovanie" - -msgctxt "#30025" -msgid "Library" -msgstr "Knižnica" - -msgctxt "#30026" -msgid "Enable custom library folder" -msgstr "Povoliť vlastný priečinok knižnice" - -msgctxt "#30027" -msgid "Custom library path" -msgstr "Cesta k vlastnej knižnici" - -msgctxt "#30028" -msgid "Playback error" -msgstr "Chyba prehrávania" - -msgctxt "#30029" -msgid "Missing InputStream addon" -msgstr "Nepodarilo sa nájsť doplnok InputStream" - -msgctxt "#30030" -msgid "Remove from library" -msgstr "Odstrániť z knižnice" - -msgctxt "#30031" -msgid "Change library title" -msgstr "Zmeniť názov v knižnici" - -msgctxt "#30032" -msgid "Tracking" -msgstr "Sledovanie" - -msgctxt "#30033" -msgid "Enable Dolby Digital Plus (DDPlus HQ and Atmos on Premium)" -msgstr "Povoliť Dolby Digital Plus (DDPlus HQ a Atmos on Premium)" - -msgctxt "#30034" -msgid "ESN (set automatically, can be changed manually)" -msgstr "ESN (nastavené automaticky, môžete to ručne zmeniť)" - -msgctxt "#30035" -msgid "InputStream Adaptive settings..." -msgstr "Nastavenia doplnku InputStream..." - -msgctxt "#30036" -msgid "Always use the original title on export" -msgstr "Pri exporte vždy použiť orignálny názov" - -msgctxt "#30037" -msgid "Views" -msgstr "Zobrazenia" - -msgctxt "#30038" -msgid "Enable custom views" -msgstr "Povoliť vlastné zobrazenia" - -msgctxt "#30039" -msgid "View for folders" -msgstr "Zobrazenie pre priečinky" - -msgctxt "#30040" -msgid "View for movies" -msgstr "Zobrazenie pre filmy" - -msgctxt "#30041" -msgid "View for shows" -msgstr "Zobrazenie pre seriály" - -msgctxt "#30042" -msgid "View for seasons" -msgstr "Zobrazenie pre série" - -msgctxt "#30043" -msgid "View for episodes" -msgstr "Zobrazenie pre epizódy" - -msgctxt "#30044" -msgid "View for profiles" -msgstr "Zobrazenie pre profily" - -msgctxt "#30045" -msgid "[COLOR cyan][B]-- NEXT PAGE --[/B][/COLOR]" -msgstr "[COLOR cyan][B]-- ĎALŠIA STRANA --[/B][/COLOR]" - -msgctxt "#30046" -msgid "InputStream addon is not enabled" -msgstr "InputStream je vypnutý" - -msgctxt "#30047" -msgid "Finally remove?" -msgstr "Naozaj odstrániť?" - -msgctxt "#30048" -msgid "Exported" -msgstr "Exportované" - -msgctxt "#30049" -msgid "Update DB" -msgstr "Aktualizovať DB" - -msgctxt "#30050" -msgid "Update successful" -msgstr "Aktualizácia úšpešná" - -msgctxt "#30051" -msgid "Request Error" -msgstr "Chyba požiadavky" - -msgctxt "#30052" -msgid "Unable to complete the request at this time" -msgstr "Tentokrát sa požiadavku nepodarilo dokončiť" - -msgctxt "#30053" -msgid "Auto Login" -msgstr "Automatické prihlasovanie" - -msgctxt "#30054" -msgid "Enable Auto Login" -msgstr "Povoliť automatické prihlasovanie" - -msgctxt "#30055" -msgid "Profile" -msgstr "Profil" - -msgctxt "#30056" -msgid "ID" -msgstr "ID" - -msgctxt "#30057" -msgid "Select profile in profile listing -> context menu" -msgstr "Profil si vyberiete v zozname profilov -> kontextové menu" - -msgctxt "#30058" -msgid "Auto Login enabled!" -msgstr "Automatické prihlasovanie zapnuté!" - -msgctxt "#30059" -msgid "Switch accounts" -msgstr "Prepnúť účty" - -msgctxt "#30060" -msgid "Enable HEVC profiles (4k for Android/HDR/DolbyVision)" -msgstr "Povoliť HEVC profily (4k pre Android/HDR/DolbyVision)" - -msgctxt "#30061" -msgid "Update inside library" -msgstr "Aktualizovať záznam v knižnici" - -msgctxt "#30062" -msgid "Enable/disable adult pin. Active:" -msgstr "Povoliť/zakázať rodičovský zámok. Aktívny:" - -msgctxt "#30213" -msgid "InputStream Helper settings..." -msgstr "Nastavenia doplnku InputStream Helper..." diff --git a/resources/language/resource.language.sv_se/strings.po b/resources/language/resource.language.sv_se/strings.po new file mode 100644 index 000000000..5f30277ef --- /dev/null +++ b/resources/language/resource.language.sv_se/strings.po @@ -0,0 +1,1256 @@ +# Kodi Media Center language file +# Addon Name: Netflix +# Addon id: plugin.video.netflix +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait +msgid "" +msgstr "" +"Project-Id-Version: plugin.video.netflix\n" +"Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" +"POT-Creation-Date: 2020-01-07 00:00+0000\n" +"PO-Revision-Date: 2020-07-26 00:00+0000\n" +"Last-Translator: Sopor\n" +"Language-Team: Swedish (Sweden)\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" + +msgctxt "Addon Summary" +msgid "Netflix" +msgstr "Netflix" + +msgctxt "Addon Description" +msgid "Netflix VOD Services Add-on" +msgstr "Netflix VOD Service-tillägg" + +msgctxt "Addon Disclaimer" +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "Användandet av detta tillägg kanske inte är lagligt i ditt hemland - kontrollera med dina lokala lagar innan du installerar." + +msgctxt "#30001" +msgid "Recommendations" +msgstr "Rekommendationer" + +msgctxt "#30002" +msgid "Enter PIN to watch restricted content" +msgstr "Ange PIN-kod för att titta på begränsat innehåll" + +#. Unused 30003 +msgctxt "#30004" +msgid "Password" +msgstr "Lösenord" + +msgctxt "#30005" +msgid "E-mail" +msgstr "E-post" + +msgctxt "#30006" +msgid "Enter PIN to access this profile" +msgstr "Ange PIN-kod för att få tillgång till den här profilen" + +msgctxt "#30007" +msgid "Parental Control PIN (4 digits)" +msgstr "PIN-kod för föräldrakontroll (4 siffror)" + +msgctxt "#30008" +msgid "Login failed" +msgstr "Inloggningen misslyckades" + +msgctxt "#30009" +msgid "Please check your e-mail and password" +msgstr "Kontrollera din e-postadress och lösenord" + +msgctxt "#30010" +msgid "Lists of all kinds" +msgstr "Listor av alla slag" + +#. Unused 30011 +msgctxt "#30012" +msgid "Profiles options and parental control" +msgstr "Profilalternativ och föräldrakontroll" + +msgctxt "#30013" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "I profillistan öppnar du innehållsmenyn i den valda profilen" + +msgctxt "#30014" +msgid "Account" +msgstr "Konto" + +msgctxt "#30015" +msgid "Other options" +msgstr "Andra alternativ" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "Om du loggar ut kommer all kontodata att tas bort, men de exporterade videofilerna i Kodi-biblioteket kommer att bevaras.[CR]Vill du fortsätta?" + +msgctxt "#30017" +msgid "Logout" +msgstr "Logga ut" + +msgctxt "#30018" +msgid "Export to library" +msgstr "Exportera till bibliotek" + +msgctxt "#30019" +msgid "Rate on Netflix" +msgstr "Betygsätt på Netflix" + +msgctxt "#30020" +msgid "Remove from 'My list'" +msgstr "Ta bort från 'Min lista'" + +msgctxt "#30021" +msgid "Add to 'My list'" +msgstr "Lägg till i 'Min lista'" + +msgctxt "#30022" +msgid "(between 0 and 10)" +msgstr "(mellan 0 och 10)" + +msgctxt "#30023" +msgid "Expert" +msgstr "Expert" + +msgctxt "#30024" +msgid "SSL verification" +msgstr "SSL-verifiering" + +msgctxt "#30025" +msgid "Library" +msgstr "Bibliotek" + +msgctxt "#30026" +msgid "Enable custom library folder" +msgstr "Aktivera anpassad biblioteksmapp" + +msgctxt "#30027" +msgid "Custom library path" +msgstr "Anpassad bibliotekssökväg" + +msgctxt "#30028" +msgid "Playback error" +msgstr "Uppspelningsfel" + +#. Unused 30029 +msgctxt "#30030" +msgid "Remove from library" +msgstr "Ta bort från biblioteket" + +msgctxt "#30031" +msgid "General" +msgstr "Allmänt" + +msgctxt "#30032" +msgid "Codecs" +msgstr "" + +msgctxt "#30033" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "" + +#. Unused 30034 +msgctxt "#30035" +msgid "InputStream Adaptive settings..." +msgstr "Inställningar för InputStream Adaptive..." + +#. Unused 30036 +msgctxt "#30037" +msgid "Skin viewtypes" +msgstr "Skalvytyper" + +msgctxt "#30038" +msgid "Enable custom viewtypes" +msgstr "Aktivera anpassade vytyper" + +msgctxt "#30039" +msgid "View ID for folders" +msgstr "Visa ID för mappar" + +msgctxt "#30040" +msgid "View ID for movies" +msgstr "Visa ID för filmer" + +msgctxt "#30041" +msgid "View ID for shows" +msgstr "Visa ID för serier" + +msgctxt "#30042" +msgid "View ID for seasons" +msgstr "Visa ID för säsonger" + +msgctxt "#30043" +msgid "View ID for episodes" +msgstr "Visa ID för avsnitt" + +msgctxt "#30044" +msgid "View ID for profiles" +msgstr "Visa ID för profiler" + +msgctxt "#30045" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "Betyget har tagits bort|Du gav betyget tumme ner|Du gav betyget tumme upp" + +msgctxt "#30046" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "Det finns ett problem med tillägget InputStream Adaptive eller Widevine-biblioteket. Kan saknas eller har inte aktiverats, åtgärden kommer att avbrytas." + +msgctxt "#30047" +msgid "Library update in progress" +msgstr "Biblioteksuppdatering pågår" + +msgctxt "#30048" +msgid "Exported" +msgstr "Exporterad" + +msgctxt "#30049" +msgid "Library" +msgstr "Bibliotek" + +msgctxt "#30050" +msgid "Enable Kodi library management" +msgstr "Aktivera Kodi-bibliotekshantering" + +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30052" +msgid "{} Set for library playback" +msgstr "{} Ställ in för biblioteksuppspelning" + +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30055" +msgid "{} Set at start-up" +msgstr "{} Ställ in vid uppstart" + +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30057" +msgid "{} Remember PIN" +msgstr "{} Kom ihåg PIN-koden" + +#. Unused 30058 +msgctxt "#30059" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "Var försiktig, du håller på att uppdatera många titlar {}.[CR]Detta kan orsaka tjänsteproblem eller tillfälligt förbud att använda kontot.[CR]Vill du fortsätta?" + +msgctxt "#30060" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "" + +msgctxt "#30061" +msgid "Update inside library" +msgstr "Uppdatera inuti biblioteket" + +msgctxt "#30062" +msgid "Parental controls" +msgstr "Föräldrakontroll" + +msgctxt "#30063" +msgid "An update is already in progress" +msgstr "En uppdatering pågår redan" + +msgctxt "#30064" +msgid "Perform auto-update" +msgstr "Utför automatisk uppdatering" + +msgctxt "#30065" +msgid "Video library update" +msgstr "Uppdatering av videobibliotek" + +msgctxt "#30066" +msgid "Enable debug logging" +msgstr "Aktivera felsökningsloggning" + +msgctxt "#30067" +msgid "daily" +msgstr "dagligen" + +msgctxt "#30068" +msgid "every other day" +msgstr "varannan dag" + +msgctxt "#30069" +msgid "every 5 days" +msgstr "var 5:e dag" + +msgctxt "#30070" +msgid "weekly" +msgstr "veckovis" + +msgctxt "#30071" +msgid "Time of Day" +msgstr "Tidpunkt på dygnet" + +msgctxt "#30072" +msgid "Only start after 5 minutes of idle" +msgstr "Starta först efter 5 minuters inaktivitet" + +msgctxt "#30073" +msgid "Always show codec information during video playback" +msgstr "Visa alltid kodekinformationen under videouppspelningen" + +msgctxt "#30074" +msgid "View for exported" +msgstr "Vy för exporterad" + +msgctxt "#30075" +msgid "Ask to skip intro and recap" +msgstr "Fråga om att hoppa över intro och sammanfattning" + +msgctxt "#30076" +msgid "Skip intro" +msgstr "Hoppa över introt" + +msgctxt "#30077" +msgid "Skip recap" +msgstr "Hoppa över sammanfattning" + +msgctxt "#30078" +msgid "Playback" +msgstr "Uppspelning" + +msgctxt "#30079" +msgid "Skip automatically" +msgstr "Hoppa över automatiskt" + +msgctxt "#30080" +msgid "Pause when skipping" +msgstr "Pausa vid överhoppning" + +msgctxt "#30081" +msgid "Force HDCP version on video streams" +msgstr "" + +msgctxt "#30082" +msgid "Remember audio / subtitle preferences" +msgstr "Kom ihåg inställningarna för ljud och undertexter" + +msgctxt "#30083" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "Tvinga fram visning av påtvingad undertext endast med ljudspråket inställt" + +msgctxt "#30084" +msgid "Cache objects TTL (minutes)" +msgstr "Cache objekt TTL (minuter)" + +msgctxt "#30085" +msgid "Cache metadata TTL (days)" +msgstr "Cache metadata TTL (dagar)" + +msgctxt "#30086" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "Cache objekt 'Min lista' TTL (minuter)" + +msgctxt "#30087" +msgid "Contains {} and more..." +msgstr "Innehåller {} och mer..." + +msgctxt "#30088" +msgid "Browse related content" +msgstr "Bläddra i relaterat innehåll" + +msgctxt "#30089" +msgid "Browse subgenres" +msgstr "Bläddra i undergenrerna" + +msgctxt "#30090" +msgid "Browse through related video lists and discover more content." +msgstr "Bläddra igenom relaterade videolistor och upptäck mer innehåll." + +msgctxt "#30091" +msgid "Browse and manage contents that were exported to the Kodi library." +msgstr "Bläddra och hantera innehåll som exporterades till Kodi-biblioteket." + +msgctxt "#30092" +msgid "Search for content and easily find what you want to watch." +msgstr "Sök efter innehåll och hitta enkelt vad du vill titta på." + +msgctxt "#30093" +msgid "Browse content by genre and easily discover related content." +msgstr "Bläddra bland innehåll efter genre och upptäck enkelt relaterat innehåll." + +msgctxt "#30094" +msgid "Browse recommendations and pick up on something new you might like." +msgstr "Bläddra bland rekommendationer och hitta något nytt som du kanske gillar." + +msgctxt "#30095" +msgid "All TV shows" +msgstr "Alla serier" + +msgctxt "#30096" +msgid "All Movies" +msgstr "Alla filmer" + +msgctxt "#30097" +msgid "Main Menu" +msgstr "Huvudmeny" + +msgctxt "#30098" +msgid "Enable HDR10" +msgstr "" + +msgctxt "#30099" +msgid "Enable Dolby Vision" +msgstr "" + +msgctxt "#30100" +msgid "Results for '{}'" +msgstr "Resultat för '{}'" + +msgctxt "#30101" +msgid "Netflix returned an unknown error." +msgstr "Netflix returnerade ett okänt fel." + +msgctxt "#30102" +msgid "Netflix returned the following error:" +msgstr "Netflix returnerade följande fel:" + +msgctxt "#30103" +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "Felinformationen har skrivits till Kodi-loggfilen. Om felmeddelandet inte ger vägledning om hur du löser problemet eller om du fortsätter att få det här felet, bör du rapportera det på GitHub genom att följa Readme-instruktionerna där." + +msgctxt "#30104" +msgid "The add-on encountered the following error:" +msgstr "Tillägget stötte på följande fel:" + +msgctxt "#30105" +msgid "Netflix Add-on Error" +msgstr "Netflix tilläggsfel" + +msgctxt "#30106" +msgid "Invalid PIN" +msgstr "Ogiltig PIN-kod" + +msgctxt "#30107" +msgid "PIN protection is off." +msgstr "PIN-skyddet är avstängt." + +msgctxt "#30108" +msgid "All content is protected by PIN." +msgstr "Allt innehåll är skyddat med PIN-kod." + +msgctxt "#30109" +msgid "Login successful" +msgstr "Inloggad" + +#. Unused 30110 +msgctxt "#30111" +msgid "There is no contents" +msgstr "Det finns inget innehåll" + +msgctxt "#30112" +msgid "You need to be logged in to use Netflix" +msgstr "Du måste vara inloggad för att kunna använda Netflix" + +msgctxt "#30113" +msgid "Logout successful" +msgstr "Utloggad" + +#. Unused 30114, 30115 +msgctxt "#30116" +msgid "Advanced Add-on Configuration" +msgstr "Avancerad tilläggskonfiguration" + +msgctxt "#30117" +msgid "Cache" +msgstr "Cache" + +#. Unused 30118, 30119 +msgctxt "#30120" +msgid "Cannot automatically remove a season from Kodi library, please do it manually" +msgstr "Kan inte ta bort en säsong automatiskt från Kodi-biblioteket, du måste göra det manuellt" + +msgctxt "#30121" +msgid "Perform full sync now" +msgstr "Utför full synkronisering nu" + +msgctxt "#30122" +msgid "Sync 'My list' to Kodi library" +msgstr "Synkronisera 'Min lista' till Kodi-biblioteket" + +msgctxt "#30123" +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "Vill du fortsätta?[CR]Fullständig synkronisering tar bort alla tidigare exporterade titlar.[CR]Om det finns många titlar kan denna operation ta lite tid." + +msgctxt "#30124" +msgid "Do you want to remove this item from the library?" +msgstr "Vill du ta bort det här objektet från biblioteket?" + +msgctxt "#30125" +msgid "Delete library contents" +msgstr "Ta bort biblioteksinnehållet" + +msgctxt "#30126" +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "Vill du verkligen rensa ditt bibliotek?[CR]Alla exporterade objekt kommer att tas bort." + +msgctxt "#30127" +msgid "Awarded rating of {}" +msgstr "Tilldelat betyg på {}" + +msgctxt "#30128" +msgid "Select a profile" +msgstr "Välj en profil" + +msgctxt "#30129" +msgid "Enable Up Next integration" +msgstr "Aktivera Up Next-integration" + +msgctxt "#30130" +msgid "Install Up Next add-on" +msgstr "Installera tillägget Up Next" + +msgctxt "#30131" +msgid "Check the logfile for detailed information" +msgstr "Kontrollera loggfilen för detaljerad information" + +msgctxt "#30132" +msgid "Clear in-memory cache" +msgstr "Rensa cachen i minnet" + +msgctxt "#30133" +msgid "Clear in-memory and on-disk cache" +msgstr "Rensa cachen i minnet och på hårddisken" + +msgctxt "#30134" +msgid "Enable execution timing" +msgstr "Aktivera körtidpunkt" + +msgctxt "#30135" +msgid "Cache cleared" +msgstr "Cachen rensad" + +msgctxt "#30136" +msgid "Waiting for service start-up..." +msgstr "Väntar på att tjänsten ska startas..." + +msgctxt "#30137" +msgid "Enable VP9 codec" +msgstr "" + +msgctxt "#30138" +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "Bakgrundstjänsterna kanske ännu inte är tillgängliga om du just startade Kodi/tillägget. Om så är fallet, försök igen om en stund." + +msgctxt "#30139" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "Aktivera IPC över HTTP (använd när meddelandet 'Addon Signals timeout' visas)" + +msgctxt "#30140" +msgid "Import existing library" +msgstr "Importera befintligt bibliotek" + +#. Unused 30141 to 30143 +msgctxt "#30144" +msgid "Disable WebVTT subtitle support" +msgstr "Inaktivera stöd för WebVTT-undertexter" + +msgctxt "#30145" +msgid "Recently added" +msgstr "Nyligen tillagd" + +msgctxt "#30146" +msgid "Discover the latest content added." +msgstr "Upptäck det senaste tillagda innehållet." + +msgctxt "#30147" +msgid "Next page »" +msgstr "Nästa sida »" + +msgctxt "#30148" +msgid "« Previous page" +msgstr "« Föregående sida" + +msgctxt "#30149" +msgid "Sort results by" +msgstr "Sortera resultat efter" + +msgctxt "#30150" +msgid "Alphabetical order (A-Z)" +msgstr "Alfabetisk ordning (A-Z)" + +msgctxt "#30151" +msgid "Alphabetical order (Z-A)" +msgstr "Alfabetisk ordning (Z-A)" + +msgctxt "#30152" +msgid "Suggestions for you" +msgstr "Förslag till dig" + +msgctxt "#30153" +msgid "Year released" +msgstr "Releaseår" + +msgctxt "#30154" +msgid "Netflix configuration wizard" +msgstr "Netflix konfigurationsguide" + +msgctxt "#30155" +msgid "Do you have a Netflix Premium account?" +msgstr "Har du ett Netflix Premium-konto?" + +#. Unused 30156 +msgctxt "#30157" +msgid "The configuration has been completed" +msgstr "Konfigurationen är klar" + +msgctxt "#30158" +msgid "Restore the add-on default configuration" +msgstr "" + +msgctxt "#30159" +msgid "View ID for main menu" +msgstr "Visa ID för huvudmenyn" + +msgctxt "#30160" +msgid "View ID for search" +msgstr "Visa ID för sök" + +msgctxt "#30161" +msgid "View ID for exported" +msgstr "Visa ID för exporterad" + +msgctxt "#30162" +msgid "Last used" +msgstr "Senast använd" + +msgctxt "#30163" +msgid "Audio description" +msgstr "Ljudbeskrivning" + +msgctxt "#30164" +msgid "Browse contents with audio description." +msgstr "Bläddra bland innehåll med ljudbeskrivning." + +msgctxt "#30165" +msgid "View ID for 'My list'" +msgstr "Visa ID för 'Min lista'" + +msgctxt "#30166" +msgid "Main menu items" +msgstr "Huvudmenyobjekt" + +msgctxt "#30167" +msgid "My list" +msgstr "Min lista" + +msgctxt "#30168" +msgid "Continue watching" +msgstr "Fortsätt titta" + +msgctxt "#30169" +msgid "Top picks" +msgstr "Toppval" + +msgctxt "#30170" +msgid "New releases" +msgstr "Nya releaser" + +msgctxt "#30171" +msgid "Trending now" +msgstr "Trendar nu" + +msgctxt "#30172" +msgid "Popular on Netflix" +msgstr "Populärt på Netflix" + +msgctxt "#30173" +msgid "Netflix Originals" +msgstr "Netflix Originals" + +msgctxt "#30174" +msgid "TV show genres" +msgstr "Genrer för TV-serier" + +msgctxt "#30175" +msgid "Movie genres" +msgstr "Genrer för film" + +msgctxt "#30176" +msgid "Enable STRM resume workaround" +msgstr "Aktivera lösningen för återuppta STRM" + +msgctxt "#30177" +msgid "Ask to resume the video" +msgstr "Fråga om att återuppta videon" + +msgctxt "#30178" +msgid "Open Up Next add-on settings" +msgstr "Öppna tilläggsinställningar för Up Next " + +msgctxt "#30179" +msgid "Show trailers and more" +msgstr "Visa trailers och mer" + +msgctxt "#30180" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "Det har uppstått ett problem med inloggningen, det är möjligt att ditt konto inte har bekräftats eller återaktiverats." + +msgctxt "#30181" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "Visa alltid undertexterna när det önskade ljudspråket inte är tillgängligt" + +msgctxt "#30182" +msgid "Export NFO files" +msgstr "Exportera NFO-filer" + +msgctxt "#30183" +msgid "Do you want to export NFO files for the {}?" +msgstr "Vill du exportera NFO-filer för {}?" + +msgctxt "#30184" +msgid "NFO Files" +msgstr "NFO-filer" + +msgctxt "#30185" +msgid "Enable NFO files export" +msgstr "Aktivera export av NFO-filer" + +msgctxt "#30186" +msgid "For Movies" +msgstr "För filmer" + +msgctxt "#30187" +msgid "For TV show" +msgstr "För TV-serier" + +msgctxt "#30188" +msgid "Ask" +msgstr "Fråga" + +msgctxt "#30189" +msgid "movie" +msgstr "film" + +msgctxt "#30190" +msgid "TV show" +msgstr "TV-serier" + +#. Unused 30191 +msgctxt "#30192" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]Dessa filer används av leverantörens informationsskrapa för att integrera information om filmer och TV-serier, användbara med bonusavsnitt eller regissörsklipp (director's cut)" + +msgctxt "#30193" +msgid "Include tv show NFO details" +msgstr "Inkludera NFO-detaljer i TV-program" + +msgctxt "#30194" +msgid "Limit video stream resolution to" +msgstr "Begränsa upplösningen av videoströmmen till" + +msgctxt "#30195" +msgid "Export new episodes" +msgstr "Exportera nya avsnitt" + +msgctxt "#30196" +msgid "Exclude from auto update" +msgstr "Uteslut från automatisk uppdatering" + +msgctxt "#30197" +msgid "Include in auto update" +msgstr "Inkludera i automatisk uppdatering" + +msgctxt "#30198" +msgid "Exporting new episodes" +msgstr "Exportera nya avsnitt" + +msgctxt "#30199" +msgid "Shared library (Kodi MySQL server is required)" +msgstr "Delat bibliotek (Kodi MySQL-server krävs)" + +msgctxt "#30200" +msgid "Use shared library MySQL-database" +msgstr "Använd MySQL-delad biblioteksdatabas" + +msgctxt "#30201" +msgid "Username" +msgstr "Användarnamn" + +msgctxt "#30202" +msgid "Connection to the MySQL-database was successful" +msgstr "Anslutningen till MySQL-databasen fungerade" + +msgctxt "#30203" +msgid "Host IP" +msgstr "Värd-IP" + +msgctxt "#30204" +msgid "Port" +msgstr "Port" + +msgctxt "#30205" +msgid "Test database connection" +msgstr "Testa databasanslutningen" + +msgctxt "#30206" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" +msgstr "FEL: MySQL-databasen kan inte nås - den lokala databasen kommer att användas" + +msgctxt "#30207" +msgid "Set this device as main auto-updates manager" +msgstr "Ställ in den här enheten som huvudhanteraren för automatiska uppdateringar" + +msgctxt "#30208" +msgid "Check if this device is the main auto-update manager" +msgstr "Kontrollera om den här enheten är huvudhanteraren för automatiska uppdateringar" + +msgctxt "#30209" +msgid "This device now handles the auto-updates of the shared library" +msgstr "Denna enhet hanterar nu automatiska uppdateringar av delade bibliotek" + +msgctxt "#30210" +msgid "This device handles the auto-updates of the shared library" +msgstr "Denna enhet hanterar automatiska uppdateringar av delade bibliotek" + +msgctxt "#30211" +msgid "Another device handles the auto-updates of the shared library" +msgstr "En annan enhet hanterar automatiska uppdateringar av delade bibliotek" + +msgctxt "#30212" +msgid "There is no device that handles the auto-updates of the shared library" +msgstr "Det finns ingen enhet som hanterar automatiska uppdateringar av delade bibliotek" + +msgctxt "#30213" +msgid "InputStream Helper settings..." +msgstr "Inställningar för InputStream Helper..." + +msgctxt "#30214" +msgid "Force refresh" +msgstr "Tvinga uppdatering" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "Färg på titlarna som ingår i \"Min lista\"" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "Färgen på titlarna markerade som \"Påminn mig\"" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "Inaktivera avisering för slutförd synkronisering" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "Kodi-biblioteket har uppdaterats" + +msgctxt "#30221" +msgid "Owner account" +msgstr "Ägarkonto" + +msgctxt "#30222" +msgid "Kid account" +msgstr "Barnkonto" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "Automatiskt uppdateringsläge" + +msgctxt "#30225" +msgid "Manual" +msgstr "Manuellt" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "Schemalagt" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "Synkronisera Kodi-biblioteket med 'Min lista'" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "Ställ in en profil för synkronisering" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "När Kodi startas" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "Sök efter uppdateringar nu" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "Vill du söka efter uppdateringar nu?" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "Åldersgräns för profilen {}" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "Visa endast titlar med åldersgränsen [B]{}[/B] för den här profilen." + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "Synkronisera visningsstatusen av videorna med Netflix" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "Ändra visningsstatusen (lokalt)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "Markerad som visad|Markerad som osedd|Återställd visningsstatus för Netflix" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Up Next-aviseringar (se nästa video)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "Färg på kompletterande innehållsinformation" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "Bakgrundstjänsterna kan inte startas på grund av följande problem:[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Open Connect CDN (lägre siffra är bättre)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "Topp 10" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "Välj det första osedda avsnittet i TV-serien" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "Tar bort filer exporterade till biblioteket" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "Det finns {} titlar som inte kan importeras.[CR]Vill du ta bort dessa filer?" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "Resultat per sida (minska värdet vid timeout-fel)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "Vill du ta bort statusen sedd för \"{}\"?" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "Inkludera Kodi-bibliotek (skickar endast data)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "Välj inloggningsmetod[CR](om inloggningen med e-postadress/lösenord alltid returnerar \"felaktigt lösenord\", använd då Autentiseringsnyckel, instruktioner finns i ReadMe på GitHub)" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "E-postadress/lösenord" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "Autentiseringsnyckel" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "Den valda filen är inte kompatibel eller skadad" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "Autentiseringsnyckeln har löpt ut" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "Ange PIN-kod" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "Sortera historik efter" + +msgctxt "#30400" +msgid "Search" +msgstr "Sök" + +msgctxt "#30401" +msgid "Type of search" +msgstr "Typ av sökning" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "Sök titlar, personer, genrer" + +msgctxt "#30403" +msgid "New search..." +msgstr "Ny sökning..." + +msgctxt "#30404" +msgid "Clear search history" +msgstr "Rensa sökhistoriken" + +msgctxt "#30405" +msgid "Select the language" +msgstr "Välj språk" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "Vill du ta bort hela innehållet?" + +msgctxt "#30407" +msgid "No matches found" +msgstr "Hittade inga matchningar" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "Efter benämning" + +msgctxt "#30411" +msgid "By audio language" +msgstr "Efter ljudspråk" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "Efter undertextspråk" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "Efter genre/undergenre-ID" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "Föredrar språket för ljud/textning med landskod (om tillgängligt)" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "Föredrar stereoljudspår som standard" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "Inställningar för ESN / Widevine" + +msgctxt "#30601" +msgid "ESN:" +msgstr "ESN:" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "Ändra ESN" + +msgctxt "#30603" +msgid "Save system info" +msgstr "Spara systeminfo" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "Widevine - Tvinga säkerhetsnivån" + +msgctxt "#30605" +msgid "Force {}" +msgstr "Tviga {}" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "ESN inställd[CR]Försök spela upp en video" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "Denna ESN kan inte användas[CR]Försök ändra ESN eller återställ ESN.[CR][CR]Feldetaljer:[CR]{}" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "Fel ESN-format" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "Vill du återställa alla inställningar?" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "Använd systembaserad kryptering för autentiseringsuppgifter" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "[B]Om det är inaktiverat kommer det att orsaka en säkerhetsläcka[/B]. Om ditt operativsystem inte stöder det kommer du att uppmanas att logga in varje gång du startar om enheten. Inaktivera [B]endast[/B] om så är fallet. Om du ändrar den här inställningen kommer du att uppmanas att logga in igen nästa gång du startar om." + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "MSL-manifestversion" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "Denna titel kommer att vara tillgänglig från:[CR]{}" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "Vill du spela upp kampanjtrailern?" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "{} Påminn mig" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "Nytt och populärt" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "Markera påbörjad TV-serier som sedda" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "Observera att genom att aktivera den här inställningen kanske objekt som är markerade som sedda inte syns om filter är aktiverade i dina skalinställningar." + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "Åsidosätt strömvalstyp" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "Åsidosätt inställningen för strömvalstyp för tillägget InputStream Adaptive. Detta för att ställa in hur ljud- / videokvaliteten väljs under uppspelningen" + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "Anpassningsbar kvalitet" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "Fast videokvalitet" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "Fråga om videokvalitet" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "Observera att aktivering av den här inställningen tvingar bort strömmar som inte ligger inom det angivna intervallet och detta kan påverka funktionen för InputStream Adaptive. Om möjligt försök att först begränsa kvaliteten från inställningarna i InputStream Adaptive." + +msgctxt "#30721" +msgid "Size" +msgstr "" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "" + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "" + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "Lägg till Netflix-mappar till Kodi-bibliotekskällor" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "Mapparna \"Netflix-filmer\" och \"Netflix-serier\" har lagts till i Kodi-källor. Starta om Kodi för att se dem i Kodi-menyerna [TV-serier] och [Filmer]. Redigera sedan mapparna för att ställa in lämplig innehållstyp och informationsleverantörsskrapa." + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "[Manuella] uppdateringar måste göras manuellt genom att använda innehållsmenyn \"Exportera nya avsnitt\" på varje tv-program, [Schemalagt] kan du upprätta automatiska uppdateringar vid schemalagda tider, [När Kodi startar] automatiska uppdateringar sker vid Kodi-start och kommer endast att göras en gång per dag." + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "Synkroniserar biblioteket med Min lista, om min lista har modifierats externt t.ex i mobilapp eller webbplats kommer uppdateringen att skjutas upp till den schemalagda tiden." + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "Om aktiverat, kommer information att läggas till avsnitten eller filmerna som är användbara när Kodi-informationsleverantörsskrapa inte tillhandahåller data. Detta kommer också att lägga till varaktigheten för videorna." + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "Aktivera endast om \"Lokal information\" informationsleverantörsskrapa används eller om vissa titlar inte visas i Kodi-biblioteket trots att de har lagts till." + +msgctxt "#30734" +msgid "Support" +msgstr "Support" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "Om tillägget Netflix" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "Genererar automatiskt nya ESN:er (lösning för 540p-begränsning)" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "VARNING: Använd inte originalenhetens ESN för den officiella appen." + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "Aktivera ljudkompensation" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "Ljudkompensation" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "Ställer in ljudkompensation genom att åsidosätta Kodi-inställningen" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "" + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "" + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "" + +msgctxt "#30747" +msgid "Fixed" +msgstr "" + +msgctxt "#30748" +msgid "Relative" +msgstr "" + +msgctxt "#30749" +msgid "Reset" +msgstr "" diff --git a/resources/language/resource.language.sv_sv/strings.po b/resources/language/resource.language.sv_sv/strings.po deleted file mode 100644 index 29dbb961b..000000000 --- a/resources/language/resource.language.sv_sv/strings.po +++ /dev/null @@ -1,160 +0,0 @@ -# Kodi Media Center language file -# Addon Name: Netflix -# Addon id: plugin.video.netflix -# Addon Provider: libdev + jojo + asciidisco + caphm + CastagnaIT -msgid "" -msgstr "" -"Project-Id-Version: plugin.video.netflix\n" -"Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" -"POT-Creation-Date: 2017-07-24 16:15+0000\n" -"PO-Revision-Date: 2019-07-08 22:15+0000\n" -"Last-Translator: Sebastian Golasch \n" -"Language-Team: Swedish\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: sv\n" - -msgctxt "Addon Summary" -msgid "Netflix" -msgstr "" - -msgctxt "Addon Description" -msgid "Netflix VOD Services Addon" -msgstr "Netflix VOD-tjänstetillägg" - -msgctxt "Addon Disclaimer" -msgid "Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing." -msgstr "Vissa delar av detta tillägg kanske inte är fullt lagliga att använda i ditt land - kontrollera dina lokala lagar innan du installerar." - -msgctxt "#30001" -msgid "Recommendations" -msgstr "Rekommendationer" - -msgctxt "#30002" -msgid "Adult Pin" -msgstr "Barnskydd" - -msgctxt "#30003" -msgid "Search term" -msgstr "Sökord" - -msgctxt "#30004" -msgid "Password" -msgstr "Lösenord" - -msgctxt "#30005" -msgid "E-mail" -msgstr "E-post" - -msgctxt "#30006" -msgid "Adult verification failed" -msgstr "Verifiering av barnskyddet misslyckades" - -msgctxt "#30007" -msgid "Please Check your adult pin" -msgstr "Kontrollera koden till barnskyddet" - -msgctxt "#30008" -msgid "Login failed" -msgstr "Inloggningen misslyckades" - -msgctxt "#30009" -msgid "Please Check your credentials" -msgstr "Kontrollera dina inloggninguppgifter" - -msgctxt "#30010" -msgid "Lists of all kinds" -msgstr "" - -msgctxt "#30011" -msgid "Search" -msgstr "Sök" - -msgctxt "#30012" -msgid "No seasons available" -msgstr "Inga säsonger tillgängliga" - -msgctxt "#30013" -msgid "No matches found" -msgstr "Inga träffar" - -msgctxt "#30014" -msgid "Account" -msgstr "Konto" - -msgctxt "#30017" -msgid "Logout" -msgstr "Logga ut" - -msgctxt "#30018" -msgid "Export to library" -msgstr "Exportera till biblioteket" - -msgctxt "#30019" -msgid "Rate on Netflix" -msgstr "Betygsätt på Netflix" - -msgctxt "#30020" -msgid "Remove from 'My list'" -msgstr "Ta bort från 'Min lista'" - -msgctxt "#30021" -msgid "Add to 'My list'" -msgstr "Lägg till 'Min lista'" - -msgctxt "#30022" -msgid "(between 0 & 10)" -msgstr "(mellan 0 & 10)" - -msgctxt "#30023" -msgid "Expert" -msgstr "" - -msgctxt "#30024" -msgid "SSL verification" -msgstr "SSL-kontroll" - -msgctxt "#30025" -msgid "Library" -msgstr "Bibliotek" - -msgctxt "#30026" -msgid "Enable custom library folder" -msgstr "Aktivera anpassad biblioteksmapp" - -msgctxt "#30027" -msgid "Custom library path" -msgstr "Anpassad bibliotekssökväg" - -msgctxt "#30028" -msgid "Playback error" -msgstr "Uppspelningsfel" - -msgctxt "#30029" -msgid "Missing InputStream addon" -msgstr "Saknar tillägget InputStream" - -msgctxt "#30030" -msgid "Remove from library" -msgstr "Ta bort från biblioteket" - -msgctxt "#30031" -msgid "Change library title" -msgstr "Ändra bibliotekstiteln" - -msgctxt "#30032" -msgid "Tracking" -msgstr "Spårning" - -msgctxt "#30034" -msgid "ESN (set automatically, can be changed manually)" -msgstr "ESN (ändra automatiskt, kan ändras manuellt)" - -msgctxt "#30035" -msgid "InputStream Adaptive settings..." -msgstr "Inställningar för tillägget InputStream..." - -msgctxt "#30213" -msgid "InputStream Helper settings..." -msgstr "Inställningar för tillägget InputStream Helper..." diff --git a/resources/language/resource.language.tr_tr/strings.po b/resources/language/resource.language.tr_tr/strings.po new file mode 100644 index 000000000..9165c0916 --- /dev/null +++ b/resources/language/resource.language.tr_tr/strings.po @@ -0,0 +1,1256 @@ +# Kodi Media Center language file +# Addon Name: Netflix +# Addon id: plugin.video.netflix +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait +msgid "" +msgstr "" +"Project-Id-Version: plugin.video.netflix\n" +"Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" +"POT-Creation-Date: 2020-01-20 16:15+0000\n" +"PO-Revision-Date: 2020-11-11 10:00+0000\n" +"Last-Translator: Snn1452 <>\n" +"Language-Team: Turkish (Turkey)\n" +"Language: tr_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 "Netflix" +msgstr "Netflix" + +msgctxt "Addon Description" +msgid "Netflix VOD Services Add-on" +msgstr "Netflix VOD Hizmetleri Eklentisi" + +msgctxt "Addon Disclaimer" +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "Bu eklentinin bazı bölümleri ikamet ettiğiniz ülkede yasal olmayabilir - lütfen yüklemeden önce yerel yasalarınıza bakın." + +msgctxt "#30001" +msgid "Recommendations" +msgstr "Önerilenler" + +msgctxt "#30002" +msgid "Enter PIN to watch restricted content" +msgstr "Kısıtlanmış içeriği izlemek için PIN kodunu girin" + +#. Unused 30003 +msgctxt "#30004" +msgid "Password" +msgstr "Şifre" + +msgctxt "#30005" +msgid "E-mail" +msgstr "E-posta" + +msgctxt "#30006" +msgid "Enter PIN to access this profile" +msgstr "Bu profile erişmek için PIN kodunu girin" + +msgctxt "#30007" +msgid "Parental Control PIN (4 digits)" +msgstr "Ebeveyn Kontrolü PIN kodu (4 basamaklı)" + +msgctxt "#30008" +msgid "Login failed" +msgstr "Giriş başarısız" + +msgctxt "#30009" +msgid "Please check your e-mail and password" +msgstr "Lütfen e-postanızı ve şifrenizi kontrol edin" + +msgctxt "#30010" +msgid "Lists of all kinds" +msgstr "Her türlü liste" + +#. Unused 30011 +msgctxt "#30012" +msgid "Profiles options and parental control" +msgstr "" + +msgctxt "#30013" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "" + +msgctxt "#30014" +msgid "Account" +msgstr "Hesap" + +msgctxt "#30015" +msgid "Other options" +msgstr "Diğer seçenekler" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "" + +msgctxt "#30017" +msgid "Logout" +msgstr "Çıkış yap" + +msgctxt "#30018" +msgid "Export to library" +msgstr "Kitaplığa aktar" + +msgctxt "#30019" +msgid "Rate on Netflix" +msgstr "Netflix'te Derecelendir" + +msgctxt "#30020" +msgid "Remove from 'My list'" +msgstr "'Listem'den kaldır" + +msgctxt "#30021" +msgid "Add to 'My list'" +msgstr "'Listem'e ekle" + +msgctxt "#30022" +msgid "(between 0 and 10)" +msgstr "(0 ile 10 arasında)" + +msgctxt "#30023" +msgid "Expert" +msgstr "Uzman" + +msgctxt "#30024" +msgid "SSL verification" +msgstr "SSL doğrulaması" + +msgctxt "#30025" +msgid "Library" +msgstr "Kitaplık" + +msgctxt "#30026" +msgid "Enable custom library folder" +msgstr "Özel kitaplık klasörünü etkinleştir" + +msgctxt "#30027" +msgid "Custom library path" +msgstr "Özel kitaplık yolu" + +msgctxt "#30028" +msgid "Playback error" +msgstr "Oynatma hatası" + +#. Unused 30029 +msgctxt "#30030" +msgid "Remove from library" +msgstr "Kitaplıktan kaldır" + +msgctxt "#30031" +msgid "General" +msgstr "Genel" + +msgctxt "#30032" +msgid "Codecs" +msgstr "" + +msgctxt "#30033" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "" + +#. Unused 30034 +msgctxt "#30035" +msgid "InputStream Adaptive settings..." +msgstr "InputStream Adaptive ayarları..." + +#. Unused 30036 +msgctxt "#30037" +msgid "Skin viewtypes" +msgstr "" + +msgctxt "#30038" +msgid "Enable custom viewtypes" +msgstr "" + +msgctxt "#30039" +msgid "View ID for folders" +msgstr "" + +msgctxt "#30040" +msgid "View ID for movies" +msgstr "" + +msgctxt "#30041" +msgid "View ID for shows" +msgstr "" + +msgctxt "#30042" +msgid "View ID for seasons" +msgstr "" + +msgctxt "#30043" +msgid "View ID for episodes" +msgstr "" + +msgctxt "#30044" +msgid "View ID for profiles" +msgstr "" + +msgctxt "#30045" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "Başparmak oyu kaldırıldı|Başparmak aşağı oyu verdiniz|Başparmak yukarı oyu verdiniz" + +msgctxt "#30046" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "InputStream Adaptive eklentisi veya Widevine kitaplığı ile ilgili bir sorun var. Eksik veya etkinleştirilmemiş olabilir, işlem iptal edilecektir." + +msgctxt "#30047" +msgid "Library update in progress" +msgstr "Kitaplık güncellemesi devam ediyor" + +msgctxt "#30048" +msgid "Exported" +msgstr "Dışa aktarılan" + +msgctxt "#30049" +msgid "Library" +msgstr "Kitaplık" + +msgctxt "#30050" +msgid "Enable Kodi library management" +msgstr "" + +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30052" +msgid "{} Set for library playback" +msgstr "{} Kitaplıktan oynatmaya ayarla" + +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30055" +msgid "{} Set at start-up" +msgstr "" + +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30057" +msgid "{} Remember PIN" +msgstr "" + +#. Unused 30058 +msgctxt "#30059" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "Dikkatli olun, birçok başlığı güncellemek üzeresiniz {}.[CR]Bu, hizmet sorunlarına veya hesabın geçici olarak yasaklanmasına neden olabilir.[CR]Devam etmek istiyor musunuz?" + +msgctxt "#30060" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "" + +msgctxt "#30061" +msgid "Update inside library" +msgstr "Kitaplık içi güncelleme" + +msgctxt "#30062" +msgid "Parental controls" +msgstr "Ebeveyn denetimleri" + +msgctxt "#30063" +msgid "An update is already in progress" +msgstr "Bir güncelleme zaten devam ediyor" + +msgctxt "#30064" +msgid "Perform auto-update" +msgstr "Otomatik güncelleme gerçekleştir" + +msgctxt "#30065" +msgid "Video library update" +msgstr "" + +msgctxt "#30066" +msgid "Enable debug logging" +msgstr "" + +msgctxt "#30067" +msgid "daily" +msgstr "günlük" + +msgctxt "#30068" +msgid "every other day" +msgstr "iki günde bir" + +msgctxt "#30069" +msgid "every 5 days" +msgstr "5 günde bir" + +msgctxt "#30070" +msgid "weekly" +msgstr "haftalık" + +msgctxt "#30071" +msgid "Time of Day" +msgstr "Gün İçinde" + +msgctxt "#30072" +msgid "Only start after 5 minutes of idle" +msgstr "Yalnızca 5 dakikalık gecikmeden sonra başlat" + +msgctxt "#30073" +msgid "Always show codec information during video playback" +msgstr "Video oynatımı sırasında codec bilgilerini her zaman göster" + +msgctxt "#30074" +msgid "View for exported" +msgstr "Dışa aktarılanlar için görünüm" + +msgctxt "#30075" +msgid "Ask to skip intro and recap" +msgstr "Tanıtımı ve özeti atlamayı sor" + +msgctxt "#30076" +msgid "Skip intro" +msgstr "Tanıtımı atla" + +msgctxt "#30077" +msgid "Skip recap" +msgstr "Özeti geç" + +msgctxt "#30078" +msgid "Playback" +msgstr "Oynatıcı" + +msgctxt "#30079" +msgid "Skip automatically" +msgstr "Otomatik atla" + +msgctxt "#30080" +msgid "Pause when skipping" +msgstr "Atlarken duraklat" + +msgctxt "#30081" +msgid "Force HDCP version on video streams" +msgstr "" + +msgctxt "#30082" +msgid "Remember audio / subtitle preferences" +msgstr "Ses / altyazı tercihlerini hatırla" + +msgctxt "#30083" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "Zorunlu altyazıların görüntülenmesini yalnızca ses dili ayarıyla zorla" + +msgctxt "#30084" +msgid "Cache objects TTL (minutes)" +msgstr "Önbellek öğesi yaşam-süresi (dakika)" + +msgctxt "#30085" +msgid "Cache metadata TTL (days)" +msgstr "Metaveri için önbellek öğesi yaşam-süresi (gün)" + +msgctxt "#30086" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "Listem için önbellek öğesi yaşam-süresi (dakika)" + +msgctxt "#30087" +msgid "Contains {} and more..." +msgstr "{} ve daha fazlasını içerir..." + +msgctxt "#30088" +msgid "Browse related content" +msgstr "İlgili içeriğe göz atın" + +msgctxt "#30089" +msgid "Browse subgenres" +msgstr "Alt türlere göz atın" + +msgctxt "#30090" +msgid "Browse through related video lists and discover more content." +msgstr "İlgili video listelerine göz atın ve daha fazla içerik keşfedin." + +msgctxt "#30091" +msgid "Browse and manage contents that were exported to the Kodi library." +msgstr "Kodi kütüphanesine aktarılan içeriklere göz atın ve bunları yönetin." + +msgctxt "#30092" +msgid "Search for content and easily find what you want to watch." +msgstr "İçerik arayın ve izlemek istediklerinizi kolayca bulun." + +msgctxt "#30093" +msgid "Browse content by genre and easily discover related content." +msgstr "İçeriğe türe göre göz atın ve ilgili içeriği kolayca keşfedin." + +msgctxt "#30094" +msgid "Browse recommendations and pick up on something new you might like." +msgstr "Önerilere göz atın ve hoşunuza gidebilecek yeni bir şey edinin." + +msgctxt "#30095" +msgid "All TV shows" +msgstr "Tüm TV Programları" + +msgctxt "#30096" +msgid "All Movies" +msgstr "Tüm Filmler" + +msgctxt "#30097" +msgid "Main Menu" +msgstr "Ana Menü" + +msgctxt "#30098" +msgid "Enable HDR10" +msgstr "" + +msgctxt "#30099" +msgid "Enable Dolby Vision" +msgstr "" + +msgctxt "#30100" +msgid "Results for '{}'" +msgstr "'{}' için sonuçlar" + +msgctxt "#30101" +msgid "Netflix returned an unknown error." +msgstr "Netflix bilinmeyen bir hata döndürdü." + +msgctxt "#30102" +msgid "Netflix returned the following error:" +msgstr "Netflix aşağıdaki hatayı döndürdü:" + +msgctxt "#30103" +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "Hata ayrıntıları Kodi günlük dosyasına yazılmıştır. Hata mesajı sorunun nasıl çözüleceğine dair rehberlik sağlamazsa veya bu hatayı almaya devam ediyorsanız, lütfen Readme talimatlarını izleyerek GitHub üzerinden bildirin." + +msgctxt "#30104" +msgid "The add-on encountered the following error:" +msgstr "Eklenti şu hatayla karşılaştı:" + +msgctxt "#30105" +msgid "Netflix Add-on Error" +msgstr "Netflix Eklentisi Hatası" + +msgctxt "#30106" +msgid "Invalid PIN" +msgstr "Geçersiz PIN" + +msgctxt "#30107" +msgid "PIN protection is off." +msgstr "PIN koruması kapalı." + +msgctxt "#30108" +msgid "All content is protected by PIN." +msgstr "Tüm içerik PIN ile korunmaktadır." + +msgctxt "#30109" +msgid "Login successful" +msgstr "Giriş başarılı" + +#. Unused 30110 +msgctxt "#30111" +msgid "There is no contents" +msgstr "İçerik yok" + +msgctxt "#30112" +msgid "You need to be logged in to use Netflix" +msgstr "Netflix'i kullanabilmek için giriş yapmalısınız" + +msgctxt "#30113" +msgid "Logout successful" +msgstr "Çıkış başarılı" + +#. Unused 30114, 30115 +msgctxt "#30116" +msgid "Advanced Add-on Configuration" +msgstr "Gelişmiş Eklenti Yapılandırması" + +msgctxt "#30117" +msgid "Cache" +msgstr "Önbellek" + +#. Unused 30118, 30119 +msgctxt "#30120" +msgid "Cannot automatically remove a season from Kodi library, please do it manually" +msgstr "Bir sezon Kodi kitaplığından otomatik olarak kaldırılamıyor, lütfen manuel olarak yapın" + +msgctxt "#30121" +msgid "Perform full sync now" +msgstr "Şimdi tam senkronizasyon gerçekleştir" + +msgctxt "#30122" +msgid "Sync 'My list' to Kodi library" +msgstr "Listemi Kodi kitaplığı ile senkronize et" + +msgctxt "#30123" +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "Devam etmek istiyor musunuz?[CR]Tam senkronizasyon önceden dışa aktarılan tüm başlıkları silecektir.[CR]Çok sayıda başlık varsa bu işlem biraz zaman alabilir." + +msgctxt "#30124" +msgid "Do you want to remove this item from the library?" +msgstr "Bu öğeyi gerçekten kaldırmak istiyor musunuz?" + +msgctxt "#30125" +msgid "Delete library contents" +msgstr "Kitaplığı temizle" + +msgctxt "#30126" +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "Kitaplığınızı gerçekten temizlemek istiyor musunuz?[CR]Tüm dışa aktarılan öğeler silinecek." + +msgctxt "#30127" +msgid "Awarded rating of {}" +msgstr "Verilen derecelendirme {}" + +msgctxt "#30128" +msgid "Select a profile" +msgstr "Bir profil seçin" + +msgctxt "#30129" +msgid "Enable Up Next integration" +msgstr "Sonraki entegrasyonunu etkinleştir" + +msgctxt "#30130" +msgid "Install Up Next add-on" +msgstr "Up Next Eklentisini Yükle" + +msgctxt "#30131" +msgid "Check the logfile for detailed information" +msgstr "Ayrıntılı bilgi için günlük dosyasına bakın" + +msgctxt "#30132" +msgid "Clear in-memory cache" +msgstr "Bellek içindeki önbelleği temizle" + +msgctxt "#30133" +msgid "Clear in-memory and on-disk cache" +msgstr "Bellek içindeki ve disk üzerindeki önbelleği temizle" + +msgctxt "#30134" +msgid "Enable execution timing" +msgstr "Yürütme zamanlamasını etkinleştir" + +msgctxt "#30135" +msgid "Cache cleared" +msgstr "Önbellek temizlendi" + +msgctxt "#30136" +msgid "Waiting for service start-up..." +msgstr "" + +msgctxt "#30137" +msgid "Enable VP9 codec" +msgstr "" + +msgctxt "#30138" +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "Kodi/Eklenti'yi yeni başlattıysanız, arka plan hizmetleri henüz kullanılamayabilir. Bu durumda, biraz sonra tekrar deneyin." + +msgctxt "#30139" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "HTTP üzerinden IPC'yi etkinleştir (Eklenti Sinyalleri zaman aşımı mesajları görüntülendiğinde kullanın)" + +msgctxt "#30140" +msgid "Import existing library" +msgstr "Mevcut kitaplığı içe aktar" + +#. Unused 30141 to 30143 +msgctxt "#30144" +msgid "Disable WebVTT subtitle support" +msgstr "WebVTT altyazı desteğini devre dışı bırak" + +msgctxt "#30145" +msgid "Recently added" +msgstr "Son eklenen" + +msgctxt "#30146" +msgid "Discover the latest content added." +msgstr "Son eklenen içeriği keşfedin." + +msgctxt "#30147" +msgid "Next page »" +msgstr "Sonraki sayfa »" + +msgctxt "#30148" +msgid "« Previous page" +msgstr "« Önceki sayfa" + +msgctxt "#30149" +msgid "Sort results by" +msgstr "Sonuçları sırala" + +msgctxt "#30150" +msgid "Alphabetical order (A-Z)" +msgstr "Alfabetik sıralama (A-Z)" + +msgctxt "#30151" +msgid "Alphabetical order (Z-A)" +msgstr "Alfabetik sıralama (Z-A)" + +msgctxt "#30152" +msgid "Suggestions for you" +msgstr "Senin için öneriler" + +msgctxt "#30153" +msgid "Year released" +msgstr "Çıkış yılı" + +msgctxt "#30154" +msgid "Netflix configuration wizard" +msgstr "Netflix yapılandırma sihirbazı" + +msgctxt "#30155" +msgid "Do you have a Netflix Premium account?" +msgstr "Netflix Premium hesabınız var mı?" + +#. Unused 30156 +msgctxt "#30157" +msgid "The configuration has been completed" +msgstr "Yapılandırma tamamlandı" + +msgctxt "#30158" +msgid "Restore the add-on default configuration" +msgstr "" + +msgctxt "#30159" +msgid "View ID for main menu" +msgstr "" + +msgctxt "#30160" +msgid "View ID for search" +msgstr "" + +msgctxt "#30161" +msgid "View ID for exported" +msgstr "" + +msgctxt "#30162" +msgid "Last used" +msgstr "Son kullanılan" + +msgctxt "#30163" +msgid "Audio description" +msgstr "Sesli betimleme " + +msgctxt "#30164" +msgid "Browse contents with audio description." +msgstr "Sesli betimlemeli içeriklere göz atın." + +msgctxt "#30165" +msgid "View ID for 'My list'" +msgstr "" + +msgctxt "#30166" +msgid "Main menu items" +msgstr "Ana menü öğeleri" + +msgctxt "#30167" +msgid "My list" +msgstr "Listem" + +msgctxt "#30168" +msgid "Continue watching" +msgstr "İzlemeye devam et" + +msgctxt "#30169" +msgid "Top picks" +msgstr "En iyi seçimler" + +msgctxt "#30170" +msgid "New releases" +msgstr "Yeni çıkanlar" + +msgctxt "#30171" +msgid "Trending now" +msgstr "Gündemdekiler" + +msgctxt "#30172" +msgid "Popular on Netflix" +msgstr "Netflix'te Popüler" + +msgctxt "#30173" +msgid "Netflix Originals" +msgstr "Netflix Orijinalleri" + +msgctxt "#30174" +msgid "TV show genres" +msgstr "TV Programı türleri" + +msgctxt "#30175" +msgid "Movie genres" +msgstr "Film türleri" + +msgctxt "#30176" +msgid "Enable STRM resume workaround" +msgstr "STRM devam etme çözümünü etkinleştir" + +msgctxt "#30177" +msgid "Ask to resume the video" +msgstr "Video'ya devam etmeyi iste" + +msgctxt "#30178" +msgid "Open Up Next add-on settings" +msgstr "Up Next eklenti ayarlarını aç" + +msgctxt "#30179" +msgid "Show trailers and more" +msgstr "Fragmanlar & daha fazlasını göster" + +msgctxt "#30180" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "Giriş yapmak ile ilgili bir sorun var, hesabınız onaylanmamış veya yeniden etkinleştirilmemiş olabilir." + +msgctxt "#30181" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "Tercih edilen ses dili mevcut olmadığında daima altyazıları göster" + +msgctxt "#30182" +msgid "Export NFO files" +msgstr "NFO dosyalarını dışa aktar" + +msgctxt "#30183" +msgid "Do you want to export NFO files for the {}?" +msgstr "{} için NFO dosyalarını dışa aktarmak istiyor musunuz?" + +msgctxt "#30184" +msgid "NFO Files" +msgstr "NFO Dosyaları" + +msgctxt "#30185" +msgid "Enable NFO files export" +msgstr "NFO dosyalarını dışa aktarmayı etkinleştir" + +msgctxt "#30186" +msgid "For Movies" +msgstr "Filmler için" + +msgctxt "#30187" +msgid "For TV show" +msgstr "TV Programları için" + +msgctxt "#30188" +msgid "Ask" +msgstr "Sor" + +msgctxt "#30189" +msgid "movie" +msgstr "film" + +msgctxt "#30190" +msgid "TV show" +msgstr "TV programı" + +#. Unused 30191 +msgctxt "#30192" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]Bu dosyalar, bonus bölümleri veya yönetmenin kesimi gibi yararlı olan, filmler ve tv programları bilgilerini entegre etmek için scraper bilgi sağlayıcıları tarafından kullanır" + +msgctxt "#30193" +msgid "Include tv show NFO details" +msgstr "" + +msgctxt "#30194" +msgid "Limit video stream resolution to" +msgstr "Video akışı çözünürlüğünü" + +msgctxt "#30195" +msgid "Export new episodes" +msgstr "Yeni bölümleri dışa aktar" + +msgctxt "#30196" +msgid "Exclude from auto update" +msgstr "Otomatik güncellemeden hariç tut" + +msgctxt "#30197" +msgid "Include in auto update" +msgstr "Otomatik güncellemeye dahil et" + +msgctxt "#30198" +msgid "Exporting new episodes" +msgstr "Yeni bölümleri dışa aktar" + +msgctxt "#30199" +msgid "Shared library (Kodi MySQL server is required)" +msgstr "Paylaşılan kitaplık (Kodi MySQL sunucusu gerekli)" + +msgctxt "#30200" +msgid "Use shared library MySQL-database" +msgstr "MySQL paylaşılan kitaplık veritabanını kullan" + +msgctxt "#30201" +msgid "Username" +msgstr "Kullanıcı Adı" + +msgctxt "#30202" +msgid "Connection to the MySQL-database was successful" +msgstr "MySQL veritabanına bağlantı başarılı" + +msgctxt "#30203" +msgid "Host IP" +msgstr "Ana Makine IP'si" + +msgctxt "#30204" +msgid "Port" +msgstr "Bağlantı Noktası" + +msgctxt "#30205" +msgid "Test database connection" +msgstr "Veritabanı bağlantısını test et" + +msgctxt "#30206" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" +msgstr "HATA: MySQL veritabanına erişilemiyor - yerel veritabanı kullanılacak" + +msgctxt "#30207" +msgid "Set this device as main auto-updates manager" +msgstr "Bu cihazı ana otomatik güncelleme yöneticisi olarak ayarla" + +msgctxt "#30208" +msgid "Check if this device is the main auto-update manager" +msgstr "Bu cihazın ana otomatik güncelleme yöneticisi olup olmadığını kontrol et" + +msgctxt "#30209" +msgid "This device now handles the auto-updates of the shared library" +msgstr "Bu cihaz artık paylaşılan kitaplığın otomatik güncellemelerini yönetiyor" + +msgctxt "#30210" +msgid "This device handles the auto-updates of the shared library" +msgstr "Bu cihaz paylaşılan kitaplığın otomatik güncellemelerini yönetir" + +msgctxt "#30211" +msgid "Another device handles the auto-updates of the shared library" +msgstr "Başka bir cihaz paylaşılan kitaplığın otomatik güncellemelerini yönetir" + +msgctxt "#30212" +msgid "There is no device that handles the auto-updates of the shared library" +msgstr "Paylaşılan kitaplığın otomatik güncellemelerini işleyen bir cihaz yok" + +msgctxt "#30213" +msgid "InputStream Helper settings..." +msgstr "InputStream Helper ayarları..." + +msgctxt "#30214" +msgid "Force refresh" +msgstr "Zorla yenile" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "Listeme dahil edilen başlıkları bir renkle vurgula" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "Senkronizasyon tamamlandı için bildirimi devre dışı bırak" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "Kodi kitaplığı güncellendi" + +msgctxt "#30221" +msgid "Owner account" +msgstr "Sahip hesabı" + +msgctxt "#30222" +msgid "Kid account" +msgstr "Çocuk hesabı" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "Otomatik güncelleme modu" + +msgctxt "#30225" +msgid "Manual" +msgstr "Elle" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "Tarifeli" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "Kodi kitaplığını Listem ile senkronize et" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "Senkronizasyon için bir profil ayarlayın" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "Güncellemeleri kontrol et" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "Güncellemeyi şimdi kontrol etmek istiyor musunuz?" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "{} için profil olgunluk derecesi" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "Yalnızca [B]{}[/B] olarak derecelendirilmiş başlıkları göster." + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "Videoların izlenme durumunu Netflix ile senkronize et" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "İzlenme durumunu değiştir (yerel olarak)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "İzlendi olarak işaretlendi|İzlenmedi olarak işaretlendi|Netflix izlenme durumu geri alındı" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Up Next bildirimleri (sonraki videoyu izle)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "Ek özet bilgisinin rengi" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "Bu sorun nedeniyle arka plan hizmetleri başlatılamıyor:[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Open Connect CDN (alt dizin daha iyidir)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "Top 10" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "İzlenmeyen ilk TV şovu bölümünü seçin" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "Kitaplığa aktarılan dosyaları silme" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "İçe aktarılamayan {} başlık var.[CR]Bu dosyaları silmek istiyor musunuz?" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "Sayfa başına sonuçlar (zaman aşımı hatası durumunda azaltın)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "\"{}\" için izlenme durumunu kaldırmak istiyor musunuz?" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "Kodi kitaplığını dahil et (yalnızca veri gönderme)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "Giriş yapma yöntemini seçin[CR](E-Posta/Şifre ile oturum açma her zaman \"yanlış şifre\" döndürürse, Kimlik Doğrulama anahtarını kullanın, GitHub ReadMe'deki talimatları uygulayın)" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "E-Posta/Şifre" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "Kimlik Doğrulama anahtarı" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "Seçili dosya uyumlu değil veya bozuk" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "Kimlik doğrulama anahtarı dosyasının süresi doldu" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "PIN girin" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "Geçmişi sıralama ölçütü" + +msgctxt "#30400" +msgid "Search" +msgstr "Ara" + +msgctxt "#30401" +msgid "Type of search" +msgstr "Arama türü" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "Başlık, kişi, tür ara" + +msgctxt "#30403" +msgid "New search..." +msgstr "Yeni arama..." + +msgctxt "#30404" +msgid "Clear search history" +msgstr "Arama geçmişini temizle" + +msgctxt "#30405" +msgid "Select the language" +msgstr "Dili seçin" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "Tüm içeriği silmek istiyor musunuz?" + +msgctxt "#30407" +msgid "No matches found" +msgstr "Hiçbir sonuç bulunamadı" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "Terime göre" + +msgctxt "#30411" +msgid "By audio language" +msgstr "Ses diline göre" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "Altyazı diline göre" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "Tür/alt tür kimliğine göre" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "Ülke koduyla birlikte ses/altyazı dilini tercih edin (varsa)" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "Varsayılan olarak stereo ses parçalarını tercih et" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "" + +msgctxt "#30601" +msgid "ESN:" +msgstr "" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "" + +msgctxt "#30603" +msgid "Save system info" +msgstr "" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "" + +msgctxt "#30605" +msgid "Force {}" +msgstr "" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "" + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "" + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "" + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "" + +msgctxt "#30721" +msgid "Size" +msgstr "" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "" + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "" + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "" + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "" + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "" + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "" + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "" + +msgctxt "#30734" +msgid "Support" +msgstr "" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "" + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "" + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "" + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "" + +msgctxt "#30747" +msgid "Fixed" +msgstr "" + +msgctxt "#30748" +msgid "Relative" +msgstr "" + +msgctxt "#30749" +msgid "Reset" +msgstr "" diff --git a/resources/language/resource.language.zh_cn/strings.po b/resources/language/resource.language.zh_cn/strings.po new file mode 100644 index 000000000..0ad8ced28 --- /dev/null +++ b/resources/language/resource.language.zh_cn/strings.po @@ -0,0 +1,1256 @@ +# Kodi Media Center language file +# Addon Name: Netflix +# Addon id: plugin.video.netflix +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait +msgid "" +msgstr "" +"Project-Id-Version: plugin.video.netflix\n" +"Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" +"POT-Creation-Date: 2017-07-24 16:15+0000\n" +"PO-Revision-Date: 2023-01-16 00:00+0000\n" +"Last-Translator: Liqianyu <>\n" +"Language-Team: Chinese\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=2; plural=(n != 1);\n" + +msgctxt "Addon Summary" +msgid "Netflix" +msgstr "Netflix" + +msgctxt "Addon Description" +msgid "Netflix VOD Services Add-on" +msgstr "Netflix VOD服务附加组件" + +msgctxt "Addon Disclaimer" +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "在您的居住国使用此附加组件可能不合法 - 请在安装前与您当地的法律核对。" + +msgctxt "#30001" +msgid "Recommendations" +msgstr "推荐" + +msgctxt "#30002" +msgid "Enter PIN to watch restricted content" +msgstr "输入PIN码来观看受限内容" + +#. Unused 30003 +msgctxt "#30004" +msgid "Password" +msgstr "密码" + +msgctxt "#30005" +msgid "E-mail" +msgstr "电子邮箱" + +msgctxt "#30006" +msgid "Enter PIN to access this profile" +msgstr "输入PIN码即可访问此个人资料" + +msgctxt "#30007" +msgid "Parental Control PIN (4 digits)" +msgstr "家长控制PIN码(4位数字)" + +msgctxt "#30008" +msgid "Login failed" +msgstr "登录失败" + +msgctxt "#30009" +msgid "Please check your e-mail and password" +msgstr "请检查您的电子邮箱和密码" + +msgctxt "#30010" +msgid "Lists of all kinds" +msgstr "各类列表" + +#. Unused 30011 +msgctxt "#30012" +msgid "Profiles options and parental control" +msgstr "个人资料选项和家长控制" + +msgctxt "#30013" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "在个人资料列表,打开所选个人资料的上下文菜单" + +msgctxt "#30014" +msgid "Account" +msgstr "账户" + +msgctxt "#30015" +msgid "Other options" +msgstr "其他选项" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "如果您注销,所有账户数据将被删除,但Kodi资料库中导出的视频文件将被保留。[CR]您要继续吗?" + +msgctxt "#30017" +msgid "Logout" +msgstr "注销" + +msgctxt "#30018" +msgid "Export to library" +msgstr "导出到资料库" + +msgctxt "#30019" +msgid "Rate on Netflix" +msgstr "在Netflix上评分" + +msgctxt "#30020" +msgid "Remove from 'My list'" +msgstr "从'我的片单'中删除" + +msgctxt "#30021" +msgid "Add to 'My list'" +msgstr "添加到'我的片单'" + +msgctxt "#30022" +msgid "(between 0 and 10)" +msgstr "(介于0到10之间)" + +msgctxt "#30023" +msgid "Expert" +msgstr "专家" + +msgctxt "#30024" +msgid "SSL verification" +msgstr "SSL验证" + +msgctxt "#30025" +msgid "Library" +msgstr "资料库" + +msgctxt "#30026" +msgid "Enable custom library folder" +msgstr "启用自定义资料库文件夹" + +msgctxt "#30027" +msgid "Custom library path" +msgstr "自定义资料库路径" + +msgctxt "#30028" +msgid "Playback error" +msgstr "播放错误" + +#. Unused 30029 +msgctxt "#30030" +msgid "Remove from library" +msgstr "从资料库中删除" + +msgctxt "#30031" +msgid "General" +msgstr "常规" + +msgctxt "#30032" +msgid "Codecs" +msgstr "" + +msgctxt "#30033" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "" + +#. Unused 30034 +msgctxt "#30035" +msgid "InputStream Adaptive settings..." +msgstr "InputStream Adaptive 设置..." + +#. Unused 30036 +msgctxt "#30037" +msgid "Skin viewtypes" +msgstr "皮肤视图类型" + +msgctxt "#30038" +msgid "Enable custom viewtypes" +msgstr "启用自定义视图类型" + +msgctxt "#30039" +msgid "View ID for folders" +msgstr "文件夹 视图ID" + +msgctxt "#30040" +msgid "View ID for movies" +msgstr "电影 视图ID" + +msgctxt "#30041" +msgid "View ID for shows" +msgstr "节目 视图ID" + +msgctxt "#30042" +msgid "View ID for seasons" +msgstr "季 视图ID" + +msgctxt "#30043" +msgid "View ID for episodes" +msgstr "集 视图ID" + +msgctxt "#30044" +msgid "View ID for profiles" +msgstr "个人资料 视图ID" + +msgctxt "#30045" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "移除评分|踩|赞" + +msgctxt "#30046" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "InputStream Adaptive附加组件或Widevine库存在问题。可能缺失或未启用,操作将被取消" + +msgctxt "#30047" +msgid "Library update in progress" +msgstr "正在更新资料库" + +msgctxt "#30048" +msgid "Exported" +msgstr "已导出" + +msgctxt "#30049" +msgid "Library" +msgstr "资料库" + +msgctxt "#30050" +msgid "Enable Kodi library management" +msgstr "启用Kodi资料库管理" + +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30052" +msgid "{} Set for library playback" +msgstr "{} 设为资料库播放" + +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30055" +msgid "{} Set at start-up" +msgstr "{} 设为自动登录" + +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30057" +msgid "{} Remember PIN" +msgstr "{} 记住PIN码" + +#. Unused 30058 +msgctxt "#30059" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "注意,您将更新许多标题{}。[CR]这可能会导致服务问题或帐户暂时封禁。[CR]您想继续吗?" + +msgctxt "#30060" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "" + +msgctxt "#30061" +msgid "Update inside library" +msgstr "在资料库中更新" + +msgctxt "#30062" +msgid "Parental controls" +msgstr "家长控制" + +msgctxt "#30063" +msgid "An update is already in progress" +msgstr "更新已经在进行中" + +msgctxt "#30064" +msgid "Perform auto-update" +msgstr "执行自动更新" + +msgctxt "#30065" +msgid "Video library update" +msgstr "视频资料库更新" + +msgctxt "#30066" +msgid "Enable debug logging" +msgstr "启用调试日志" + +msgctxt "#30067" +msgid "daily" +msgstr "每日" + +msgctxt "#30068" +msgid "every other day" +msgstr "每隔一天" + +msgctxt "#30069" +msgid "every 5 days" +msgstr "每隔五天" + +msgctxt "#30070" +msgid "weekly" +msgstr "每周" + +msgctxt "#30071" +msgid "Time of Day" +msgstr "指定时间" + +msgctxt "#30072" +msgid "Only start after 5 minutes of idle" +msgstr "仅闲置5分钟后启动" + +msgctxt "#30073" +msgid "Always show codec information during video playback" +msgstr "在视频播放期间始终显示编解码器信息" + +msgctxt "#30074" +msgid "View for exported" +msgstr "查看导出" + +msgctxt "#30075" +msgid "Ask to skip intro and recap" +msgstr "询问跳过片头和回顾" + +msgctxt "#30076" +msgid "Skip intro" +msgstr "跳过片头" + +msgctxt "#30077" +msgid "Skip recap" +msgstr "跳过回顾" + +msgctxt "#30078" +msgid "Playback" +msgstr "播放" + +msgctxt "#30079" +msgid "Skip automatically" +msgstr "自动跳过" + +msgctxt "#30080" +msgid "Pause when skipping" +msgstr "跳过时暂停" + +msgctxt "#30081" +msgid "Force HDCP version on video streams" +msgstr "" + +msgctxt "#30082" +msgid "Remember audio / subtitle preferences" +msgstr "记住音频/字幕首选项" + +msgctxt "#30083" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "仅在设置音频语言后显示强制字幕" + +msgctxt "#30084" +msgid "Cache objects TTL (minutes)" +msgstr "对象缓存TTL(分钟)" + +msgctxt "#30085" +msgid "Cache metadata TTL (days)" +msgstr "元数据缓存TTL(天)" + +msgctxt "#30086" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "对象缓存'我的片单'TTL(分钟)" + +msgctxt "#30087" +msgid "Contains {} and more..." +msgstr "包含{}以及更多..." + +msgctxt "#30088" +msgid "Browse related content" +msgstr "浏览相关内容" + +msgctxt "#30089" +msgid "Browse subgenres" +msgstr "浏览子类型" + +msgctxt "#30090" +msgid "Browse through related video lists and discover more content." +msgstr "浏览相关的视频列表并发现更多内容" + +msgctxt "#30091" +msgid "Browse and manage contents that were exported to the Kodi library." +msgstr "浏览和管理已导出到Kodi资料库的内容" + +msgctxt "#30092" +msgid "Search for content and easily find what you want to watch." +msgstr "搜索内容并轻松找到想要观看的内容。" + +msgctxt "#30093" +msgid "Browse content by genre and easily discover related content." +msgstr "按类型浏览内容并轻松发现相关内容。" + +msgctxt "#30094" +msgid "Browse recommendations and pick up on something new you might like." +msgstr "浏览推荐并挑选您可能喜欢的新内容。" + +msgctxt "#30095" +msgid "All TV shows" +msgstr "所有电视节目" + +msgctxt "#30096" +msgid "All Movies" +msgstr "所有电影" + +msgctxt "#30097" +msgid "Main Menu" +msgstr "主菜单" + +msgctxt "#30098" +msgid "Enable HDR10" +msgstr "" + +msgctxt "#30099" +msgid "Enable Dolby Vision" +msgstr "" + +msgctxt "#30100" +msgid "Results for '{}'" +msgstr "“{}”的结果" + +msgctxt "#30101" +msgid "Netflix returned an unknown error." +msgstr "Netflix返回未知错误。" + +msgctxt "#30102" +msgid "Netflix returned the following error:" +msgstr "Netflix返回如下错误:" + +msgctxt "#30103" +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "错误详细信息已写入Kodi日志文件。如果错误消息未提供如何解决问题的指导,或者您继续收到此错误,请按照自述文件说明在GitHub上进行报告。" + +msgctxt "#30104" +msgid "The add-on encountered the following error:" +msgstr "附加组件遇到以下错误:" + +msgctxt "#30105" +msgid "Netflix Add-on Error" +msgstr "Netflix附加组件错误" + +msgctxt "#30106" +msgid "Invalid PIN" +msgstr "PIN码无效" + +msgctxt "#30107" +msgid "PIN protection is off." +msgstr "PIN码保护已关闭" + +msgctxt "#30108" +msgid "All content is protected by PIN." +msgstr "所有内容均受PIN码保护" + +msgctxt "#30109" +msgid "Login successful" +msgstr "登录成功" + +#. Unused 30110 +msgctxt "#30111" +msgid "There is no contents" +msgstr "没有内容" + +msgctxt "#30112" +msgid "You need to be logged in to use Netflix" +msgstr "您必须先登录才能使用Netflix" + +msgctxt "#30113" +msgid "Logout successful" +msgstr "注销成功" + +#. Unused 30114, 30115 +msgctxt "#30116" +msgid "Advanced Add-on Configuration" +msgstr "高级附加组件配置" + +msgctxt "#30117" +msgid "Cache" +msgstr "缓存" + +#. Unused 30118, 30119 +msgctxt "#30120" +msgid "Cannot automatically remove a season from Kodi library, please do it manually" +msgstr "无法自动从Kodi资料库中删除季,请手动操作" + +msgctxt "#30121" +msgid "Perform full sync now" +msgstr "立即执行完全同步" + +msgctxt "#30122" +msgid "Sync 'My list' to Kodi library" +msgstr "同步'我的片单'到Kodi资料库" + +msgctxt "#30123" +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "您想继续吗?[CR]完全同步将删除所有以前导出的标题。[CR]如果有很多标题,这个操作可能需要一些时间。" + +msgctxt "#30124" +msgid "Do you want to remove this item from the library?" +msgstr "您要从资料库中删除该项目吗?" + +msgctxt "#30125" +msgid "Delete library contents" +msgstr "删除资料库内容" + +msgctxt "#30126" +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "您是否真的要清除资料库?[CR]所有导出的项目将被删除" + +msgctxt "#30127" +msgid "Awarded rating of {}" +msgstr "{}获得的评分" + +msgctxt "#30128" +msgid "Select a profile" +msgstr "选择个人资料" + +msgctxt "#30129" +msgid "Enable Up Next integration" +msgstr "启用Up Next集成" + +msgctxt "#30130" +msgid "Install Up Next add-on" +msgstr "安装Up Next附加组件" + +msgctxt "#30131" +msgid "Check the logfile for detailed information" +msgstr "检查日志文件以获取详细信息”" + +msgctxt "#30132" +msgid "Clear in-memory cache" +msgstr "清除内存缓存" + +msgctxt "#30133" +msgid "Clear in-memory and on-disk cache" +msgstr "清除内存和磁盘缓存" + +msgctxt "#30134" +msgid "Enable execution timing" +msgstr "启用执行时间" + +msgctxt "#30135" +msgid "Cache cleared" +msgstr "缓存已清除" + +msgctxt "#30136" +msgid "Waiting for service start-up..." +msgstr "等待服务启动..." + +msgctxt "#30137" +msgid "Enable VP9 codec" +msgstr "" + +msgctxt "#30138" +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "如果您刚启动Kodi/附加组件,则可能无法使用后台服务。在这种情况下,请稍后重试。" + +msgctxt "#30139" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "启用基于HTTP的IPC(在附加组件提示信号超时信息时使用)" + +msgctxt "#30140" +msgid "Import existing library" +msgstr "导入现有资料库" + +#. Unused 30141 to 30143 +msgctxt "#30144" +msgid "Disable WebVTT subtitle support" +msgstr "禁用WebVTT字幕支持" + +msgctxt "#30145" +msgid "Recently added" +msgstr "最新上线" + +msgctxt "#30146" +msgid "Discover the latest content added." +msgstr "发现最新上线内容" + +msgctxt "#30147" +msgid "Next page »" +msgstr "下一页 »" + +msgctxt "#30148" +msgid "« Previous page" +msgstr "« 上一页" + +msgctxt "#30149" +msgid "Sort results by" +msgstr "结果排序方式" + +msgctxt "#30150" +msgid "Alphabetical order (A-Z)" +msgstr "字母顺序(A-Z)" + +msgctxt "#30151" +msgid "Alphabetical order (Z-A)" +msgstr "字母顺序(Z-A)" + +msgctxt "#30152" +msgid "Suggestions for you" +msgstr "推荐给您" + +msgctxt "#30153" +msgid "Year released" +msgstr "发布年份" + +msgctxt "#30154" +msgid "Netflix configuration wizard" +msgstr "Netflix配置向导" + +msgctxt "#30155" +msgid "Do you have a Netflix Premium account?" +msgstr "您有Netflix高级方案账户吗?" + +#. Unused 30156 +msgctxt "#30157" +msgid "The configuration has been completed" +msgstr "配置已完成" + +msgctxt "#30158" +msgid "Restore the add-on default configuration" +msgstr "" + +msgctxt "#30159" +msgid "View ID for main menu" +msgstr "主菜单 视图ID" + +msgctxt "#30160" +msgid "View ID for search" +msgstr "搜索 视图ID" + +msgctxt "#30161" +msgid "View ID for exported" +msgstr "已导出 视图ID" + +msgctxt "#30162" +msgid "Last used" +msgstr "最后使用" + +msgctxt "#30163" +msgid "Audio description" +msgstr "音频描述" + +msgctxt "#30164" +msgid "Browse contents with audio description." +msgstr "浏览带有音频描述的内容" + +msgctxt "#30165" +msgid "View ID for 'My list'" +msgstr "'我的片单' 视图ID" + +msgctxt "#30166" +msgid "Main menu items" +msgstr "主菜单项" + +msgctxt "#30167" +msgid "My list" +msgstr "我的片单" + +msgctxt "#30168" +msgid "Continue watching" +msgstr "继续观赏" + +msgctxt "#30169" +msgid "Top picks" +msgstr "最佳推荐" + +msgctxt "#30170" +msgid "New releases" +msgstr "过去一年推出的影片" + +msgctxt "#30171" +msgid "Trending now" +msgstr "现正热播" + +msgctxt "#30172" +msgid "Popular on Netflix" +msgstr "热门选择" + +msgctxt "#30173" +msgid "Netflix Originals" +msgstr "Netflix独家" + +msgctxt "#30174" +msgid "TV show genres" +msgstr "电视节目类型" + +msgctxt "#30175" +msgid "Movie genres" +msgstr "电影类型" + +msgctxt "#30176" +msgid "Enable STRM resume workaround" +msgstr "启用STRM继续播放变通方法" + +msgctxt "#30177" +msgid "Ask to resume the video" +msgstr "继续播放时询问" + +msgctxt "#30178" +msgid "Open Up Next add-on settings" +msgstr "打开Up Next附加组件设置" + +msgctxt "#30179" +msgid "Show trailers and more" +msgstr "显示预告片及更多内容" + +msgctxt "#30180" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "登录出现问题,可能是您的帐户没有被确认或重新激活。" + +msgctxt "#30181" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "当首选的音频语言不可用时,总是显示字幕" + +msgctxt "#30182" +msgid "Export NFO files" +msgstr "导出NFO文件" + +msgctxt "#30183" +msgid "Do you want to export NFO files for the {}?" +msgstr "您是否要为{}导出NFO文件?" + +msgctxt "#30184" +msgid "NFO Files" +msgstr "NFO文件" + +msgctxt "#30185" +msgid "Enable NFO files export" +msgstr "启用NFO文件导出" + +msgctxt "#30186" +msgid "For Movies" +msgstr "应用于电影" + +msgctxt "#30187" +msgid "For TV show" +msgstr "应用于电视节目" + +msgctxt "#30188" +msgid "Ask" +msgstr "询问" + +msgctxt "#30189" +msgid "movie" +msgstr "电影" + +msgctxt "#30190" +msgid "TV show" +msgstr "电视节目" + +#. Unused 30191 +msgctxt "#30192" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]这些文件由提供商信息收集器用来集成电影和电视节目信息,可用于额外章节或导演剪辑。" + +msgctxt "#30193" +msgid "Include tv show NFO details" +msgstr "包括电视节目NFO详细信息" + +msgctxt "#30194" +msgid "Limit video stream resolution to" +msgstr "将视频流分辨率限制为" + +msgctxt "#30195" +msgid "Export new episodes" +msgstr "导出新剧集" + +msgctxt "#30196" +msgid "Exclude from auto update" +msgstr "从自动更新中排除" + +msgctxt "#30197" +msgid "Include in auto update" +msgstr "包含在自动更新中" + +msgctxt "#30198" +msgid "Exporting new episodes" +msgstr "导出新剧集" + +msgctxt "#30199" +msgid "Shared library (Kodi MySQL server is required)" +msgstr "共享资料库(需要Kodi MySQL服务器)" + +msgctxt "#30200" +msgid "Use shared library MySQL-database" +msgstr "使用MySQL共享资料库数据库" + +msgctxt "#30201" +msgid "Username" +msgstr "用户名" + +msgctxt "#30202" +msgid "Connection to the MySQL-database was successful" +msgstr "连接到MySQL数据库成功" + +msgctxt "#30203" +msgid "Host IP" +msgstr "主机IP" + +msgctxt "#30204" +msgid "Port" +msgstr "端口" + +msgctxt "#30205" +msgid "Test database connection" +msgstr "测试数据库连接" + +msgctxt "#30206" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" +msgstr "错误:无法访问MySQL数据库 - 将使用本地数据库" + +msgctxt "#30207" +msgid "Set this device as main auto-updates manager" +msgstr "将此设备设置为主要的自动更新管理器" + +msgctxt "#30208" +msgid "Check if this device is the main auto-update manager" +msgstr "检查此设备是否是主要的自动更新管理器" + +msgctxt "#30209" +msgid "This device now handles the auto-updates of the shared library" +msgstr "此设备现在可以处理共享资料库的自动更新" + +msgctxt "#30210" +msgid "This device handles the auto-updates of the shared library" +msgstr "此设备处理共享资料库的自动更新" + +msgctxt "#30211" +msgid "Another device handles the auto-updates of the shared library" +msgstr "另一台设备处理共享资料库的自动更新" + +msgctxt "#30212" +msgid "There is no device that handles the auto-updates of the shared library" +msgstr "没有设备可以处理共享资料库的自动更新" + +msgctxt "#30213" +msgid "InputStream Helper settings..." +msgstr "InputStream Helper 设置..." + +msgctxt "#30214" +msgid "Force refresh" +msgstr "强制刷新" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "加入'我的片单'的标题颜色" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "标为'提醒我'的标题颜色" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "禁用同步完成通知" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "Kodi资料库已更新" + +msgctxt "#30221" +msgid "Owner account" +msgstr "所有者帐户" + +msgctxt "#30222" +msgid "Kid account" +msgstr "儿童帐户" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "自动更新模式" + +msgctxt "#30225" +msgid "Manual" +msgstr "手动" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "计划" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "将Kodi资料库与'我的片单'同步" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "设置同步个人资料" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "当Kodi启动" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "立即检查更新" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "您要立即检查更新吗?" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "{}的年龄分级" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "仅显示评分为[B]{}[/B]的标题。" + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "将视频的观看状态与Netflix同步" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "更改观看状态(本地)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "标记为已观看|标记为未观看|已恢复Netflix观看状态" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Up Next通知(观看下个视频)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "补充情节信息文字颜色" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "由于以下问题而无法启动后台服务:[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Open Connect CDN(索引越小越好)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "Top 10" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "选择首个未观看的电视节目剧集" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "删除导出到资料库的内容" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "有{}标题无法导入。[CR]您是否要删除这些内容" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "每页结果(如遇超时错误,请减少)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "您是否要删除\"{}\"的已观看状态?" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "包括Kodi资料库(仅发送数据)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "选择登录方式[CR](如果使用电子邮件/密码登录总是返回“不正确的密码”,请使用身份验证密钥,查看GitHub自述文件中的说明)" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "电子邮箱/密码" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "身份验证密钥" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "所选文件不兼容或已损坏" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "认证密钥文件已过期" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "输入PIN码" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "历史记录排序方式" + +msgctxt "#30400" +msgid "Search" +msgstr "搜索" + +msgctxt "#30401" +msgid "Type of search" +msgstr "搜索类型" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "搜索标题,人物,类型" + +msgctxt "#30403" +msgid "New search..." +msgstr "新搜索..." + +msgctxt "#30404" +msgid "Clear search history" +msgstr "清除搜索记录" + +msgctxt "#30405" +msgid "Select the language" +msgstr "选择语言" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "您要删除全部内容吗?" + +msgctxt "#30407" +msgid "No matches found" +msgstr "未找到匹配项" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "按关键词" + +msgctxt "#30411" +msgid "By audio language" +msgstr "按音频语言" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "按字幕语言" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "按类型/子类型ID" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "根据地区代码首选音频/字幕语言(如果可用)" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "默认首选立体声音轨" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "ESN / Widevine 设置" + +msgctxt "#30601" +msgid "ESN:" +msgstr "ESN:" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "更改ESN" + +msgctxt "#30603" +msgid "Save system info" +msgstr "保存系统信息" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "Widevine - 强制安全级别" + +msgctxt "#30605" +msgid "Force {}" +msgstr "强制{}" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "ESN 设置成功[CR]请尝试播放视频" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "这个ESN不能使用[CR]请尝试切换或重置ESN。[CR][CR]错误详情如下:[CR]{}" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "ESN格式错误" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "您确定要重置所有设置吗?" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "对凭证使用基于系统的加密" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "[B]如果禁用,将导致安全漏洞[/B]。如果您的操作系统不支持它,您每次重启设备时都会被提示登录,[B]仅在[/B]这种情况下禁用它。如果您改变这个设置,在您下次重启时,您将被提示重新登录。" + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "MSL清单版本" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "此标题:[CR]{}" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "您想播放预告片吗?" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "{} 提醒我" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "最新热门影片" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "标记开始观看的电视节目" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "请注意,如果您在皮肤设置中启用过滤器,启用此设置后,标记为已观看的项目可能不可见。" + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "覆盖流选择类型" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "覆盖InputStream Adaptive附加组件的流选择类型设置,以设置播放时如何选择音频/视频流质量。" + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "自适应质量" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "固定视频质量" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "询问视频质量" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "请注意,启用此设置将强制移除不在设定范围内的流,这可能影响InputStream Adaptive的操作。如果可能的话,先尝试从InputStream Adaptive设置中限制质量。" + +msgctxt "#30721" +msgid "Size" +msgstr "" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "" + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "" + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "将Netflix文件夹添加到Kodi资料库源" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "文件夹 \"Netflix-Movies\" 和 \"Netflix-Shows\"已经被添加到Kodi源,重新启动Kodi以在Kodi [TV Shows] / [Movies] 菜单中查看它们。然后编辑这些文件夹以设置适当的内容类型和信息提供者刮削器。" + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "[手动]更新必须通过在每个电视节目上使用上下文菜单\"导出新剧集\"手动完成,[计划]您可以在预定时间建立自动更新,[当Kodi启动]在Kodi启动时自动更新,每天仅一次。" + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "将资料库与'我的片单'同步,如果'我的片单'被外部修改,例如移动应用程序或网站,更新将被推迟到预定时间。" + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "如果启用,则将信息添加到剧集或电影中,这在 Kodi 信息提供者刮削器不提供信息时很有用。这也将添加视频的片长。" + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "仅在使用\"Local Information\"信息提供者刮削器的情况下启用,或者尽管已经添加了某些标题,但在Kodi资料库中没有显示。" + +msgctxt "#30734" +msgid "Support" +msgstr "支持" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "关于Netflix附加组件" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "自动生成新ESN(540p限制变通方法)" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "警告:不要使用官方应用程序的原始设备ESN" + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "启用音频偏移" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "音频偏移" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "覆盖Kodi音频偏移设置" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "" + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "" + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "" + +msgctxt "#30747" +msgid "Fixed" +msgstr "" + +msgctxt "#30748" +msgid "Relative" +msgstr "" + +msgctxt "#30749" +msgid "Reset" +msgstr "" diff --git a/resources/language/resource.language.zh_tw/strings.po b/resources/language/resource.language.zh_tw/strings.po new file mode 100644 index 000000000..05f447cca --- /dev/null +++ b/resources/language/resource.language.zh_tw/strings.po @@ -0,0 +1,1256 @@ +# Kodi Media Center language file +# Addon Name: Netflix +# Addon id: plugin.video.netflix +# Addon Provider: libdev, jojo, asciidisco, caphm, castagnait +msgid "" +msgstr "" +"Project-Id-Version: plugin.video.netflix\n" +"Report-Msgid-Bugs-To: https://github.com/CastagnaIT/plugin.video.netflix\n" +"POT-Creation-Date: 2017-07-24 16:15+0000\n" +"PO-Revision-Date: 2023-12-17 02:00+0800\n" +"Last-Translator: JuenTingShie <>\n" +"Language-Team: Taiwanese\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=2; plural=(n != 1);\n" + +msgctxt "Addon Summary" +msgid "Netflix" +msgstr "Netflix" + +msgctxt "Addon Description" +msgid "Netflix VOD Services Add-on" +msgstr "Netflix VOD服務套件" + +msgctxt "Addon Disclaimer" +msgid "The use of this add-on may not be legal in your country of residence - please check with your local laws before installing." +msgstr "在您居住的國家使用此套件可能並不合法 - 請在安裝前與您當地的法律核對。" + +msgctxt "#30001" +msgid "Recommendations" +msgstr "推薦" + +msgctxt "#30002" +msgid "Enter PIN to watch restricted content" +msgstr "輸入PIN碼來觀看受限內容" + +#. Unused 30003 +msgctxt "#30004" +msgid "Password" +msgstr "密碼" + +msgctxt "#30005" +msgid "E-mail" +msgstr "電子信箱" + +msgctxt "#30006" +msgid "Enter PIN to access this profile" +msgstr "輸入PIN碼即可檢視此設定檔" + +msgctxt "#30007" +msgid "Parental Control PIN (4 digits)" +msgstr "家長控制PIN碼(4位數字)" + +msgctxt "#30008" +msgid "Login failed" +msgstr "登入失敗" + +msgctxt "#30009" +msgid "Please check your e-mail and password" +msgstr "請檢查您的電子信箱和密碼" + +msgctxt "#30010" +msgid "Lists of all kinds" +msgstr "所有種類的清單" + +#. Unused 30011 +msgctxt "#30012" +msgid "Profiles options and parental control" +msgstr "設定檔選項與家長控制" + +msgctxt "#30013" +msgid "In the profile list, open the context menu in the chosen profile" +msgstr "在設定清單中,打開所選設定檔中的選單" + +msgctxt "#30014" +msgid "Account" +msgstr "帳戶" + +msgctxt "#30015" +msgid "Other options" +msgstr "其他選項" + +msgctxt "#30016" +msgid "If you log out, all account data will be deleted, but the exported video files in the Kodi library will be preserved.[CR]Do you want to continue?" +msgstr "如果你登出,所有帳號資訊將會被刪除,但已經匯出至Kodi資料庫的影片檔案將會保留下來。[CR]你確定要繼續嗎?" + +msgctxt "#30017" +msgid "Logout" +msgstr "登出" + +msgctxt "#30018" +msgid "Export to library" +msgstr "匯出到資料庫" + +msgctxt "#30019" +msgid "Rate on Netflix" +msgstr "在Netflix上評分" + +msgctxt "#30020" +msgid "Remove from 'My list'" +msgstr "從'我的清單'中刪除" + +msgctxt "#30021" +msgid "Add to 'My list'" +msgstr "新增到'我的清單'" + +msgctxt "#30022" +msgid "(between 0 and 10)" +msgstr "(介於0和10之間)" + +msgctxt "#30023" +msgid "Expert" +msgstr "專家" + +msgctxt "#30024" +msgid "SSL verification" +msgstr "SSL驗證" + +msgctxt "#30025" +msgid "Library" +msgstr "資料庫" + +msgctxt "#30026" +msgid "Enable custom library folder" +msgstr "啟用自訂資料庫資料夾" + +msgctxt "#30027" +msgid "Custom library path" +msgstr "自訂資料庫路徑" + +msgctxt "#30028" +msgid "Playback error" +msgstr "播放錯誤" + +#. Unused 30029 +msgctxt "#30030" +msgid "Remove from library" +msgstr "從資料庫中刪除" + +msgctxt "#30031" +msgid "General" +msgstr "一般" + +msgctxt "#30032" +msgid "Codecs" +msgstr "編碼" + +msgctxt "#30033" +msgid "Enable Dolby Digital Plus codec (Atmos on Premium account)" +msgstr "啟動Dolby Digital Plus編碼(Premium帳號的環景音效)" + +#. Unused 30034 +msgctxt "#30035" +msgid "InputStream Adaptive settings..." +msgstr "InputStream Adaptive設定..." + +#. Unused 30036 +msgctxt "#30037" +msgid "Skin viewtypes" +msgstr "主題視圖樣式" + +msgctxt "#30038" +msgid "Enable custom viewtypes" +msgstr "啟用自訂視圖樣式" + +msgctxt "#30039" +msgid "View ID for folders" +msgstr "查看目錄ID" + +msgctxt "#30040" +msgid "View ID for movies" +msgstr "查看電影ID" + +msgctxt "#30041" +msgid "View ID for shows" +msgstr "查看節目ID" + +msgctxt "#30042" +msgid "View ID for seasons" +msgstr "查看季ID" + +msgctxt "#30043" +msgid "View ID for episodes" +msgstr "查看影集ID" + +msgctxt "#30044" +msgid "View ID for profiles" +msgstr "查看設定檔ID" + +msgctxt "#30045" +msgid "Thumb rating removed|You rated a thumb down|You rated a thumb up" +msgstr "移除評分|我不喜歡|我喜歡" + +msgctxt "#30046" +msgid "There is a problem with InputStream Adaptive add-on or the Widevine library. May be missing or not enabled, the operation will be cancelled." +msgstr "InputStream Adaptive套件程式或Widevine資料庫出現問題。可能遺失或未啟用,操作將被取消。" + +msgctxt "#30047" +msgid "Library update in progress" +msgstr "正在更新資料庫" + +msgctxt "#30048" +msgid "Exported" +msgstr "已匯出" + +msgctxt "#30049" +msgid "Library" +msgstr "資料庫" + +msgctxt "#30050" +msgid "Enable Kodi library management" +msgstr "啟用Kodi資料庫管理" + +#. Unused 30051 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30052" +msgid "{} Set for library playback" +msgstr "{} 設為資料庫播放設置" + +#. Unused 30053, 30054 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30055" +msgid "{} Set at start-up" +msgstr "{} 設為自動啟用" + +#. Unused 30056 +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30057" +msgid "{} Remember PIN" +msgstr "{} 記住PIN碼" + +#. Unused 30058 +msgctxt "#30059" +msgid "Be careful, you are about to update many titles {}.[CR]This may cause service problems or temporary ban of the account.[CR]Do you want to continue?" +msgstr "注意,您將更新許多標題{}。[CR]這可能會導致服務問題或帳戶暫時封禁。[CR]您想繼續嗎?" + +msgctxt "#30060" +msgid "Enable HEVC codec (4k/HDR/DolbyVision for Android)" +msgstr "啟動HEVC編碼(適用於Android的4k/HDR/DolbyVision)" + +msgctxt "#30061" +msgid "Update inside library" +msgstr "在資料庫中更新" + +msgctxt "#30062" +msgid "Parental controls" +msgstr "家長控制" + +msgctxt "#30063" +msgid "An update is already in progress" +msgstr "更新已經在進行中" + +msgctxt "#30064" +msgid "Perform auto-update" +msgstr "執行自動更新" + +msgctxt "#30065" +msgid "Video library update" +msgstr "影片資料庫更新" + +msgctxt "#30066" +msgid "Enable debug logging" +msgstr "啟用偵錯紀錄" + +msgctxt "#30067" +msgid "daily" +msgstr "每天" + +msgctxt "#30068" +msgid "every other day" +msgstr "每隔一天" + +msgctxt "#30069" +msgid "every 5 days" +msgstr "每隔五天" + +msgctxt "#30070" +msgid "weekly" +msgstr "每週" + +msgctxt "#30071" +msgid "Time of Day" +msgstr "指定時間" + +msgctxt "#30072" +msgid "Only start after 5 minutes of idle" +msgstr "僅在閒置5分鐘後啟動" + +msgctxt "#30073" +msgid "Always show codec information during video playback" +msgstr "在影片播放期間一律顯示編碼資訊" + +msgctxt "#30074" +msgid "View for exported" +msgstr "查看匯出" + +msgctxt "#30075" +msgid "Ask to skip intro and recap" +msgstr "詢問跳過片頭和前情回顧" + +msgctxt "#30076" +msgid "Skip intro" +msgstr "跳過片頭" + +msgctxt "#30077" +msgid "Skip recap" +msgstr "跳過前情回顧" + +msgctxt "#30078" +msgid "Playback" +msgstr "播放" + +msgctxt "#30079" +msgid "Skip automatically" +msgstr "自動跳過" + +msgctxt "#30080" +msgid "Pause when skipping" +msgstr "跳過時暫停" + +msgctxt "#30081" +msgid "Force HDCP version on video streams" +msgstr "強制使用HDCP版本在影片串流" + +msgctxt "#30082" +msgid "Remember audio / subtitle preferences" +msgstr "記憶音訊/字幕選項" + +msgctxt "#30083" +msgid "Force the display of forced subtitles only with the audio language set" +msgstr "僅在設置音訊語言後強制顯示字幕" + +msgctxt "#30084" +msgid "Cache objects TTL (minutes)" +msgstr "暫存對象TTL(分鐘)" + +msgctxt "#30085" +msgid "Cache metadata TTL (days)" +msgstr "暫存元資料TTL(天)" + +msgctxt "#30086" +msgid "Cache objects 'My list' TTL (minutes)" +msgstr "暫存對象“我的清單” TTL(分鐘)" + +msgctxt "#30087" +msgid "Contains {} and more..." +msgstr "包含{}以及更多..." + +msgctxt "#30088" +msgid "Browse related content" +msgstr "瀏覽相關內容" + +msgctxt "#30089" +msgid "Browse subgenres" +msgstr "瀏覽子類別" + +msgctxt "#30090" +msgid "Browse through related video lists and discover more content." +msgstr "瀏覽相關的影片清單並探索更多內容。" + +msgctxt "#30091" +msgid "Browse and manage contents that were exported to the Kodi library." +msgstr "瀏覽和管理已匯出到Kodi資料庫的內容。" + +msgctxt "#30092" +msgid "Search for content and easily find what you want to watch." +msgstr "搜尋內容並輕鬆找到想要觀看的內容。" + +msgctxt "#30093" +msgid "Browse content by genre and easily discover related content." +msgstr "按類型瀏覽內容並輕鬆探索相關內容。" + +msgctxt "#30094" +msgid "Browse recommendations and pick up on something new you might like." +msgstr "瀏覽推薦並挑選您可能喜歡的新內容。" + +msgctxt "#30095" +msgid "All TV shows" +msgstr "所有電視節目" + +msgctxt "#30096" +msgid "All Movies" +msgstr "所有電影" + +msgctxt "#30097" +msgid "Main Menu" +msgstr "主選單" + +msgctxt "#30098" +msgid "Enable HDR10" +msgstr "啟用HDR10" + +msgctxt "#30099" +msgid "Enable Dolby Vision" +msgstr "啟用Dolby Vision" + +msgctxt "#30100" +msgid "Results for '{}'" +msgstr "“{}”的結果" + +msgctxt "#30101" +msgid "Netflix returned an unknown error." +msgstr "Netflix返回了未知錯誤。" + +msgctxt "#30102" +msgid "Netflix returned the following error:" +msgstr "Netflix返回了如下錯誤:" + +msgctxt "#30103" +msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it on GitHub by following the Readme instructions." +msgstr "錯誤詳細資訊已寫入Kodi日誌檔。如果錯誤消息未提供如何解決問題的指導,或者您繼續收到此錯誤,請按照讀我檔案說明在GitHub上進行回報。" + +msgctxt "#30104" +msgid "The add-on encountered the following error:" +msgstr "套件程式遇到以下錯誤:" + +msgctxt "#30105" +msgid "Netflix Add-on Error" +msgstr "Netflix套件程式錯誤" + +msgctxt "#30106" +msgid "Invalid PIN" +msgstr "PIN碼無效" + +msgctxt "#30107" +msgid "PIN protection is off." +msgstr "PIN碼保護已關閉" + +msgctxt "#30108" +msgid "All content is protected by PIN." +msgstr "所有內容均受PIN碼保護" + +msgctxt "#30109" +msgid "Login successful" +msgstr "登入成功" + +#. Unused 30110 +msgctxt "#30111" +msgid "There is no contents" +msgstr "沒有內容" + +msgctxt "#30112" +msgid "You need to be logged in to use Netflix" +msgstr "您必須先登入才能使用Netflix" + +msgctxt "#30113" +msgid "Logout successful" +msgstr "登出成功" + +#. Unused 30114, 30115 +msgctxt "#30116" +msgid "Advanced Add-on Configuration" +msgstr "高級套件程式設定" + +msgctxt "#30117" +msgid "Cache" +msgstr "暫存" + +#. Unused 30118, 30119 +msgctxt "#30120" +msgid "Cannot automatically remove a season from Kodi library, please do it manually" +msgstr "無法自動從Kodi資料庫中刪除季,請手動執行" + +msgctxt "#30121" +msgid "Perform full sync now" +msgstr "立即執行完全同步" + +msgctxt "#30122" +msgid "Sync 'My list' to Kodi library" +msgstr "將我的清單同步到Kodi資料庫" + +msgctxt "#30123" +msgid "Do you want to continue?[CR]The full synchronization will delete all previously exported titles.[CR]If there are many titles this operation may take some time." +msgstr "您想繼續嗎?[CR]完全同步將刪除所有以前匯出的標題。[CR]如果有很多標題,這個操作可能需要一些時間。" + +msgctxt "#30124" +msgid "Do you want to remove this item from the library?" +msgstr "您要從資料庫中刪除該項目嗎?" + +msgctxt "#30125" +msgid "Delete library contents" +msgstr "刪除資料庫內容" + +msgctxt "#30126" +msgid "Do you really want to purge your library?[CR]All exported items will be deleted." +msgstr "您是否真的要清除資料庫?[CR]所有匯出的項目將被刪除" + +msgctxt "#30127" +msgid "Awarded rating of {}" +msgstr "{}獲得的評分" + +msgctxt "#30128" +msgid "Select a profile" +msgstr "選擇個人資料" + +msgctxt "#30129" +msgid "Enable Up Next integration" +msgstr "啟用Up Next集成" + +msgctxt "#30130" +msgid "Install Up Next add-on" +msgstr "安裝Up Next套件程式" + +msgctxt "#30131" +msgid "Check the logfile for detailed information" +msgstr "檢查日誌檔以獲取詳細資訊" + +msgctxt "#30132" +msgid "Clear in-memory cache" +msgstr "清除記憶體暫存" + +msgctxt "#30133" +msgid "Clear in-memory and on-disk cache" +msgstr "清除記憶體和硬碟暫存" + +msgctxt "#30134" +msgid "Enable execution timing" +msgstr "啟用執行時間" + +msgctxt "#30135" +msgid "Cache cleared" +msgstr "暫存已清除" + +msgctxt "#30136" +msgid "Waiting for service start-up..." +msgstr "等待服務啟動..." + +msgctxt "#30137" +msgid "Enable VP9 codec" +msgstr "啟用VP9編碼" + +msgctxt "#30138" +msgid "The background services may not yet be available if you just started Kodi/the add-on. In this case, try again in a moment." +msgstr "如果您剛啟動Kodi/套件程式,則可能無法使用背景服務。在這種情況下,請稍後重試。" + +msgctxt "#30139" +msgid "Enable IPC over HTTP (use when Addon Signals timeout messages appear)" +msgstr "啟用基於HTTP的IPC(在Addon Signals超時資訊顯示時使用)" + +msgctxt "#30140" +msgid "Import existing library" +msgstr "匯入現有的資料庫" + +#. Unused 30141 to 30143 +msgctxt "#30144" +msgid "Disable WebVTT subtitle support" +msgstr "停用WebVTT字幕支援" + +msgctxt "#30145" +msgid "Recently added" +msgstr "最近新增" + +msgctxt "#30146" +msgid "Discover the latest content added." +msgstr "探索最新新增的內容。" + +msgctxt "#30147" +msgid "Next page »" +msgstr "下一頁»" + +msgctxt "#30148" +msgid "« Previous page" +msgstr "«上一頁" + +msgctxt "#30149" +msgid "Sort results by" +msgstr "結果排序依據" + +msgctxt "#30150" +msgid "Alphabetical order (A-Z)" +msgstr "字母順序(A-Z)" + +msgctxt "#30151" +msgid "Alphabetical order (Z-A)" +msgstr "字母順序(Z-A)" + +msgctxt "#30152" +msgid "Suggestions for you" +msgstr "推薦給您" + +msgctxt "#30153" +msgid "Year released" +msgstr "發佈年份" + +msgctxt "#30154" +msgid "Netflix configuration wizard" +msgstr "Netflix設定引導" + +msgctxt "#30155" +msgid "Do you have a Netflix Premium account?" +msgstr "您有Netflix高級方案帳戶嗎?" + +#. Unused 30156 +msgctxt "#30157" +msgid "The configuration has been completed" +msgstr "設定已完成" + +msgctxt "#30158" +msgid "Restore the add-on default configuration" +msgstr "還原擴充元件預設設定" + +msgctxt "#30159" +msgid "View ID for main menu" +msgstr "查看主目錄ID" + +msgctxt "#30160" +msgid "View ID for search" +msgstr "查看搜尋ID" + +msgctxt "#30161" +msgid "View ID for exported" +msgstr "查看匯出ID" + +msgctxt "#30162" +msgid "Last used" +msgstr "最後使用" + +msgctxt "#30163" +msgid "Audio description" +msgstr "音訊描述" + +msgctxt "#30164" +msgid "Browse contents with audio description." +msgstr "瀏覽帶有音訊描述的內容。" + +msgctxt "#30165" +msgid "View ID for 'My list'" +msgstr "查看'我的清單'ID" + +msgctxt "#30166" +msgid "Main menu items" +msgstr "主功能表項目" + +msgctxt "#30167" +msgid "My list" +msgstr "我的清單" + +msgctxt "#30168" +msgid "Continue watching" +msgstr "繼續觀看" + +msgctxt "#30169" +msgid "Top picks" +msgstr "熱門推薦" + +msgctxt "#30170" +msgid "New releases" +msgstr "最新發佈" + +msgctxt "#30171" +msgid "Trending now" +msgstr "目前流行" + +msgctxt "#30172" +msgid "Popular on Netflix" +msgstr "在Netflix上受歡迎" + +msgctxt "#30173" +msgid "Netflix Originals" +msgstr "Netflix 原創作品" + +msgctxt "#30174" +msgid "TV show genres" +msgstr "電視節目類型" + +msgctxt "#30175" +msgid "Movie genres" +msgstr "電影類型" + +msgctxt "#30176" +msgid "Enable STRM resume workaround" +msgstr "啟用STRM繼續播放" + +msgctxt "#30177" +msgid "Ask to resume the video" +msgstr "播放時詢問" + +msgctxt "#30178" +msgid "Open Up Next add-on settings" +msgstr "打開Up Next套件程式設置" + +msgctxt "#30179" +msgid "Show trailers and more" +msgstr "顯示預告片及更多內容" + +msgctxt "#30180" +msgid "There has been a problem with login, it is possible that your account has not been confirmed or reactivated." +msgstr "登入出現問題,可能是您的帳戶沒有被確認或重新啟動。" + +msgctxt "#30181" +msgid "Always show the subtitles when the preferred audio language is not available" +msgstr "當首選的音訊語言不可用時,總是顯示字幕" + +msgctxt "#30182" +msgid "Export NFO files" +msgstr "匯出NFO文件" + +msgctxt "#30183" +msgid "Do you want to export NFO files for the {}?" +msgstr "您是否要為{}匯出NFO檔?" + +msgctxt "#30184" +msgid "NFO Files" +msgstr "NFO文件" + +msgctxt "#30185" +msgid "Enable NFO files export" +msgstr "啟用NFO文件匯出" + +msgctxt "#30186" +msgid "For Movies" +msgstr "應用於電影" + +msgctxt "#30187" +msgid "For TV show" +msgstr "應用於電視節目" + +msgctxt "#30188" +msgid "Ask" +msgstr "詢問" + +msgctxt "#30189" +msgid "movie" +msgstr "電影" + +msgctxt "#30190" +msgid "TV show" +msgstr "電視節目" + +#. Unused 30191 +msgctxt "#30192" +msgid "[CR]These files are used by the provider information scrapers to integrate movies and TV shows info, useful with bonus episodes or director's cut" +msgstr "[CR]這些檔由提供商資訊收集器用來集成電影和電視節目資訊,可用於額外章節或導演剪輯。" + +msgctxt "#30193" +msgid "Include tv show NFO details" +msgstr "包含電視節目NFO詳細資訊" + +msgctxt "#30194" +msgid "Limit video stream resolution to" +msgstr "將影片串流解析度限制為" + +msgctxt "#30195" +msgid "Export new episodes" +msgstr "匯出新影集" + +msgctxt "#30196" +msgid "Exclude from auto update" +msgstr "從自動更新中排除" + +msgctxt "#30197" +msgid "Include in auto update" +msgstr "包含在自動更新中" + +msgctxt "#30198" +msgid "Exporting new episodes" +msgstr "匯出新影集" + +msgctxt "#30199" +msgid "Shared library (Kodi MySQL server is required)" +msgstr "共用資料庫(需要Kodi MySQL伺服器)" + +msgctxt "#30200" +msgid "Use shared library MySQL-database" +msgstr "使用MySQL共用資料庫" + +msgctxt "#30201" +msgid "Username" +msgstr "使用者名稱" + +msgctxt "#30202" +msgid "Connection to the MySQL-database was successful" +msgstr "到MySQL資料庫的連接成功" + +msgctxt "#30203" +msgid "Host IP" +msgstr "主機IP" + +msgctxt "#30204" +msgid "Port" +msgstr "埠" + +msgctxt "#30205" +msgid "Test database connection" +msgstr "測試資料庫連接" + +msgctxt "#30206" +msgid "ERROR: The MySQL-database is not reachable - the local database will be used" +msgstr "錯誤:無法訪問MySQL資料庫 - 將使用本地資料庫" + +msgctxt "#30207" +msgid "Set this device as main auto-updates manager" +msgstr "將此設備設置為主要的自動更新管理器" + +msgctxt "#30208" +msgid "Check if this device is the main auto-update manager" +msgstr "檢查此設備是否是主要的自動更新管理器" + +msgctxt "#30209" +msgid "This device now handles the auto-updates of the shared library" +msgstr "此設備現在可以處理共用資料庫的自動更新" + +msgctxt "#30210" +msgid "This device handles the auto-updates of the shared library" +msgstr "此設備處理共用資料庫的自動更新" + +msgctxt "#30211" +msgid "Another device handles the auto-updates of the shared library" +msgstr "另一台設備處理共用資料庫的自動更新" + +msgctxt "#30212" +msgid "There is no device that handles the auto-updates of the shared library" +msgstr "沒有設備可以處理共用資料庫的自動更新" + +msgctxt "#30213" +msgid "InputStream Helper settings..." +msgstr "InputStream Helper設置..." + +msgctxt "#30214" +msgid "Force refresh" +msgstr "強制更新" + +msgctxt "#30215" +msgid "Color of the titles included in \"My list\"" +msgstr "存在於\"我的清單\"中的標題顏色" + +msgctxt "#30216" +msgid "Color of the titles marked as \"Remind me\"" +msgstr "標記為\"提醒我\"的標題顏色" + +#. Unused 30217 to 30218 +msgctxt "#30219" +msgid "Disable notification for synchronization completed" +msgstr "停用同步完成通知" + +msgctxt "#30220" +msgid "The Kodi library has been updated" +msgstr "Kodi資料庫已更新" + +msgctxt "#30221" +msgid "Owner account" +msgstr "擁有者帳戶" + +msgctxt "#30222" +msgid "Kid account" +msgstr "兒童帳戶" + +#. Unused 30223 +msgctxt "#30224" +msgid "Auto update mode" +msgstr "自動更新模式" + +msgctxt "#30225" +msgid "Manual" +msgstr "手動" + +msgctxt "#30226" +msgid "Scheduled" +msgstr "計畫" + +msgctxt "#30227" +msgid "Synchronize Kodi library with 'My list'" +msgstr "將Kodi資料庫與我的清單同步" + +msgctxt "#30228" +msgid "Set a profile for synchronization" +msgstr "為同步設置一個設定檔" + +msgctxt "#30229" +msgid "When Kodi starts" +msgstr "當Kodi啟動" + +msgctxt "#30230" +msgid "Check for updates now" +msgstr "立即檢查更新" + +msgctxt "#30231" +msgid "Do you want to check update now?" +msgstr "您要立即檢查更新嗎?" + +msgctxt "#30232" +msgid "Profile maturity rating for {}" +msgstr "{}的個人資料年齡分級" + +msgctxt "#30233" +msgid "Only show titles rated [B]{}[/B]." +msgstr "僅顯示評分為[B]{}[/B]的標題。" + +#. Unused 30234 +msgctxt "#30235" +msgid "Synchronize the watched status of the videos with Netflix" +msgstr "將影片的觀看狀態與Netflix同步" + +msgctxt "#30236" +msgid "Change watched status (locally)" +msgstr "更改監視狀態(本地)" + +msgctxt "#30237" +msgid "Marked as watched|Marked as unwatched|Restored Netflix watched status" +msgstr "標記為已觀看|標記為未觀看|已恢復Netflix觀看狀態" + +msgctxt "#30238" +msgid "Up Next notifications (watch next video)" +msgstr "Up Next通知(觀看下一個影片)" + +msgctxt "#30239" +msgid "Color of supplemental plot info" +msgstr "補充資訊文字顏色" + +msgctxt "#30240" +msgid "The background services cannot be started due to this problem:[CR]{}" +msgstr "由於以下問題而無法啟動背景服務:[CR]{}" + +msgctxt "#30241" +msgid "Open Connect CDN (the lower index is better)" +msgstr "Open Connect CDN(索引越低越好)" + +msgctxt "#30242" +msgid "Top 10" +msgstr "十大排行" + +msgctxt "#30243" +msgid "Select first unwatched TV show episode" +msgstr "選擇第一個未觀看的電視節目影集" + +#. Unused 30244 +msgctxt "#30245" +msgid "Deleting files exported to the library" +msgstr "刪除匯出到資料庫的內容" + +msgctxt "#30246" +msgid "There are {} titles that cannot be imported.[CR]Do you want to delete these files?" +msgstr "有{}標題無法導入,[CR]您是否要刪除這些內容?" + +msgctxt "#30247" +msgid "Results per page (in case of timeout error decrease it)" +msgstr "每頁結果(請盡量請減少以避免逾時錯誤)" + +#. Unused 30248 to 30299 +msgctxt "#30300" +msgid "Do you want to remove watched status of \"{}\"?" +msgstr "您是否要刪除已監視的\"{}\"狀態?" + +msgctxt "#30301" +msgid "Include Kodi library (only sending data)" +msgstr "包括Kodi資料庫(僅發送資料)" + +#. Unused 30302 to 30339 +msgctxt "#30340" +msgid "Choose the login method[CR](if the login with E-Mail/Password always returns \"incorrect password\", then use Authentication key, instructions in the GitHub ReadMe)" +msgstr "選擇登入方式[CR](如果使用電子郵件/密碼登入總是返回“不正確的密碼”,請使用Authentication key登入,查看GitHub讀我檔案中的說明)" + +msgctxt "#30341" +msgid "E-Mail/Password" +msgstr "電子信箱/密碼" + +msgctxt "#30342" +msgid "Authentication key" +msgstr "Authentication key" + +msgctxt "#30343" +msgid "The selected file is not compatible or corrupted" +msgstr "所選文件不相容或已損壞" + +msgctxt "#30344" +msgid "The authentication key file is expired" +msgstr "Authentication key已過期" + +msgctxt "#30345" +msgid "Enter PIN" +msgstr "輸入PIN碼" + +#. Unused 30346 to 30398 +msgctxt "#30399" +msgid "Sort history by" +msgstr "歷史記錄排序依據" + +msgctxt "#30400" +msgid "Search" +msgstr "搜尋" + +msgctxt "#30401" +msgid "Type of search" +msgstr "搜尋類型" + +msgctxt "#30402" +msgid "Search titles, people, genres" +msgstr "搜尋標題,人物,類型" + +msgctxt "#30403" +msgid "New search..." +msgstr "新搜尋..." + +msgctxt "#30404" +msgid "Clear search history" +msgstr "清除搜尋記錄" + +msgctxt "#30405" +msgid "Select the language" +msgstr "選擇語言" + +msgctxt "#30406" +msgid "Do you want to delete the entire contents?" +msgstr "您要刪除全部內容嗎?" + +msgctxt "#30407" +msgid "No matches found" +msgstr "未找到匹配項目" + +#. Unused 30408, 30409 +msgctxt "#30410" +msgid "By term" +msgstr "按關鍵字" + +msgctxt "#30411" +msgid "By audio language" +msgstr "按音訊語言" + +msgctxt "#30412" +msgid "By subtitles language" +msgstr "按字幕語言" + +msgctxt "#30413" +msgid "By genre/subgenre ID" +msgstr "按類型/子類型ID" + +#. Unused 30414 to 30499 +msgctxt "#30500" +msgid "Prefer the audio/subtitle language with country code (if available)" +msgstr "根據地區代碼首選音訊/字幕語言(如果可用)" + +msgctxt "#30501" +msgid "Prefer stereo audio tracks by default" +msgstr "預設選擇立體聲音軌" + +#. Unused 30502 to 30599 +msgctxt "#30600" +msgid "ESN / Widevine settings" +msgstr "ESN / Widevine 設定" + +msgctxt "#30601" +msgid "ESN:" +msgstr "ESN:" + +msgctxt "#30602" +msgid "Change ESN" +msgstr "更換ESN" + +msgctxt "#30603" +msgid "Save system info" +msgstr "儲存系統資訊" + +msgctxt "#30604" +msgid "Widevine - Force security level" +msgstr "Widevine - 強制安全層級" + +msgctxt "#30605" +msgid "Force {}" +msgstr "強制 {}" + +msgctxt "#30606" +msgid "ESN set successfully[CR]Try to play a video" +msgstr "ESN設定成功[CR]嘗試播放影片" + +msgctxt "#30607" +msgid "This ESN cannot be used[CR]Try change ESN or reset ESN.[CR][CR]Error details:[CR]{}" +msgstr "此ESN不能使用[CR]嘗試更換ESN或重設ESN[CR][CR]詳細錯誤:[CR]{}" + +msgctxt "#30608" +msgid "Wrong ESN format" +msgstr "ESN格式錯誤" + +msgctxt "#30609" +msgid "Do you want to reset all settings?" +msgstr "您想要重設所有設定嗎?" + +msgctxt "#30610" +msgid "Use system-based encryption for credentials" +msgstr "將基於系統的加密方式使用於證書" + +# Help description for setting id 30610 +msgctxt "#30611" +msgid "[B]If disabled it will cause a security leak[/B]. If your operating system does not support it, you will be prompted to login each time you reboot the device, [B]only[/B] in this case disable it. If you change this setting you will be prompted to login again in the next time you reboot." +msgstr "[B]如果取消此設定將會導致安全性危險。[/B]如果你的作業系統不支援此功能,在每一次重新開機後,都會被要求重新登入。[B]只有[/B]在這種情形下才取消此設定。修改此設定會在下一次重新開機後被要求重新登入。" + +msgctxt "#30612" +msgid "MSL manifest version" +msgstr "MSL manifest 版本" + +#. Unused 30613 to 30619 +msgctxt "#30620" +msgid "This title will be available from:[CR]{}" +msgstr "此標題在以下可以找到:[CR]{}" + +msgctxt "#30621" +msgid "Do you want to play the promo trailer?" +msgstr "您想要撥放推薦預告片嗎?" + +#. The {} will be replaced with the graphic circle to mark when the functionality is active +msgctxt "#30622" +msgid "{} Remind me" +msgstr "{} 提醒我" + +#. Unused 30623 to 30699 +msgctxt "#30700" +msgid "New and popular" +msgstr "最新與熱門" + +msgctxt "#30701" +msgid "Marks started tv shows as watched" +msgstr "將已經開始的節目標記為已觀看" + +#. Description of setting ID #30701 +msgctxt "#30702" +msgid "Please note that by enabling this setting if filters are enabled in your Skin settings, items marked as watched may not be visible." +msgstr "請注意,若在外觀設定中有開啟過濾,開啟此設定可能會使被標記為已觀看的項目不可見。" + +msgctxt "#30703" +msgid "Override stream selection type" +msgstr "覆寫串流選擇類型" + +msgctxt "#30704" +msgid "Override the Stream selection type setting of the InputStream Adaptive add-on, to set how the audio / video streams quality will be chosen during playback." +msgstr "覆寫 InputStream Adaptive 插件的串流選擇類型,以設定如何選擇播放中的 音訊/影像 串流品質。" + +msgctxt "#30705" +msgid "Adaptive quality" +msgstr "自適應品質" + +msgctxt "#30706" +msgid "Fixed video quality" +msgstr "固定影像品質" + +msgctxt "#30707" +msgid "Ask video quality" +msgstr "詢問影像品質" + +msgctxt "#30720" +msgid "Please note that enabling this setting will force the removal of streams that are not within the set range and this may affect the operation of InputStream Adaptive. If possible try limiting the quality from the InputStream Adaptive settings first." +msgstr "請注意,啟用此設定將強制刪除不在設定範圍內的串流,這可能會影響 InputStream Adaptive 的運作。如果可以的話,請先嘗試從 InputStream Adaptive 設定中限制品質。" + +msgctxt "#30721" +msgid "Size" +msgstr "尺寸" + +#. Description of setting ID #30721 +msgctxt "#30722" +msgid "Minimise the black bars by apply a zoom effect. [ Fixed ] Sets the percentage of screen space that black bars can occupy, will be constant for all videos. [ Relative ] Sets the percentage of black band minimisation, will depend on each video size. [ Reset ] Reset the zoom effect previously applied." +msgstr "透過應用縮放效果最小化黑色邊。[ 固定 ]設置黑色邊可以佔據的螢幕空間百分比,對於所有影片都是固定的。 [ 相對 ] 設置黑色邊最小化的百分比,將取決於每個影片大小。 [ Reset ] 重置之前應用的縮放效果。" + +#. Unused 30723 +#. Description of setting ID #30723 +msgctxt "#30724" +msgid "This feature make use of Kodi zoom effect that is permanently stored in the \"View mode\" video setting, if you disable this feature in the future the zoom will be preserved, to reset the zoom change to \"Reset\" setting mode." +msgstr "此功能利用永久儲存在\"查看模式\"影片設定中的 Kodi 縮放效果,如果您將來禁用此功能,縮放將被保留,將縮放更改重置為\"重置\"設置模式." + +#. Unused 30725, 30726 +msgctxt "#30727" +msgid "Enable AV1 codec" +msgstr "啟用AV1編碼" + +msgctxt "#30728" +msgid "Add Netflix folders to Kodi library sources" +msgstr "將Netflix資料夾新增至Kodi資料庫來源" + +msgctxt "#30729" +msgid "The folders \"Netflix-Movies\" and \"Netflix-Shows\" have been added to Kodi sources, restart Kodi to view them in the Kodi [TV Shows] / [Movies] menus. Then edit the folders to set the appropriate content type and information provider scraper." +msgstr "\"Netflix-電影\" 與 \"Netflix-節目\" 資料夾已經加入至Kodi來源,重新啟動Kodi並於 [電視劇] / [電影] 選單中檢視。然後為資料夾設定合適的內容類型以及資訊提供者抓取器。" + +#. Description of setting ID 30224 +msgctxt "#30730" +msgid "[Manual] updates must be done manually by using \"Export new episodes\" context menu on each tv show, [Scheduled] you can establish automatic updates at scheduled times, [When Kodi starts] automatic updates start at Kodi start-up and will only be done once a day." +msgstr "[手動] 更新必須透過手動對於每一個電視劇使用 \"匯出新影集\" 內容選單,[排程] 你可以設定自動在排定的時間更新,[當Kodi啟動時] 在Kodi啟動時自動更新一天僅會執行一次。" + +#. Description of setting ID 30227 +msgctxt "#30731" +msgid "Synchronises the library with My list, if my list has been modified externally e.g. mobile app or website, the update will be postponed to the scheduled time." +msgstr "與我的清單同步,如果我的清單被外部修改過,例如手機應用程式或是網站,更新將會延後至下一個排定時間。" + +#. Description of setting ID 30185 +msgctxt "#30732" +msgid "If enabled, adds information to episodes or movies that are useful when Kodi information provider scraper does not provide data. This will also add the duration of the videos." +msgstr "如果啟用,將會在Kodi資訊提供者抓取器沒有提供資訊時把資訊新增至影集或電影。此選項也會一併新增影片時長。" + +#. Description of setting ID 30193 +msgctxt "#30733" +msgid "Only enable if \"Local Information\" information provider scraper is used, or if certain titles are not displayed in Kodi library despite having been added." +msgstr "只有在使用 \"本地資料\" 資訊提供者抓取器時啟用,或是某些標題儘管已經被加入於Kodi資料庫卻沒有顯示。" + +msgctxt "#30734" +msgid "Support" +msgstr "支援" + +msgctxt "#30735" +msgid "About Netflix add-on" +msgstr "關於 Netflix套件" + +msgctxt "#30736" +msgid "Automatically generates new ESNs (workaround for 540p limit)" +msgstr "自動產生新的 ESN(被限制於540p的解決方法)" + +msgctxt "#30737" +msgid "WARNING: Do not use the original device ESN of the official app." +msgstr "警告: 請勿使用官方程式原始裝置的ESN" + +msgctxt "#30738" +msgid "Enable audio offset" +msgstr "啟用聲音偏移" + +msgctxt "#30739" +msgid "Audio offset" +msgstr "聲音偏移" + +#. Description of setting ID 30739 +msgctxt "#30740" +msgid "Sets the audio offset by overriding the Kodi setting" +msgstr "使用聲音偏移覆蓋Kodi設定" + +msgctxt "#30741" +msgid "Enable VP9 Profile 2 (10/12 bit color depth)" +msgstr "啟用VP9設定檔2(10/12 bit色深)" + +msgctxt "#30742" +msgid "Do you want to enable HDR10 support?" +msgstr "是否要啟用HDR10支援?" + +msgctxt "#30743" +msgid "Do you want to enable HDR Dolby Vision support?" +msgstr "是否要啟用HDR Dolby Vision支援?" + +#. Description of setting ID 30081 +msgctxt "#30744" +msgid "The add-on already sets the appropriate HDCP version, but if required you can force the request of video streams for a different HDCP version." +msgstr "此插件已經設置了適當的HDCP版本,但如果需要,您可以強制請求不同HDCP版本的影片串流。" + +msgctxt "#30745" +msgid "If your device does not support this codec, you may experience black screen, no image or corrupted images. If so disable the codec." +msgstr "如果您的設備不支持此編解碼器,您可能會遇到黑屏、無圖像或圖像損壞的情況。如果遇到此情形,請禁用編解碼器。" + +msgctxt "#30746" +msgid "Black bars minimise mode" +msgstr "黑色邊最小化模式" + +msgctxt "#30747" +msgid "Fixed" +msgstr "已修復" + +msgctxt "#30748" +msgid "Relative" +msgstr "相對的" + +msgctxt "#30749" +msgid "Reset" +msgstr "重設" diff --git a/resources/lib/__init__.py b/resources/lib/__init__.py index e69de29bb..c6723a588 100644 --- a/resources/lib/__init__.py +++ b/resources/lib/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) +# Copyright (C) 2018 Caphm (original implementation module) + +# SPDX-License-Identifier: MIT +# See LICENSES/MIT.md for more information. diff --git a/resources/lib/api/__init__.py b/resources/lib/api/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/resources/lib/api/data_types.py b/resources/lib/api/data_types.py deleted file mode 100644 index eade53ad5..000000000 --- a/resources/lib/api/data_types.py +++ /dev/null @@ -1,251 +0,0 @@ -# -*- coding: utf-8 -*- -"""Convenience representations of datatypes returned by the API""" -# pylint: disable=too-few-public-methods -from __future__ import absolute_import, division, unicode_literals - -from collections import OrderedDict - -import resources.lib.common as common - -from .paths import resolve_refs - - -class LoLoMo(object): - """List of list of movies (lolomo)""" - # pylint: disable=invalid-name - def __init__(self, path_response, lolomoid=None): - self.data = path_response - common.debug('LoLoMo data: {}'.format(self.data)) - _filterout_contexts(self.data, ['billboard', 'showAsARow']) - self.id = (lolomoid - if lolomoid - else next(self.data['lolomos'].iterkeys())) - self.lists = OrderedDict( - (key, VideoList(self.data, key)) - for key, _ - in resolve_refs(self.data['lolomos'][self.id], self.data)) - - def __getitem__(self, key): - return _check_sentinel(self.data['lolomos'][self.id][key]) - - def get(self, key, default=None): - """Pass call on to the backing dict of this LoLoMo.""" - return self.data['lolomos'][self.id].get(key, default) - - def lists_by_context(self, context, break_on_first=False): - """Return a generator expression that iterates over all video - lists with the given context. - Will match any video lists with type contained in context - if context is a list.""" - # 'context' may contain a list of multiple contexts or a single - # 'context' can be passed as a string, convert to simplify code - if not isinstance(context, list): - context = [context] - - match_context = ((lambda context, contexts: context in contexts) - if isinstance(context, list) - else (lambda context, target: context == target)) - - # Keep sort order of context list - lists = {} - for context_name in context: - for list_id, video_list in self.lists.iteritems(): - if match_context(video_list['context'], context_name): - lists.update({list_id: VideoList(self.data, list_id)}) - if break_on_first: - break - return iter(lists.iteritems()) - - -class VideoList(object): - """A video list""" - # pylint: disable=invalid-name - def __init__(self, path_response, list_id=None): - self.perpetual_range_selector = path_response.get('_perpetual_range_selector') - self.data = path_response - has_data = True if path_response.get('lists') else False - self.videos = OrderedDict() - self.artitem = None - self.contained_titles = None - self.videoids = None - if has_data: - self.id = common.VideoId( - videoid=(list_id - if list_id - else next(self.data['lists'].iterkeys()))) - # self.title = self['displayName'] Not more used - self.videos = OrderedDict(resolve_refs(self.data['lists'][self.id.value], self.data)) - if self.videos: - self.artitem = next(self.videos.itervalues()) - self.contained_titles = _get_titles(self.videos) - try: - self.videoids = _get_videoids(self.videos) - except KeyError: - self.videoids = None - - def __getitem__(self, key): - return _check_sentinel(self.data['lists'][self.id.value][key]) - - def get(self, key, default=None): - """Pass call on to the backing dict of this VideoList.""" - return _check_sentinel(self.data['lists'][self.id.value].get(key, default)) - - -class VideoListSorted(object): - """A video list""" - # pylint: disable=invalid-name - def __init__(self, path_response, context_name, context_id, req_sort_order_type): - # common.debug('VideoListSorted data: {}'.format(path_response)) - self.perpetual_range_selector = path_response.get('_perpetual_range_selector') - self.data = path_response - self.context_name = context_name - has_data = True if (context_id and path_response.get(context_name) - and path_response[context_name].get(context_id)) or \ - (not context_id and path_response.get(context_name)) else False - self.data_lists = {} - self.videos = OrderedDict() - self.artitem = None - self.contained_titles = None - self.videoids = None - if has_data: - self.data_lists = path_response[context_name][context_id][req_sort_order_type] \ - if context_id else path_response[context_name][req_sort_order_type] - self.videos = OrderedDict(resolve_refs(self.data_lists, self.data)) - if self.videos: - self.artitem = next(self.videos.itervalues()) - self.contained_titles = _get_titles(self.videos) - try: - self.videoids = _get_videoids(self.videos) - except KeyError: - self.videoids = None - - def __getitem__(self, key): - return _check_sentinel(self.data_lists[key]) - - def get(self, key, default=None): - """Pass call on to the backing dict of this VideoList.""" - return _check_sentinel(self.data_lists.get(key, default)) - - -class SearchVideoList(object): - """A video list with search results""" - # pylint: disable=invalid-name - def __init__(self, path_response): - self.perpetual_range_selector = path_response.get('_perpetual_range_selector') - self.data = path_response - has_data = 'search' in path_response - self.videos = OrderedDict() - self.videoids = None - self.artitem = None - self.contained_titles = None - if has_data: - self.title = common.get_local_string(30100).format(self.data['search']['byTerm'].keys()[0][1:]) - self.videos = OrderedDict(resolve_refs(self.data['search']['byReference'].values()[0], self.data)) - self.videoids = _get_videoids(self.videos) - self.artitem = next(self.videos.itervalues(), None) - self.contained_titles = _get_titles(self.videos) - else: - common.debug('SearchVideoList - No data in path_response') - - def __getitem__(self, key): - return _check_sentinel(self.data['search'][key]) - - def get(self, key, default=None): - """Pass call on to the backing dict of this VideoList.""" - return _check_sentinel(self.data['search'].get(key, default)) - - -class CustomVideoList(object): - """A video list""" - # pylint: disable=invalid-name - def __init__(self, path_response): - self.perpetual_range_selector = path_response.get('_perpetual_range_selector') - self.data = path_response - self.title = common.get_local_string(30048) - self.videos = self.data['videos'] - self.videoids = _get_videoids(self.videos) - self.artitem = next(self.videos.itervalues()) - self.contained_titles = _get_titles(self.videos) - - def __getitem__(self, key): - return _check_sentinel(self.data[key]) - - def get(self, key, default=None): - """Pass call on to the backing dict of this VideoList.""" - return _check_sentinel(self.data.get(key, default)) - - -class SeasonList(object): - """A list of seasons. Includes tvshow art.""" - def __init__(self, videoid, path_response): - self.perpetual_range_selector = path_response.get('_perpetual_range_selector') - self.data = path_response - self.videoid = videoid - self.tvshow = self.data['videos'][self.videoid.tvshowid] - self.seasons = OrderedDict( - resolve_refs(self.tvshow['seasonList'], self.data)) - - -class EpisodeList(object): - """A list of episodes. Includes tvshow art.""" - def __init__(self, videoid, path_response): - self.perpetual_range_selector = path_response.get('_perpetual_range_selector') - self.data = path_response - self.videoid = videoid - self.tvshow = self.data['videos'][self.videoid.tvshowid] - self.season = self.data['seasons'][self.videoid.seasonid] - self.episodes = OrderedDict( - resolve_refs(self.season['episodes'], self.data)) - - -class SubgenreList(object): - """A list of subgenre.""" - def __init__(self, path_response): - common.debug('Subgenre data: {}'.format(path_response)) - self.lists = [] - if path_response: - self.perpetual_range_selector = path_response.get('_perpetual_range_selector') - genre_id = next(path_response.get('genres', {}).iterkeys()) - self.subgenre_data = path_response['genres'].get(genre_id, {}).get('subgenres') - self.lists = path_response['genres'].get(genre_id, {}).get('subgenres').items() - - -def _check_sentinel(value): - return (None - if isinstance(value, dict) and value.get('$type') == 'sentinel' - else value) - - -def _get_title(video): - """Get the title of a video (either from direct key or nested within - summary)""" - return video.get('title', video.get('summary', {}).get('title')) - - -def _get_titles(videos): - """Return a list of videos' titles""" - return [_get_title(video) - for video in videos.itervalues() - if _get_title(video)] - - -def _get_videoids(videos): - """Return a list of VideoId objects for the videos""" - return [common.VideoId.from_videolist_item(video) - for video in videos.itervalues()] - - -def _filterout_contexts(data, contexts): - """Deletes from the data all records related to the specified contexts""" - _id = next(data['lolomos'].iterkeys()) - for context in contexts: - for listid in data.get('lists', {}).keys(): - if not data['lists'][listid].get('context'): - continue - if data['lists'][listid]['context'] != context: - continue - for idkey in data['lolomos'][_id].keys(): - if listid in data['lolomos'][_id][idkey]: - del data['lolomos'][_id][idkey] - break - del data['lists'][listid] diff --git a/resources/lib/api/exceptions.py b/resources/lib/api/exceptions.py deleted file mode 100644 index 2982f0f04..000000000 --- a/resources/lib/api/exceptions.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -"""Common exception types for API operations""" -from __future__ import absolute_import, division, unicode_literals - - -class MissingCredentialsError(Exception): - """There are no stored credentials to load""" - pass - - -class InvalidReferenceError(Exception): - """The provided reference cannot be dealt with as it is in an - unexpected format""" - pass - - -class InvalidVideoListTypeError(Exception): - """No video list of a given was available""" - pass - - -class WebsiteParsingError(Exception): - """Parsing info from the Netflix Website failed""" - pass - - -class InvalidAuthURLError(WebsiteParsingError): - """The authURL is invalid""" - pass - - -class InvalidProfilesError(WebsiteParsingError): - """Cannot extract profiles from Netflix webpage""" - pass - - -class InvalidMembershipStatusError(WebsiteParsingError): - """The user logging in does not have a valid subscription""" - pass - - -class LoginFailedError(Exception): - """The login attempt has failed""" - pass - - -class LoginValidateError(Exception): - """The login validate has generated an error""" - pass - - -class NotLoggedInError(Exception): - """The requested operation requires a valid and active login, which - is not present""" - pass - - -class APIError(Exception): - """The requested API operation has resulted in an error""" - pass diff --git a/resources/lib/api/paths.py b/resources/lib/api/paths.py deleted file mode 100644 index b79ed2063..000000000 --- a/resources/lib/api/paths.py +++ /dev/null @@ -1,210 +0,0 @@ -# -*- coding: utf-8 -*- -"""Path info to query the Shakti pathEvaluator""" -from __future__ import absolute_import, division, unicode_literals - -import resources.lib.common as common - -from .exceptions import InvalidReferenceError - -MAX_PATH_REQUEST_SIZE = 47 # Stands for 48 results, is the default value defined by netflix for a single request - -RANGE_SELECTOR = 'RANGE_SELECTOR' - -ART_SIZE_POSTER = '_342x684' -ART_SIZE_FHD = '_1920x1080' -ART_SIZE_SD = '_665x375' - -LENGTH_ATTRIBUTES = { - 'stdlist': lambda r, context, key: len(r[context][key]), - 'stdlist_wid': lambda r, context, uid, key: len(r[context][uid][key]), - 'searchlist': lambda r, context, key: len(next(r[context][key].itervalues())) -} -"""Predefined lambda expressions that return the number of video results within a path response dict""" - -ART_PARTIAL_PATHS = [ - ['boxarts', [ART_SIZE_SD, ART_SIZE_FHD, ART_SIZE_POSTER], 'jpg'], - ['interestingMoment', [ART_SIZE_SD, ART_SIZE_FHD], 'jpg'], - ['storyarts', '_1632x873', 'jpg'], # storyarts seem no more used, never found it in the results - ['bb2OGLogo', '_550x124', 'png'], - ['BGImages', '720', 'jpg'] -] - -VIDEO_LIST_PARTIAL_PATHS = [ - [['summary', 'title', 'synopsis', 'regularSynopsis', 'evidence', 'queue', - 'episodeCount', 'info', 'maturity', 'runtime', 'seasonCount', - 'releaseYear', 'userRating', 'numSeasonsLabel', 'bookmarkPosition', - 'watched', 'delivery']], - [['genres', 'tags', 'creators', 'directors', 'cast'], - {'from': 0, 'to': 10}, ['id', 'name']] -] + ART_PARTIAL_PATHS - -VIDEO_LIST_BASIC_PARTIAL_PATHS = [ - [['title', 'queue', 'watched']] -] - -GENRE_PARTIAL_PATHS = [ - [["id", "requestId", "summary", "name"]], - [{"from": 0, "to": 50}, - ["context", "displayName", "genreId", "id", "isTallRow", "length", - "requestId", "type", "videoId"]] -] - -SEASONS_PARTIAL_PATHS = [ - ['seasonList', RANGE_SELECTOR, 'summary'], - ['title'] -] + ART_PARTIAL_PATHS - -EPISODES_PARTIAL_PATHS = [ - [['summary', 'synopsis', 'title', 'runtime', 'releaseYear', 'queue', - 'info', 'maturity', 'userRating', 'bookmarkPosition', 'creditsOffset', - 'watched', 'delivery']], - [['genres', 'tags', 'creators', 'directors', 'cast'], - {'from': 0, 'to': 10}, ['id', 'name']] -] + ART_PARTIAL_PATHS - -INFO_MAPPINGS = { - 'title': 'title', - 'year': 'releaseYear', - 'plot': 'synopsis', - 'season': ['summary', 'season'], - 'episode': ['summary', 'episode'], - 'rating': ['userRating', 'matchScore'], - 'userrating': ['userRating', 'userRating'], - 'mpaa': ['maturity', 'rating', 'value'], - 'duration': 'runtime', - # 'bookmark': 'bookmarkPosition', - # 'playcount': 'watched' -} - -TRAILER_PARTIAL_PATHS = [ - [['availability', 'summary', 'synopsis', 'title', 'trackId', 'delivery']] -] + ART_PARTIAL_PATHS - -INFO_TRANSFORMATIONS = { - 'rating': lambda r: r / 10, - 'playcount': lambda w: int(w) # pylint: disable=unnecessary-lambda -} - -REFERENCE_MAPPINGS = { - 'cast': 'cast', - 'director': 'directors', - 'writer': 'creators', - 'genre': 'genres' -} - - -def resolve_refs(references, targets): - """Return a generator expression that returns the objects in targets - by resolving the references in sorted order""" - return (common.get_path(ref, targets, include_key=True) - for index, ref in iterate_references(references)) - - -def iterate_references(source): - """Generator expression that iterates over a dictionary of - index=>reference pairs (sorted in ascending order by indices) until it - reaches the first empty reference, which signals the end of the reference - list. - Items with a key that do not represent an integer are ignored.""" - for index, ref in sorted({int(k): v - for k, v in source.iteritems() - if common.is_numeric(k)}.iteritems()): - path = reference_path(ref) - if path is None: - break - elif path[0] == 'characters': - # TODO: Implement handling of character references in Kids profiles - continue - else: - yield (index, path) - - -def count_references(source): - counter = 0 - for index, ref in sorted({int(k): v # pylint: disable=unused-variable - for k, v in source.iteritems() - if common.is_numeric(k)}.iteritems()): - path = reference_path(ref) - - if path is None: - continue - elif path[0] == 'characters': - continue - else: - counter += 1 - return counter - - -def reference_path(ref): - """Return the actual reference path (a list of path items to follow) - for a reference item. - - The Netflix API returns references in several different formats. - In both cases, we want to get at the innermost list, which describes - the path to follow when resolving the reference: - - List-based references: - [ - "lists", - "09a4eb6f-8f6b-45fe-a65b-c64c4fcdc6b8_60070239X20XX1539870260807" - ] - - Dict-based references which have a type and a value: - { - "$type": "ref", - "value": [ - "videos", - "80018294" - ] - } - - Empty references indicate the end of a list of references if there - are fewer entries available than were requested. They are always a dict, - regardless of valid references in the list being list-based or dict-based. - They don't have a value attribute and are either of type sentinel or atom: - { "$type": "sentinel" } - or - { "$type": "atom" } - - In some cases, references are requested via the 'reference' attribute of - a Netlix list type like so: - ["genres", "83", "rw", "shortform", - { "from": 0, "to": 50 }, - { "from": 0, "to": 7 }, - "reference", - "ACTUAL ATTRIBUTE OF REFERENCED ITEM"] - In this case, the reference we want to get the value of is nested into an - additional 'reference' attribute like so: - - list-based nested reference: - { - "reference": [ <== additional nesting - "videos", - "80178971" - ] - } - - dict-based nested reference: - { - "reference": { <== additional nesting - "$type": "ref", - "value": [ - "videos", - "80018294" - ] - } - } - To get to the value, we simply remove the additional layer of nesting by - doing ref = ref['reference'] and continue with analyzing ref. - """ - ref = _remove_nesting(ref) - if isinstance(ref, list): - return ref - elif isinstance(ref, dict): - return ref['value'] if ref.get('$type') == 'ref' else None - raise InvalidReferenceError( - 'Unexpected reference format encountered: {}'.format(ref)) - - -def _remove_nesting(ref): - """Remove the outer layer of nesting if ref is a nested reference. - Return the original reference if it's not nested""" - return (ref['reference'] - if isinstance(ref, dict) and 'reference' in ref - else ref) diff --git a/resources/lib/api/shakti.py b/resources/lib/api/shakti.py deleted file mode 100644 index 78e14e9f8..000000000 --- a/resources/lib/api/shakti.py +++ /dev/null @@ -1,418 +0,0 @@ -# -*- coding: utf-8 -*- -"""Access to Netflix's Shakti API""" -from __future__ import absolute_import, division, unicode_literals - -from functools import wraps - -from resources.lib.globals import g -import resources.lib.common as common -import resources.lib.cache as cache -import resources.lib.kodi.ui as ui - -from .data_types import (LoLoMo, VideoList, VideoListSorted, SeasonList, EpisodeList, - SearchVideoList, CustomVideoList, SubgenreList) -from .paths import (VIDEO_LIST_PARTIAL_PATHS, VIDEO_LIST_BASIC_PARTIAL_PATHS, - SEASONS_PARTIAL_PATHS, EPISODES_PARTIAL_PATHS, ART_PARTIAL_PATHS, - GENRE_PARTIAL_PATHS, RANGE_SELECTOR, MAX_PATH_REQUEST_SIZE, - TRAILER_PARTIAL_PATHS) -from .exceptions import (InvalidVideoListTypeError, APIError, MissingCredentialsError) - - -def catch_api_errors(func): - """Decorator that catches API errors and displays a notification""" - # pylint: disable=missing-docstring - @wraps(func) - def api_error_wrapper(*args, **kwargs): - try: - return func(*args, **kwargs) - except APIError as exc: - ui.show_notification(common.get_local_string(30118).format(exc)) - return api_error_wrapper - - -def logout(): - """Logout of the current account""" - url = common.build_url(['root'], mode=g.MODE_DIRECTORY) - common.make_call('logout', url) - g.CACHE.invalidate() - - -def login(): - """Perform a login""" - g.CACHE.invalidate() - try: - ui.ask_credentials() - if not common.make_call('login'): - # Login not validated - # ui.show_notification(common.get_local_string(30009)) - return False - return True - except MissingCredentialsError: - # Aborted from user or leave an empty field - ui.show_notification(common.get_local_string(30112)) - raise MissingCredentialsError - - -def update_profiles_data(): - """Fetch profiles data from website""" - return common.make_call('update_profiles_data') - - -def activate_profile(profile_id): - """Activate the profile with the given ID""" - if common.make_call('activate_profile', profile_id): - g.CACHE.invalidate() - - -@common.time_execution(immediate=False) -@cache.cache_output(g, cache.CACHE_COMMON, fixed_identifier='root_lists') -def root_lists(): - """Retrieve initial video lists to display on homepage""" - common.debug('Requesting root lists from API') - return LoLoMo(common.make_call( - 'path_request', - [['lolomo', - {'from': 0, 'to': 40}, - ['displayName', 'context', 'id', 'index', 'length', 'genreId']]] + - # Titles of first 4 videos in each video list - [['lolomo', - {'from': 0, 'to': 40}, - {'from': 0, 'to': 3}, 'reference', ['title', 'summary']]] + - # Art for first video in each video list - # (will be displayed as video list art) - build_paths(['lolomo', - {'from': 0, 'to': 40}, - {'from': 0, 'to': 0}, 'reference'], - ART_PARTIAL_PATHS))) - - -@cache.cache_output(g, cache.CACHE_COMMON, identify_from_kwarg_name='list_type') -def list_id_for_type(list_type): - """Return the dynamic video list ID for a video list of known type""" - try: - list_id = next(root_lists().lists_by_context(list_type))[0] - except StopIteration: - raise InvalidVideoListTypeError( - 'No lists of type {} available'.format(list_type)) - common.debug( - 'Resolved list ID for {} to {}'.format(list_type, list_id)) - return list_id - - -@common.time_execution(immediate=False) -@cache.cache_output(g, cache.CACHE_COMMON, identify_from_kwarg_name='list_id') -def video_list(list_id, perpetual_range_start=None): - """Retrieve a single video list - some of this type of request seems to have results fixed at ~40 from netflix - and the 'length' tag never return to the actual total count of the elements - """ - common.debug('Requesting video list {}'.format(list_id)) - paths = build_paths(['lists', list_id, RANGE_SELECTOR, 'reference'], - VIDEO_LIST_PARTIAL_PATHS) - callargs = { - 'paths': paths, - 'length_params': ['stdlist', ['lists', list_id]], - 'perpetual_range_start': perpetual_range_start - } - return VideoList(common.make_call('perpetual_path_request', callargs)) - - -@common.time_execution(immediate=False) -@cache.cache_output(g, cache.CACHE_COMMON, identify_from_kwarg_name='context_id', - identify_append_from_kwarg_name='perpetual_range_start') -def video_list_sorted(context_name, context_id=None, perpetual_range_start=None, menu_data=None): - """Retrieve a single video list sorted - this type of request allows to obtain more than ~40 results - """ - common.debug( - 'Requesting video list sorted for context name: "{}", context id: "{}"'.format(context_name, context_id)) - base_path = [context_name] - response_type = 'stdlist' - if context_id: - base_path.append(context_id) - response_type = 'stdlist_wid' - - # enum order: AZ|ZA|Suggested|Year - sort_order_types = ['az', 'za', 'su', 'yr'] - req_sort_order_type = sort_order_types[ - int(g.ADDON.getSettingInt('_'.join(('menu_sortorder', menu_data['path'][1]))))] - base_path.append(req_sort_order_type) - paths = build_paths(base_path + [RANGE_SELECTOR], VIDEO_LIST_PARTIAL_PATHS) - callargs = { - 'paths': paths, - 'length_params': [response_type, base_path], - 'perpetual_range_start': perpetual_range_start - } - return VideoListSorted(common.make_call('perpetual_path_request', callargs), - context_name, context_id, req_sort_order_type) - - -@common.time_execution(immediate=False) -def custom_video_list(video_ids): - """Retrieve a video list which contains the videos specified by - video_ids""" - common.debug('Requesting custom video list with {} videos' - .format(len(video_ids))) - return CustomVideoList(common.make_call( - 'path_request', - build_paths(['videos', video_ids], VIDEO_LIST_PARTIAL_PATHS))) - - -@common.time_execution(immediate=False) -@cache.cache_output(g, cache.CACHE_GENRES, identify_from_kwarg_name='genre_id') -def genre(genre_id): - """Retrieve LoLoMos for the given genre""" - common.debug('Requesting LoLoMos for genre {}'.format(genre_id)) - return LoLoMo(common.make_call( - 'path_request', - build_paths(['genres', genre_id, 'rw'], GENRE_PARTIAL_PATHS) + - # Titles and art of standard lists' items - build_paths(['genres', genre_id, 'rw', - {"from": 0, "to": 50}, - {"from": 0, "to": 3}, "reference"], - [['title', 'summary']] + ART_PARTIAL_PATHS) + - # IDs and names of subgenres - [['genres', genre_id, 'subgenres', {'from': 0, 'to': 30}, - ['id', 'name']]])) - - -def subgenre(genre_id): - """Retrieve subgenres for the given genre""" - common.debug('Requesting subgenres for genre {}'.format(genre_id)) - return SubgenreList(common.make_call( - 'path_request', - [['genres', genre_id, 'subgenres', {'from': 0, 'to': 47}, ['id', 'name']]])) - - -@common.time_execution(immediate=False) -@cache.cache_output(g, cache.CACHE_COMMON) -def seasons(videoid): - """Retrieve seasons of a TV show""" - if videoid.mediatype != common.VideoId.SHOW: - raise common.InvalidVideoId('Cannot request season list for {}' - .format(videoid)) - common.debug('Requesting season list for show {}'.format(videoid)) - paths = build_paths(['videos', videoid.tvshowid], SEASONS_PARTIAL_PATHS) - callargs = { - 'paths': paths, - 'length_params': ['stdlist_wid', ['videos', videoid.tvshowid, 'seasonList']] - } - return SeasonList(videoid, common.make_call('perpetual_path_request', callargs)) - - -@common.time_execution(immediate=False) -@cache.cache_output(g, cache.CACHE_COMMON) -def episodes(videoid): - """Retrieve episodes of a season""" - if videoid.mediatype != common.VideoId.SEASON: - raise common.InvalidVideoId('Cannot request episode list for {}' - .format(videoid)) - common.debug('Requesting episode list for {}'.format(videoid)) - paths = [['seasons', videoid.seasonid, 'summary']] - paths.extend(build_paths(['seasons', videoid.seasonid, 'episodes', RANGE_SELECTOR], - EPISODES_PARTIAL_PATHS)) - paths.extend(build_paths(['videos', videoid.tvshowid], - ART_PARTIAL_PATHS + [['title']])) - callargs = { - 'paths': paths, - 'length_params': ['stdlist_wid', ['seasons', videoid.seasonid, 'episodes']] - } - return EpisodeList(videoid, common.make_call('perpetual_path_request', callargs)) - - -@common.time_execution(immediate=False) -@cache.cache_output(g, cache.CACHE_SUPPLEMENTAL) -def supplemental_video_list(videoid, supplemental_type): - """Retrieve a supplemental video list""" - if videoid.mediatype != common.VideoId.SHOW and videoid.mediatype != common.VideoId.MOVIE: - raise common.InvalidVideoId('Cannot request supplemental list for {}' - .format(videoid)) - common.debug('Requesting supplemental ({}) list for {}' - .format(supplemental_type, videoid)) - callargs = build_paths( - ['videos', videoid.value, supplemental_type, - {"from": 0, "to": 35}], TRAILER_PARTIAL_PATHS) - return VideoListSorted(common.make_call('path_request', callargs), - 'videos', videoid.value, supplemental_type) - - -@common.time_execution(immediate=False) -@cache.cache_output(g, cache.CACHE_COMMON) -def single_info(videoid): - """Retrieve info for a single episode""" - if videoid.mediatype not in [common.VideoId.EPISODE, common.VideoId.MOVIE]: - raise common.InvalidVideoId('Cannot request info for {}' - .format(videoid)) - common.debug('Requesting info for {}'.format(videoid)) - paths = build_paths(['videos', videoid.value], EPISODES_PARTIAL_PATHS) - if videoid.mediatype == common.VideoId.EPISODE: - paths.extend(build_paths(['videos', videoid.tvshowid], - ART_PARTIAL_PATHS + [['title']])) - return common.make_call('path_request', paths) - - -def custom_video_list_basicinfo(list_id): - """Retrieve a single video list - used only to know which videos are in my list without requesting additional information - """ - common.debug('Requesting custom video list basic info {}'.format(list_id)) - paths = build_paths(['lists', list_id, RANGE_SELECTOR, 'reference'], - VIDEO_LIST_BASIC_PARTIAL_PATHS) - callargs = { - 'paths': paths, - 'length_params': ['stdlist', ['lists', list_id]], - 'perpetual_range_start': None, - 'no_limit_req': True - } - # When the list is empty the server returns an empty response - path_response = common.make_call('perpetual_path_request', callargs) - return {} if not path_response else VideoList(path_response) - - -@cache.cache_output(g, cache.CACHE_COMMON, fixed_identifier='my_list_items') -def mylist_items(): - """Return a list of all the items currently contained in my list""" - common.debug('Try to perform a request to get the id list of the videos in my list') - try: - items = [] - videos = custom_video_list_basicinfo(list_id_for_type(g.MAIN_MENU_ITEMS['myList']['lolomo_contexts'][0])) - if videos: - items = [video_id for video_id, video in videos.videos.iteritems() - if video['queue'].get('inQueue', False)] - return items - except InvalidVideoListTypeError: - return [] - - -@catch_api_errors -@common.time_execution(immediate=False) -def rate(videoid, rating): - """Rate a video on Netflix""" - common.debug('Rating {} as {}'.format(videoid.value, rating)) - # In opposition to Kodi, Netflix uses a rating from 0 to in 0.5 steps - rating = min(10, max(0, rating)) / 2 - common.make_call( - 'post', - {'component': 'set_video_rating', - 'params': { - 'titleid': videoid.value, - 'rating': rating}}) - ui.show_notification(common.get_local_string(30127).format(rating * 2)) - - -@catch_api_errors -@common.time_execution(immediate=False) -def update_my_list(videoid, operation): - """Call API to update my list with either add or remove action""" - common.debug('My List: {} {}'.format(operation, videoid)) - # We want the tvshowid for seasons and episodes (such videoids may be - # passed by the mylist/library auto-sync feature) - videoid_value = (videoid.movieid - if videoid.mediatype == common.VideoId.MOVIE - else videoid.tvshowid) - common.make_call( - 'post', - {'component': 'update_my_list', - 'data': { - 'operation': operation, - 'videoId': int(videoid_value)}}) - ui.show_notification(common.get_local_string(30119)) - try: - g.CACHE.invalidate_entry(cache.CACHE_COMMON, list_id_for_type('queue')) - except InvalidVideoListTypeError: - pass - g.CACHE.invalidate_entry(cache.CACHE_COMMON, 'queue') - g.CACHE.invalidate_entry(cache.CACHE_COMMON, 'mylist') - g.CACHE.invalidate_entry(cache.CACHE_COMMON, 'my_list_items') - g.CACHE.invalidate_entry(cache.CACHE_COMMON, 'root_lists') - - -@common.time_execution(immediate=False) -def metadata(videoid, refresh=False): - """Retrieve additional metadata for the given VideoId""" - - # Invalidate cache if we need to refresh the all metadata - if refresh: - g.CACHE.invalidate_entry(cache.CACHE_METADATA, videoid, True) - - if videoid.mediatype not in [common.VideoId.EPISODE, common.VideoId.SEASON]: - return _metadata(videoid), None - if videoid.mediatype == common.VideoId.SEASON: - return _metadata(videoid.derive_parent(None)), None - try: - return _episode_metadata(videoid) - except KeyError as exc: - # Episode metadata may not exist if its a new episode and cached - # data is outdated. In this case, invalidate the cache entry and - # try again safely (if it doesn't exist this time, there is no - # metadata for the episode, so we assign an empty dict). - common.debug('{}, refreshing cache'.format(exc)) - g.CACHE.invalidate_entry(cache.CACHE_METADATA, videoid.tvshowid) - try: - return _episode_metadata(videoid) - except KeyError as exc: - common.error(exc) - return {}, None - - -@common.time_execution(immediate=False) -def _episode_metadata(videoid): - show_metadata = _metadata(videoid) - episode_metadata, season_metadata = common.find_episode_metadata( - videoid, show_metadata) - return episode_metadata, season_metadata, show_metadata - - -@common.time_execution(immediate=False) -@cache.cache_output(g, cache.CACHE_METADATA, identify_from_kwarg_name='video_id', - ttl=g.CACHE_METADATA_TTL, to_disk=True) -def _metadata(video_id): - """Retrieve additional metadata for a video.This is a separate method from - metadata(videoid) to work around caching issues when new episodes are added - to a show by Netflix.""" - common.debug('Requesting metadata for {}'.format(video_id)) - # Always use params 'movieid' to all videoid identifier - return common.make_call( - 'get', - { - 'component': 'metadata', - 'req_type': 'api', - 'params': {'movieid': video_id.value} - })['video'] - - -@common.time_execution(immediate=False) -def search(search_term, perpetual_range_start=None): - """Retrieve a video list of search results""" - common.debug('Searching for {}'.format(search_term)) - base_path = ['search', 'byTerm', '|' + search_term, 'titles', MAX_PATH_REQUEST_SIZE] - paths = [base_path + [['id', 'name', 'requestId']]] - paths.extend(build_paths(base_path + [RANGE_SELECTOR, 'reference'], - VIDEO_LIST_PARTIAL_PATHS)) - callargs = { - 'paths': paths, - 'length_params': ['searchlist', ['search', 'byReference']], - 'perpetual_range_start': perpetual_range_start - } - return SearchVideoList(common.make_call('perpetual_path_request', callargs)) - - -@common.time_execution(immediate=False) -def verify_pin(pin): - """Send adult PIN to Netflix and verify it.""" - # pylint: disable=broad-except - try: - return common.make_call( - 'post', - {'component': 'adult_pin', - 'data': { - 'pin': pin}}).get('success', False) - except Exception: - return False - - -def build_paths(base_path, partial_paths): - """Build a list of full paths by concatenating each partial path - with the base path""" - paths = [base_path + partial_path for partial_path in partial_paths] - return paths diff --git a/resources/lib/api/website.py b/resources/lib/api/website.py deleted file mode 100644 index 5fa28fd61..000000000 --- a/resources/lib/api/website.py +++ /dev/null @@ -1,250 +0,0 @@ -# -*- coding: utf-8 -*- -"""Parsing of Netflix Website""" -from __future__ import absolute_import, division, unicode_literals - -import json -import traceback -from re import compile as recompile, DOTALL, sub -from collections import OrderedDict - -import resources.lib.common as common - -from resources.lib.database.db_utils import (TABLE_SESSION) -from resources.lib.globals import g -from .paths import resolve_refs -from .exceptions import (InvalidProfilesError, InvalidAuthURLError, - WebsiteParsingError, LoginValidateError) - -PAGE_ITEMS_INFO = [ - 'models/userInfo/data/name', - 'models/userInfo/data/guid', # Main profile guid - 'models/userInfo/data/userGuid', # Current profile guid - 'models/userInfo/data/countryOfSignup', - 'models/userInfo/data/membershipStatus', - 'models/userInfo/data/isTestAccount', - 'models/userInfo/data/deviceTypeId', - 'models/userInfo/data/isAdultVerified', - 'models/userInfo/data/isKids', - 'models/userInfo/data/pinEnabled', - 'models/serverDefs/data/BUILD_IDENTIFIER', - 'models/esnGeneratorModel/data/esn', - 'models/memberContext/data/geo/preferredLocale' -] - -PAGE_ITEMS_API_URL = { - 'auth_url': 'models/userInfo/data/authURL', - # 'ichnaea_log': 'models/serverDefs/data/ICHNAEA_ROOT', can be for XSS attacks? - 'api_endpoint_root_url': 'models/serverDefs/data/API_ROOT', - 'api_endpoint_url': 'models/playerModel/data/config/ui/initParams/apiUrl' -} - -PAGE_ITEM_ERROR_CODE = 'models/flow/data/fields/errorCode/value' -PAGE_ITEM_ERROR_CODE_LIST = 'models\\i18nStrings\\data\\login/login' - -JSON_REGEX = r'netflix\.%s\s*=\s*(.*?);\s*' -AVATAR_SUBPATH = ['images', 'byWidth', '320', 'value'] - - -@common.time_execution(immediate=True) -def extract_session_data(content): - """ - Call all the parsers we need to extract all - the session relevant data from the HTML page - """ - common.debug('Extracting session data...') - falcor_cache = extract_json(content, 'falcorCache') - react_context = extract_json(content, 'reactContext') - extract_profiles(falcor_cache) - user_data = extract_userdata(react_context) - api_data = extract_api_data(react_context) - # Save only some info of the current profile from user data - g.LOCAL_DB.set_value('build_identifier', user_data.get('BUILD_IDENTIFIER'), TABLE_SESSION) - if not g.LOCAL_DB.get_value('esn', table=TABLE_SESSION): - g.LOCAL_DB.set_value('esn', generate_esn(user_data), TABLE_SESSION) - g.LOCAL_DB.set_value('locale_id', user_data.get('preferredLocale').get('id', 'en-US')) - # Save api urls - for key, path in api_data.items(): - g.LOCAL_DB.set_value(key, path, TABLE_SESSION) - if user_data.get('membershipStatus') != 'CURRENT_MEMBER': - common.debug(user_data) - # Ignore this for now - # raise InvalidMembershipStatusError(user_data.get('membershipStatus')) - - -def validate_session_data(content): - """ - Try calling the parsers to extract the session data, to verify the login - """ - common.debug('Validating session data...') - extract_json(content, 'falcorCache') - react_context = extract_json(content, 'reactContext') - extract_userdata(react_context, False) - extract_api_data(react_context, False) - - -@common.time_execution(immediate=True) -def extract_profiles(falkor_cache): - """Extract profile information from Netflix website""" - try: - profiles_list = OrderedDict(resolve_refs(falkor_cache['profilesList'], falkor_cache)) - if not profiles_list: - common.error('The profiles list from falkor cache is empty. ' - 'The profiles were not parsed nor updated!') - else: - _delete_non_existing_profiles(profiles_list) - sort_order = 0 - for guid, profile in profiles_list.items(): - common.debug('Parsing profile {}'.format(guid)) - avatar_url = _get_avatar(falkor_cache, profile) - profile = profile['summary']['value'] - debug_info = ['profileName', 'isAccountOwner', 'isActive', 'isKids', 'maturityLevel'] - for k_info in debug_info: - common.debug('Profile info {}'.format({k_info: profile[k_info]})) - is_active = profile.pop('isActive') - g.LOCAL_DB.set_profile(guid, is_active, sort_order) - g.SHARED_DB.set_profile(guid, sort_order) - for key, value in profile.items(): - g.LOCAL_DB.set_profile_config(key, value, guid) - g.LOCAL_DB.set_profile_config('avatar', avatar_url, guid) - sort_order += 1 - except Exception: - common.error(traceback.format_exc()) - common.error('Falkor cache: {}'.format(falkor_cache)) - raise InvalidProfilesError - - -def _delete_non_existing_profiles(profiles_list): - list_guid = g.LOCAL_DB.get_guid_profiles() - for guid in list_guid: - if guid not in profiles_list.keys(): - common.debug('Deleting non-existing profile {}'.format(guid)) - g.LOCAL_DB.delete_profile(guid) - g.SHARED_DB.delete_profile(guid) - - -def _get_avatar(falkor_cache, profile): - try: - profile['avatar']['value'].extend(AVATAR_SUBPATH) - return common.get_path(profile['avatar']['value'], falkor_cache) - except KeyError: - common.warn('Cannot find avatar for profile {guid}' - .format(guid=profile['summary']['value']['guid'])) - return '' - - -@common.time_execution(immediate=True) -def extract_userdata(react_context, debug_log=True): - """Extract essential userdata from the reactContext of the webpage""" - common.debug('Extracting userdata from webpage') - user_data = {} - for path in ([path_item for path_item in path.split('/')] - for path in PAGE_ITEMS_INFO): - try: - extracted_value = {path[-1]: common.get_path(path, react_context)} - user_data.update(extracted_value) - if 'esn' not in path and debug_log: - common.debug('Extracted {}'.format(extracted_value)) - except (AttributeError, KeyError): - common.debug('Could not extract {}'.format(path)) - return user_data - - -def extract_api_data(react_context, debug_log=True): - """Extract api urls from the reactContext of the webpage""" - common.debug('Extracting api urls from webpage') - api_data = {} - for key, value in PAGE_ITEMS_API_URL.items(): - path = [path_item for path_item in value.split('/')] - try: - extracted_value = {key: common.get_path(path, react_context)} - api_data.update(extracted_value) - if debug_log: - common.debug('Extracted {}'.format(extracted_value)) - except (AttributeError, KeyError): - common.debug('Could not extract {}'.format(path)) - return assert_valid_auth_url(api_data) - - -def assert_valid_auth_url(user_data): - """Raise an exception if user_data does not contain a valid authURL""" - if len(user_data.get('auth_url', '')) != 42: - raise InvalidAuthURLError('authURL is invalid') - return user_data - - -def validate_login(content): - react_context = extract_json(content, 'reactContext') - path_code_list = [path_item for path_item in PAGE_ITEM_ERROR_CODE_LIST.split('\\')] - path_error_code = [path_item for path_item in PAGE_ITEM_ERROR_CODE.split('/')] - if common.check_path_exists(path_error_code, react_context): - # If the path exists, a login error occurs - try: - error_code_list = common.get_path(path_code_list, react_context) - error_code = common.get_path(path_error_code, react_context) - common.debug('Login not valid, error code {}'.format(error_code)) - error_description = common.get_local_string(30102) + error_code - if error_code in error_code_list: - error_description = error_code_list[error_code] - if 'email_' + error_code in error_code_list: - error_description = error_code_list['email_' + error_code] - if 'login_' + error_code in error_code_list: - error_description = error_code_list['login_' + error_code] - return common.remove_html_tags(error_description) - except (AttributeError, KeyError): - common.error( - 'Something is wrong in PAGE_ITEM_ERROR_CODE or PAGE_ITEM_ERROR_CODE_LIST paths.' - 'react_context data may have changed.') - raise LoginValidateError - return None - - -def generate_esn(user_data): - """Generate an ESN if on android or return the one from user_data""" - import subprocess - try: - manufacturer = subprocess.check_output( - ['/system/bin/getprop', 'ro.product.manufacturer']) - if manufacturer: - esn = ('NFANDROID1-PRV-' - if subprocess.check_output( - ['/system/bin/getprop', 'ro.build.characteristics'] - ).strip(' \t\n\r') != 'tv' - else 'NFANDROID2-PRV-') - inp = subprocess.check_output( - ['/system/bin/getprop', 'ro.nrdp.modelgroup']).strip(' \t\n\r') - if not inp: - esn += 'T-L3-' - else: - esn += inp + '-' - esn += '{:=<5}'.format(manufacturer.strip(' \t\n\r').upper()) - inp = subprocess.check_output( - ['/system/bin/getprop', 'ro.product.model']) - esn += inp.strip(' \t\n\r').replace(' ', '=').upper() - esn = sub(r'[^A-Za-z0-9=-]', '=', esn) - common.debug('Android generated ESN:' + esn) - return esn - except OSError: - pass - - return user_data.get('esn', '') - - -@common.time_execution(immediate=True) -def extract_json(content, name): - """Extract json from netflix content page""" - common.debug('Extracting {} JSON'.format(name)) - json_str = None - try: - json_array = recompile(JSON_REGEX % name, DOTALL).findall(content) - json_str = json_array[0] - json_str = json_str.replace('\"', '\\"') # Escape double-quotes - json_str = json_str.replace('\\s', '\\\\s') # Escape \s - json_str = json_str.replace('\\n', '\\\\n') # Escape line feed - json_str = json_str.replace('\\t', '\\\\t') # Escape tab - json_str = json_str.decode('unicode_escape') # finally decoding... - return json.loads(json_str) - except Exception: - if json_str: - common.error('JSON string trying to load: {}'.format(json_str)) - common.error(traceback.format_exc()) - raise WebsiteParsingError('Unable to extract {}'.format(name)) diff --git a/resources/lib/cache.py b/resources/lib/cache.py deleted file mode 100644 index d0eaa8654..000000000 --- a/resources/lib/cache.py +++ /dev/null @@ -1,347 +0,0 @@ -# -*- coding: utf-8 -*- -"""General caching facilities. Caches are segmented into buckets. -Within each bucket, identifiers for cache entries must be unique. - -Must not be used within these modules, because stale values may -be used and cause inconsistencies: -resources.lib.self.common -resources.lib.services -resources.lib.kodi.ui -resources.lib.services.nfsession -""" -from __future__ import absolute_import, division, unicode_literals - -import os -from time import time -from functools import wraps -try: - import cPickle as pickle -except ImportError: - import pickle - -import xbmc -import xbmcgui -import xbmcvfs - -CACHE_COMMON = 'cache_common' -CACHE_GENRES = 'cache_genres' -CACHE_SUPPLEMENTAL = 'cache_supplemental' -CACHE_METADATA = 'cache_metadata' -CACHE_INFOLABELS = 'cache_infolabels' -CACHE_ARTINFO = 'cache_artinfo' -CACHE_MANIFESTS = 'cache_manifests' - -BUCKET_NAMES = [CACHE_COMMON, CACHE_GENRES, CACHE_SUPPLEMENTAL, CACHE_METADATA, - CACHE_INFOLABELS, CACHE_ARTINFO, CACHE_MANIFESTS] - -BUCKET_LOCKED = 'LOCKED_BY_{:04d}_AT_{}' - -# 100 years TTL should be close enough to infinite -TTL_INFINITE = 60 * 60 * 24 * 365 * 100 - - -class CacheMiss(Exception): - """Requested item is not in the cache""" - pass - - -class UnknownCacheBucketError(Exception): - """The requested cahce bucket does ot exist""" - pass - - -# Logic to get the identifier -# cache_output: called without params, use the first argument value of the function as identifier -# cache_output: with identify_from_kwarg_name, get value identifier from kwarg name specified, if None value fallback to first function argument value - -# identify_append_from_kwarg_name - if specified append the value after the kwarg identify_from_kwarg_name, to creates a more specific identifier -# identify_fallback_arg_index - to change the default fallback arg index (0), where the identifier get the value from the func arguments -# fixed_identifier - note if specified all other params are ignored - -def cache_output(g, bucket, fixed_identifier=None, - identify_from_kwarg_name='videoid', - identify_append_from_kwarg_name=None, - identify_fallback_arg_index=0, - ttl=None, - to_disk=False): - """Decorator that ensures caching the output of a function""" - # pylint: disable=missing-docstring, invalid-name, too-many-arguments - def caching_decorator(func): - @wraps(func) - def wrapper(*args, **kwargs): - try: - identifier = _get_identifier(fixed_identifier, - identify_from_kwarg_name, - identify_append_from_kwarg_name, - identify_fallback_arg_index, - args, - kwargs) - if not identifier: - # Do not cache if identifier couldn't be determined - return func(*args, **kwargs) - return g.CACHE.get(bucket, identifier) - except CacheMiss: - output = func(*args, **kwargs) - g.CACHE.add(bucket, identifier, output, ttl=ttl, - to_disk=to_disk) - return output - return wrapper - return caching_decorator - - -def _get_identifier(fixed_identifier, identify_from_kwarg_name, - identify_append_from_kwarg_name, identify_fallback_arg_index, args, kwargs): - """Return the identifier to use with the caching_decorator""" - # import resources.lib.common as common - # common.debug('Get_identifier args: {}'.format(args)) - # common.debug('Get_identifier kwargs: {}'.format(kwargs)) - if fixed_identifier: - identifier = fixed_identifier - else: - identifier = kwargs.get(identify_from_kwarg_name) - if identifier and identify_append_from_kwarg_name and kwargs.get(identify_append_from_kwarg_name): - identifier = identifier + '_' + kwargs.get(identify_append_from_kwarg_name) - if not identifier and args: - identifier = args[identify_fallback_arg_index] - # common.debug('Get_identifier identifier value: {}'.format(identifier if identifier else 'None')) - return identifier - - -# def inject_from_cache(cache, bucket, injection_param, -# identifying_param_index=0, -# identifying_param_name=None, -# fixed_identifier=None, -# to_disk=False): -# """Decorator that injects a cached value as parameter if available. -# The decorated function must return a value to be added to the cache.""" -# # pylint: disable=missing-docstring -# def injecting_cache_decorator(func): -# @wraps(func) -# def wrapper(*args, **kwargs): -# identifier = _get_identifier(fixed_identifier, -# identifying_param_name, -# kwargs, -# identifying_param_index, -# args) -# try: -# value_to_inject = cache.get(bucket, identifier) -# except CacheMiss: -# value_to_inject = None -# kwargs[injection_param] = value_to_inject -# output = func(*args, **kwargs) -# cache.add(bucket, identifier, output, ttl=ttl, to_disk=to_disk) -# return output -# return wrapper -# return injecting_cache_decorator - - -class Cache(object): - def __init__(self, common, cache_path, ttl, metadata_ttl, plugin_handle): - # pylint: disable=too-many-arguments - # We have the self.common module injected as a dependency to work - # around circular dependencies with gloabl variable initialization - self.common = common - self.plugin_handle = plugin_handle - self.cache_path = cache_path - self.ttl = ttl - self.metadata_ttl = metadata_ttl - self.buckets = {} - self.window = xbmcgui.Window(10000) - - def lock_marker(self): - """Return a lock marker for this instance and the current time""" - # Return maximum timestamp for library to prevent stale lock - # overrides which may lead to inconsistencies - timestamp = int(time()) - return str(BUCKET_LOCKED.format(self.plugin_handle, timestamp)) - - def get(self, bucket, identifier, use_disk_fallback=True): - """Retrieve an item from a cache bucket""" - try: - cache_entry = self._get_bucket(bucket)[identifier] - except KeyError: - if not use_disk_fallback: - raise CacheMiss() - cache_entry = self._get_from_disk(bucket, identifier) - self.add(bucket, identifier, cache_entry['content']) - self.verify_ttl(bucket, identifier, cache_entry) - return cache_entry['content'] - - def add(self, bucket, identifier, content, ttl=None, to_disk=False, eol=None): - """Add an item to a cache bucket""" - # pylint: disable=too-many-arguments - if not eol: - eol = int(time() + (ttl if ttl else self.ttl)) - # self.common.debug('Adding {} to {} (valid until {})' - # .format(identifier, bucket, eol)) - cache_entry = {'eol': eol, 'content': content} - self._get_bucket(bucket).update( - {identifier: cache_entry}) - if to_disk: - self._add_to_disk(bucket, identifier, cache_entry) - - def commit(self): - """Persist cache contents in window properties""" - # pylint: disable=global-statement - for bucket, contents in self.buckets.items(): - self._persist_bucket(bucket, contents) - # The self.buckets dict survives across addon invocations if the - # same languageInvoker thread is being used so we MUST clear its - # contents to allow cache consistency between instances - # del self.buckets[bucket] - self.common.debug('Cache commit successful') - - def invalidate(self, on_disk=False): - """Clear all cache buckets""" - # pylint: disable=global-statement - for bucket in BUCKET_NAMES: - self.window.clearProperty(_window_property(bucket)) - if bucket in self.buckets: - del self.buckets[bucket] - - if on_disk: - self._invalidate_on_disk() - self.common.info('Cache invalidated') - - def _invalidate_on_disk(self): - for bucket in BUCKET_NAMES: - self.common.delete_folder_contents( - os.path.join(self.cache_path, bucket)) - - def invalidate_entry(self, bucket, identifier, on_disk=False): - """Remove an item from a bucket""" - try: - self._purge_entry(bucket, identifier, on_disk) - self.common.debug('Invalidated {} in {}' - .format(identifier, bucket)) - except KeyError: - self.common.debug('Nothing to invalidate, {} was not in {}' - .format(identifier, bucket)) - - def _get_bucket(self, key): - """Get a cache bucket. - Load it lazily from window property if it's not yet in memory""" - if key not in BUCKET_NAMES: - raise UnknownCacheBucketError() - if key not in self.buckets: - self.buckets[key] = self._load_bucket(key) - return self.buckets[key] - - def _load_bucket(self, bucket): - wnd_property = '' - # Try 10 times to acquire a lock - for _ in range(1, 10): - wnd_property = self.window.getProperty(_window_property(bucket)) - # pickle stores byte data, so we must compare against a str - if wnd_property.startswith(str('LOCKED')): - self.common.debug('Waiting for release of {}'.format(bucket)) - xbmc.sleep(50) - else: - return self._load_bucket_from_wndprop(bucket, wnd_property) - self.common.warn('{} is locked. Working with an empty instance...' - .format(bucket)) - return {} - - def _load_bucket_from_wndprop(self, bucket, wnd_property): - # pylint: disable=broad-except - try: - bucket_instance = pickle.loads(wnd_property) - except Exception: - self.common.debug('No instance of {} found. Creating new instance.' - .format(bucket)) - bucket_instance = {} - self._lock(bucket) - self.common.debug('Acquired lock on {}'.format(bucket)) - return bucket_instance - - def _lock(self, bucket): - self.window.setProperty(_window_property(bucket), - self.lock_marker()) - - def _get_from_disk(self, bucket, identifier): - """Load a cache entry from disk and add it to the in memory bucket""" - handle = xbmcvfs.File(self._entry_filename(bucket, identifier), 'r') - try: - return pickle.loads(handle.read()) - except Exception: - raise CacheMiss() - finally: - handle.close() - - def _add_to_disk(self, bucket, identifier, cache_entry): - """Write a cache entry to disk""" - # pylint: disable=broad-except - cache_filename = self._entry_filename(bucket, identifier) - handle = xbmcvfs.File(cache_filename, 'w') - try: - return pickle.dump(cache_entry, handle) - except Exception as exc: - self.common.error('Failed to write cache entry to {}: {}' - .format(cache_filename, exc)) - finally: - handle.close() - - def _entry_filename(self, bucket, identifier): - file_loc = [self.cache_path, bucket, '{}.cache'.format(identifier)] - return xbmc.translatePath(os.path.join(*file_loc)) - - def _persist_bucket(self, bucket, contents): - # pylint: disable=broad-except - if not self.is_safe_to_persist(bucket): - self.common.warn( - '{} is locked by another instance. Discarding changes' - .format(bucket)) - return - - try: - self.window.setProperty(_window_property(bucket), - pickle.dumps(contents)) - except Exception as exc: - self.common.error('Failed to persist {} to wnd properties: {}' - .format(bucket, exc)) - self.window.clearProperty(_window_property(bucket)) - finally: - self.common.debug('Released lock on {}'.format(bucket)) - - def is_safe_to_persist(self, bucket): - # Only persist if we acquired the original lock or if the lock is older - # than 15 seconds (override stale locks) - lock = self.window.getProperty(_window_property(bucket)) - is_own_lock = lock[:14] == self.lock_marker()[:14] - try: - is_stale_lock = int(lock[18:] or 1) <= time() - 15 - except ValueError: - is_stale_lock = False - if is_stale_lock: - self.common.info('Overriding stale cache lock {} on {}' - .format(lock, bucket)) - return is_own_lock or is_stale_lock - - def verify_ttl(self, bucket, identifier, cache_entry): - """Verify if cache_entry has reached its EOL. - Remove from in-memory and disk cache if so and raise CacheMiss""" - if cache_entry['eol'] < int(time()): - self.common.debug('Cache entry {} in {} has expired => cache miss' - .format(identifier, bucket)) - self._purge_entry(bucket, identifier) - raise CacheMiss() - - def _purge_entry(self, bucket, identifier, on_disk=False): - # To ensure removing disk cache, it must be loaded first or it will trigger an exception - cache_filename = self._entry_filename(bucket, identifier) - cache_exixts = os.path.exists(cache_filename) - - if on_disk and cache_exixts: - cache_entry = self._get_from_disk(bucket, identifier) - self.add(bucket, identifier, cache_entry['content']) - - # Remove from in-memory cache - del self._get_bucket(bucket)[identifier] - # Remove from disk cache if it exists - - if cache_exixts: - os.remove(cache_filename) - - -def _window_property(bucket): - return 'nfmemcache_{}'.format(bucket) diff --git a/resources/lib/common/__init__.py b/resources/lib/common/__init__.py index b7260d884..f4ae35dc0 100644 --- a/resources/lib/common/__init__.py +++ b/resources/lib/common/__init__.py @@ -1,16 +1,21 @@ # -*- coding: utf-8 -*- # pylint: disable=wildcard-import, wrong-import-position -"""Common plugin operations and utilities""" -from __future__ import absolute_import, division, unicode_literals +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Common plugin operations and utilities -from .logging import * + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" from .ipc import * from .videoid import * from .credentials import * -from .storage import * from .fileops import * -from .kodiops import * +from .kodi_ops import * +from .kodi_library_ops import * from .pathops import * +from .device_utils import * from .misc_utils import * from .data_conversion import * from .uuid_device import * diff --git a/resources/lib/common/cache.py b/resources/lib/common/cache.py new file mode 100644 index 000000000..ee4a3c82b --- /dev/null +++ b/resources/lib/common/cache.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2020 Stefano Gottardo (original implementation module) + Cache IPC interface - allow access to the add-on service cache from an add-on "frontend" instance + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +from resources.lib.common import make_call, IPC_ENDPOINT_CACHE + + +class Cache: + """Cache IPC interface""" + + def get(self, bucket, identifier): + """Get a item from cache bucket""" + call_args = { + 'bucket': bucket, + 'identifier': identifier + } + return make_call('get', call_args, IPC_ENDPOINT_CACHE) + + def add(self, bucket, identifier, data, ttl=None, expires=None, delayed_db_op=False): + """ + Add or update an item to a cache bucket + + :param bucket: bucket where save the data + :param identifier: key identifier of the data + :param data: the content + :param ttl: override default expiration (in seconds) + :param expires: override default expiration (in timestamp) if specified override also the 'ttl' value + :param delayed_db_op: if True, queues the adding operation for the db, then is mandatory to call + 'execute_pending_db_add' at end of all operations to apply the changes to the db + (only for persistent buckets) + """ + call_args = { + 'bucket': bucket, + 'identifier': identifier, + 'data': data, + 'ttl': ttl, + 'expires': expires, + 'delayed_db_op': delayed_db_op + } + make_call('add', call_args, IPC_ENDPOINT_CACHE) + + def delete(self, bucket, identifier, including_suffixes=False): + """ + Delete an item from cache bucket + + :param including_suffixes: if true will delete all items with the identifier that start with it + """ + call_args = { + 'bucket': bucket, + 'identifier': identifier, + 'including_suffixes': including_suffixes + } + make_call('delete', call_args, IPC_ENDPOINT_CACHE) + + def clear(self, buckets=None, clear_database=True): + """ + Clear the cache + + :param buckets: list of buckets to clear, if not specified clear all the cache + :param clear_database: if True clear also the database data + """ + call_args = { + 'buckets': buckets, + 'clear_database': clear_database + } + make_call('clear', call_args, IPC_ENDPOINT_CACHE) diff --git a/resources/lib/common/cache_utils.py b/resources/lib/common/cache_utils.py new file mode 100644 index 000000000..eaf122e35 --- /dev/null +++ b/resources/lib/common/cache_utils.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2020 Stefano Gottardo (original implementation module) + Miscellaneous utility functions for cache + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +import pickle +from functools import wraps + +from resources.lib.globals import G +from resources.lib.utils.logging import LOG +from .exceptions import CacheMiss + +# Cache buckets (the default_ttl is the variable name in 'global' class) +CACHE_COMMON = {'name': 'cache_common', 'is_persistent': False, 'default_ttl': 'CACHE_TTL'} +CACHE_GENRES = {'name': 'cache_genres', 'is_persistent': False, 'default_ttl': 'CACHE_TTL'} +CACHE_SUPPLEMENTAL = {'name': 'cache_supplemental', 'is_persistent': False, 'default_ttl': 'CACHE_TTL'} +CACHE_METADATA = {'name': 'cache_metadata', 'is_persistent': True, 'default_ttl': 'CACHE_METADATA_TTL'} +CACHE_INFOLABELS = {'name': 'cache_infolabels', 'is_persistent': True, 'default_ttl': 'CACHE_METADATA_TTL'} +CACHE_ARTINFO = {'name': 'cache_artinfo', 'is_persistent': True, 'default_ttl': 'CACHE_METADATA_TTL'} +CACHE_MANIFESTS = {'name': 'cache_manifests', 'is_persistent': False, 'default_ttl': 'CACHE_TTL'} +CACHE_BOOKMARKS = {'name': 'cache_bookmarks', 'is_persistent': False, 'default_ttl': 'CACHE_TTL'} +CACHE_MYLIST = {'name': 'cache_mylist', 'is_persistent': False, 'default_ttl': 'CACHE_MYLIST_TTL'} +CACHE_SEARCH = {'name': 'cache_search', 'is_persistent': False, 'default_ttl': ''} # Only customized ttl + +# The complete list of buckets (to obtain the list quickly) +BUCKET_NAMES = ['cache_common', 'cache_genres', 'cache_supplemental', 'cache_metadata', 'cache_infolabels', + 'cache_artinfo', 'cache_manifests', 'cache_bookmarks', 'cache_mylist', 'cache_search'] + +BUCKETS = [CACHE_COMMON, CACHE_GENRES, CACHE_SUPPLEMENTAL, CACHE_METADATA, CACHE_INFOLABELS, + CACHE_ARTINFO, CACHE_MANIFESTS, CACHE_BOOKMARKS, CACHE_MYLIST, CACHE_SEARCH] + + +# Logic to get the identifier +# cache_output: called without params, use the first argument value of the function as identifier +# cache_output: with identify_from_kwarg_name, get value identifier from kwarg name specified, +# if None value fallback to first function argument value + +# identify_append_from_kwarg_name - if specified append the value after the kwarg identify_from +# _kwarg_name, to creates a more specific identifier +# identify_fallback_arg_index - to change the default fallback arg index (0), where the identifier +# get the value from the func arguments +# fixed_identifier - note if specified all other params are ignored + +def cache_output(bucket, fixed_identifier=None, + identify_from_kwarg_name='videoid', + identify_append_from_kwarg_name=None, + identify_fallback_arg_index=0, + ttl=None, + ignore_self_class=False): + """Decorator that ensures caching the output of a function""" + def caching_decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + # To avoid use cache add to the kwargs the value: 'no_use_cache'=True + arg_value, identifier = _get_identifier(fixed_identifier, + identify_from_kwarg_name, + identify_append_from_kwarg_name, + identify_fallback_arg_index, + args[1:] if ignore_self_class else args, + kwargs) + if not identifier: + # Do not cache if identifier couldn't be determined + return func(*args, **kwargs) + _bucket = CACHE_MYLIST if arg_value == 'mylist' else bucket + try: + return G.CACHE.get(_bucket, identifier) + except CacheMiss: + output = func(*args, **kwargs) + G.CACHE.add(_bucket, identifier, output, ttl=ttl) + return output + return wrapper + return caching_decorator + + +def _get_identifier(fixed_identifier, identify_from_kwarg_name, + identify_append_from_kwarg_name, identify_fallback_arg_index, args, kwargs): + """Return the identifier to use with the caching_decorator""" + # LOG.debug('Get_identifier args: {}', args) + # LOG.debug('Get_identifier kwargs: {}', kwargs) + if kwargs.pop('no_use_cache', False): + return None, None + arg_value = None + if fixed_identifier: + identifier = fixed_identifier + if identify_append_from_kwarg_name and kwargs.get(identify_append_from_kwarg_name): + identifier += f'_{kwargs.get(identify_append_from_kwarg_name)}' + else: + identifier = str(kwargs.get(identify_from_kwarg_name) or '') + if not identifier and args: + arg_value = str(args[identify_fallback_arg_index] or '') + identifier = arg_value + if identifier and identify_append_from_kwarg_name and kwargs.get(identify_append_from_kwarg_name): + identifier += f'_{kwargs.get(identify_append_from_kwarg_name)}' + # LOG.debug('Get_identifier identifier value: {}', identifier if identifier else 'None') + return arg_value, identifier + + +def serialize_data(value): + return pickle.dumps(value, protocol=pickle.HIGHEST_PROTOCOL) + + +def deserialize_data(value): + try: + return pickle.loads(value) + except pickle.UnpicklingError as exc: + LOG.error('It was not possible to deserialize the cache data, try purge cache from expert settings menu') + raise CacheMiss from exc diff --git a/resources/lib/common/cookies.py b/resources/lib/common/cookies.py deleted file mode 100644 index dc75d8b09..000000000 --- a/resources/lib/common/cookies.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- -"""Persistent cookie management""" -from __future__ import absolute_import, division, unicode_literals - -from time import time -try: - import cPickle as pickle -except ImportError: - import pickle - -import xbmc -import xbmcvfs - -from resources.lib.globals import g -import resources.lib.common as common - - -class MissingCookiesError(Exception): - """No session cookies have been stored""" - pass - - -class CookiesExpiredError(Exception): - """Stored cookies are expired""" - pass - - -def save(account_hash, cookie_jar): - """Save a cookie jar to file and in-memory storage""" - # pylint: disable=broad-except - g.COOKIES[account_hash] = cookie_jar - cookie_file = xbmcvfs.File(cookie_filename(account_hash), 'w') - try: - pickle.dump(cookie_jar, cookie_file) - except Exception as exc: - common.error('Failed to save cookies to file: {exc}', exc) - finally: - cookie_file.close() - - -def delete(account_hash): - """Delete cookies for an account from in-memory storage and the disk""" - # pylint: disable=broad-except - if g.COOKIES.get(account_hash): - del g.COOKIES[account_hash] - try: - xbmcvfs.delete(cookie_filename(account_hash)) - except Exception as exc: - common.error('Failed to delete cookies on disk: {exc}', exc) - - -def load(account_hash): - """Load cookies for a given account and check them for validity""" - try: - filename = cookie_filename(account_hash) - common.debug('Loading cookies from {}'.format(filename)) - cookie_file = xbmcvfs.File(filename, 'r') - cookie_jar = pickle.loads(cookie_file.read()) - except Exception as exc: - common.debug('Failed to load cookies from file: {exc}', exc) - raise MissingCookiesError() - finally: - cookie_file.close() - # Clear flwssn cookie if present, as it is trouble with early expiration - try: - cookie_jar.clear(domain='.netflix.com', path='/', name='flwssn') - except KeyError: - pass - - debug_output = 'Loaded cookies:\n' - for cookie in cookie_jar: - remaining_ttl = ((cookie.expires or 0) - time() / 60) if cookie.expires else None - debug_output += '{} (expires {} - remaining TTL {})\n'.format(cookie.name, - cookie.expires, - remaining_ttl) - common.debug(debug_output) - if expired(cookie_jar): - raise CookiesExpiredError() - return cookie_jar - - -def expired(cookie_jar): - """Check if one of the cookies in the jar is already expired""" - earliest_expiration = 99999999999999999999 - for cookie in cookie_jar: - if cookie.expires is not None: - earliest_expiration = min(int(cookie.expires), earliest_expiration) - return int(time()) > earliest_expiration - - -def cookie_filename(account_hash): - """Return a filename to store cookies for a given account""" - return xbmc.translatePath('{}_{}'.format(g.COOKIE_PATH, account_hash)) diff --git a/resources/lib/common/credentials.py b/resources/lib/common/credentials.py index 34c70e39f..a95010d19 100644 --- a/resources/lib/common/credentials.py +++ b/resources/lib/common/credentials.py @@ -1,54 +1,72 @@ # -*- coding: utf-8 -*- -"""Handling of account credentials""" -from __future__ import absolute_import, division, unicode_literals - -from resources.lib.globals import g -from resources.lib.api.exceptions import MissingCredentialsError - -from .logging import error +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Liberty-developer (original implementation module) + Copyright (C) 2018 Caphm + Handling of account credentials + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +import base64 +import json +from datetime import datetime + +from resources.lib.common.exceptions import MissingCredentialsError, ErrorMsgNoReport +from resources.lib.globals import G +from resources.lib.utils.logging import LOG +from .fileops import load_file +from .kodi_ops import get_local_string from .uuid_device import get_crypt_key __BLOCK_SIZE__ = 32 -def encrypt_credential(raw): +def encrypt_string(raw): """ Encodes data - - :param data: Data to be encoded - :type data: str + :param raw: Data to be encoded + :type raw: str :returns: string -- Encoded data """ - # pylint: disable=invalid-name,import-error - import base64 - from Cryptodome import Random - from Cryptodome.Cipher import AES - from Cryptodome.Util import Padding - raw = bytes(Padding.pad(data_to_pad=raw, block_size=__BLOCK_SIZE__)) + # Keep these imports within the method otherwise if the packages are not installed, + # the addon crashes and the user does not read the warning message + try: # The crypto package depends on the library installed (see Wiki) + from Cryptodome import Random + from Cryptodome.Cipher import AES + from Cryptodome.Util import Padding + except ImportError: + from Crypto import Random + from Crypto.Cipher import AES + from Crypto.Util import Padding + raw = bytes(Padding.pad(data_to_pad=raw.encode('utf-8'), block_size=__BLOCK_SIZE__)) iv = Random.new().read(AES.block_size) cipher = AES.new(get_crypt_key(), AES.MODE_CBC, iv) - return base64.b64encode(iv + cipher.encrypt(raw)) + return base64.b64encode(iv + cipher.encrypt(raw)).decode('utf-8') -def decrypt_credential(enc, secret=None): +def decrypt_string(enc): """ Decodes data - - :param data: Data to be decoded - :type data: str + :param enc: Data to be decoded + :type enc: str :returns: string -- Decoded data """ - # pylint: disable=invalid-name,import-error - import base64 - from Cryptodome.Cipher import AES - from Cryptodome.Util import Padding + # Keep these imports within the method otherwise if the packages are not installed, + # the addon crashes and the user does not read the warning message + try: # The crypto package depends on the library installed (see Wiki) + from Cryptodome.Cipher import AES + from Cryptodome.Util import Padding + except ImportError: + from Crypto.Cipher import AES + from Crypto.Util import Padding enc = base64.b64decode(enc) iv = enc[:AES.block_size] - cipher = AES.new(secret or get_crypt_key(), AES.MODE_CBC, iv) + cipher = AES.new(get_crypt_key(), AES.MODE_CBC, iv) decoded = Padding.unpad( padded_data=cipher.decrypt(enc[AES.block_size:]), - block_size=__BLOCK_SIZE__).decode('utf-8') - return decoded + block_size=__BLOCK_SIZE__) + return decoded.decode('utf-8') def get_credentials(): @@ -56,55 +74,149 @@ def get_credentials(): Retrieve stored account credentials. :return: The stored account credentials or an empty dict if none exist. """ - email = g.LOCAL_DB.get_value('account_email') - password = g.LOCAL_DB.get_value('account_password') + email = G.LOCAL_DB.get_value('account_email') + password = G.LOCAL_DB.get_value('account_password') verify_credentials(email and password) try: return { - 'email': decrypt_credential(email), - 'password': decrypt_credential(password) + 'email': decrypt_string(email), + 'password': decrypt_string(password) } - except Exception: - import traceback - error(traceback.format_exc()) - raise MissingCredentialsError( - 'Existing credentials could not be decrypted') + except Exception as exc: # pylint: disable=broad-except + raise MissingCredentialsError('Existing credentials could not be decrypted') from exc -# noinspection PyBroadException def check_credentials(): """ Check if account credentials exists and can be decrypted. """ - email = g.LOCAL_DB.get_value('account_email') - password = g.LOCAL_DB.get_value('account_password') + email = G.LOCAL_DB.get_value('account_email') + password = G.LOCAL_DB.get_value('account_password') try: verify_credentials(email and password) - decrypt_credential(email) - decrypt_credential(password) + decrypt_string(email) + decrypt_string(password) return True except Exception: # pylint: disable=broad-except - pass - return False + return False -def set_credentials(email, password): +def set_credentials(credentials): """ - Encrypt account credentials and save them to the settings. - Does nothing if either email or password are not supplied. + Encrypt account credentials and save them. """ - if email and password: - g.LOCAL_DB.set_value('account_email', encrypt_credential(email)) - g.LOCAL_DB.set_value('account_password', encrypt_credential(password)) + G.LOCAL_DB.set_value('account_email', encrypt_string(credentials['email'])) + G.LOCAL_DB.set_value('account_password', encrypt_string(credentials['password'])) def purge_credentials(): """Delete the stored credentials""" - g.LOCAL_DB.set_value('account_email', None) - g.LOCAL_DB.set_value('account_password', None) + G.LOCAL_DB.set_value('account_email', None) + G.LOCAL_DB.set_value('account_password', None) def verify_credentials(credential): """Verify credentials for plausibility""" if not credential: raise MissingCredentialsError() + + +def run_nf_authentication_key(): + """ + Start operations to do the login with the authentication key file + :return: data to send to service or None if user cancel operations or something was wrong + """ + from resources.lib.kodi import ui + file_path = ui.show_browse_dialog(get_local_string(30400) + ': NFAuthentication.key', 1, extensions='.key') + if file_path: + data = '' + while data == '': + pin = ui.show_dlg_input_numeric(get_local_string(30345)) + if pin: + data = _get_authentication_key_data(file_path, pin) + else: + data = None + if data and _verify_authentication_key_data(data): + return _prepare_authentication_key_data(data) + return None + + +def _get_authentication_key_data(file_path, pin): + """Open the auth key file""" + from resources.lib.kodi import ui + # Keep these imports within the method otherwise if the packages are not installed, + # the addon crashes and the user does not read the warning message + try: # The crypto package depends on the library installed (see Wiki) + from Cryptodome.Cipher import AES + from Cryptodome.Util import Padding + except ImportError: + from Crypto.Cipher import AES + from Crypto.Util import Padding + try: + file_content = load_file(file_path) + iv = '\x00' * 16 + cipher = AES.new((pin + pin + pin + pin).encode("utf-8"), AES.MODE_CBC, iv.encode("utf-8")) + decoded = Padding.unpad(padded_data=cipher.decrypt(base64.b64decode(file_content)), + block_size=16) + return json.loads(decoded.decode('utf-8')) + except ValueError: + # ValueError should always means wrong decryption due to wrong key + ui.show_ok_dialog(get_local_string(30342), get_local_string(30106)) + return '' + except Exception as exc: # pylint: disable=broad-except + LOG.warn('Exception raised: {}', exc) + ui.show_ok_dialog(get_local_string(30342), get_local_string(30343)) + return None + + +def _verify_authentication_key_data(data): + """Verify the data structure""" + from resources.lib.kodi import ui + fields = ['app_name', 'app_version', 'app_system', 'app_author', 'timestamp', 'data'] + if not all(name in fields for name in data): + ui.show_ok_dialog(get_local_string(30342), get_local_string(30343)) + return False + if not data['data'] or 'cookies' not in data['data']: + ui.show_ok_dialog(get_local_string(30342), get_local_string(30343)) + return False + # Check timestamp, session data is not immortal and could cause others side effects + if datetime.fromtimestamp(data['timestamp']) < datetime.now(): + ui.show_ok_dialog(get_local_string(30342), get_local_string(30344)) + return False + return True + + +def _prepare_authentication_key_data(data): + """Check type of app used and prepare data for the service""" + from resources.lib.utils.cookies import convert_chrome_cookie + if (data['app_name'] == 'NFAuthenticationKey' and + data['app_system'] == 'Windows' and + # data['app_version'] == '1.0.0.0' and + data['app_author'] == 'CastagnaIT'): + result_data = {'cookies': []} + for cookie in data['data']['cookies']: + if 'netflix' not in cookie['domain']: + continue + result_data['cookies'].append(convert_chrome_cookie(cookie)) + return result_data + if (data['app_name'] == 'NFAuthenticationKey' and + data['app_system'] == 'Linux' and + # data['app_version'] == '1.0.0' and + data['app_author'] == 'CastagnaIT'): + result_data = {'cookies': []} + for cookie in data['data']['cookies']: + if 'netflix' not in cookie['domain']: + continue + result_data['cookies'].append(convert_chrome_cookie(cookie)) + return result_data + if (data['app_name'] == 'NFAuthenticationKey' and + data['app_system'] == 'MacOS' and + # data['app_version'] == '1.0.0' and + data['app_author'] == 'CastagnaIT'): + result_data = {'cookies': []} + for cookie in data['data']['cookies']: + if 'netflix' not in cookie['domain']: + continue + result_data['cookies'].append(convert_chrome_cookie(cookie)) + return result_data + raise ErrorMsgNoReport('Authentication key file not supported') diff --git a/resources/lib/common/data_conversion.py b/resources/lib/common/data_conversion.py index 3d6089ca8..5c884d9ab 100644 --- a/resources/lib/common/data_conversion.py +++ b/resources/lib/common/data_conversion.py @@ -1,32 +1,36 @@ # -*- coding: utf-8 -*- -"""Data type conversion""" -from __future__ import absolute_import, division, unicode_literals - -import json +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2019 Stefano Gottardo - @CastagnaIT (original implementation module) + Data type conversion + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" import datetime - +import json from ast import literal_eval -from .logging import error +from collections import OrderedDict + +from resources.lib.utils.logging import LOG class DataTypeNotMapped(Exception): """Data type not mapped""" - pass def convert_to_string(value): if value is None: return None data_type = type(value) - if data_type in (str, unicode): + if data_type == str: return value - converter = None if data_type in (int, float, bool, tuple, datetime.datetime): converter = _conv_standard_to_string - if data_type in (list, dict): + elif data_type in (list, dict, OrderedDict): converter = _conv_json_to_string - if not converter: - error('Data type {} not mapped'.format(data_type)) + else: + LOG.error('convert_to_string: Data type {} not mapped', data_type) raise DataTypeNotMapped return converter(value) @@ -34,17 +38,16 @@ def convert_to_string(value): def convert_from_string(value, to_data_type): if value is None: return None - if to_data_type in (str, unicode, int, float): + if to_data_type in (str, int, float): return to_data_type(value) if to_data_type in (bool, list, tuple): return literal_eval(value) - converter = None if to_data_type == dict: converter = _conv_string_to_json - if to_data_type == datetime.datetime: + elif to_data_type == datetime.datetime: converter = _conv_string_to_datetime - if not converter: - error('Data type {} not mapped'.format(to_data_type)) + else: + LOG.error('convert_from_string: Data type {} not mapped', to_data_type) raise DataTypeNotMapped return converter(value) @@ -62,4 +65,9 @@ def _conv_string_to_json(value): def _conv_string_to_datetime(value): - return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S.%f') + try: + return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S.%f') + except (TypeError, ImportError): + # Python bug https://bugs.python.org/issue27400 + import time + return datetime.datetime(*(time.strptime(value, '%Y-%m-%d %H:%M:%S.%f')[0:6])) diff --git a/resources/lib/common/device_utils.py b/resources/lib/common/device_utils.py new file mode 100644 index 000000000..b1c955cfc --- /dev/null +++ b/resources/lib/common/device_utils.py @@ -0,0 +1,228 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2020 Stefano Gottardo - @CastagnaIT (original implementation module) + Miscellaneous utility functions related to the device + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +import xbmc + +from resources.lib.globals import G +from resources.lib.utils.esn import WidevineForceSecLev +from resources.lib.utils.logging import LOG + + +def select_port(service): + """Select an unused port on the host machine for a server and store it in the settings""" + port = select_unused_port() + G.LOCAL_DB.set_value(f'{service.lower()}_service_port', port) + LOG.info('[{}] Picked Port: {}', service, port) + return port + + +def select_unused_port(): + """ + Helper function to select an unused port on the host machine + + :return: int - Free port + """ + import socket + from contextlib import closing + # pylint: disable=no-member + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: + sock.bind(('127.0.0.1', 0)) + _, port = sock.getsockname() + return port + + +def get_system_platform(): + if not hasattr(get_system_platform, 'cached'): + platform = "unknown" + if xbmc.getCondVisibility('system.platform.linux') and not xbmc.getCondVisibility('system.platform.android'): + platform = "linux" + elif xbmc.getCondVisibility('system.platform.linux') and xbmc.getCondVisibility('system.platform.android'): + platform = "android" + elif xbmc.getCondVisibility('system.platform.uwp'): + platform = "uwp" + elif xbmc.getCondVisibility('system.platform.windows'): + platform = "windows" + elif xbmc.getCondVisibility('system.platform.osx'): + platform = "osx" + elif xbmc.getCondVisibility('system.platform.ios'): + platform = "ios" + elif xbmc.getCondVisibility('system.platform.tvos'): + platform = "tvos" + get_system_platform.cached = platform + return get_system_platform.cached + + +def get_machine(): + """Get machine architecture""" + from platform import machine + try: + return machine() + except Exception: # pylint: disable=broad-except + # Due to OS restrictions on 'ios' and 'tvos' this generate an exception + # See python limits in the wiki development page + # Fallback with a generic arm + return 'arm' + + +def is_android_tv(props=None): + """Check Android properties to determine if the device has an Android TV system""" + if props is None: + props = get_android_system_props() + ret = 'TV' in props.get('ro.build.characteristics', '').upper() + # Xiaomi box devices like Mi Box 3, Mi Box S, Tv Stick, and maybe other models + # don't have "tv" value into ro.build.characteristics + # therefore we check the name from ro.com.google.clientidbase, that at least to the mentioned models are the same + if (not ret and props.get('ro.product.manufacturer', '').upper() == 'XIAOMI' + and props.get('ro.com.google.clientidbase', '').upper() == 'ANDROID-XIAOMI-TV'): + ret = True + return ret + + +def is_device_4k_capable(): + """Check if the device is 4k capable""" + # Currently only on android is it possible to use 4K + if get_system_platform() == 'android': + from resources.lib.database.db_utils import TABLE_SESSION + # Check if the drm has security level L1 + wv_force_sec_lev = G.LOCAL_DB.get_value('widevine_force_seclev', + WidevineForceSecLev.DISABLED, + table=TABLE_SESSION) + is_l3_forced = wv_force_sec_lev != WidevineForceSecLev.DISABLED + is_drm_l1_security_level = (G.LOCAL_DB.get_value('drm_security_level', '', table=TABLE_SESSION) == 'L1' + and not is_l3_forced) + # Check if HDCP level is 2.2 or up + hdcp_4k_capable = get_hdcp_level() >= 2.2 + return bool(is_drm_l1_security_level and hdcp_4k_capable) + return False + + +def is_device_l1_enabled(): + """Check if L1 security level is enabled""" + from resources.lib.database.db_utils import TABLE_SESSION + wv_force_sec_lev = G.LOCAL_DB.get_value('widevine_force_seclev', + WidevineForceSecLev.DISABLED, + table=TABLE_SESSION) + is_l3_forced = wv_force_sec_lev != WidevineForceSecLev.DISABLED + return G.LOCAL_DB.get_value('drm_security_level', '', table=TABLE_SESSION) == 'L1' and not is_l3_forced + + +def get_hdcp_level(): + """Get the HDCP level""" + from re import findall + from resources.lib.database.db_utils import TABLE_SESSION + drm_hdcp_level = findall('\\d+\\.\\d+', G.LOCAL_DB.get_value('drm_hdcp_level', '', table=TABLE_SESSION)) + return float(drm_hdcp_level[0]) if drm_hdcp_level else 1.4 + + +def get_user_agent(enable_android_mediaflag_fix=False): + """ + Determines the user agent string for the current platform. + Needed to retrieve a valid ESN (except for Android, where the ESN can be generated locally) + + :returns: str -- User agent string + """ + system = get_system_platform() + if enable_android_mediaflag_fix and system == 'android' and is_device_4k_capable(): + # The UA affects not only the ESNs in the login, but also the video details, + # so the UAs seem refer to exactly to these conditions: https://help.netflix.com/en/node/23742 + # This workaround is needed because currently we do not login through the netflix native android API, + # but redirect everything through the website APIs, and the website APIs do not really support android. + # Then on android usually we use the 'arm' UA which refers to chrome os, but this is limited to 1080P, so the + # labels on the 4K devices appears wrong (in the Kodi skin the 4K videos have 1080P media flags instead of 4K), + # the Windows UA is not limited, so we can use it to get the right video media flags. + system = 'windows' + + chrome_version = 'Chrome/108.0.0.0' + base = 'Mozilla/5.0 ' + base += '%PL% ' + base += 'AppleWebKit/537.36 (KHTML, like Gecko) ' + base += '%CH_VER% Safari/537.36'.replace('%CH_VER%', chrome_version) + + if system in ['osx', 'ios', 'tvos']: + return base.replace('%PL%', '(Macintosh; Intel Mac OS X 10_15_5)') + if system in ['windows', 'uwp']: + return base.replace('%PL%', '(Windows NT 10.0; Win64; x64)') + # ARM based Linux + machine_arch = get_machine() + if machine_arch.startswith('arm'): + # Last number is the platform version of Chrome OS + return base.replace('%PL%', '(X11; CrOS armv7l 15183.69.0)') + if machine_arch.startswith('aarch'): + # Last number is the platform version of Chrome OS + return base.replace('%PL%', '(X11; CrOS aarch64 15183.69.0)') + # x86 Linux + return base.replace('%PL%', '(X11; Linux x86_64)') + + +def is_internet_connected(): + """ + Check internet status + :return: True if connected + """ + if not xbmc.getCondVisibility('System.InternetState'): + # Double check when Kodi say that it is not connected + # i'm not sure the InfoLabel will work properly when Kodi was started a few seconds ago + # using getInfoLabel instead of getCondVisibility often return delayed results.. + return _check_internet() + return True + + +def _check_internet(): + """ + Checks via socket if the internet works (in about 0,7sec with no timeout error) + :return: True if connected + """ + import socket + for timeout in [1, 1]: + try: + socket.setdefaulttimeout(timeout) + host = socket.gethostbyname("www.google.com") + s = socket.create_connection((host, 80), timeout) + s.close() + return True + except Exception: # pylint: disable=broad-except + # Error when is not reachable + pass + return False + + +def get_supported_hdr_types(): + """ + Get supported HDR types by the display + :return: supported type as list ['hdr10', 'hlg', 'hdr10+', 'dolbyvision'] + """ + if G.KODI_VERSION < 20: # The infolabel 'System.SupportedHDRTypes' is supported from Kodi v20 + return [] + # The infolabel System.SupportedHDRTypes returns the HDR types supported by the hardware as a string: + # "HDR10, HLG, HDR10+, Dolby Vision" + return xbmc.getInfoLabel('System.SupportedHDRTypes').replace(' ', '').lower().split(',') + + +def get_android_system_props(): + """Get Android system properties by parsing the raw output of getprop into a dictionary""" + try: + import subprocess + info_dict = {} + info = subprocess.check_output(['/system/bin/getprop']).decode('utf-8', errors='ignore').replace('\r\n', '\n') + for line in info.split(']\n'): + if not line: + continue + try: + name, value = line.split(': ', 1) + except ValueError: + LOG.debug('Failed to parse getprop line: {}', line) + continue + name = name.strip()[1:-1] # Remove brackets [] and spaces + if value and value[0] == '[': + value = value[1:] + info_dict[name] = value + return info_dict + except OSError: + LOG.error('Cannot get "getprop" data due to system error.') + return {} diff --git a/resources/lib/common/exceptions.py b/resources/lib/common/exceptions.py new file mode 100644 index 000000000..ef17352bf --- /dev/null +++ b/resources/lib/common/exceptions.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Specific exceptions types + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +# Note: This module is also used to dynamically raise exceptions for IPC (see _raise_for_error in ipc.py) + + +# Exceptions for API's / DATA PROCESSING / WEB DATA PROCESSING + +class APIError(Exception): + """The requested API operation has resulted in an error""" + + +class HttpError401(Exception): + """The request has returned http error 401 unauthorized for url ...""" + + +class WebsiteParsingError(Exception): + """Parsing info from the Netflix Website failed""" + + +class MissingCookiesError(Exception): + """No session cookies have been stored""" + + +class InvalidAuthURLError(WebsiteParsingError): + """The authURL is not valid""" + + +class InvalidReferenceError(Exception): + """The provided reference cannot be dealt with as it is in an unexpected format""" + + +class InvalidVideoListTypeError(Exception): + """No video list of a given was available""" + + +class InvalidProfilesError(Exception): + """Cannot get profiles data from Netflix""" + + +class InvalidVideoId(Exception): + """The provided video id is not valid""" + + +class MetadataNotAvailable(Exception): + """Metadata not found""" + + +# Exceptions for MSL specific + +class MSLError(Exception): + """A specific MSL error""" + def __init__(self, message, err_number=None): + self.message = message + self.err_number = err_number + super().__init__(self.message) + + +class LicenseError(MSLError): + """License processing error""" + + +class ManifestError(MSLError): + """Manifest processing error""" + + +# Exceptions for ACCOUNT / LOGIN + +class MissingCredentialsError(Exception): + """There are no stored credentials to load""" + + +class LoginError(Exception): + """The login has failed""" + + +class LoginValidateError(Exception): + """The login request has failed for a specified reason""" + + +class NotLoggedInError(Exception): + """A check has determined the non-logged status""" + + +class MbrStatusError(Exception): + """Membership status error: The user logging in does not have a valid subscription""" + + +class MbrStatusAnonymousError(Exception): + """ + Membership status error: The user logging failed --mainly-- for: + password changed / expired cookies / request to disconnect devices + there may also be other unknown cases + """ + + +class MbrStatusNeverMemberError(Exception): + """Membership status error: The user logging failed because of account not been confirmed""" + + +class MbrStatusFormerMemberError(Exception): + """Membership status error: The user logging failed because of account not been reactivated""" + + +# Exceptions for DATABASE + +class DBSQLiteConnectionError(Exception): + """An error occurred in the database connection""" + + +class DBSQLiteError(Exception): + """An error occurred in the database operations""" + + +class DBMySQLConnectionError(Exception): + """An error occurred in the database connection""" + + +class DBMySQLError(Exception): + """An error occurred in the database operations""" + + +class DBProfilesMissing(Exception): + """There are no stored profiles in database""" + + +class DBRecordNotExistError(Exception): + """The record do not exist in database""" + + +# All other exceptions + +class ErrorMsg(Exception): + """Raise a generic error message""" + +class ErrorMsgNoReport(Exception): + """Raise an error message by displaying a GUI dialog box WITHOUT instructions for reporting a bug""" + + +class InvalidPathError(Exception): + """The requested path is invalid and could not be routed""" + + +class BackendNotReady(Exception): + """The background services are not started yet""" + + +class NotConnected(Exception): + """Internet status not connected""" + + +class CacheMiss(Exception): + """The Requested item is not in the cache""" + + +class UnknownCacheBucketError(Exception): + """The requested cache bucket does not exist""" + + +class ItemNotFound(Exception): + """The requested item could not be found in the Kodi library""" + + +class InputStreamHelperError(Exception): + """An internal error has occurred to InputStream Helper add-on""" + + +class SlotNotImplemented(Exception): + """IPC Slot not implemented""" diff --git a/resources/lib/common/fileops.py b/resources/lib/common/fileops.py index 73f3fee33..9747eb0db 100644 --- a/resources/lib/common/fileops.py +++ b/resources/lib/common/fileops.py @@ -1,18 +1,25 @@ # -*- coding: utf-8 -*- -"""Helper functions for file operations""" -from __future__ import absolute_import, division, unicode_literals - +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Helper functions for file operations + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" import os +import xml.etree.ElementTree as ET import xbmc import xbmcvfs -from resources.lib.globals import g +from resources.lib.globals import G +from .misc_utils import build_url def check_folder_path(path): """ - Check if folderpath ends with path delimator + Check if folder path ends with path delimiter If not correct it (makes sure xbmcvfs.exists is working correct) """ end = '' @@ -23,13 +30,31 @@ def check_folder_path(path): return path + end -def file_exists(filename, data_path=g.DATA_PATH): +def folder_exists(path): + """ + Checks if a given path exists + :param path: The path + :return: True if exists + """ + return xbmcvfs.exists(check_folder_path(path)) + + +def create_folder(path): + """ + Create a folder if not exists + :param path: The path + """ + if not folder_exists(path): + xbmcvfs.mkdirs(path) + + +def file_exists(file_path): """ Checks if a given file exists - :param filename: The filename + :param file_path: File path to check :return: True if exists """ - return xbmcvfs.exists(xbmc.translatePath(os.path.join(data_path, filename))) + return xbmcvfs.exists(xbmcvfs.translatePath(file_path)) def copy_file(from_path, to_path): @@ -40,64 +65,134 @@ def copy_file(from_path, to_path): :return: True if copied """ try: - return xbmcvfs.copy(xbmc.translatePath(from_path), - xbmc.translatePath(to_path)) + return xbmcvfs.copy(xbmcvfs.translatePath(from_path), + xbmcvfs.translatePath(to_path)) finally: pass -def save_file(filename, content, mode='w'): +def save_file_def(filename, content, mode='wb'): """ - Saves the given content under given filename + Saves the given content under given filename, in the default add-on data folder :param filename: The filename :param content: The content of the file + :param mode: optional mode options """ - file_handle = xbmcvfs.File( - xbmc.translatePath(os.path.join(g.DATA_PATH, filename)), mode) - try: - file_handle.write(content.encode('utf-8')) - finally: - file_handle.close() + save_file(os.path.join(G.DATA_PATH, filename), content, mode) -def load_file(filename, mode='r'): +def save_file(file_path, content, mode='wb'): """ - Loads the content of a given filename + Saves the given content under given filename path + :param file_path: The filename path + :param content: The content of the file + :param mode: optional mode options + """ + with xbmcvfs.File(xbmcvfs.translatePath(file_path), mode) as file_handle: + file_handle.write(bytearray(content)) + + +def load_file_def(filename, mode='rb'): + """ + Loads the content of a given filename, from the default add-on data folder :param filename: The file to load + :param mode: optional mode options :return: The content of the file """ - file_handle = xbmcvfs.File( - xbmc.translatePath(os.path.join(g.DATA_PATH, filename)), mode) - try: - return file_handle.read().decode('utf-8') - finally: - file_handle.close() + return load_file(os.path.join(G.DATA_PATH, filename), mode) + + +def load_file(file_path, mode='rb'): + """ + Loads the content of a given filename + :param file_path: The file path to load + :param mode: optional mode options + :return: The content of the file + """ + with xbmcvfs.File(xbmcvfs.translatePath(file_path), mode) as file_handle: + return file_handle.readBytes().decode('utf-8') + + +def delete_file_safe(file_path): + if xbmcvfs.exists(file_path): + try: + xbmcvfs.delete(file_path) + finally: + pass def delete_file(filename): - file_path = xbmc.translatePath(os.path.join(g.DATA_PATH, filename)) + file_path = xbmcvfs.translatePath(os.path.join(G.DATA_PATH, filename)) try: xbmcvfs.delete(file_path) finally: pass -def list_dir(data_path=g.DATA_PATH): +def list_dir(path): """ List the contents of a folder - :return: The contents of the folder + :return: The contents of the folder as tuple (directories, files) """ - return xbmcvfs.listdir(xbmc.translatePath(data_path)) + return xbmcvfs.listdir(path) + + +def delete_folder_contents(path, delete_subfolders=False): + """ + Delete all files in a folder + :param path: Path to perform delete contents + :param delete_subfolders: If True delete also all subfolders + """ + directories, files = list_dir(xbmcvfs.translatePath(path)) + for filename in files: + xbmcvfs.delete(os.path.join(path, filename)) + if not delete_subfolders: + return + for directory in directories: + delete_folder_contents(os.path.join(path, directory), True) + # Give time because the system performs previous op. otherwise it can't delete the folder + xbmc.sleep(80) + xbmcvfs.rmdir(os.path.join(path, directory)) + + +def delete_folder(path): + """Delete a folder with all his contents""" + delete_folder_contents(path, True) + # Give time because the system performs previous op. otherwise it can't delete the folder + xbmc.sleep(80) + xbmcvfs.rmdir(xbmcvfs.translatePath(path)) + + +def write_strm_file(videoid, file_path): + """Write a playable URL to a STRM file""" + filehandle = xbmcvfs.File(xbmcvfs.translatePath(file_path), 'wb') + try: + filehandle.write(bytearray(build_url(videoid=videoid, + mode=G.MODE_PLAY_STRM).encode('utf-8'))) + finally: + filehandle.close() + + +def write_nfo_file(nfo_data, file_path): + """Write a NFO file""" + filehandle = xbmcvfs.File(xbmcvfs.translatePath(file_path), 'wb') + try: + filehandle.write(bytearray(''.encode('utf-8'))) + filehandle.write(bytearray(ET.tostring(nfo_data, encoding='utf-8', method='xml'))) + finally: + filehandle.close() -def delete_folder_contents(path): - """Delete all files in a folder""" - for filename in list_dir(path)[1]: - xbmcvfs.delete(filename) +def join_folders_paths(*args): + """Join multiple folder paths in a safe way""" + # Avoid the use of os.path.join, in some cases with special chars like % break the path + return xbmcvfs.makeLegalFilename('/'.join(args)) -def delete_ndb_files(data_path=g.DATA_PATH): - """Delete all .ndb files in a folder""" - for filename in list_dir(data_path)[1]: - if filename.endswith('.ndb'): - xbmcvfs.delete(os.path.join(g.DATA_PATH, filename)) +def get_xml_nodes_text(nodelist): + """Get the text value of text node list""" + rc = [] + for node in nodelist: + if node.nodeType == node.TEXT_NODE: + rc.append(node.data) + return ''.join(rc) diff --git a/resources/lib/common/ipc.py b/resources/lib/common/ipc.py index fc9364be9..efb20f26a 100644 --- a/resources/lib/common/ipc.py +++ b/resources/lib/common/ipc.py @@ -1,149 +1,188 @@ # -*- coding: utf-8 -*- -"""Helper functions for inter-process communication via AddonSignals""" -from __future__ import absolute_import, division, unicode_literals +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Helper functions for inter-process communication via AddonSignals -import traceback -from functools import wraps + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +import pickle +from base64 import b64encode, b64decode import AddonSignals -from resources.lib.globals import g -import resources.lib.api.exceptions as apierrors +from resources.lib.common import exceptions +from resources.lib.globals import G +from resources.lib.utils.logging import LOG, measure_exec_time_decorator +from .misc_utils import run_threaded -from .logging import debug, error -from .misc_utils import time_execution +IPC_TIMEOUT_SECS = 20 +# IPC over HTTP endpoints +IPC_ENDPOINT_CACHE = '/netflix_service/cache' +IPC_ENDPOINT_MSL = '/netflix_service/msl' +IPC_ENDPOINT_NFSESSION = '/netflix_service/nfsession' +IPC_ENDPOINT_NFSESSION_TEST = '/netflix_service/nfsessiontest' -class BackendNotReady(Exception): - """The background services are not started yet""" - pass - -class Signals(object): +class Signals: # pylint: disable=no-init,too-few-public-methods """Signal names for use with AddonSignals""" - # pylint: disable=too-few-public-methods PLAYBACK_INITIATED = 'playback_initiated' - ESN_CHANGED = 'esn_changed' - LIBRARY_UPDATE_REQUESTED = 'library_update_requested' + REQUEST_KODI_LIBRARY_UPDATE = 'request_kodi_library_update' + SWITCH_EVENTS_HANDLER = 'switch_events_handler' -def register_slot(callback, signal=None, source_id=None): +def register_slot(callback, signal=None, source_id=None, is_signal=False): """Register a callback with AddonSignals for return calls""" - name = signal if signal else _signal_name(callback) + name = signal if signal else callback.__name__ + _callback = (EnvelopeAddonSignalsCallback(callback).call + if is_signal else EnvelopeAddonSignalsCallback(callback).return_call) AddonSignals.registerSlot( - signaler_id=source_id or g.ADDON_ID, + signaler_id=source_id or G.ADDON_ID, signal=name, - callback=callback) - debug('Registered AddonSignals slot {} to {}'.format(name, callback)) + callback=_callback) + LOG.debug('Registered AddonSignals slot {} to {}', name, callback) def unregister_slot(callback, signal=None): """Remove a registered callback from AddonSignals""" - name = signal if signal else _signal_name(callback) + name = signal if signal else callback.__name__ AddonSignals.unRegisterSlot( - signaler_id=g.ADDON_ID, + signaler_id=G.ADDON_ID, signal=name) - debug('Unregistered AddonSignals slot {}'.format(name)) + LOG.debug('Unregistered AddonSignals slot {}', name) -def send_signal(signal, data=None): +def send_signal(signal, data=None, non_blocking=False): """Send a signal via AddonSignals""" + # Using sendSignal of AddonSignals you might think that it is not a blocking call instead is blocking because it + # uses executeJSONRPC that is a blocking call, so the invoker will remain blocked until the function called by + # executeJSONRPC has completed his operations, even if it does not return any data. + # This workaround call sendSignal in a separate thread so immediately releases the invoker. + # This is to be considered according to the functions to be called, + # because it could keep the caller blocked for a certain amount of time unnecessarily. + # To note that several consecutive calls, are made in sequence not at the same time. + run_threaded(non_blocking, _send_signal, signal, data) + + +def _send_signal(signal, data): + _data = b64encode(pickle.dumps(data, pickle.HIGHEST_PROTOCOL)).decode('ascii') AddonSignals.sendSignal( - source_id=g.ADDON_ID, + source_id=G.ADDON_ID, signal=signal, - data=data) - - -@time_execution(immediate=False) -def make_call(callname, data=None): - if g.IPC_OVER_HTTP: - return make_http_call(callname, data) - return make_addonsignals_call(callname, data) - - -def make_http_call(callname, data): - """Make an IPC call via HTTP and wait for it to return. - The contents of data will be expanded to kwargs and passed into the target - function.""" - from collections import OrderedDict - import urllib2 - import json - debug('Handling HTTP IPC call to {}'.format(callname)) - # don't use proxy for localhost - url = 'http://127.0.0.1:{}/{}'.format( - g.LOCAL_DB.get_value('ns_service_port', 8001), callname) - urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler({}))) + data=_data) + + +@measure_exec_time_decorator() +def make_call(func_name, data=None, endpoint=IPC_ENDPOINT_NFSESSION): + """ + Make an IPC call + :param func_name: function name + :param data: the data to send, if will be passed a dict will be expanded to kwargs into the target function + :param endpoint: used to override the endpoint on IPC via HTTP + :return: the data if provided by the target function + :raise: can raise exceptions raised from the target function + """ + # Note: IPC over HTTP - handle a FULL objects serialization (like classes) + # IPC over AddonSignals - by default NOT HANDLE objects serialization, + # but we have implemented a double data conversion to support a full objects serialization + # (as predisposition for AddonConnector) the main problem is Kodi memory leak, see: + # https://github.com/xbmc/xbmc/issues/19332 + # https://github.com/CastagnaIT/script.module.addon.connector + if G.IPC_OVER_HTTP: + return make_http_call(endpoint, func_name, data) + return make_addonsignals_call(func_name, data) + + +def make_http_call(endpoint, func_name, data=None): + """ + Make an IPC call via HTTP and wait for it to return. + The contents of data will be expanded to kwargs and passed into the target function. + """ + from urllib.request import build_opener, install_opener, ProxyHandler, urlopen + from urllib.error import URLError + # Note: Using 'localhost' as address slowdown the call (Windows OS is affected) not sure if it is an urllib issue + url = f'http://127.0.0.1:{G.LOCAL_DB.get_value("nf_server_service_port")}{endpoint}/{func_name}' + LOG.debug('Handling HTTP IPC call to {}', url) + install_opener(build_opener(ProxyHandler({}))) # don't use proxy for localhost try: - result = json.loads( - urllib2.urlopen(url=url, data=json.dumps(data)).read(), - object_pairs_hook=OrderedDict) - except urllib2.URLError: - raise BackendNotReady - _raise_for_error(callname, result) - return result + with urlopen(url=url, + data=pickle.dumps(data, protocol=pickle.HIGHEST_PROTOCOL), + timeout=IPC_TIMEOUT_SECS) as f: + received_data = f.read() + if received_data: + _data = pickle.loads(received_data) + if isinstance(_data, Exception): + raise _data + return _data + return None + # except HTTPError as exc: + # raise exc + except URLError as exc: + err_msg = str(exc) + if '10049' in err_msg: + err_msg += '\r\nPossible cause is wrong localhost settings in your operative system.' + LOG.error(err_msg) + raise exceptions.BackendNotReady(err_msg) from exc def make_addonsignals_call(callname, data): - """Make an IPC call via AddonSignals and wait for it to return. - The contents of data will be expanded to kwargs and passed into the target - function.""" - debug('Handling AddonSignals IPC call to {}'.format(callname)) + """ + Make an IPC call via AddonSignals and wait for it to return. + The contents of data will be expanded to kwargs and passed into the target function. + """ + LOG.debug('Handling AddonSignals IPC call to {}', callname) + _data = b64encode(pickle.dumps(data, pickle.HIGHEST_PROTOCOL)).decode('ascii') result = AddonSignals.makeCall( - source_id=g.ADDON_ID, + source_id=G.ADDON_ID, signal=callname, - data=data, - timeout_ms=16000) - _raise_for_error(callname, result) - if result is None: - raise Exception('AddonSignals call timed out') - return result - - -def _raise_for_error(callname, result): - if isinstance(result, dict) and 'error' in result: - error('IPC call {callname} returned {error}: {message}' - .format(callname=callname, **result)) + data=_data, + timeout_ms=IPC_TIMEOUT_SECS * 1000, + use_timeout_exception=True) + _result = pickle.loads(b64decode(result)) + if isinstance(_result, Exception): + raise _result + return _result + + +class EnvelopeAddonSignalsCallback: + """ + Handle an AddonSignals function callback, + allow to use funcs with multiple args/kwargs, + allow an automatic AddonSignals.returnCall callback, + can handle catching and forwarding of exceptions + """ + def __init__(self, func): + self._func = func + + def call(self, data): + """In memory reference for the target func""" try: - raise apierrors.__dict__[result['error']](result['message']) - except KeyError: - raise Exception(result['error']) - - -def addonsignals_return_call(func): - """Makes func return callable through AddonSignals and - handles catching, conversion and forwarding of exceptions""" - @wraps(func) - def make_return_call(instance, data): - """Makes func return callable through AddonSignals and - handles catching, conversion and forwarding of exceptions""" - # pylint: disable=broad-except + _data = pickle.loads(b64decode(data)) + _call(self._func, _data) + except Exception: # pylint: disable=broad-except + import traceback + LOG.error(traceback.format_exc()) + + def return_call(self, data): + """In memory reference for the target func, with an automatic AddonSignals.returnCall callback""" try: - result = call(instance, func, data) - except Exception as exc: - error('IPC callback raised exception: {exc}', exc) - error(traceback.format_exc()) - result = { - 'error': exc.__class__.__name__, - 'message': exc.__unicode__() - } - if g.IPC_OVER_HTTP: - return result - # Do not return None or AddonSignals will keep waiting till timeout - if result is None: - result = {} - AddonSignals.returnCall( - signal=_signal_name(func), source_id=g.ADDON_ID, data=result) - return result - return make_return_call - - -def call(instance, func, data): + _data = pickle.loads(b64decode(data)) + result = _call(self._func, _data) + except Exception as exc: # pylint: disable=broad-except + if exc.__class__.__name__ not in ['CacheMiss', 'MetadataNotAvailable']: + LOG.error('IPC callback raised exception: {exc}', exc=exc) + import traceback + LOG.error(traceback.format_exc()) + result = exc + _result = b64encode(pickle.dumps(result, pickle.HIGHEST_PROTOCOL)).decode('ascii') + AddonSignals.returnCall(signal=self._func.__name__, source_id=G.ADDON_ID, data=_result) + + +def _call(func, data): if isinstance(data, dict): - return func(instance, **data) - elif data is not None: - return func(instance, data) - return func(instance) - - -def _signal_name(func): - return func.__name__ + return func(**data) + if data is not None: + return func(data) + return func() diff --git a/resources/lib/common/kodi_library_ops.py b/resources/lib/common/kodi_library_ops.py new file mode 100644 index 000000000..8d370de3b --- /dev/null +++ b/resources/lib/common/kodi_library_ops.py @@ -0,0 +1,188 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Helper functions for Kodi library operations + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +import os + +import xbmcvfs + +from resources.lib.globals import G +from resources.lib.utils.logging import LOG +from .exceptions import ItemNotFound, DBRecordNotExistError +from .kodi_ops import json_rpc, get_local_string, json_rpc_multi +from .videoid import VideoId + + +LIBRARY_PROPS = { + 'episode': ['title', 'plot', 'writer', 'playcount', 'director', 'season', + 'episode', 'originaltitle', 'showtitle', 'lastplayed', 'file', + 'resume', 'dateadded', 'art', 'userrating', 'firstaired', 'runtime'], + 'movie': ['title', 'genre', 'year', 'director', 'trailer', + 'tagline', 'plot', 'plotoutline', 'originaltitle', 'lastplayed', + 'playcount', 'writer', 'studio', 'mpaa', 'country', + 'imdbnumber', 'runtime', 'set', 'showlink', 'premiered', + 'top250', 'file', 'sorttitle', 'resume', 'setid', 'dateadded', + 'tag', 'art', 'userrating'] +} + + +def update_library_item_details(dbtype, dbid, details): + """Update properties of an item in the Kodi library""" + method = f'VideoLibrary.Set{dbtype.capitalize()}Details' + params = {f'{dbtype}id': dbid} + params.update(details) + return json_rpc(method, params) + + +def get_library_items(dbtype, video_filter=None): + """Return a list of all items in the Kodi library that are of type dbtype (either movie or episode)""" + method = f'VideoLibrary.Get{dbtype.capitalize()}s' + params = {'properties': ['file']} + if video_filter: + params.update({'filter': video_filter}) + return json_rpc(method, params)[dbtype + 's'] + + +def get_library_item_details(dbtype, itemid): + """Return details for an item from the Kodi library""" + method = f'VideoLibrary.Get{dbtype.capitalize()}Details' + params = { + dbtype + 'id': itemid, + 'properties': LIBRARY_PROPS[dbtype]} + return json_rpc(method, params)[dbtype + 'details'] + + +def scan_library(path=''): + """ + Start a Kodi library scanning in a specified folder to find new items + :param path: Update only the library elements in the specified path (fast processing) + """ + method = 'VideoLibrary.Scan' + params = {'directory': xbmcvfs.makeLegalFilename(xbmcvfs.translatePath(path))} + return json_rpc(method, params) + + +def clean_library(show_dialog=True, path=''): + """ + Start a Kodi library cleaning to remove non-existing items + :param show_dialog: True a progress dialog is shown + :param path: Clean only the library elements in the specified path (fast processing) + """ + method = 'VideoLibrary.Clean' + params = {'content': 'video', + 'showdialogs': show_dialog} + if path: + params['directory'] = xbmcvfs.makeLegalFilename(xbmcvfs.translatePath(path)) + return json_rpc(method, params) + + +def get_library_item_by_videoid(videoid): + """Find an item in the Kodi library by its Netflix videoid and return Kodi DBID and mediatype""" + try: + # Obtain a file path for this videoid from add-on library database + file_path, media_type = _get_videoid_file_path(videoid) + # Ask to Kodi to find this file path in Kodi library database, and get all item details + return _get_item_details_from_kodi(media_type, file_path) + except (KeyError, IndexError, ItemNotFound, DBRecordNotExistError) as exc: + raise ItemNotFound(f'The video with id {videoid} is not present in the Kodi library') from exc + + +def _get_videoid_file_path(videoid): + """Get a file path of a file referred to the videoid (to tvshow/season will be taken a random file episode)""" + if videoid.mediatype == VideoId.MOVIE: + file_path = G.SHARED_DB.get_movie_filepath(videoid.value) + media_type = videoid.mediatype + elif videoid.mediatype == VideoId.EPISODE: + file_path = G.SHARED_DB.get_episode_filepath(videoid.tvshowid, + videoid.seasonid, + videoid.episodeid) + media_type = videoid.mediatype + elif videoid.mediatype == VideoId.SHOW: + file_path = G.SHARED_DB.get_random_episode_filepath_from_tvshow(videoid.value) + media_type = VideoId.EPISODE + elif videoid.mediatype == VideoId.SEASON: + file_path = G.SHARED_DB.get_random_episode_filepath_from_season(videoid.tvshowid, + videoid.seasonid) + media_type = VideoId.EPISODE + else: + # Items of other mediatype are never in library + raise ItemNotFound + return file_path, media_type + + +def _get_item_details_from_kodi(mediatype, file_path): + """Get a Kodi library item with details (from Kodi database) by searching with the file path""" + # To ensure compatibility with previously exported items, make the filename legal + file_path = xbmcvfs.makeLegalFilename(file_path) + dir_path = os.path.dirname(xbmcvfs.translatePath(file_path)) + filename = os.path.basename(xbmcvfs.translatePath(file_path)) + # We get the data from Kodi library using filters, this is much faster than loading all episodes in memory. + if file_path[:10] == 'special://': + # If the path is special, search with real directory path and also special path + special_dir_path = os.path.dirname(file_path) + path_filter = {'or': [{'field': 'path', 'operator': 'startswith', 'value': dir_path}, + {'field': 'path', 'operator': 'startswith', 'value': special_dir_path}]} + else: + path_filter = {'field': 'path', 'operator': 'startswith', 'value': dir_path} + # Now build the all request and call the json-rpc function through get_library_items + library_items = get_library_items( + mediatype, + {'and': [path_filter, {'field': 'filename', 'operator': 'is', 'value': filename}]} + ) + if not library_items: + raise ItemNotFound + return get_library_item_details(mediatype, library_items[0][mediatype + 'id']) + + +def remove_videoid_from_kodi_library(videoid): + """Remove an item from the Kodi library database (not related files)""" + try: + # Get a single file result by searching by videoid + kodi_library_items = [get_library_item_by_videoid(videoid)] + LOG.debug('Removing {} ({}) from Kodi library', + videoid, + kodi_library_items[0].get('showtitle', kodi_library_items[0]['title'])) + media_type = videoid.mediatype + if videoid.mediatype in [VideoId.SHOW, VideoId.SEASON]: + # Retrieve the all episodes in the export folder + tvshow_path = os.path.dirname(kodi_library_items[0]['file']) + filters = {'and': [ + {'field': 'path', 'operator': 'startswith', + 'value': tvshow_path}, + {'field': 'filename', 'operator': 'endswith', 'value': '.strm'} + ]} + if videoid.mediatype == VideoId.SEASON: + # Use the single file result to figure out what the season is, + # then add a season filter to get only the episodes of the specified season + filters['and'].append({'field': 'season', 'operator': 'is', + 'value': str(kodi_library_items[0]['season'])}) + kodi_library_items = get_library_items(VideoId.EPISODE, filters) + media_type = VideoId.EPISODE + rpc_params = { + 'movie': ['VideoLibrary.RemoveMovie', 'movieid'], + # We should never remove an entire show + # 'show': ['VideoLibrary.RemoveTVShow', 'tvshowid'], + # Instead we delete all episodes listed in the JSON query above + 'show': ['VideoLibrary.RemoveEpisode', 'episodeid'], + 'season': ['VideoLibrary.RemoveEpisode', 'episodeid'], + 'episode': ['VideoLibrary.RemoveEpisode', 'episodeid'] + } + list_rpc_params = [] + # Collect multiple json-rpc commands + for item in kodi_library_items: + params = rpc_params[media_type] + list_rpc_params.append({params[1]: item[params[1]]}) + rpc_method = rpc_params[media_type][0] + # Execute all the json-rpc commands in one call + json_rpc_multi(rpc_method, list_rpc_params) + except ItemNotFound: + LOG.warn('Cannot remove {} from Kodi library, item not present', videoid) + except KeyError as exc: + from resources.lib.kodi import ui + ui.show_notification(get_local_string(30120), time=7500) + LOG.error('Cannot remove {} from Kodi library, mediatype not supported', exc) diff --git a/resources/lib/common/kodi_ops.py b/resources/lib/common/kodi_ops.py new file mode 100644 index 000000000..14cac3f0a --- /dev/null +++ b/resources/lib/common/kodi_ops.py @@ -0,0 +1,297 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Helper functions for Kodi operations + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +import itertools +import json +from contextlib import contextmanager + +import xbmc + +from resources.lib.globals import G +from resources.lib.utils.logging import LOG +from .misc_utils import CmpVersion + +__CURRENT_KODI_PROFILE_NAME__ = None + +LOCALE_CONV_TABLE = { + 'es-ES': 'es-Spain', + 'pt-BR': 'pt-Brazil', + 'fr-CA': 'fr-Canada', + 'ar-EG': 'ar-Egypt', + 'nl-BE': 'nl-Belgium', + 'en-GB': 'en-UnitedKingdom' +} +REPLACE_MACRO_LANG = { + # 'language code' : [macro language codes] + 'no': ['nb', 'nn'] +} +REPLACE_MACRO_LIST = list(itertools.chain.from_iterable(REPLACE_MACRO_LANG.values())) + + +def json_rpc(method, params=None): + """ + Executes a JSON-RPC in Kodi + + :param method: The JSON-RPC method to call + :type method: string + :param params: The parameters of the method call (optional) + :type params: dict + :returns: dict -- Method call result + """ + request_data = {'jsonrpc': '2.0', 'method': method, 'id': 1, + 'params': params or {}} + request = json.dumps(request_data) + LOG.debug('Executing JSON-RPC: {}', request) + raw_response = xbmc.executeJSONRPC(request) + # debug('JSON-RPC response: {}'.format(raw_response)) + response = json.loads(raw_response) + if 'error' in response: + raise IOError(f'JSONRPC-Error {response["error"]["code"]}: {response["error"]["message"]}') + return response['result'] + + +def json_rpc_multi(method, list_params=None): + """ + Executes multiple JSON-RPC with the same method in Kodi + + :param method: The JSON-RPC method to call + :type method: string + :param list_params: Multiple list of parameters of the method call + :type list_params: a list of dict + :returns: dict -- Method call result + """ + request_data = [{'jsonrpc': '2.0', 'method': method, 'id': 1, 'params': params or {}} for params in list_params] + request = json.dumps(request_data) + LOG.debug('Executing JSON-RPC: {}', request) + raw_response = xbmc.executeJSONRPC(request) + if 'error' in raw_response: + raise IOError(f'JSONRPC-Error {raw_response}') + return json.loads(raw_response) + + +def container_refresh(use_delay=False): + """Refresh the current container""" + if use_delay: + # When operations are performed in the Kodi library before call this method + # can be necessary to apply a delay before run the refresh, otherwise the page does not refresh correctly + # seems to be caused by a race condition with the Kodi library update (but i am not really sure) + from time import sleep + sleep(1) + WndHomeProps[WndHomeProps.IS_CONTAINER_REFRESHED] = 'True' + xbmc.executebuiltin('Container.Refresh') + + +def container_update(url, reset_history=False): + """Update the current container""" + func_str = f'Container.Update({url},replace)' if reset_history else f'Container.Update({url})' + xbmc.executebuiltin(func_str) + + +@contextmanager +def show_busy_dialog(): + """Context to show the busy dialog on the screen""" + xbmc.executebuiltin('ActivateWindow(busydialognocancel)') + try: + yield + finally: + xbmc.executebuiltin('Dialog.Close(busydialognocancel)') + + +def get_local_string(string_id): + """Retrieve a localized string by its id""" + src = xbmc if string_id < 30000 else G.ADDON + return src.getLocalizedString(string_id) + + +def run_plugin_action(path, block=False): + """Create an action that can be run with xbmc.executebuiltin in order to run a Kodi plugin specified by path. + If block is True (default=False), the execution of code will block until the called plugin has finished running.""" + return f'RunPlugin({path}, {block})' + + +def run_plugin(path, block=False): + """Run a Kodi plugin specified by path. If block is True (default=False), + the execution of code will block until the called plugin has finished running.""" + xbmc.executebuiltin(run_plugin_action(path, block)) + + +def schedule_builtin(time, command, name='NetflixTask'): + """Set an alarm to run builtin command after time has passed""" + xbmc.executebuiltin(f'AlarmClock({name},{command},{time},silent)') + + +def play_media(media): + """Play a media in Kodi""" + xbmc.executebuiltin(f'PlayMedia({media})') + + +def stop_playback(): + """Stop the running playback""" + xbmc.executebuiltin('PlayerControl(Stop)') + + +def get_current_kodi_profile_name(no_spaces=True): + """Lazily gets the name of the Kodi profile currently used""" + if not hasattr(get_current_kodi_profile_name, 'cached'): + name = json_rpc('Profiles.GetCurrentProfile', {'properties': ['thumbnail', 'lockmode']}).get('label', 'unknown') + get_current_kodi_profile_name.cached = name.replace(' ', '_') if no_spaces else name + return get_current_kodi_profile_name.cached + + +class _WndProps: # pylint: disable=no-init + """Read and write a property to the Kodi home window""" + # Default Properties keys + SERVICE_STATUS = 'service_status' + """Return current service status""" + IS_CONTAINER_REFRESHED = 'is_container_refreshed' + """Return 'True' when container_refresh in kodi_ops.py is used by context menus, etc.""" + CURRENT_DIRECTORY = 'current_directory' + CURRENT_DIRECTORY_MENU_ID = 'current_directory_menu_id' + """ + Return the name of the currently loaded directory (so the method name of directory.py class), otherwise: + [''] When the add-on is in his first run instance, so startup page + ['root'] When add-on startup page is re-loaded (like refresh) or manually called + Notice: In some cases the value may not be consistent example: + - when you exit to Kodi home + - external calls to the add-on while browsing the add-on + """ + def __getitem__(self, key): + try: + # If you use multiple Kodi profiles you need to distinguish the property of current profile + return G.WND_KODI_HOME.getProperty(f'netflix_{get_current_kodi_profile_name()}_{key}') + except Exception: # pylint: disable=broad-except + return '' + + def __setitem__(self, key, newvalue): + # If you use multiple Kodi profiles you need to distinguish the property of current profile + G.WND_KODI_HOME.setProperty(f'netflix_{get_current_kodi_profile_name()}_{key}', newvalue) + + +WndHomeProps = _WndProps() + + +def get_kodi_audio_language(iso_format=xbmc.ISO_639_1): + """ + Return the audio language from Kodi settings + WARNING: Based on Kodi player settings can also return values as: 'mediadefault', 'original' + """ + audio_language = json_rpc('Settings.GetSettingValue', {'setting': 'locale.audiolanguage'})['value'] + if audio_language in ['mediadefault', 'original']: + return audio_language + if audio_language == 'default': # "User interface language" + return get_kodi_ui_language(iso_format) + return convert_language_iso(audio_language, iso_format) + + +def get_kodi_subtitle_language(iso_format=xbmc.ISO_639_1): + """ + Return the subtitle language from Kodi settings + WARNING: Based on Kodi player settings can also return values as: 'forced_only', 'original', or: + 'default' when set as "User interface language" + 'none' when set as "None" + """ + subtitle_language = json_rpc('Settings.GetSettingValue', {'setting': 'locale.subtitlelanguage'})['value'] + if subtitle_language in ['forced_only', 'original', 'default', 'none']: + return subtitle_language + return convert_language_iso(subtitle_language, iso_format) + + +def get_kodi_ui_language(iso_format=xbmc.ISO_639_1): + """Return the Kodi UI interface language""" + setting = json_rpc('Settings.GetSettingValue', {'setting': 'locale.language'})['value'] + # The value returned is as "resource.language.en_gb" we keep only the first two chars "en" + return convert_language_iso(setting.split('.')[-1][:2], iso_format) + + +def get_kodi_is_prefer_sub_impaired(): + """Return True if subtitles for impaired are enabled in Kodi settings""" + return json_rpc('Settings.GetSettingValue', {'setting': 'accessibility.subhearing'})['value'] + + +def get_kodi_is_prefer_audio_impaired(): + """Return True if audio for impaired is enabled in Kodi settings""" + return json_rpc('Settings.GetSettingValue', {'setting': 'accessibility.audiovisual'})['value'] + + +def convert_language_iso(from_value, iso_format=xbmc.ISO_639_1): + """ + Convert given value (English name or two/three letter code) to the specified format + :param iso_format: specify the iso format (two letter code ISO_639_1 or three letter code ISO_639_2) + """ + return xbmc.convertLanguage(from_value, iso_format) + + +def apply_lang_code_changes(data_list): + """Apply changes to the language codes""" + lang_list = [item['language'] for item in data_list if not item.get('isNoneTrack', False)] + for item in data_list: + if item.get('isNoneTrack', False): + continue + convert_macro_languages(item, lang_list) + fix_locale_languages(item) + + +def convert_macro_languages(item, lang_list): + """Covert the macrolanguage's code to their primary language code""" + # Kodi handles the macrolanguage's separately, then if the user sets a primary language to audio/subtitles, + # it will not be able to automatically fallback to his macrolanguage when the primary language not exist. + # e.g. if you set Norwegian (no) and the video played has only the macro lang. Norwegian Bokmål (nb) + # the macro language will not be selected, and the user will have to manually select it. + # To avoid this we will convert the macro (nb) code to the main lang code (no) + if item['language'] in REPLACE_MACRO_LIST: + main_lang = next(k for k, v in REPLACE_MACRO_LANG.items() if item['language'] in v) + # Convert the macro code to the main lang code only if the primary language not already exist + if main_lang not in lang_list: + item['language'] = main_lang + + +def fix_locale_languages(item): + """Replace all the languages with the country code because Kodi does not support IETF BCP 47 standard""" + # Languages with the country code causes the display of wrong names in Kodi settings like + # es-ES as 'Spanish-Spanish', pt-BR as 'Portuguese-Breton', nl-BE as 'Dutch-Belarusian', etc + # and the impossibility to set them as the default audio/subtitle language + # Issue: https://github.com/xbmc/xbmc/issues/15308 + if item['language'] == 'pt-BR': + # Replace pt-BR with pb, is an unofficial ISO 639-1 Portuguese (Brazil) language code + # has been added to Kodi 18.7 and Kodi 19.x PR: https://github.com/xbmc/xbmc/pull/17689 + item['language'] = 'pb' + # From Kodi v20, this problem has been improved by https://github.com/xbmc/xbmc/pull/21776 + # it is not needed anymore manually rename country codes to avoid inconsistent descriptions on kodi GUI + # and so we allow users to use advancedsettings.xml to specify custom descriptions + if G.KODI_VERSION < '20' and len(item['language']) > 2: + # Replace know locale with country + # so Kodi will not recognize the modified country code and will show the string as it is + if item['language'] in LOCALE_CONV_TABLE: + item['language'] = LOCALE_CONV_TABLE[item['language']] + else: + LOG.error('fix_locale_languages: missing mapping conversion for locale "{}"', item['language']) + + +class KodiVersion(CmpVersion): + """Comparator for Kodi version numbers""" + # Examples of some types of supported strings: + # 10.1 Git:Unknown PRE-11.0 Git:Unknown 11.0-BETA1 Git:20111222-22ad8e4 + # 18.1-RC1 Git:20190211-379f5f9903 19.0-ALPHA1 Git:20190419-c963b64487 + def __init__(self): + import re + self.build_version = xbmc.getInfoLabel('System.BuildVersion') + # Parse the version number + result = re.search(r'\d+\.\d+', self.build_version) + version = result.group(0) if result else '' + super().__init__(version) + # Parse the date of GIT build + result = re.search(r'(Git:)(\d+?(?=(-|$)))', self.build_version) + self.date = int(result.group(2)) if result and len(result.groups()) >= 2 else None + # Parse the stage name + result = re.search(r'(\d+\.\d+-)(.+)(?=\s)', self.build_version) + if not result: + result = re.search(r'^(.+)(-\d+\.\d+)', self.build_version) + self.stage = result.group(1) if result else '' + else: + self.stage = result.group(2) if result else '' diff --git a/resources/lib/common/kodi_wrappers.py b/resources/lib/common/kodi_wrappers.py new file mode 100644 index 000000000..c097301cc --- /dev/null +++ b/resources/lib/common/kodi_wrappers.py @@ -0,0 +1,174 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2021 Stefano Gottardo - @CastagnaIT (original implementation module) + Wrappers of Kodi methods and objects + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +from typing import Dict, List, Tuple + +import xbmc +import xbmcgui + +from resources.lib.globals import G + +# Convert the deprecated ListItem.setInfo keys to use method names of the new xbmc.InfoTagVideo object +INFO_CONVERT_KEY = { + 'Title': 'setTitle', + 'Year': 'setYear', + 'Plot': 'setPlot', + 'PlotOutline': 'setPlotOutline', + 'Season': 'setSeason', + 'Episode': 'setEpisode', + 'Rating': 'setRating', + 'UserRating': 'setUserRating', + 'Mpaa': 'setMpaa', + 'Duration': 'setDuration', + 'Trailer': 'setTrailer', + 'DateAdded': 'setDateAdded', + 'Director': 'setDirectors', + 'Writer': 'setWriters', + 'Genre': 'setGenres', + 'MediaType': 'setMediaType', + 'TVShowTitle': 'setTvShowTitle', + 'PlayCount': 'setPlaycount' +} + +# xbmcgui.ListItem do not support any kind of object serialisation, then transferring directories of ListItem's +# from two python instances (add-on service instance to an add-on client instance) is usually impossible, +# then better simplify the code despite a slight overhead in directory loading. + +# pylint: disable=redefined-builtin,invalid-name,no-member +class ListItemW(xbmcgui.ListItem): + """ + Wrapper for xbmcgui.ListItem to add support for Pickle serialisation and add some helper functions + ('offscreen' will be True by default) + """ + + def __init__(self, label='', label2='', path=''): + super().__init__(label, label2, path, True) + self.__dict__.update({ + 'properties': {}, + 'infolabels': {}, + 'art': {}, + 'stream_info': {} + }) + + def __getnewargs__(self): # Pickle method + """Passes arguments to __new__ method""" + return self.getLabel(), self.getLabel2(), self.getPath(), True + + def __setstate__(self, state): # Pickle method + """Restore the state of the object data""" + self.setContentLookup(False) + if G.IS_OLD_KODI_MODULES: + super().setInfo('video', state['infolabels']) + for stream_type, quality_info in state['stream_info'].items(): + super().addStreamInfo(stream_type, quality_info) + else: + video_info = super().getVideoInfoTag() + set_video_info_tag(state['infolabels'], video_info) + if state['stream_info']: + video_info.addVideoStream(xbmc.VideoStreamDetail(**state['stream_info']['video'])) + video_info.addAudioStream(xbmc.AudioStreamDetail(**state['stream_info']['audio'])) + # From Kodi 20 "ResumeTime" and "TotalTime" must be set with setResumePoint of InfoTagVideo object + if 'ResumeTime' in state['properties']: + resume_time = float(state['properties'].pop('ResumeTime', 0)) + total_time = float(state['properties'].pop('TotalTime', 0)) + video_info.setResumePoint(resume_time, total_time) + super().setProperties(state['properties']) + super().setArt(state['art']) + super().addContextMenuItems(state.get('context_menus', [])) + super().select(state.get('is_selected', False)) + + # To improve performances we override the xbmcgui.ListItem methods to store values locally (to self.__dict__) + # without call the base method, then when the object will be unpickled with __setstate__, + # the local values will be assigned to the base original ListItem methods. + + def setInfo(self, type: str, infoLabels: Dict[str, str]): + # NOTE: 'type' argument is ignored because we use only 'video' type, but kept for future changes + if G.IS_SERVICE: + self.__dict__['infolabels'] = infoLabels + else: + super().setInfo(type, infoLabels) + + def getProperty(self, key: str): + if G.IS_SERVICE: + return self.__dict__['properties'].get(key) + return super().getProperty(key) + + def setProperty(self, key: str, value: str): + if G.IS_SERVICE: + self.__dict__['properties'][key] = value + else: + super().setProperty(key, value) + + def setProperties(self, dictionary: Dict[str, str]): + if G.IS_SERVICE: + self.__dict__['properties'].update(dictionary) + else: + super().setProperties(dictionary) + + def getArt(self, key: str): + if G.IS_SERVICE: + return self.__dict__['art'].get(key) + return super().getArt(key) + + def setArt(self, dictionary: Dict[str, str]): + if G.IS_SERVICE: + self.__dict__['art'].update(dictionary) + else: + super().setArt(dictionary) + + def addStreamInfo(self, cType: str, dictionary: Dict[str, str]): + if G.IS_SERVICE: + self.__dict__['stream_info'][cType] = dictionary + else: + super().addStreamInfo(cType, dictionary) + + def addContextMenuItems(self, items: List[Tuple[str, str]], replaceItems=False): + if G.IS_SERVICE: + self.__dict__['context_menus'] = items + else: + super().addContextMenuItems(items, replaceItems) + + def isSelected(self): + if G.IS_SERVICE: + return self.__dict__.get('is_selected', False) + return super().isSelected() + + def select(self, selected: bool): + if G.IS_SERVICE: + self.__dict__['is_selected'] = selected + else: + super().select(selected) + + # Custom helper methods, for service instance only + + def addStreamInfoFromDict(self, dictionary): + """ + Add or update all stream info from a dictionary + [CAN BE USED ON SERVICE INSTANCE ONLY] + """ + self.__dict__['stream_info'].update(dictionary) + + def updateInfo(self, dictionary): + """ + Add or update data over the existing data previously added with 'setInfo' + [CAN BE USED ON SERVICE INSTANCE ONLY] + """ + self.__dict__['infolabels'].update(dictionary) + + +def set_video_info_tag(info: Dict[str, str], video_info_tag: xbmc.InfoTagVideo): + """Convert old info data (for ListItem.setInfo) and use it to set the new methods of InfoTagVideo object""" + # From Kodi v20 ListItem.setInfo is deprecated, we need to use the methods of InfoTagVideo object + # "Cast" and "Tag" keys need to be converted + cast_names = info.pop('Cast', []) + video_info_tag.setCast([xbmc.Actor(name) for name in cast_names]) + tag_names = info.pop('Tag', []) + video_info_tag.setTagLine(' / '.join(tag_names)) + for key, value in info.items(): + getattr(video_info_tag, INFO_CONVERT_KEY[key])(value) diff --git a/resources/lib/common/kodiops.py b/resources/lib/common/kodiops.py deleted file mode 100644 index 8c4be4d2f..000000000 --- a/resources/lib/common/kodiops.py +++ /dev/null @@ -1,191 +0,0 @@ -# -*- coding: utf-8 -*- -"""Helper functions for Kodi operations""" -from __future__ import absolute_import, division, unicode_literals - -import json - -import xbmc - -from resources.lib.globals import g - -from .logging import debug - -LIBRARY_PROPS = { - 'episode': ['title', 'plot', 'writer', 'playcount', 'director', 'season', - 'episode', 'originaltitle', 'showtitle', 'lastplayed', 'file', - 'resume', 'dateadded', 'art', 'userrating', 'firstaired'], - 'movie': ['title', 'genre', 'year', 'director', 'trailer', - 'tagline', 'plot', 'plotoutline', 'originaltitle', 'lastplayed', - 'playcount', 'writer', 'studio', 'mpaa', 'country', - 'imdbnumber', 'runtime', 'set', 'showlink', 'premiered', - 'top250', 'file', 'sorttitle', 'resume', 'setid', 'dateadded', - 'tag', 'art', 'userrating'] -} - - -def json_rpc(method, params=None): - """ - Executes a JSON-RPC in Kodi - - :param method: The JSON-RPC method to call - :type method: string - :param params: The parameters of the method call (optional) - :type params: dict - :returns: dict -- Method call result - """ - request_data = {'jsonrpc': '2.0', 'method': method, 'id': 1, - 'params': params or {}} - request = json.dumps(request_data) - debug('Executing JSON-RPC: {}'.format(request)) - raw_response = unicode(xbmc.executeJSONRPC(request), 'utf-8') - # debug('JSON-RPC response: {}'.format(raw_response)) - response = json.loads(raw_response) - if 'error' in response: - raise IOError('JSONRPC-Error {}: {}' - .format(response['error']['code'], - response['error']['message'])) - return response['result'] - - -def update_library_item_details(dbtype, dbid, details): - """ - Update properties of an item in the Kodi library - """ - method = 'VideoLibrary.Set{}Details'.format(dbtype.capitalize()) - params = {'{}id'.format(dbtype): dbid} - params.update(details) - return json_rpc(method, params) - - -def get_library_items(dbtype, video_filter=None): - """Return a list of all items in the Kodi library that are of type - dbtype (either movie or episode)""" - method = 'VideoLibrary.Get{}s'.format(dbtype.capitalize()) - params = {'properties': ['file']} - if video_filter: - params.update({'filter': video_filter}) - return json_rpc(method, params)[dbtype + 's'] - - -def get_library_item_details(dbtype, itemid): - """Return details for an item from the Kodi library""" - method = 'VideoLibrary.Get{}Details'.format(dbtype.capitalize()) - params = { - dbtype + 'id': itemid, - 'properties': LIBRARY_PROPS[dbtype]} - return json_rpc(method, params)[dbtype + 'details'] - - -def scan_library(path=""): - """Start a library scanning in a specified folder""" - method = 'VideoLibrary.Scan' - params = {'directory': path} - return json_rpc(method, params) - - -def refresh_container(): - """Refresh the current container""" - xbmc.executebuiltin('Container.Refresh') - - -def get_local_string(string_id): - """Retrieve a localized string by its id""" - src = xbmc if string_id < 30000 else g.ADDON - return src.getLocalizedString(string_id) - - -def run_plugin_action(path, block=False): - """Create an action that can be run with xbmc.executebuiltin in order - to run a Kodi plugin specified by path. If block is True (default=False), - the execution of code will block until the called plugin has finished - running.""" - return 'XBMC.RunPlugin({}, {})'.format(path, block) - - -def run_plugin(path, block=False): - """Run a Kodi plugin specified by path. If block is True (default=False), - the execution of code will block until the called plugin has finished - running.""" - xbmc.executebuiltin(run_plugin_action(path, block)) - - -def schedule_builtin(time, command, name='NetflixTask'): - """Set an alarm to run builtin command after time has passed""" - xbmc.executebuiltin('AlarmClock({},{},{},silent)' - .format(name, command, time)) - - -def play_media(media): - """Play a media in Kodi""" - xbmc.executebuiltin('PlayMedia({})'.format(media)) - - -def stop_playback(): - """Stop the running playback""" - xbmc.executebuiltin('PlayerControl(Stop)') - - -def get_kodi_audio_language(): - """ - Return the audio language from Kodi settings - """ - audio_language = json_rpc('Settings.GetSettingValue', {'setting': 'locale.audiolanguage'}) - audio_language = xbmc.convertLanguage(audio_language['value'].encode('utf-8'), xbmc.ISO_639_1) - audio_language = audio_language if audio_language else xbmc.getLanguage(xbmc.ISO_639_1, False) - return audio_language if audio_language else 'en' - - -def get_kodi_subtitle_language(): - """ - Return the subtitle language from Kodi settings - """ - subtitle_language = json_rpc('Settings.GetSettingValue', {'setting': 'locale.subtitlelanguage'}) - if subtitle_language['value'] == 'forced_only': - return subtitle_language['value'] - subtitle_language = xbmc.convertLanguage(subtitle_language['value'].encode('utf-8'), xbmc.ISO_639_1) - subtitle_language = subtitle_language if subtitle_language else xbmc.getLanguage(xbmc.ISO_639_1, False) - subtitle_language = subtitle_language if subtitle_language else 'en' - return subtitle_language - - -def fix_locale_languages(data_list): - """Replace locale code, Kodi does not understand the country code""" - # Get all the ISO 639-1 codes (without country) - locale_list_nocountry = [] - for item in data_list: - if item.get('isNoneTrack', False): - continue - if len(item['language']) == 2 and not item['language'] in locale_list_nocountry: - locale_list_nocountry.append(item['language']) - # Replace the locale languages with country with a new one - for item in data_list: - if item.get('isNoneTrack', False): - continue - if len(item['language']) == 2: - continue - item['language'] = _adjust_locale(item['language'], item['language'][0:2] in locale_list_nocountry) - - -def _adjust_locale(locale_code, lang_code_without_country_exists): - """ - Locale conversion helper - Conversion table to prevent Kodi to display - es-ES as Spanish - Spanish, pt-BR as Portuguese - Breton, and so on - """ - locale_conversion_table = { - 'es-ES': 'es-Spain', - 'pt-BR': 'pt-Brazil', - 'fr-CA': 'fr-Canada', - 'ar-EG': 'ar-Egypt', - 'nl-BE': 'nl-Belgium', - 'en-GB': 'en-UnitedKingdom' - } - language_code = locale_code[0:2] - if not lang_code_without_country_exists: - return language_code - - if locale_code in locale_conversion_table: - return locale_conversion_table[locale_code] - - debug('AdjustLocale - missing mapping conversion for locale: {}'.format(locale_code)) - return locale_code diff --git a/resources/lib/common/logging.py b/resources/lib/common/logging.py deleted file mode 100644 index 7c05d87e2..000000000 --- a/resources/lib/common/logging.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- -"""Helper functions for logging""" -from __future__ import absolute_import, division, unicode_literals - -from functools import wraps - -import xbmc - -from resources.lib.globals import g - - -def log(msg, exc=None, level=xbmc.LOGDEBUG): - """Log a message to the Kodi logfile. - If msg contains a format placeholder for exc and exc is not none, - exc will be formatted into the message.""" - msg = msg.format(exc=exc) if exc is not None and '{exc}' in msg else msg - if isinstance(msg, str): - msg = msg.decode('utf-8') - xbmc.log('[{identifier} ({handle})] {msg}' - .format(identifier=g.ADDON_ID, handle=g.PLUGIN_HANDLE, msg=msg) - .encode('utf-8'), - level) - - -def debug(msg='{exc}', exc=None): - """Log a debug message.""" - log(msg, exc, xbmc.LOGDEBUG) - - -def info(msg='{exc}', exc=None): - """Log an info message.""" - log(msg, exc, xbmc.LOGINFO) - - -def warn(msg='{exc}', exc=None): - """Log a warning message.""" - log(msg, exc, xbmc.LOGWARNING) - - -def error(msg='{exc}', exc=None): - """Log an error message.""" - log(msg, exc, xbmc.LOGERROR) - - -def logdetails(func): - """ - Log decarator that is used to annotate methods & output everything to - the Kodi debug log - - :param delay: retry delay in sec - :type delay: int - :returns: string -- Devices MAC address - """ - name = func.func_name - - @wraps(func) - def wrapped(*args, **kwargs): - """Wrapper function to maintain correct stack traces""" - that = args[0] - class_name = that.__class__.__name__ - arguments = [':{} = {}:'.format(key, value) - for key, value in kwargs.iteritems() - if key not in ['account', 'credentials']] - if arguments: - log('{cls}::{method} called with arguments {args}' - .format(cls=class_name, method=name, args=''.join(arguments))) - else: - log('{cls}::{method} called'.format(cls=class_name, method=name)) - result = func(*args, **kwargs) - log('{cls}::{method} return {result}' - .format(cls=class_name, method=name, result=result)) - return result - - wrapped.__doc__ = func.__doc__ - return wrapped diff --git a/resources/lib/common/misc_utils.py b/resources/lib/common/misc_utils.py index adcaf8542..8fa22b9ee 100644 --- a/resources/lib/common/misc_utils.py +++ b/resources/lib/common/misc_utils.py @@ -1,24 +1,16 @@ # -*- coding: utf-8 -*- -# pylint: disable=unused-import -"""Miscellanneous utility functions""" -from __future__ import absolute_import, division, unicode_literals +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Miscellaneous utility functions -import traceback -from datetime import datetime -from urllib import urlencode, quote -from functools import wraps -from time import clock -import gzip -import base64 -import re -from StringIO import StringIO + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +import operator +from urllib.parse import quote, urlencode -import xbmc -import xbmcgui - -from resources.lib.globals import g -from .logging import debug, info, error -from .kodiops import get_local_string +from resources.lib.globals import G def find(value_to_find, attribute, search_space): @@ -26,36 +18,14 @@ def find(value_to_find, attribute, search_space): for video in search_space: if video[attribute] == value_to_find: return video - raise KeyError('Metadata for {} does not exist'.format(value_to_find)) + raise KeyError(f'Metadata for {value_to_find} does not exist') -def find_episode_metadata(videoid, metadata): +def find_episode_metadata(episode_videoid, metadata): """Find metadata for a specific episode within a show metadata dict""" - season = find(int(videoid.seasonid), 'id', metadata['seasons']) - return (find(int(videoid.episodeid), 'id', season.get('episodes', {})), - season) - - -def select_port(service): - """Select a port for a server and store it in the settings""" - port = select_unused_port() - g.LOCAL_DB.set_value('{}_service_port'.format(service.lower()), port) - info('[{}] Picked Port: {}'.format(service, port)) - return port - - -def select_unused_port(): - """ - Helper function to select an unused port on the host machine - - :return: int - Free port - """ - import socket - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.bind(('127.0.0.1', 0)) - _, port = sock.getsockname() - sock.close() - return port + season = find(int(episode_videoid.seasonid), 'id', metadata['seasons']) + episode = find(int(episode_videoid.episodeid), 'id', season.get('episodes', {})) + return episode, season def get_class_methods(class_item=None): @@ -68,48 +38,15 @@ def get_class_methods(class_item=None): """ from types import FunctionType _type = FunctionType - return [x - for x, y in class_item.__dict__.iteritems() + return [x for x, y in class_item.__dict__.items() if isinstance(y, _type)] -def get_user_agent(): - """ - Determines the user agent string for the current platform. - Needed to retrieve a valid ESN (except for Android, where the ESN can - be generated locally) - - :returns: str -- User agent string - """ - import platform - chrome_version = 'Chrome/73.0.3683.103' - base = 'Mozilla/5.0 ' - base += '%PL% ' - base += 'AppleWebKit/537.36 (KHTML, like Gecko) ' - base += '%CH_VER% Safari/537.36'.replace('%CH_VER%', chrome_version) - system = platform.system() - # Mac OSX - if system == 'Darwin': - return base.replace('%PL%', '(Macintosh; Intel Mac OS X 10_10_1)') - # Windows - if system == 'Windows': - return base.replace('%PL%', '(Windows NT 6.1; WOW64)') - # ARM based Linux - if platform.machine().startswith('arm'): - return base.replace('%PL%', '(X11; CrOS armv7l 7647.78.0)') - # x86 Linux - return base.replace('%PL%', '(X11; Linux x86_64)') - - def build_url(pathitems=None, videoid=None, params=None, mode=None): - """Build a plugin URL from pathitems and query parameters. - Add videoid to the path if it's present.""" + """Build a plugin URL from pathitems and query parameters. Add videoid to the path if it's present.""" if not (pathitems or videoid): raise ValueError('Either pathitems or videoid must be set.') - path = '{netloc}/{path}/{qs}'.format( - netloc=g.BASE_URL, - path=_encode_path(mode, pathitems, videoid), - qs=_encode_params(params)) + path = f'{G.BASE_URL}/{_encode_path(mode, pathitems, videoid)}/{_encode_params(params)}' return path @@ -129,7 +66,7 @@ def _encode_path(mode, pathitems, videoid): def _encode_params(params): - return ('?' + urlencode(params)) if params else '' + return f'?{urlencode(params)}' if params else '' def is_numeric(string): @@ -148,12 +85,14 @@ def strp(value, form): :return: datetime - parsed datetime object """ # pylint: disable=broad-except - from time import strptime + from datetime import datetime def_value = datetime.utcfromtimestamp(0) try: return datetime.strptime(value, form) except TypeError: + # Python bug https://bugs.python.org/issue27400 try: + from time import strptime return datetime(*(strptime(value, form)[0:6])) except ValueError: return def_value @@ -161,55 +100,31 @@ def strp(value, form): return def_value -def execute_tasks(title, tasks, task_handler, **kwargs): - """Run all tasks through task_handler and display a progress - dialog in the GUI. Additional kwargs will be passed into task_handler - on each invocation. - Returns a list of errors that occured during execution of tasks.""" - errors = [] - notify_errors = kwargs.pop('notify_errors', False) - progress = xbmcgui.DialogProgress() - progress.create(title) - for task_num, task in enumerate(tasks): - # pylint: disable=broad-except - task_title = task.get('title', 'Unknown Task') - progress.update(percent=int(task_num * 100 / len(tasks)), - line1=task_title) - xbmc.sleep(25) - if progress.iscanceled(): - break - try: - task_handler(task, **kwargs) - except Exception as exc: - error(traceback.format_exc()) - errors.append({ - 'task_title': task_title, - 'error': '{}: {}'.format(type(exc).__name__, exc)}) - _show_errors(notify_errors, errors) - return errors - +def strf_timestamp(timestamp, form): + """ + Helper function to safely create string date time from a timestamp value -def _show_errors(notify_errors, errors): - if notify_errors and errors: - xbmcgui.Dialog().ok(get_local_string(0), - '\n'.join(['{} ({})'.format(err['task_title'], - err['error']) - for err in errors])) + :return: string - date time in the specified form + """ + from datetime import datetime + try: + return datetime.utcfromtimestamp(timestamp).strftime(form) + except Exception: # pylint: disable=broad-except + return '' -def compress_data(data): - """GZIP and b64 encode data""" - out = StringIO() - with gzip.GzipFile(fileobj=out, mode='w') as outh: - outh.write(data) - return base64.standard_b64encode(out.getvalue()) +# def compress_data(data): +# """GZIP and b64 encode data""" +# out = StringIO() +# with gzip.GzipFile(fileobj=out, mode='w') as outh: +# outh.write(data) +# return base64.standard_b64encode(out.getvalue()) def merge_dicts(dict_to_merge, merged_dict): """Recursively merge the contents of dict_to_merge into merged_dict. - Values that are already present in merged_dict will be overwritten - if they are also present in dict_to_merge""" - for key, value in dict_to_merge.iteritems(): + Values that are already present in merged_dict will be overwritten if they are also present in dict_to_merge""" + for key, value in dict_to_merge.items(): if isinstance(merged_dict.get(key), dict): merge_dicts(value, merged_dict[key]) else: @@ -217,70 +132,28 @@ def merge_dicts(dict_to_merge, merged_dict): return merged_dict +def compare_dict_keys(dict_a, dict_b, compare_keys): + """Compare two dictionaries with the specified keys""" + return all(dict_a[k] == dict_b[k] for k in dict_a if k in compare_keys) + + +def chunked_list(seq, chunk_len): + for start in range(0, len(seq), chunk_len): + yield seq[start:start + chunk_len] + + def any_value_except(mapping, excluded_keys): - """Return a random value from a dict that is not associated with - excluded_key. Raises StopIteration if there are no other keys than - excluded_key""" + """Return a random value from a dict that is not associated with excluded_key. + Raises StopIteration if there are no other keys than excluded_key""" return next(mapping[key] for key in mapping if key not in excluded_keys) -def time_execution(immediate): - """A decorator that wraps a function call and times its execution""" - # pylint: disable=missing-docstring - def time_execution_decorator(func): - @wraps(func) - def timing_wrapper(*args, **kwargs): - g.add_time_trace_level() - start = clock() - try: - return func(*args, **kwargs) - finally: - if g.TIME_TRACE_ENABLED: - execution_time = int((clock() - start) * 1000) - if immediate: - debug('Call to {} took {}ms' - .format(func.func_name, execution_time)) - else: - g.TIME_TRACE.append([func.func_name, execution_time, - g.time_trace_level]) - g.remove_time_trace_level() - return timing_wrapper - return time_execution_decorator - - -def log_time_trace(): - """Write the time tracing info to the debug log""" - if not g.TIME_TRACE_ENABLED: - return - - time_trace = ['Execution time info for this run:\n'] - g.TIME_TRACE.reverse() - for trace in g.TIME_TRACE: - time_trace.append(' ' * trace[2]) - time_trace.append(format(trace[0], '<30')) - time_trace.append('{:>5} ms\n'.format(trace[1])) - debug(''.join(time_trace)) - g.reset_time_trace() - - -def is_edge_esn(esn): - """Return True if the esn is an EDGE esn""" - return esn.startswith('NFCDIE-02-') - - -def is_minimum_version(version, min_version): - """Return True if version is equal or greater to min_version""" - return map(int, version.split('.')) >= map(int, min_version.split('.')) - - -def is_less_version(version, max_version): - """Return True if version is less to max_version""" - return map(int, version.split('.')) < map(int, max_version.split('.')) +def enclose_quotes(content): + return f'"{content}"' def make_list(arg): - """Return a list with arg as its member or arg if arg is already a list. - Returns an empty list if arg is None""" + """Return a list with arg as its member or arg if arg is already a list. Returns an empty list if arg is None""" return (arg if isinstance(arg, list) else ([arg] @@ -293,53 +166,92 @@ def convert_seconds_to_hms_str(time): time %= 3600 m = int(time // 60) s = int(time % 60) - return '{:02d}:{:02d}:{:02d}'.format(h, m, s) + return f'{h:02d}:{m:02d}:{s:02d}' def remove_html_tags(raw_html): - h = re.compile('<.*?>') - text = re.sub(h, '', raw_html) - return text - - -def get_system_platform(): - platform = "unknown" - if xbmc.getCondVisibility('system.platform.linux') and not xbmc.getCondVisibility('system.platform.android'): - platform = "linux" - elif xbmc.getCondVisibility('system.platform.linux') and xbmc.getCondVisibility('system.platform.android'): - platform = "android" - elif xbmc.getCondVisibility('system.platform.xbox'): - platform = "xbox" - elif xbmc.getCondVisibility('system.platform.windows'): - platform = "windows" - elif xbmc.getCondVisibility('system.platform.osx'): - platform = "osx" - elif xbmc.getCondVisibility('system.platform.ios'): - platform = "ios" - return platform - - -class GetKodiVersion(object): - """Get the kodi version, git date, stage name""" - - def __init__(self): - # Examples of some types of supported strings: - # 10.1 Git:Unknown PRE-11.0 Git:Unknown 11.0-BETA1 Git:20111222-22ad8e4 - # 18.1-RC1 Git:20190211-379f5f9903 19.0-ALPHA1 Git:20190419-c963b64487 - build_version_str = xbmc.getInfoLabel('System.BuildVersion') - re_kodi_version = re.search('\\d+\\.\\d+?(?=(\\s|-))', build_version_str) - if re_kodi_version: - self.version = re_kodi_version.group(0) - else: - self.version = 'Unknown' - re_git_date = re.search('(Git:)(\\d+?(?=(-|$)))', build_version_str) - if re_git_date and len(re_git_date.groups()) >= 2: - self.date = int(re_git_date.group(2)) - else: - self.date = 0 - re_stage = re.search('(\\d+\\.\\d+-)(.+)(?=\\s)', build_version_str) - if not re_stage: - re_stage = re.search('^(.+)(-\\d+\\.\\d+)', build_version_str) - self.stage = re_stage.group(1) if re_stage else '' - else: - self.stage = re_stage.group(2) if re_stage else '' + import re + pattern = re.compile('<.*?>') + return re.sub(pattern, '', raw_html) + + +def censure(value, length=3): + """Censor part of the string with asterisks""" + if not value: + return value + return value[:-length] + '*' * length + + +def run_threaded(non_blocking, target_func, *args, **kwargs): + """Call a function in a thread, when specified""" + if not non_blocking: + return target_func(*args, **kwargs) + from threading import Thread + Thread(target=target_func, args=args, kwargs=kwargs).start() + return None + + +class CmpVersion: + """Comparator for version numbers""" + def __init__(self, version): + self.__version = str(version or '') + self.__ver_list = (self.__version or '0').split('.') + + def __str__(self): + return self.__version + + def __repr__(self): + return self.__version + + def __bool__(self): + """ + Allow "if" operator to check if there is a version set. + Will return False only when "version" set is an empty string or None. + """ + return bool(self.__version) + + def __iter__(self): + """Allow to get the version list by using "list" command builtin""" + return iter(self.__ver_list) + + def __lt__(self, other): + """Operator <""" + return operator.lt(*zip(*map(lambda x, y: (x or 0, y or 0), + map(int, self.__ver_list), + map(int, self.__conv_to_list(other))))) + + def __le__(self, other): + """Operator <=""" + return operator.le(*zip(*map(lambda x, y: (x or 0, y or 0), + map(int, self.__ver_list), + map(int, self.__conv_to_list(other))))) + + def __gt__(self, other): + """Operator >""" + return operator.gt(*zip(*map(lambda x, y: (x or 0, y or 0), + map(int, self.__ver_list), + map(int, self.__conv_to_list(other))))) + + def __ge__(self, other): + """Operator >=""" + return operator.ge(*zip(*map(lambda x, y: (x or 0, y or 0), + map(int, self.__ver_list), + map(int, self.__conv_to_list(other))))) + + def __eq__(self, other): + """Operator ==""" + return operator.eq(*zip(*map(lambda x, y: (x or 0, y or 0), + map(int, self.__ver_list), + map(int, self.__conv_to_list(other))))) + + def __ne__(self, other): + """Operator !=""" + return operator.ne(*zip(*map(lambda x, y: (x or 0, y or 0), + map(int, self.__ver_list), + map(int, self.__conv_to_list(other))))) + + def __conv_to_list(self, value): + """Convert a string or number or CmpVersion object to a list of strings""" + if isinstance(value, CmpVersion): + return list(value) + return str(value or '0').split('.') diff --git a/resources/lib/common/pathops.py b/resources/lib/common/pathops.py index bc67953e3..befa51c63 100644 --- a/resources/lib/common/pathops.py +++ b/resources/lib/common/pathops.py @@ -1,6 +1,12 @@ # -*- coding: utf-8 -*- -"""Helper functions for retrieving values from nested dicts""" -from __future__ import absolute_import, division, unicode_literals +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Helper functions for retrieving values from nested dicts + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" def get_path(path, search_space, include_key=False): @@ -8,7 +14,7 @@ def get_path(path, search_space, include_key=False): Throws KeyError if any key along the path does not exist""" if not isinstance(path, (tuple, list)): path = [path] - current_value = search_space[path[0]] + current_value = search_space[str(path[0])] if len(path) == 1: return (path[0], current_value) if include_key else current_value return get_path(path[1:], current_value, include_key) diff --git a/resources/lib/common/storage.py b/resources/lib/common/storage.py deleted file mode 100644 index 0c1907d0a..000000000 --- a/resources/lib/common/storage.py +++ /dev/null @@ -1,80 +0,0 @@ -# -*- coding: utf-8 -*- -"""Generic persistent on disk storage""" -from __future__ import absolute_import, division, unicode_literals - -import json - -from .logging import debug, warn -from .fileops import save_file, load_file - - -class PersistentStorage(object): - """ - Key-Value storage with a backing file on disk. - Reads entire dict structure into memory on first access and updates - the backing file with each changed entry. - - IMPORTANT: Changes to mutable objects inserted into the key-value-store - are not automatically written to disk. You need to call commit() to - persist these changes. - """ - def __init__(self, storage_id, no_save_on_destroy=False): - self.storage_id = storage_id - self.backing_file = self.storage_id + '.ndb' - self._contents = {} - self._dirty = True - self._no_save_on_destroy = no_save_on_destroy - debug('Instantiated {}'.format(self.storage_id)) - - def __del__(self): - debug('Destroying storage instance (no_save_on_destroy={0}) {1}'.format(str(self._no_save_on_destroy), self.storage_id)) - if not self._no_save_on_destroy: - self.commit() - - def __getitem__(self, key): - return self.contents[key] - - def __setitem__(self, key, value): - self.contents[key] = value - self.commit() - - @property - def contents(self): - """ - The contents of the storage file - """ - if self._dirty: - self._load_from_disk() - return self._contents - - def get(self, key, default=None): - """ - Return the value associated with key. If key does not exist, - return default (defaults to None) - """ - return self.contents.get(key, default) - - def commit(self): - """ - Write current contents to disk - """ - save_file(self.backing_file, json.dumps(self._contents)) - debug('Committed changes to backing file') - - def clear(self): - """ - Clear contents and backing file - """ - self._contents = {} - self.commit() - - def _load_from_disk(self): - # pylint: disable=broad-except - debug('Trying to load contents from disk') - try: - self._contents = json.loads(load_file(self.backing_file)) - debug('Loaded contents from backing file: {}' - .format(self._contents)) - except Exception: - warn('Backing file does not exist or is not readable') - self._dirty = False diff --git a/resources/lib/common/uuid_device.py b/resources/lib/common/uuid_device.py index 712deda7a..41754f679 100644 --- a/resources/lib/common/uuid_device.py +++ b/resources/lib/common/uuid_device.py @@ -1,27 +1,24 @@ # -*- coding: utf-8 -*- -"""Get the UUID of the device""" -from __future__ import absolute_import, division, unicode_literals +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2019 Stefano Gottardo - @CastagnaIT (original implementation module) + Get the UUID of the device -from .logging import debug -from .misc_utils import get_system_platform - -try: # Python 2 - from __builtin__ import str as text -except ImportError: # Python 3 - from builtins import str as text - -__CRYPT_KEY__ = None + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +from resources.lib.globals import G +from resources.lib.utils.logging import LOG +from .device_utils import get_system_platform def get_crypt_key(): """ Lazily generate the crypt key and return it """ - # pylint: disable=global-statement - global __CRYPT_KEY__ - if not __CRYPT_KEY__: - __CRYPT_KEY__ = _get_system_uuid() - return __CRYPT_KEY__ + if not hasattr(get_crypt_key, 'cached'): + get_crypt_key.cached = _get_system_uuid() + return get_crypt_key.cached def get_random_uuid(): @@ -30,7 +27,16 @@ def get_random_uuid(): :return: a string of a random uuid """ import uuid - return text(uuid.uuid4()) + return str(uuid.uuid4()) + + +def get_namespace_uuid(name): + """ + Generate a namespace uuid + :return: uuid object + """ + import uuid + return uuid.uuid5(uuid.NAMESPACE_DNS, name) def _get_system_uuid(): @@ -38,32 +44,31 @@ def _get_system_uuid(): Try to get an uuid from the system, if it's not possible generates a fake uuid :return: an uuid converted to MD5 """ - import uuid - uuid_value = None - system = get_system_platform() - if system in ['windows', 'xbox']: - uuid_value = _get_windows_uuid() - elif system == 'android': - uuid_value = _get_android_uuid() - elif system == 'linux': - uuid_value = _get_linux_uuid() - elif system in ['osx', 'ios']: - uuid_value = _get_macos_uuid() - if not uuid_value: - debug('It is not possible to get a system UUID creating a new UUID') - uuid_value = _get_fake_uuid(system != 'android') - return uuid.uuid5(uuid.NAMESPACE_DNS, text(uuid_value)).bytes + uuid_value = '' + if G.ADDON.getSettingBool('credentials_system_encryption'): + system = get_system_platform() + if system in ['windows', 'uwp']: + uuid_value = _get_windows_uuid() + elif system == 'android': + uuid_value = _get_android_uuid() + elif system == 'linux': + uuid_value = _get_linux_uuid() + elif system == 'osx': + # Due to OS restrictions on 'ios' and 'tvos' is not possible to use _get_macos_uuid() + # See python limits in the wiki development page + uuid_value = _get_macos_uuid() + if not uuid_value: + LOG.debug('It is not possible to get a system UUID creating a new UUID') + uuid_value = _get_fake_uuid(system not in ['android', 'linux']) + return get_namespace_uuid(str(uuid_value)).bytes def _get_windows_uuid(): # pylint: disable=broad-except - # pylint: disable=no-member + # pylint: disable=import-error # Under linux pylint rightly complains uuid_value = None try: - try: # Python 2 - import _winreg as winreg - except ImportError: # Python 3 - import winreg as winreg + import winreg registry = winreg.HKEY_LOCAL_MACHINE address = 'SOFTWARE\\Microsoft\\Cryptography' keyargs = winreg.KEY_READ | winreg.KEY_WOW64_64KEY @@ -89,15 +94,16 @@ def _get_linux_uuid(): import subprocess uuid_value = None try: - uuid_value = subprocess.check_output(['cat', '/var/lib/dbus/machine-id']).decode('utf-8') - except Exception: - pass + uuid_value = subprocess.check_output(['cat', '/etc/machine-id']).decode('utf-8') + except Exception as exc: + import traceback + LOG.error('_get_linux_uuid first attempt returned: {}', exc) + LOG.error(traceback.format_exc()) if not uuid_value: try: - # Fedora linux - uuid_value = subprocess.check_output(['cat', '/etc/machine-id']).decode('utf-8') - except Exception: - pass + uuid_value = subprocess.check_output(['cat', '/var/lib/dbus/machine-id']).decode('utf-8') + except Exception as exc: + LOG.error('_get_linux_uuid second attempt returned: {}', exc) return uuid_value @@ -112,9 +118,8 @@ def _get_android_uuid(): 'ro.product.manufacturer', 'ro.product.model', 'ro.product.platform', 'persist.sys.timezone', 'persist.sys.locale', 'net.hostname'] # Warning net.hostname property starting from android 10 is deprecated return empty - proc = subprocess.Popen(['/system/bin/getprop'], stdout=subprocess.PIPE) - output_data = proc.stdout.read().decode('utf-8') - proc.stdout.close() + with subprocess.Popen(['/system/bin/getprop'], stdout=subprocess.PIPE) as proc: + output_data = proc.communicate()[0].decode('utf-8') list_values = output_data.splitlines() for value in list_values: value_splitted = re.sub(r'\[|\]|\s', '', value).split(':') @@ -122,7 +127,7 @@ def _get_android_uuid(): values += value_splitted[1] except Exception: pass - return values + return values.encode('utf-8') def _get_macos_uuid(): @@ -130,19 +135,18 @@ def _get_macos_uuid(): import subprocess sp_dict_values = None try: - proc = subprocess.Popen( - ['/usr/sbin/system_profiler', 'SPHardwareDataType', '-detaillevel', 'full', '-xml'], - stdout=subprocess.PIPE) - output_data = proc.stdout.read().decode('utf-8') - proc.stdout.close() + with subprocess.Popen( + ['/usr/sbin/system_profiler', 'SPHardwareDataType', '-detaillevel', 'full', '-xml'], + stdout=subprocess.PIPE) as proc: + output_data = proc.communicate()[0].decode('utf-8') if output_data: sp_dict_values = _parse_osx_xml_plist_data(output_data) except Exception as exc: - debug('Failed to fetch OSX/IOS system profile {}'.format(exc)) + LOG.debug('Failed to fetch OSX/IOS system profile {}', exc) if sp_dict_values: - if 'UUID' in sp_dict_values.keys(): + if 'UUID' in sp_dict_values: return sp_dict_values['UUID'] - if 'serialnumber' in sp_dict_values.keys(): + if 'serialnumber' in sp_dict_values: return sp_dict_values['serialnumber'] return None @@ -151,20 +155,15 @@ def _parse_osx_xml_plist_data(data): import plistlib import re dict_values = {} - try: # Python 2 - xml_data = plistlib.readPlistFromString(data) - except AttributeError: # Python => 3.4 - # pylint: disable=no-member - xml_data = plistlib.loads(data) - + xml_data = plistlib.loads(data) items_dict = xml_data[0]['_items'][0] r = re.compile(r'.*UUID.*') # Find to example "platform_UUID" key - uuid_keys = filter(r.match, items_dict.keys()) + uuid_keys = list(filter(r.match, list(items_dict.keys()))) if uuid_keys: dict_values['UUID'] = items_dict[uuid_keys[0]] if not uuid_keys: r = re.compile(r'.*serial.*number.*') # Find to example "serial_number" key - serialnumber_keys = filter(r.match, items_dict.keys()) + serialnumber_keys = list(filter(r.match, list(items_dict.keys()))) if serialnumber_keys: dict_values['serialnumber'] = items_dict[serialnumber_keys[0]] return dict_values @@ -178,5 +177,11 @@ def _get_fake_uuid(with_hostname=True): import platform list_values = [xbmc.getInfoLabel('System.Memory(total)')] if with_hostname: - list_values.append(platform.node()) + # Note: on linux systems hostname content may change after every system update + try: + list_values.append(platform.node()) + except Exception: # pylint: disable=broad-except + # Due to OS restrictions on 'ios' and 'tvos' an error happen + # See python limits in the wiki development page + pass return '_'.join(list_values) diff --git a/resources/lib/common/videoid.py b/resources/lib/common/videoid.py index 4daaec460..9dfab1342 100644 --- a/resources/lib/common/videoid.py +++ b/resources/lib/common/videoid.py @@ -1,18 +1,19 @@ # -*- coding: utf-8 -*- -"""Universal representation of VideoIds""" -from __future__ import absolute_import, division, unicode_literals - +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Universal representation of VideoIds + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" from functools import wraps - -class InvalidVideoId(Exception): - """The provided video id is not valid""" - pass +from .exceptions import InvalidVideoId, ErrorMsg -class VideoId(object): - """Universal representation of a video id. Video IDs can be of multiple - types: +class VideoId: + """Universal representation of a video id. Video IDs can be of multiple types: - supplemental: a single identifier only for supplementalid, all other values must be None - movie: a single identifier only for movieid, all other values must be None - show: a single identifier only for tvshowid, all other values must be None @@ -40,7 +41,6 @@ def __init__(self, **kwargs): self._id_values = _get_unicode_kwargs(kwargs) # debug('VideoId validation values: {}'.format(self._id_values)) self._validate() - self._menu_parameters = MenuIdParameters(id_values=self._assigned_id_values()[0]) def _validate(self): validation_mask = 0 @@ -50,19 +50,19 @@ def _validate(self): validation_mask |= (value is not None) << (5 - index) try: self._mediatype = VideoId.VALIDATION_MASKS[validation_mask] - except KeyError: - raise InvalidVideoId + except KeyError as exc: + raise InvalidVideoId from exc @classmethod def from_path(cls, pathitems): """Create a VideoId instance from pathitems""" if pathitems[0] == VideoId.MOVIE: return cls(movieid=pathitems[1]) - elif pathitems[0] == VideoId.SHOW: + if pathitems[0] == VideoId.SHOW: return cls(tvshowid=_path_attr(pathitems, 1), seasonid=_path_attr(pathitems, 3), episodeid=_path_attr(pathitems, 5)) - elif pathitems[0] == VideoId.SUPPLEMENTAL: + if pathitems[0] == VideoId.SUPPLEMENTAL: return cls(supplementalid=pathitems[1]) return cls(videoid=pathitems[0]) @@ -72,40 +72,33 @@ def from_dict(cls, dict_items): mediatype = dict_items['mediatype'] if mediatype == VideoId.MOVIE: return cls(movieid=dict_items['movieid']) - elif mediatype in VideoId.TV_TYPES: + if mediatype in VideoId.TV_TYPES: return cls(tvshowid=_path_attr_dict(dict_items, 'tvshowid'), seasonid=_path_attr_dict(dict_items, 'seasonid'), episodeid=_path_attr_dict(dict_items, 'episodeid')) - elif mediatype == VideoId.SUPPLEMENTAL: + if mediatype == VideoId.SUPPLEMENTAL: return cls(supplementalid=dict_items['supplementalid']) raise InvalidVideoId @classmethod def from_videolist_item(cls, video): - """Create a VideoId from a video item contained in a - videolist path response""" - mediatype = video['summary']['type'] - video_id = video['summary']['id'] + """Create a VideoId from a video item contained in a video list path response""" + mediatype = video['summary']['value']['type'] + video_id = video['summary']['value']['id'] if mediatype == VideoId.MOVIE: return cls(movieid=video_id) - elif mediatype == VideoId.SHOW: + if mediatype == VideoId.SHOW: return cls(tvshowid=video_id) - elif mediatype == VideoId.SUPPLEMENTAL: + if mediatype == VideoId.SUPPLEMENTAL: return cls(supplementalid=video_id) - else: - raise InvalidVideoId( - 'Can only construct a VideoId from a show/movie/supplemental item') + raise InvalidVideoId( + 'Can only construct a VideoId from a show/movie/supplemental item') @property def value(self): """The value of this VideoId""" return self._assigned_id_values()[0] - @property - def menu_parameters(self): - """The menu parameters of the videoid value, if it exists""" - return self._menu_parameters - @property def videoid(self): """The videoid value, if it exists""" @@ -159,6 +152,21 @@ def convert_old_videoid_type(self): return VideoId.from_dict(videoid_dict) return self + def to_string(self): + """Generate a valid pathitems as string ('show'/tvshowid/...) from this instance""" + if self.videoid: + return self.videoid + if self.movieid: + return '/'.join([self.MOVIE, self.movieid]) + if self.supplementalid: + return '/'.join([self.SUPPLEMENTAL, self.supplementalid]) + pathitems = [self.SHOW, self.tvshowid] + if self.seasonid: + pathitems.extend([self.SEASON, self.seasonid]) + if self.episodeid: + pathitems.extend([self.EPISODE, self.episodeid]) + return '/'.join(pathitems) + def to_path(self): """Generate a valid pathitems list (['show', tvshowid, ...]) from this instance""" @@ -187,10 +195,10 @@ def to_dict(self): """Return a dict containing the relevant properties of this instance""" result = {'mediatype': self.mediatype} - result.update({prop: self.__getattribute__(prop) + result.update({prop: getattr(self, prop) for prop in ['videoid', 'supplementalid', 'movieid', 'tvshowid', 'seasonid', 'episodeid'] - if self.__getattribute__(prop) is not None}) + if getattr(self, prop, None) is not None}) return result def derive_season(self, seasonid): @@ -198,33 +206,38 @@ def derive_season(self, seasonid): of this show. Raises InvalidVideoId is this instance does not represent a show.""" if self.mediatype != VideoId.SHOW: - raise InvalidVideoId('Cannot derive season VideoId from {}' - .format(self)) - return type(self)(tvshowid=self.tvshowid, seasonid=unicode(seasonid)) + raise InvalidVideoId(f'Cannot derive season VideoId from {self}') + return type(self)(tvshowid=self.tvshowid, seasonid=str(seasonid)) def derive_episode(self, episodeid): """Return a new VideoId instance that represents the given episode of this season. Raises InvalidVideoId is this instance does not represent a season.""" if self.mediatype != VideoId.SEASON: - raise InvalidVideoId('Cannot derive episode VideoId from {}' - .format(self)) + raise InvalidVideoId(f'Cannot derive episode VideoId from {self}') return type(self)(tvshowid=self.tvshowid, seasonid=self.seasonid, - episodeid=unicode(episodeid)) + episodeid=str(episodeid)) - def derive_parent(self, depth): - """Returns a new videoid for the parent mediatype (season for episodes, - show for seasons) that is at the depth's level of the mediatype - hierarchy or this instance if there is no parent mediatype.""" - if self.mediatype == VideoId.SEASON: + def derive_parent(self, videoid_type): + """ + Derive a parent VideoId, you can obtain: + [tvshow] from season, episode + [season] from episode + When it is not possible get a derived VideoId, it is returned the same VideoId instance. + + :param videoid_type: The type of VideoId to be derived + :return: The parent VideoId of specified type, or when not match the same VideoId instance. + """ + if videoid_type == VideoId.SHOW: + if self.mediatype not in [VideoId.SEASON, VideoId.EPISODE]: + return self return type(self)(tvshowid=self.tvshowid) - if self.mediatype == VideoId.EPISODE: - if depth == 0: - return type(self)(tvshowid=self.tvshowid) - if depth == 1: - return type(self)(tvshowid=self.tvshowid, - seasonid=self.seasonid) - return self + if videoid_type == VideoId.SEASON: + if self.mediatype != VideoId.SEASON: + return self + return type(self)(tvshowid=self.tvshowid, + seasonid=self.seasonid) + raise InvalidVideoId(f'VideoId type {videoid_type} not valid') def _assigned_id_values(self): """Return a list of all id_values that are not None""" @@ -233,22 +246,27 @@ def _assigned_id_values(self): if id_value is not None] def __str__(self): - return '{}_{}'.format(self.mediatype, self.value) + return f'{self.mediatype}_{self.value}' def __hash__(self): return hash(str(self)) def __eq__(self, other): # pylint: disable=protected-access + if not isinstance(other, VideoId): + return False return self._id_values == other._id_values def __neq__(self, other): return not self.__eq__(other) + def __repr__(self): + return f'VideoId object [{self}]' + def _get_unicode_kwargs(kwargs): - # Example of return value: (None, None, '70084801', None, None, None, None) this is a movieid - return tuple((unicode(kwargs[idpart]) + # Example of return value: (None, None, '70084801', None, None, None) this is a movieid + return tuple((str(kwargs[idpart]) if kwargs.get(idpart) else None) for idpart @@ -278,9 +296,8 @@ def wrapper(*args, **kwargs): try: _path_to_videoid(kwargs, pathitems_arg, path_offset, inject_remaining_pathitems, inject_full_pathitems) - except KeyError: - raise Exception('Pathitems must be passed as kwarg {}' - .format(pathitems_arg)) + except KeyError as exc: + raise ErrorMsg(f'Pathitems must be passed as kwarg {pathitems_arg}') from exc return func(*args, **kwargs) return wrapper return injecting_decorator @@ -296,63 +313,7 @@ def _path_to_videoid(kwargs, pathitems_arg, path_offset, from the kwargs dict.""" kwargs['videoid'] = VideoId.from_path(kwargs[pathitems_arg][path_offset:]) if inject_remaining_pathitems or inject_full_pathitems: - if inject_full_pathitems: - kwargs[pathitems_arg] = kwargs[pathitems_arg] - else: + if inject_remaining_pathitems: kwargs[pathitems_arg] = kwargs[pathitems_arg][:path_offset] else: del kwargs[pathitems_arg] - - -class MenuIdParameters(object): - """Distinguishes the information grouped in a id value of a menu - - I am not sure that the definitions of the data info are correct, for my intuition i have distinguished them in this way - 8f0bcda8-a281-4ca3-9f56-f64ee1d76219_68180357X28X1430972X1551542684270 - [ request id ]X[ type id ]X[ context id ]X[ group id ] - """ - - def __init__(self, **kwargs): - _id_values = kwargs.get('id_values') - - # Check if the idvalues is a menu id value - if _id_values and _id_values.count('-') == 4 and _id_values.count('_') == 1 and _id_values.count('X') == 3: - self._is_menu_id = True - self._request_id = _id_values.split('X')[0] - self._type_id = _id_values.split('X')[1] - self._context_id = _id_values.split('X')[2] - self._group_id = _id_values.split('X')[3] - else: - self._is_menu_id = False - - @property - def is_menu_id(self): - """Return True if is a Menu Id""" - return self._is_menu_id - - @property - def request_id(self): - """Return the menu id""" - return self._request_id if self._is_menu_id else None - - @property - def type_id(self): - """Return the menu type - Menu types can be distinguished by numeric code, some example: - 6 - My list menu - 20 - Featured menu - 28 - Generic type of menu that returns tv series - 29 - Generic type of "Other content similar to" - 55 - Original netflix menu - """ - return self._type_id if self._is_menu_id else None - - @property - def context_id(self): - """Return the menu context id""" - return self._context_id if self._is_menu_id else None - - @property - def group_id(self): - """Return the menu group id""" - return self._group_id if self._is_menu_id else None diff --git a/resources/lib/config_wizard.py b/resources/lib/config_wizard.py new file mode 100644 index 000000000..a4ab3fb32 --- /dev/null +++ b/resources/lib/config_wizard.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2020 Stefano Gottardo - @CastagnaIT (original implementation module) + Add-on configuration wizard + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +import inputstreamhelper +from xbmc import getCondVisibility +from xbmcaddon import Addon +from xbmcgui import getScreenHeight, getScreenWidth + +from resources.lib.common import (get_system_platform, is_device_4k_capable, get_local_string, json_rpc, + get_supported_hdr_types, is_device_l1_enabled, is_android_tv) +from resources.lib.common.exceptions import InputStreamHelperError +from resources.lib.globals import G +from resources.lib.kodi.ui import show_ok_dialog, ask_for_confirmation +from resources.lib.utils.logging import LOG + + +def run_addon_configuration(restore=False): + """ + Add-on configuration wizard, + automatically configures profiles and add-ons dependencies, based on user-supplied data and device characteristics + and restore to default some expert settings when requested + """ + LOG.debug('Running add-on configuration wizard') + _set_codec_profiles() + _set_kodi_settings() + _set_isa_addon_settings(get_system_platform() == 'android') + + # For L3 devices we disable by default the esn auto generation (1080p workaround) + # see workaround details in the chunked_request method of msl_requests.py + if get_system_platform() == 'android' and not is_device_l1_enabled(): + G.LOCAL_DB.set_value('esn_auto_generate', False) + + # Restore default settings that may have been misconfigured by the user + if restore: + G.ADDON.setSettingString('isa_streamselection_override', 'disabled') + G.ADDON.setSettingString('stream_max_resolution', '--') + G.ADDON.setSettingString('stream_force_hdcp', '--') + G.ADDON.setSettingString('msl_manifest_version', 'default') + G.ADDON.setSettingString('cdn_server', 'Server 1') + + # Enable UpNext if it is installed and enabled + G.ADDON.setSettingBool('UpNextNotifier_enabled', getCondVisibility('System.AddonIsEnabled(service.upnext)')) + if restore: + show_ok_dialog(get_local_string(30154), get_local_string(30157)) + + +def _set_isa_addon_settings(hdcp_override): + """Method for self-configuring of InputStream Adaptive add-on""" + try: + is_helper = inputstreamhelper.Helper('mpd') + if not is_helper.check_inputstream(): + show_ok_dialog(get_local_string(30154), get_local_string(30046)) + return + except Exception as exc: # pylint: disable=broad-except + # Captures all types of ISH internal errors + import traceback + LOG.error(traceback.format_exc()) + raise InputStreamHelperError(str(exc)) from exc + + if G.KODI_VERSION < '20': + # Only needed for Kodi <= v19, this has been fixed on Kodi 20 + isa_addon = Addon('inputstream.adaptive') + isa_addon.setSettingBool('HDCPOVERRIDE', hdcp_override) + if isa_addon.getSettingInt('STREAMSELECTION') == 1: + # Stream selection must never be set to 'Manual' or cause problems with the streams + isa_addon.setSettingInt('STREAMSELECTION', 0) + # 'Ignore display' should only be set when Kodi display resolution is not 4K + isa_addon.setSettingBool('IGNOREDISPLAY', + is_device_4k_capable() and (getScreenWidth() != 3840 or getScreenHeight() != 2160)) + + +def _set_codec_profiles(): + """Method for self-configuring of netflix manifest codec profiles""" + enable_vp9_profiles = True + enable_hevc_profiles = False + if get_system_platform() == 'android': + # We cannot determine the codecs supported by the device in advance so... + # ...we do not enable VP9 because many older mobile devices do not support it + enable_vp9_profiles = False + # ...we enable HEVC by default on tv boxes and 4K capable devices + enable_hevc_profiles = is_android_tv() or is_device_4k_capable() + # Get supported HDR types by the display (configuration works from Kodi v20) + supported_hdr_types = get_supported_hdr_types() + if supported_hdr_types and enable_hevc_profiles: # for now only HEVC have HDR/DV + is_hdr10_enabled = False + is_dv_enabled = False + # Ask to enable HDR10 + if 'hdr10' in supported_hdr_types: + is_hdr10_enabled = ask_for_confirmation('Netflix', get_local_string(30742)) + # Ask to enable Dolby Vision + if is_hdr10_enabled and 'dolbyvision' in supported_hdr_types: + is_dv_enabled = ask_for_confirmation('Netflix', get_local_string(30743)) + G.ADDON.setSettingBool('enable_hdr_profiles', is_hdr10_enabled) + G.ADDON.setSettingBool('enable_dolbyvision_profiles', is_dv_enabled) + G.ADDON.setSettingBool('enable_vp9_profiles', enable_vp9_profiles) + G.ADDON.setSettingBool('enable_vp9.2_profiles', False) + G.ADDON.setSettingBool('enable_hevc_profiles', enable_hevc_profiles) + G.ADDON.setSettingBool('enable_av1_profiles', False) + G.ADDON.setSettingBool('disable_webvtt_subtitle', False) + + +def _set_kodi_settings(): + """Method for self-configuring Kodi settings""" + if get_system_platform() == 'android': + # Media Codec hardware acceleration is mandatory, otherwise only the audio stream is played + try: + json_rpc('Settings.SetSettingValue', {'setting': 'videoplayer.usemediacodecsurface', 'value': True}) + json_rpc('Settings.SetSettingValue', {'setting': 'videoplayer.usemediacodec', 'value': True}) + except IOError as exc: + LOG.error('Changing Kodi settings caused the following error: {}', exc) diff --git a/resources/lib/database/__init__.py b/resources/lib/database/__init__.py index e69de29bb..eea835dc0 100644 --- a/resources/lib/database/__init__.py +++ b/resources/lib/database/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) +# Copyright (C) 2019 Stefano Gottardo - @CastagnaIT (original implementation module) +# Data type conversion + +# SPDX-License-Identifier: MIT +# See LICENSES/MIT.md for more information. diff --git a/resources/lib/database/db_base.py b/resources/lib/database/db_base.py index 4aff8c20a..72520ac05 100644 --- a/resources/lib/database/db_base.py +++ b/resources/lib/database/db_base.py @@ -1,15 +1,20 @@ # -*- coding: utf-8 -*- -"""Common interface for all types of databases""" -from __future__ import absolute_import, division, unicode_literals +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2019 Stefano Gottardo - @CastagnaIT (original implementation module) + Common interface for all types of databases + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" -class BaseDatabase(object): + +class BaseDatabase: """ Base class to handle various types of databases """ def __init__(self): self.conn = None - self.is_connected = False self._initialize_connection() def _initialize_connection(self): @@ -18,7 +23,7 @@ def _initialize_connection(self): """ raise NotImplementedError - def _execute_non_query(self, query, params=None, cursor=None): + def _execute_non_query(self, query, params=None, cursor=None, **kwargs): """ Execute a query without returning a value :param query: sql query diff --git a/resources/lib/database/db_base_mysql.py b/resources/lib/database/db_base_mysql.py index 9750b6c4c..3c320fe10 100644 --- a/resources/lib/database/db_base_mysql.py +++ b/resources/lib/database/db_base_mysql.py @@ -1,7 +1,12 @@ # -*- coding: utf-8 -*- -"""MySQL database""" -from __future__ import absolute_import, division, unicode_literals - +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2019 Stefano Gottardo - @CastagnaIT (original implementation module) + Main functions for access to MySQL database + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" from functools import wraps import mysql.connector @@ -10,8 +15,9 @@ import resources.lib.database.db_base as db_base import resources.lib.database.db_utils as db_utils import resources.lib.database.db_create_mysql as db_create_mysql -from resources.lib.database.db_exceptions import (MySQLConnectionError, MySQLError) -from resources.lib.globals import g +from resources.lib.common.exceptions import DBMySQLConnectionError, DBMySQLError +from resources.lib.globals import G +from resources.lib.utils.logging import LOG def handle_connection(func): @@ -23,23 +29,18 @@ def wrapper(*args, **kwargs): if not args[0].is_mysql_database: # If database is not mysql pass to next decorator return func(*args, **kwargs) - conn = None try: if not args[0].conn or (args[0].conn and not args[0].conn.is_connected()): args[0].conn = mysql.connector.connect(**args[0].config) - conn = args[0].conn return func(*args, **kwargs) - except mysql.connector.Error as e: - common.error("MySQL error {}:".format(e)) - raise MySQLConnectionError - finally: - if conn and conn.is_connected(): - conn.close() + except mysql.connector.Error as exc: + LOG.error('MySQL error {}:', exc) + raise DBMySQLConnectionError from exc return wrapper class MySQLDatabase(db_base.BaseDatabase): - def __init__(self, test_config=None): + def __init__(self, test_config=None): # pylint: disable=super-on-old-class self.is_mysql_database = True self.database = 'netflix_addon' if test_config: @@ -48,42 +49,45 @@ def __init__(self, test_config=None): else: self.is_connection_test = False self.config = { - 'user': g.ADDON.getSetting('mysql_username'), - 'password': g.ADDON.getSetting('mysql_password'), - 'host': g.ADDON.getSetting('mysql_host'), - 'port': g.ADDON.getSettingInt('mysql_port'), + 'user': G.ADDON.getSetting('mysql_username'), + 'password': G.ADDON.getSetting('mysql_password'), + 'host': G.ADDON.getSetting('mysql_host'), + 'port': G.ADDON.getSettingInt('mysql_port'), 'database': 'netflix_addon', 'autocommit': True, 'charset': 'utf8', 'use_unicode': True } - super(MySQLDatabase, self).__init__() + super().__init__() def _initialize_connection(self): try: - common.debug('Trying connection to the MySQL database {}'.format(self.database)) + LOG.debug('Trying connection to the MySQL database {}', self.database) self.conn = mysql.connector.connect(**self.config) if self.conn.is_connected(): db_info = self.conn.get_server_info() - common.debug('MySQL database connection was successful (MySQL server ver. {})' - .format(db_info)) - except mysql.connector.Error as e: - if e.errno == 1049 and not self.is_connection_test: + LOG.debug('MySQL database connection was successful (MySQL server ver. {})', + db_info) + except mysql.connector.Error as exc: + if exc.errno == 1049 and not self.is_connection_test: # Database does not exist, create a new one try: db_create_mysql.create_database(self.config.copy()) self._initialize_connection() return except mysql.connector.Error as e: - common.error("MySql error {}:".format(e)) - raise MySQLConnectionError - common.error("MySql error {}:".format(e)) - raise MySQLConnectionError + LOG.error('MySql error {}:', e) + if e.errno == 1115: # Unknown character set: 'utf8mb4' + # Means an outdated MySQL/MariaDB version in use, needed MySQL => 5.5.3 or MariaDB => 5.5 + raise DBMySQLError('Your MySQL/MariaDB version is outdated, consider an upgrade') from e + raise DBMySQLError(str(e)) from e + LOG.error('MySql error {}:', exc) + raise DBMySQLConnectionError from exc finally: if self.conn and self.conn.is_connected(): self.conn.close() - def _execute_non_query(self, query, params=None, cursor=None, **kwargs): # pylint: disable=arguments-differ + def _execute_non_query(self, query, params=None, cursor=None, **kwargs): try: if cursor is None: cursor = self.get_cursor() @@ -96,13 +100,13 @@ def _execute_non_query(self, query, params=None, cursor=None, **kwargs): # pyli # 'multi' is lazy statement run sql only when needed for result in results: # pylint: disable=unused-variable pass - except mysql.connector.Error as e: - common.error("MySQL error {}:".format(e)) - raise MySQLError - except ValueError as exc_ve: - common.error('Value {}'.format(str(params))) - common.error('Value type {}'.format(type(params))) - raise exc_ve + except mysql.connector.Error as exc: + LOG.error('MySQL error {}:', exc) + raise DBMySQLError from exc + except ValueError: + LOG.error('Value {}', str(params)) + LOG.error('Value type {}', type(params)) + raise def _execute_query(self, query, params=None, cursor=None): try: @@ -114,13 +118,13 @@ def _execute_query(self, query, params=None, cursor=None): else: cursor.execute(query) return cursor - except mysql.connector.Error as e: - common.error("MySQL error {}:".format(e.args[0])) - raise MySQLError - except ValueError as exc_ve: - common.error('Value {}'.format(str(params))) - common.error('Value type {}'.format(type(params))) - raise exc_ve + except mysql.connector.Error as exc: + LOG.error('MySQL error {}:', exc.args[0]) + raise DBMySQLError from exc + except ValueError: + LOG.error('Value {}', str(params)) + LOG.error('Value type {}', type(params)) + raise def get_cursor(self): return self.conn.cursor() @@ -152,9 +156,7 @@ def get_value(self, key, default_value=None, table=db_utils.TABLE_SHARED_APP_CON """ table_name = table[0] table_columns = table[1] - query = 'SELECT {} FROM {} WHERE {} = ?'.format(table_columns[1], - table_name, - table_columns[0]) + query = f'SELECT {table_columns[1]} FROM {table_name} WHERE {table_columns[0]} = ?' cur = self._execute_query(query, (key,)) result = cur.fetchone() if default_value is not None: @@ -175,9 +177,7 @@ def get_values(self, key, default_value=None, table=db_utils.TABLE_SHARED_APP_CO """ table_name = table[0] table_columns = table[1] - query = 'SELECT {} FROM {} WHERE {} = ?'.format(table_columns[1], - table_name, - table_columns[0]) + query = f'SELECT {table_columns[1]} FROM {table_name} WHERE {table_columns[0]} = ?' cur = self._execute_query(query, (key,)) result = cur.fetchall() return result if result is not None else default_value @@ -207,7 +207,7 @@ def delete_key(self, key, table=db_utils.TABLE_SHARED_APP_CONF): """ table_name = table[0] table_columns = table[1] - query = 'DELETE FROM {} WHERE {} = ?'.format(table_name, table_columns[0]) + query = f'DELETE FROM {table_name} WHERE {table_columns[0]} = ?' cur = self._execute_query(query, (key,)) return cur.rowcount diff --git a/resources/lib/database/db_base_sqlite.py b/resources/lib/database/db_base_sqlite.py index a563388e8..2ceb8477f 100644 --- a/resources/lib/database/db_base_sqlite.py +++ b/resources/lib/database/db_base_sqlite.py @@ -1,15 +1,24 @@ # -*- coding: utf-8 -*- -"""SQLite database""" -from __future__ import absolute_import, division, unicode_literals +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2019 Stefano Gottardo - @CastagnaIT (original implementation module) + Main functions for access to SQLite database + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" import sqlite3 as sql +import threading from functools import wraps import resources.lib.common as common import resources.lib.database.db_base as db_base import resources.lib.database.db_create_sqlite as db_create_sqlite import resources.lib.database.db_utils as db_utils -from resources.lib.database.db_exceptions import (SQLiteConnectionError, SQLiteError) +from resources.lib.common.exceptions import DBSQLiteConnectionError, DBSQLiteError +from resources.lib.globals import G +from resources.lib.utils.logging import LOG + CONN_ISOLATION_LEVEL = None # Autocommit mode @@ -31,39 +40,54 @@ def wrapper(*args, **kwargs): # If database is mysql pass to next decorator return func(*args, **kwargs) conn = None + is_not_thread_safe = not G.IS_SQLITE3_THREADSAFE try: if not args[0].is_connected: + if is_not_thread_safe: + args[0].mutex.acquire() args[0].conn = sql.connect(args[0].db_file_path, - isolation_level=CONN_ISOLATION_LEVEL) + isolation_level=CONN_ISOLATION_LEVEL, + check_same_thread = is_not_thread_safe) args[0].is_connected = True conn = args[0].conn return func(*args, **kwargs) - except sql.Error as e: - common.error("SQLite error {}:".format(e.args[0])) - raise SQLiteConnectionError + except sql.Error as exc: + LOG.error('SQLite error {}:', exc.args[0]) + raise DBSQLiteConnectionError from exc finally: - if conn: - conn.close() + if conn and is_not_thread_safe: args[0].is_connected = False + conn.close() + args[0].mutex.release() return wrapper class SQLiteDatabase(db_base.BaseDatabase): - def __init__(self, db_filename): + def __init__(self, db_filename): # pylint: disable=super-on-old-class + self.mutex = threading.Lock() + self.local_storage = threading.local() self.is_mysql_database = False self.db_filename = db_filename self.db_file_path = db_utils.get_local_db_path(db_filename) - super(SQLiteDatabase, self).__init__() + super().__init__() + + @property + def is_connected(self): + return getattr(self.local_storage, 'is_connected', False) + + @is_connected.setter + def is_connected(self, val): + self.local_storage.is_connected = val def _initialize_connection(self): try: - - common.debug('Trying connection to the database {}'.format(self.db_filename)) - self.conn = sql.connect(self.db_file_path) + LOG.debug('Trying connection to the SQLite database {}', self.db_filename) + self.conn = sql.connect(self.db_file_path, check_same_thread=False) cur = self.conn.cursor() cur.execute(str('SELECT SQLITE_VERSION()')) - common.debug('Database connection {} was successful (SQLite ver. {})' - .format(self.db_filename, cur.fetchone()[0])) + LOG.debug('Database connection {} was successful (SQLite ver. {} {})', + self.db_filename, cur.fetchone()[0], + 'thread safe' if G.IS_SQLITE3_THREADSAFE else 'not thread safe') cur.row_factory = lambda cursor, row: row[0] cur.execute(str('SELECT name FROM sqlite_master WHERE type=\'table\' ' 'AND name NOT LIKE \'sqlite_%\'')) @@ -72,14 +96,27 @@ def _initialize_connection(self): # If no tables exist create a new one self.conn.close() db_create_sqlite.create_database(self.db_file_path, self.db_filename) - except sql.Error as e: - common.error("SQLite error {}:".format(e.args[0])) - raise SQLiteConnectionError + except sql.Error as exc: + LOG.error('SQLite error {}:', exc.args[0]) + raise DBSQLiteConnectionError from exc finally: if self.conn: self.conn.close() - def _execute_non_query(self, query, params=None, cursor=None): + def _executemany_non_query(self, query, params, cursor=None): + try: + if cursor is None: + cursor = self.get_cursor() + cursor.executemany(query, params) + except sql.Error as exc: + LOG.error('SQLite error {}:', exc.args[0]) + raise DBSQLiteError from exc + except ValueError: + LOG.error('Value {}', str(params)) + LOG.error('Value type {}', type(params)) + raise + + def _execute_non_query(self, query, params=None, cursor=None, **kwargs): try: if cursor is None: cursor = self.get_cursor() @@ -87,13 +124,13 @@ def _execute_non_query(self, query, params=None, cursor=None): cursor.execute(query, params) else: cursor.execute(query) - except sql.Error as e: - common.error("SQLite error {}:".format(e.args[0])) - raise SQLiteError - except ValueError as exc_ve: - common.error('Value {}'.format(str(params))) - common.error('Value type {}'.format(type(params))) - raise exc_ve + except sql.Error as exc: + LOG.error('SQLite error {}:', exc.args[0]) + raise DBSQLiteError from exc + except ValueError: + LOG.error('Value {}', str(params)) + LOG.error('Value type {}', type(params)) + raise def _execute_query(self, query, params=None, cursor=None): try: @@ -104,20 +141,20 @@ def _execute_query(self, query, params=None, cursor=None): else: cursor.execute(query) return cursor - except sql.Error as e: - common.error("SQLite error {}:".format(e.args[0])) - raise SQLiteError - except ValueError as exc_ve: - common.error('Value {}'.format(str(params))) - common.error('Value type {}'.format(type(params))) - raise exc_ve + except sql.Error as exc: + LOG.error('SQLite error {}:', exc.args[0]) + raise DBSQLiteError from exc + except ValueError: + LOG.error('Value {}', str(params)) + LOG.error('Value type {}', type(params)) + raise def get_cursor(self): return self.conn.cursor() def get_cursor_for_dict_results(self): conn_cursor = self.conn.cursor() - conn_cursor.row_factory = lambda c, r: dict(zip([col[0] for col in c.description], r)) + conn_cursor.row_factory = lambda c, r: dict(list(zip([col[0] for col in c.description], r))) return conn_cursor def get_cursor_for_list_results(self): @@ -141,9 +178,7 @@ def get_value(self, key, default_value=None, table=db_utils.TABLE_APP_CONF, data """ table_name = table[0] table_columns = table[1] - query = 'SELECT {} FROM {} WHERE {} = ?'.format(table_columns[1], - table_name, - table_columns[0]) + query = f'SELECT {table_columns[1]} FROM {table_name} WHERE {table_columns[0]} = ?' cur = self._execute_query(query, (key,)) result = cur.fetchone() if default_value is not None: @@ -164,9 +199,7 @@ def get_values(self, key, default_value=None, table=db_utils.TABLE_APP_CONF): """ table_name = table[0] table_columns = table[1] - query = 'SELECT {} FROM {} WHERE {} = ?'.format(table_columns[1], - table_name, - table_columns[0]) + query = f'SELECT {table_columns[1]} FROM {table_name} WHERE {table_columns[0]} = ?' cur = self._execute_query(query, (key,)) result = cur.fetchall() return result if result is not None else default_value @@ -181,16 +214,45 @@ def set_value(self, key, value, table=db_utils.TABLE_APP_CONF): """ table_name = table[0] table_columns = table[1] - # Update or insert approach, if there is no updated row then insert new one (no id changes) - update_query = 'UPDATE {} SET {} = ? WHERE {} = ?'.format(table_name, - table_columns[1], - table_columns[0]) value = common.convert_to_string(value) - cur = self._execute_query(update_query, (value, key)) - if cur.rowcount == 0: - insert_query = 'INSERT INTO {} ({}, {}) VALUES (?, ?)'\ - .format(table_name, table_columns[0], table_columns[1]) - self._execute_non_query(insert_query, (key, value)) + # Update or insert approach, if there is no updated row then insert new one (no id changes) + if common.CmpVersion(sql.sqlite_version) < '3.24.0': + query = f'INSERT OR REPLACE INTO {table_name} ({table_columns[0]}, {table_columns[1]}) VALUES (?, ?)' + self._execute_non_query(query, (key, value)) + else: + # sqlite UPSERT clause exists only on sqlite >= 3.24.0 + query = (f'INSERT INTO {table_name} ({table_columns[0]}, {table_columns[1]}) VALUES (?, ?) ' + f'ON CONFLICT({table_columns[0]}) DO UPDATE SET {table_columns[1]} = ? ' + f'WHERE {table_columns[0]} = ?') + self._execute_non_query(query, (key, value, value, key)) + + @handle_connection + def set_values(self, dict_values, table=db_utils.TABLE_APP_CONF): + """ + Store multiple values to database + :param dict_values: The key/value to store + :param table: Table map + """ + table_name = table[0] + table_columns = table[1] + # Doing many sqlite operations at the same makes the performance much worse (especially on Kodi 18) + # The use of 'executemany' and 'transaction' can improve performance up to about 75% !! + if common.CmpVersion(sql.sqlite_version) < '3.24.0': + query = f'INSERT OR REPLACE INTO {table_name} ({table_columns[0]}, {table_columns[1]}) VALUES (?, ?)' + records_values = [(key, common.convert_to_string(value)) for key, value in dict_values.items()] + else: + # sqlite UPSERT clause exists only on sqlite >= 3.24.0 + query = (f'INSERT INTO {table_name} ({table_columns[0]}, {table_columns[1]}) VALUES (?, ?) ' + f'ON CONFLICT({table_columns[0]}) DO UPDATE SET {table_columns[1]} = ? ' + f'WHERE {table_columns[0]} = ?') + records_values = [] + for key, value in dict_values.items(): + value_str = common.convert_to_string(value) + records_values.append((key, value_str, value_str, key)) + cur = self.get_cursor() + cur.execute("BEGIN TRANSACTION;") + self._executemany_non_query(query, records_values, cur) + cur.execute("COMMIT;") @handle_connection def delete_key(self, key, table=db_utils.TABLE_APP_CONF): @@ -202,7 +264,7 @@ def delete_key(self, key, table=db_utils.TABLE_APP_CONF): """ table_name = table[0] table_columns = table[1] - query = 'DELETE FROM {} WHERE {} = ?'.format(table_name, table_columns[0]) + query = f'DELETE FROM {table_name} WHERE {table_columns[0]} = ?' cur = self._execute_query(query, (key,)) return cur.rowcount diff --git a/resources/lib/database/db_create_mysql.py b/resources/lib/database/db_create_mysql.py index 7e04bad62..473fcf830 100644 --- a/resources/lib/database/db_create_mysql.py +++ b/resources/lib/database/db_create_mysql.py @@ -1,16 +1,21 @@ # -*- coding: utf-8 -*- -"""Functions to create new databases""" -from __future__ import absolute_import, division, unicode_literals +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2019 Stefano Gottardo - @CastagnaIT (original implementation module) + Functions to create a new MySQL database + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" import mysql.connector -import resources.lib.common as common +from resources.lib.utils.logging import LOG def create_database(config): """Create a new database""" db_name = config.pop('database', None) - common.debug('The MySQL database {} does not exist, creating a new one'.format(db_name)) + LOG.debug('The MySQL database {} does not exist, creating a new one', db_name) conn = mysql.connector.connect(**config) cur = conn.cursor() @@ -88,5 +93,17 @@ def create_database(config): cur.execute(table) cur.execute(alter_tbl) + table = ('CREATE TABLE netflix_addon.watched_status_override (' + 'ProfileGuid VARCHAR(50) NOT NULL,' + 'VideoID INT(11) NOT NULL,' + 'Value TEXT DEFAULT NULL,' + 'PRIMARY KEY (ProfileGuid, VideoID))' + 'ENGINE = INNODB, CHARACTER SET utf8mb4, COLLATE utf8mb4_unicode_ci;') + alter_tbl = ('ALTER TABLE netflix_addon.watched_status_override ' + 'ADD CONSTRAINT FK_watchedstatusoverride_ProfileGuid FOREIGN KEY (ProfileGuid)' + 'REFERENCES netflix_addon.profiles(Guid) ON DELETE CASCADE ON UPDATE CASCADE;') + cur.execute(table) + cur.execute(alter_tbl) + if conn and conn.is_connected(): conn.close() diff --git a/resources/lib/database/db_create_sqlite.py b/resources/lib/database/db_create_sqlite.py index c04adb04d..98f1c59e0 100644 --- a/resources/lib/database/db_create_sqlite.py +++ b/resources/lib/database/db_create_sqlite.py @@ -1,15 +1,20 @@ # -*- coding: utf-8 -*- -"""Functions to create new databases""" -from __future__ import absolute_import, division, unicode_literals - +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2019 Stefano Gottardo - @CastagnaIT (original implementation module) + Functions to create a new SQLite database + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" import sqlite3 as sql -import resources.lib.common as common import resources.lib.database.db_utils as db_utils +from resources.lib.utils.logging import LOG def create_database(db_file_path, db_filename): - common.debug('The SQLite database {} is empty, creating tables'.format(db_filename)) + LOG.debug('The SQLite database {} is empty, creating tables', db_filename) if db_utils.LOCAL_DB_FILENAME == db_filename: _create_local_database(db_file_path) if db_utils.SHARED_DB_FILENAME == db_filename: @@ -58,6 +63,15 @@ def _create_local_database(db_file_path): 'Value TEXT);') cur.execute(table) + table = str('CREATE TABLE search (' + 'ID INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,' + 'Guid TEXT NOT NULL REFERENCES profiles (Guid) ON DELETE CASCADE ON UPDATE CASCADE,' + 'Type TEXT NOT NULL,' + 'Value TEXT NOT NULL,' + 'Parameters TEXT,' + 'LastAccess TEXT);') + cur.execute(table) + if conn: conn.close() @@ -114,5 +128,14 @@ def _create_shared_database(db_file_path): 'NfoExport TEXT NOT NULL DEFAULT (\'False\'));') cur.execute(table) + table = str('CREATE TABLE watched_status_override (' + 'ProfileGuid TEXT NOT NULL,' + 'VideoID INTEGER NOT NULL,' + 'Value TEXT,' + 'PRIMARY KEY (ProfileGuid, VideoID ),' + 'FOREIGN KEY (ProfileGuid)' + 'REFERENCES Profiles (Guid) ON DELETE CASCADE ON UPDATE CASCADE);') + cur.execute(table) + if conn: conn.close() diff --git a/resources/lib/database/db_exceptions.py b/resources/lib/database/db_exceptions.py deleted file mode 100644 index 69c54150d..000000000 --- a/resources/lib/database/db_exceptions.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -"""Common exception types for database operations""" -from __future__ import absolute_import, division, unicode_literals - - -class SQLiteConnectionError(Exception): - """An error occurred in the database connection""" - pass - - -class SQLiteError(Exception): - """An error occurred in the database operations""" - pass - - -class MySQLConnectionError(Exception): - """An error occurred in the database connection""" - pass - - -class MySQLError(Exception): - """An error occurred in the database operations""" - pass - - -class ProfilesMissing(Exception): - """There are no stored profiles in database""" - pass diff --git a/resources/lib/database/db_local.py b/resources/lib/database/db_local.py index 5b4f95e8e..25a704ba9 100644 --- a/resources/lib/database/db_local.py +++ b/resources/lib/database/db_local.py @@ -1,23 +1,41 @@ # -*- coding: utf-8 -*- -"""Local database access and functions""" -from __future__ import absolute_import, division, unicode_literals +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2019 Stefano Gottardo - @CastagnaIT (original implementation module) + Local database access and functions + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +from datetime import datetime import resources.lib.common as common import resources.lib.database.db_base_sqlite as db_sqlite import resources.lib.database.db_utils as db_utils -from resources.lib.database.db_exceptions import (ProfilesMissing) +from resources.lib.common.exceptions import DBProfilesMissing class NFLocalDatabase(db_sqlite.SQLiteDatabase): def __init__(self): - super(NFLocalDatabase, self).__init__(db_utils.LOCAL_DB_FILENAME) + super().__init__(db_utils.LOCAL_DB_FILENAME) def _get_active_guid_profile(self): query = 'SELECT Guid FROM profiles WHERE IsActive = 1' cur = self._execute_query(query) result = cur.fetchone() if result is None: - raise ProfilesMissing + raise DBProfilesMissing + return result[0] + + @db_sqlite.handle_connection + def get_guid_owner_profile(self): + """Get the guid of owner account profile""" + query = 'SELECT Guid FROM profiles_config WHERE ' \ + 'Name = \'isAccountOwner\' AND Value = \'True\'' + cur = self._execute_query(query) + result = cur.fetchone() + if result is None: + raise DBProfilesMissing return result[0] @db_sqlite.handle_connection @@ -46,7 +64,11 @@ def get_profile_config(self, key, default_value=None, guid=None, data_type=None) @db_sqlite.handle_connection def set_profile_config(self, key, value, guid=None): - """Store a value to a profile, if guid is not specified, is stored to active profile""" + """ + Store a value to a profile, if guid is not specified, is stored to active profile + + NOTE: Config key name starting with "addon_" will be preserved until the profile is deleted. + """ # Update or insert approach, if there is no updated row then insert new one (no id changes) if not guid: guid = self._get_active_guid_profile() @@ -57,6 +79,27 @@ def set_profile_config(self, key, value, guid=None): insert_query = 'INSERT INTO profiles_config (Guid, Name, Value) VALUES (?, ?, ?)' self._execute_non_query(insert_query, (guid, key, value)) + @db_sqlite.handle_connection + def insert_profile_configs(self, dict_values, guid=None): + """ + Store multiple values to a profile by deleting all existing values, + if guid is not specified, is stored to active profile. + + NOTE: Config key names starting with "addon_" will be preserved until the profile is deleted. + """ + # Doing many sqlite operations at the same makes the performance much worse (especially on Kodi 18) + # The use of 'executemany' and 'transaction' can improve performance up to about 75% !! + if not guid: + guid = self._get_active_guid_profile() + cur = self.get_cursor() + cur.execute("BEGIN TRANSACTION;") + query = 'DELETE FROM profiles_config WHERE Guid = ? AND NOT Name LIKE \'addon_%\'' + self._execute_non_query(query, (guid,), cur) + records_values = [(guid, key, common.convert_to_string(value)) for key, value in dict_values.items()] + insert_query = 'INSERT INTO profiles_config (Guid, Name, Value) VALUES (?, ?, ?)' + self._executemany_non_query(insert_query, records_values, cur) + cur.execute("COMMIT;") + @db_sqlite.handle_connection def set_profile(self, guid, is_active, sort_order): """Update or Insert a profile""" @@ -93,3 +136,60 @@ def get_guid_profiles(self): query = 'SELECT Guid FROM profiles ORDER BY SortOrder' cur = self._execute_query(query) return [row[0] for row in cur.fetchall()] + + @db_sqlite.handle_connection + def get_search_list(self): + guid = self.get_active_profile_guid() + query = ('SELECT * FROM search ' + 'WHERE Guid = ? ' + 'ORDER BY datetime("LastAccess") DESC') + cur = self.get_cursor_for_dict_results() + cur = self._execute_query(query, (guid,), cur) + return cur.fetchall() + + @db_sqlite.handle_connection + def get_search_item(self, row_id): + query = 'SELECT * FROM search WHERE ID = ?' + cur = self.get_cursor_for_dict_results() + cur = self._execute_query(query, (row_id,), cur) + return cur.fetchone() + + @db_sqlite.handle_connection + def insert_search_item(self, search_type, value, parameters=None): + """Insert a new search item and return the ID of the new entry""" + insert_query = ('INSERT INTO search (Guid, Type, Value, Parameters, LastAccess) ' + 'VALUES (?, ?, ?, ?, ?)') + if parameters: + parameters = common.convert_to_string(parameters) + guid = self.get_active_profile_guid() + date_last_access = common.convert_to_string(datetime.now()) + cur = self.get_cursor() + self._execute_non_query(insert_query, (guid, search_type, value, parameters, date_last_access), cur) + return str(cur.lastrowid) + + @db_sqlite.handle_connection + def delete_search_item(self, row_id): + """Delete a search item""" + query = 'DELETE FROM search WHERE ID = ?' + self._execute_non_query(query, (row_id,)) + + @db_sqlite.handle_connection + def clear_search_items(self): + """Delete all search items""" + query = 'DELETE FROM search WHERE Guid = ?' + guid = self.get_active_profile_guid() + self._execute_non_query(query, (guid,)) + + @db_sqlite.handle_connection + def update_search_item_last_access(self, row_id): + """Update the last access data to a search item""" + update_query = 'UPDATE search SET LastAccess = ? WHERE ID = ?' + date_last_access = common.convert_to_string(datetime.now()) + self._execute_non_query(update_query, (date_last_access, row_id)) + + @db_sqlite.handle_connection + def update_search_item_value(self, row_id, value): + """Update the 'value' data to a search item""" + update_query = 'UPDATE search SET Value = ?, LastAccess = ? WHERE ID = ?' + date_last_access = common.convert_to_string(datetime.now()) + self._execute_non_query(update_query, (value, date_last_access, row_id)) diff --git a/resources/lib/database/db_shared.py b/resources/lib/database/db_shared.py index 3c7aa8c9d..da4ea1123 100644 --- a/resources/lib/database/db_shared.py +++ b/resources/lib/database/db_shared.py @@ -1,39 +1,43 @@ # -*- coding: utf-8 -*- -"""Shared database access and functions""" -from __future__ import absolute_import, division, unicode_literals - +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2019 Stefano Gottardo - @CastagnaIT (original implementation module) + Sharable database access and functions + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" from datetime import datetime import resources.lib.common as common import resources.lib.database.db_base_mysql as db_base_mysql import resources.lib.database.db_base_sqlite as db_base_sqlite import resources.lib.database.db_utils as db_utils -from resources.lib.globals import g +from resources.lib.common.exceptions import DBRecordNotExistError -def get_shareddb_class(force_sqlite=False): +def get_shareddb_class(use_mysql=False): # Dynamically sets the inherit class - use_mysql = g.ADDON.getSettingBool('use_mysql') and not force_sqlite base_class = db_base_mysql.MySQLDatabase if use_mysql else db_base_sqlite.SQLiteDatabase class NFSharedDatabase(base_class): def __init__(self): if use_mysql: - super(NFSharedDatabase, self).__init__() + super().__init__(None) else: - super(NFSharedDatabase, self).__init__(db_utils.SHARED_DB_FILENAME) + super().__init__(db_utils.SHARED_DB_FILENAME) def get_value(self, key, default_value=None, table=db_utils.TABLE_SHARED_APP_CONF, data_type=None): # pylint: disable=useless-super-delegation - return super(NFSharedDatabase, self).get_value(key, default_value, table, data_type) + return super().get_value(key, default_value, table, data_type) def get_values(self, key, default_value=None, table=db_utils.TABLE_SHARED_APP_CONF): # pylint: disable=useless-super-delegation - return super(NFSharedDatabase, self).get_values(key, default_value, table) + return super().get_values(key, default_value, table) def set_value(self, key, value, table=db_utils.TABLE_SHARED_APP_CONF): # pylint: disable=useless-super-delegation - super(NFSharedDatabase, self).set_value(key, value, table) + super().set_value(key, value, table) def delete_key(self, key, table=db_utils.TABLE_SHARED_APP_CONF): # pylint: disable=useless-super-delegation - super(NFSharedDatabase, self).delete_key(key, table) + super().delete_key(key, table) @db_base_mysql.handle_connection @db_base_sqlite.handle_connection @@ -64,16 +68,18 @@ def delete_profile(self, guid): @db_base_mysql.handle_connection @db_base_sqlite.handle_connection - def get_movie_filepath(self, movieid, default_value=None): + def get_movie_filepath(self, movieid): """Get movie filepath for given id""" query = 'SELECT FilePath FROM video_lib_movies WHERE MovieID = ?' cur = self._execute_query(query, (movieid,)) result = cur.fetchone() - return result[0] if result else default_value + if not result: + raise DBRecordNotExistError + return result[0] @db_base_mysql.handle_connection @db_base_sqlite.handle_connection - def get_episode_filepath(self, tvshowid, seasonid, episodeid, default_value=None): + def get_episode_filepath(self, tvshowid, seasonid, episodeid): """Get movie filepath for given id""" query =\ ('SELECT FilePath FROM video_lib_episodes ' @@ -84,7 +90,9 @@ def get_episode_filepath(self, tvshowid, seasonid, episodeid, default_value=None 'video_lib_episodes.EpisodeID = ?') cur = self._execute_query(query, (tvshowid, seasonid, episodeid)) result = cur.fetchone() - return result[0] if result is not None else default_value + if not result: + raise DBRecordNotExistError + return result[0] @db_base_mysql.handle_connection @db_base_sqlite.handle_connection @@ -99,7 +107,10 @@ def get_all_episodes_ids_and_filepath_from_tvshow(self, tvshowid): 'ON video_lib_episodes.SeasonID = video_lib_seasons.SeasonID ' 'WHERE video_lib_seasons.TvShowID = ?') cur = self._execute_query(query, (tvshowid,), cur) - return cur.fetchall() + result = cur.fetchall() + if not result: + raise DBRecordNotExistError + return result @db_base_mysql.handle_connection @db_base_sqlite.handle_connection @@ -115,11 +126,14 @@ def get_all_episodes_ids_and_filepath_from_season(self, tvshowid, seasonid): 'WHERE video_lib_seasons.TvShowID = ? AND ' 'video_lib_seasons.SeasonID = ?') cur = self._execute_query(query, (tvshowid, seasonid), cur) - return cur.fetchall() + result = cur.fetchall() + if not result: + raise DBRecordNotExistError + return result @db_base_mysql.handle_connection @db_base_sqlite.handle_connection - def get_random_episode_filepath_from_tvshow(self, tvshowid, default_value=None): + def get_random_episode_filepath_from_tvshow(self, tvshowid): """Get random episode filepath of a show of a given id""" rand_func_name = 'RAND()' if self.is_mysql_database else 'RANDOM()' query =\ @@ -127,14 +141,16 @@ def get_random_episode_filepath_from_tvshow(self, tvshowid, default_value=None): 'INNER JOIN video_lib_seasons ' 'ON video_lib_episodes.SeasonID = video_lib_seasons.SeasonID ' 'WHERE video_lib_seasons.TvShowID = ? ' - 'ORDER BY {} LIMIT 1').format(rand_func_name) + f'ORDER BY {rand_func_name} LIMIT 1') cur = self._execute_query(query, (tvshowid,)) result = cur.fetchone() - return result[0] if result is not None else default_value + if not result: + raise DBRecordNotExistError + return result[0] @db_base_mysql.handle_connection @db_base_sqlite.handle_connection - def get_random_episode_filepath_from_season(self, tvshowid, seasonid, default_value=None): + def get_random_episode_filepath_from_season(self, tvshowid, seasonid): """Get random episode filepath of a show of a given id""" rand_func_name = 'RAND()' if self.is_mysql_database else 'RANDOM()' query =\ @@ -142,10 +158,12 @@ def get_random_episode_filepath_from_season(self, tvshowid, seasonid, default_va 'INNER JOIN video_lib_seasons ' 'ON video_lib_episodes.SeasonID = video_lib_seasons.SeasonID ' 'WHERE video_lib_seasons.TvShowID = ? AND video_lib_seasons.SeasonID = ? ' - 'ORDER BY {} LIMIT 1').format(rand_func_name) + f'ORDER BY {rand_func_name} LIMIT 1') cur = self._execute_query(query, (tvshowid, seasonid)) result = cur.fetchone() - return result[0] if result is not None else default_value + if not result: + raise DBRecordNotExistError + return result[0] @db_base_mysql.handle_connection @db_base_sqlite.handle_connection @@ -169,8 +187,8 @@ def get_tvshows_id_list(self, enum_vid_prop=None, prop_value=None): """ cur = self.get_cursor_for_list_results() if enum_vid_prop and prop_value: - query = ('SELECT TvShowID FROM video_lib_tvshows' - 'WHERE ' + enum_vid_prop.value + ' = ?') + query = ('SELECT TvShowID FROM video_lib_tvshows ' + f'WHERE {enum_vid_prop} = ?') cur = self._execute_query(query, (str(prop_value),), cur) else: query = 'SELECT TvShowID FROM video_lib_tvshows' @@ -340,7 +358,7 @@ def get_tvshow_property(self, tvshowid, enum_vid_prop, default_value=None, data_ :param data_type: OPTIONAL Used to set data type conversion only when default_value is None :return: the property value """ - query = 'SELECT ' + enum_vid_prop.value + ' FROM video_lib_tvshows WHERE TvShowID = ?' + query = f'SELECT {enum_vid_prop} FROM video_lib_tvshows WHERE TvShowID = ?' cur = self._execute_query(query, (tvshowid,)) result = cur.fetchone() if default_value is not None: @@ -354,10 +372,54 @@ def get_tvshow_property(self, tvshowid, enum_vid_prop, default_value=None, data_ @db_base_sqlite.handle_connection def set_tvshow_property(self, tvshowid, enum_vid_prop, value): update_query = ('UPDATE video_lib_tvshows ' - 'SET ' + enum_vid_prop.value + ' = ? WHERE TvShowID = ?') + f'SET {enum_vid_prop} = ? WHERE TvShowID = ?') value = common.convert_to_string(value) self._execute_query(update_query, (value, tvshowid)) + @db_base_mysql.handle_connection + @db_base_sqlite.handle_connection + def get_watched_status(self, profile_guid, videoid, default_value=None, data_type=None): + """Get override watched status value of a given id stored to current profile""" + query = 'SELECT Value FROM watched_status_override WHERE ProfileGuid = ? AND VideoID = ?' + cur = self._execute_query(query, (profile_guid, videoid)) + result = cur.fetchone() + if default_value is not None: + data_type = type(default_value) + elif data_type is None: + data_type = str + return common.convert_from_string(result[0], data_type) \ + if result is not None else default_value + + @db_base_mysql.handle_connection + @db_base_sqlite.handle_connection + def set_watched_status(self, profile_guid, videoid, value): + """Update or insert the watched status override value to current profile""" + # Update or insert approach, if there is no updated row then insert new one + value = common.convert_to_string(value) + if self.is_mysql_database: + query = db_utils.mysql_insert_or_update('watched_status_override', + ['ProfileGuid', 'VideoID'], + ['Value']) + self._execute_non_query(query, (profile_guid, videoid, value), + multi=True) + else: + update_query = ('UPDATE watched_status_override ' + 'SET Value = ? ' + 'WHERE ProfileGuid = ? AND VideoID = ?') + cur = self._execute_query(update_query, (value, profile_guid, videoid)) + if cur.rowcount == 0: + insert_query = ('INSERT INTO watched_status_override ' + '(ProfileGuid, VideoID, Value) ' + 'VALUES (?, ?, ?)') + self._execute_non_query(insert_query, (profile_guid, videoid, value)) + + @db_base_mysql.handle_connection + @db_base_sqlite.handle_connection + def delete_watched_status(self, profile_guid, videoid): + """Delete a watched status override from database""" + query = 'DELETE FROM watched_status_override WHERE ProfileGuid = ? AND VideoID = ?' + self._execute_query(query, (profile_guid, videoid)) + @db_base_mysql.handle_connection @db_base_sqlite.handle_connection def get_stream_continuity(self, profile_guid, videoid, default_value=None, data_type=None): @@ -398,4 +460,24 @@ def set_stream_continuity(self, profile_guid, videoid, value): self._execute_non_query(insert_query, (profile_guid, videoid, value, date_last_modified)) + @db_base_mysql.handle_connection + @db_base_sqlite.handle_connection + def clear_stream_continuity(self): + """Clear all stream continuity data""" + query = 'DELETE FROM stream_continuity' + self._execute_non_query(query) + + @db_base_mysql.handle_connection + @db_base_sqlite.handle_connection + def purge_library(self): + """Delete all records from library tables""" + query = 'DELETE FROM video_lib_movies' + self._execute_non_query(query) + query = 'DELETE FROM video_lib_episodes' + self._execute_non_query(query) + query = 'DELETE FROM video_lib_seasons' + self._execute_non_query(query) + query = 'DELETE FROM video_lib_tvshows' + self._execute_non_query(query) + return NFSharedDatabase diff --git a/resources/lib/database/db_update.py b/resources/lib/database/db_update.py index f84559302..ca5843d4b 100644 --- a/resources/lib/database/db_update.py +++ b/resources/lib/database/db_update.py @@ -1,26 +1,97 @@ # -*- coding: utf-8 -*- -"""Database update functions""" -from __future__ import absolute_import, division, unicode_literals +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2019 Stefano Gottardo - @CastagnaIT (original implementation module) + Functions for updating the databases -import resources.lib.common as common -from resources.lib.globals import g + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +from resources.lib.common import CmpVersion +from resources.lib.globals import G -def run_local_db_updates(db_version, db_new_version): +# pylint: disable=unused-argument +def run_local_db_updates(current_version: CmpVersion, upgrade_to_version: CmpVersion): """Perform database actions for a db version change""" # The changes must be left in sequence to allow cascade operations on non-updated databases - if common.is_less_version(db_version, '0.2'): - pass - if common.is_less_version(db_version, '0.3'): + if current_version < '0.2': + # Changes: added table 'search' + import sqlite3 as sql + from resources.lib.database.db_base_sqlite import CONN_ISOLATION_LEVEL + from resources.lib.database import db_utils + + shared_db_conn = sql.connect(db_utils.get_local_db_path(db_utils.LOCAL_DB_FILENAME), + isolation_level=CONN_ISOLATION_LEVEL) + cur = shared_db_conn.cursor() + + table = str('CREATE TABLE search (' + 'ID INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,' + 'Guid TEXT NOT NULL REFERENCES profiles (Guid) ON DELETE CASCADE ON UPDATE CASCADE,' + 'Type TEXT NOT NULL,' + 'Value TEXT NOT NULL,' + 'Parameters TEXT,' + 'LastAccess TEXT);') + cur.execute(table) + shared_db_conn.close() + + if current_version < '0.3': pass - g.LOCAL_DB.set_value('db_version', db_new_version) -def run_shared_db_updates(db_version, db_new_version): +# pylint: disable=unused-argument +def run_shared_db_updates(current_version: CmpVersion, upgrade_to_version: CmpVersion): """Perform database actions for a db version change""" # The changes must be left in sequence to allow cascade operations on non-updated databases - if common.is_less_version(db_version, '0.2'): - pass - if common.is_less_version(db_version, '0.3'): + + if current_version < '0.2': + # Changes: added table 'watched_status_override' + + # SQLite + import sqlite3 as sql + from resources.lib.database.db_base_sqlite import CONN_ISOLATION_LEVEL + from resources.lib.database import db_utils + + shared_db_conn = sql.connect(db_utils.get_local_db_path(db_utils.SHARED_DB_FILENAME), + isolation_level=CONN_ISOLATION_LEVEL) + cur = shared_db_conn.cursor() + + cur.execute('SELECT name FROM sqlite_master WHERE type="table";') + tables = cur.fetchall() + # Check if watched_status_override exists + # (temporary check, usually not needed, applied for previous oversight in the code, can be removed in future) + if ('watched_status_override',) not in tables: + table = str('CREATE TABLE watched_status_override (' + 'ProfileGuid TEXT NOT NULL,' + 'VideoID INTEGER NOT NULL,' + 'Value TEXT,' + 'PRIMARY KEY (ProfileGuid, VideoID ),' + 'FOREIGN KEY (ProfileGuid)' + 'REFERENCES Profiles (Guid) ON DELETE CASCADE ON UPDATE CASCADE);') + cur.execute(table) + shared_db_conn.close() + + # MySQL + if G.ADDON.getSettingBool('use_mysql'): + import mysql.connector + from resources.lib.database.db_base_mysql import MySQLDatabase + + shared_db_conn = MySQLDatabase() + shared_db_conn.conn = mysql.connector.connect(**shared_db_conn.config) + cur = shared_db_conn.conn.cursor() + + table = ('CREATE TABLE netflix_addon.watched_status_override (' + 'ProfileGuid VARCHAR(50) NOT NULL,' + 'VideoID INT(11) NOT NULL,' + 'Value TEXT DEFAULT NULL,' + 'PRIMARY KEY (ProfileGuid, VideoID))' + 'ENGINE = INNODB, CHARACTER SET utf8mb4, COLLATE utf8mb4_unicode_ci;') + alter_tbl = ('ALTER TABLE netflix_addon.watched_status_override ' + 'ADD CONSTRAINT FK_watchedstatusoverride_ProfileGuid FOREIGN KEY (ProfileGuid)' + 'REFERENCES netflix_addon.profiles(Guid) ON DELETE CASCADE ON UPDATE CASCADE;') + cur.execute(table) + cur.execute(alter_tbl) + shared_db_conn.conn.close() + + if current_version < '0.3': pass - g.LOCAL_DB.set_value('db_version', db_new_version) diff --git a/resources/lib/database/db_utils.py b/resources/lib/database/db_utils.py index 574aef45f..0c30f8133 100644 --- a/resources/lib/database/db_utils.py +++ b/resources/lib/database/db_utils.py @@ -1,14 +1,18 @@ # -*- coding: utf-8 -*- -"""Miscellaneous database utility functions""" -from __future__ import absolute_import, division, unicode_literals - +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2019 Stefano Gottardo - @CastagnaIT (original implementation module) + Miscellaneous database utility functions + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" import os -from enum import Enum -import xbmc import xbmcvfs -from resources.lib.globals import g +from resources.lib.globals import G +from resources.lib.utils.logging import LOG LOCAL_DB_FILENAME = 'nf_local.sqlite3' @@ -22,19 +26,21 @@ TABLE_SHARED_APP_CONF = ('shared_app_config', ['Name', 'Value']) -# Enum mapping the video library columns of the tables -class VidLibProp(Enum): - exclude_update = 'ExcludeUpdate' - nfo_export = 'NfoExport' - file_path = 'FilePath' +# Mapping the video library columns of the tables +VidLibProp = { + 'exclude_update': 'ExcludeUpdate', + 'nfo_export': 'NfoExport', + 'file_path': 'FilePath' +} def get_local_db_path(db_filename): # First ensure database folder exists - db_folder = xbmc.translatePath(os.path.join(g.DATA_PATH, 'database')) - if not xbmcvfs.exists(db_folder): + from resources.lib.common import folder_exists + db_folder = xbmcvfs.translatePath(os.path.join(G.DATA_PATH, 'database')) + if not folder_exists(db_folder): xbmcvfs.mkdirs(db_folder) - return xbmc.translatePath(os.path.join(g.DATA_PATH, 'database', db_filename)) + return os.path.join(db_folder, db_filename) def sql_filtered_update(table, set_columns, where_columns, values): @@ -49,12 +55,10 @@ def sql_filtered_update(table, set_columns, where_columns, values): del set_columns[index] del values[index] set_columns = [col + ' = ?' for col in set_columns] + columns_to_set = ', '.join(set_columns) where_columns = [col + ' = ?' for col in where_columns] - query = 'UPDATE {} SET {} WHERE {}'.format( - table, - ', '.join(set_columns), - ' AND '.join(where_columns) - ) + where_condition = ' AND '.join(where_columns) + query = f'UPDATE {table} SET {columns_to_set} WHERE {where_condition}' return query, values @@ -70,11 +74,9 @@ def sql_filtered_insert(table, set_columns, values): del set_columns[index] del values[index] values_fields = ['?'] * len(set_columns) - query = 'INSERT INTO {} ({}) VALUES ({})'.format( - table, - ', '.join(set_columns), - ', '.join(values_fields) - ) + query_columns = ', '.join(set_columns) + values_fields = ', '.join(values_fields) + query = f'INSERT INTO {table} ({query_columns}) VALUES ({values_fields})' return query, values @@ -83,13 +85,30 @@ def mysql_insert_or_update(table, id_columns, columns): Create a MySQL insert or update query (required multi=True) """ columns[0:0] = id_columns - sets_columns = ['@' + col for col in columns] - sets = [col + ' = %s' for col in sets_columns] - query_set = 'SET {};'.format(', '.join(sets)) - query_insert = 'INSERT INTO {} ({}) VALUES ({})'.format(table, - ', '.join(columns), - ', '.join(sets_columns)) + sets_columns = [f'@{col}' for col in columns] + sets = [f'{col} = %s' for col in sets_columns] + query_set = f'SET {", ".join(sets)};' + query_columns = ', '.join(columns) + values = ', '.join(sets_columns) + query_insert = f'INSERT INTO {table} ({query_columns}) VALUES ({values})' + columns = list(set(columns) - set(id_columns)) # Fastest method to remove list to list tested - on_duplicate_params = [col + ' = @' + col for col in columns] - query_duplicate = 'ON DUPLICATE KEY UPDATE {}'.format(', '.join(on_duplicate_params)) + ';' + on_duplicate_params = [f'{col} = @{col}' for col in columns] + query_duplicate = f'ON DUPLICATE KEY UPDATE {", ".join(on_duplicate_params)};' return ' '.join([query_set, query_insert, query_duplicate]) + + +def is_sqlite3_threadsafe(): + """ + Check if SQLite3 module is threadsafe + """ + try: + import sqlite3 as sql + conn = sql.connect(':memory:') + threadsafety = conn.execute('SELECT * FROM pragma_compile_options WHERE compile_options LIKE \'THREADSAFE=%\'').fetchone()[0] + conn.close() + if int(threadsafety.split("=")[1]) == 1: + return True + except Exception as exc: # pylint: disable=broad-except + LOG.error('Failed to check sqlite thread safe: {}', exc) + return False diff --git a/resources/lib/globals.py b/resources/lib/globals.py index 4ba226973..261c41a62 100644 --- a/resources/lib/globals.py +++ b/resources/lib/globals.py @@ -1,27 +1,25 @@ # -*- coding: utf-8 -*- -"""Global addon constants. -Everything that is to be globally accessible must be defined in this module -and initialized in GlobalVariables.init_globals. -When reusing Kodi languageInvokers, only the code in the main module -(addon.py or service.py) will be run every time the addon is called. -All other code executed on module level will only be executed once, when -the module is first imported on the first addon invocation.""" -from __future__ import absolute_import, division, unicode_literals - +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Global addon constants + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +# Everything that is to be globally accessible must be defined in this module. +# Using the Kodi reuseLanguageInvoker feature, only the code in the addon.py or service.py module +# will be run every time the addon is called. +# All other modules (imports) are initialized only on the first invocation of the add-on. import collections import os -import sys -from urllib import unquote +from urllib.parse import parse_qsl, unquote, urlparse -from urlparse import urlparse, parse_qsl -import xbmc import xbmcaddon -import xbmcvfs - -import resources.lib.cache as cache +from xbmcgui import Window -class GlobalVariables(object): +class GlobalVariables: """Encapsulation for global variables to work around quirks with Kodi's reuseLanguageInvoker behavior""" # pylint: disable=attribute-defined-outside-init @@ -49,118 +47,139 @@ class GlobalVariables(object): ''' --Main Menu key infos-- - path : passes information to the called method generally structured as follows: [func. name, menu id, context id] - lolomo_contexts : contexts used to obtain the list of contents (use only one context when lolomo_known = True) - lolomo_known : if True, keys label_id/description_id/icon are ignored, the values are obtained from lolomo list - label_id : menu title - description_id : description info text - icon : set a default image - view : override the default "partial menu id" of view - content_type : override the default content type (CONTENT_SHOW) + path Passes information to the called method + generally structured as follows: [func. name, menu id, context id] + loco_contexts Contexts used to obtain the list of contents (use only one context when loco_known = True) + loco_known If True, keys label_id/description_id/icon are ignored, these values are obtained from LoCo list + label_id The ID for the menu title + description_id Description info text + icon Set a default image + view Override the default "partial menu id" of view + content_type Override the default content type (CONTENT_SHOW) + has_show_setting Means that the menu has the show/hide settings, by default is True + has_sort_setting Means that the menu has the sort settings, by default is False + no_use_cache The cache will not be used to store the contents of the menu Explanation of function names in the 'path' key: - video_list: automatically gets the list_id by making a lolomo request, - the list_id search is made using the value specified on the lolomo_contexts key - video_list_sorted: to work must have a third argument on the path that is the context_id - or instead specified the key request_context_name + video_list Automatically gets the list_id by making a loco request, + the list_id search is made using the value specified on the loco_contexts key + video_list_sorted To work must have a third argument on the path that is the context_id + or instead specified the key request_context_name ''' MAIN_MENU_ITEMS = collections.OrderedDict([ ('myList', {'path': ['video_list_sorted', 'myList'], - 'lolomo_contexts': ['queue'], - 'lolomo_known': True, + 'loco_contexts': ['queue'], + 'loco_known': True, 'request_context_name': 'mylist', - 'view': VIEW_MYLIST}), + 'view': VIEW_MYLIST, + 'has_sort_setting': True, + 'query_without_reference': True}), ('continueWatching', {'path': ['video_list', 'continueWatching'], - 'lolomo_contexts': ['continueWatching'], - 'lolomo_known': True}), + 'loco_contexts': ['continueWatching'], + 'loco_known': True}), + ('newAndPopular', {'path': ['category_list', 'newAndPopular'], + 'loco_contexts': ['comingSoon'], + 'loco_known': False, + 'label_id': 30700, + 'description_id': 30146, + 'icon': 'DefaultRecentlyAddedMovies.png'}), ('chosenForYou', {'path': ['video_list', 'chosenForYou'], - 'lolomo_contexts': ['topTen'], - 'lolomo_known': True}), + 'loco_contexts': ['topTen'], + 'loco_known': True}), ('recentlyAdded', {'path': ['video_list_sorted', 'recentlyAdded', '1592210'], - 'lolomo_contexts': None, - 'lolomo_known': False, + 'loco_contexts': None, + 'loco_known': False, 'request_context_name': 'genres', 'label_id': 30145, 'description_id': 30146, - 'icon': 'DefaultRecentlyAddedMovies.png'}), + 'icon': 'DefaultRecentlyAddedMovies.png', + 'has_sort_setting': True}), ('newRelease', {'path': ['video_list_sorted', 'newRelease'], - 'lolomo_contexts': ['newRelease'], - 'lolomo_known': True, - 'request_context_name': 'newrelease'}), + 'loco_contexts': ['newRelease'], + 'loco_known': True, + 'request_context_name': 'newrelease', + 'has_sort_setting': True, + 'query_without_reference': True}), ('currentTitles', {'path': ['video_list', 'currentTitles'], - 'lolomo_contexts': ['trendingNow'], - 'lolomo_known': True}), + 'loco_contexts': ['trendingNow'], + 'loco_known': True}), ('mostViewed', {'path': ['video_list', 'mostViewed'], - 'lolomo_contexts': ['popularTitles'], - 'lolomo_known': True}), + 'loco_contexts': ['popularTitles'], + 'loco_known': True}), ('netflixOriginals', {'path': ['video_list_sorted', 'netflixOriginals', '839338'], - 'lolomo_contexts': ['netflixOriginals'], - 'lolomo_known': True, - 'request_context_name': 'genres'}), + 'loco_contexts': ['netflixOriginals'], + 'loco_known': True, + 'request_context_name': 'genres', + 'has_sort_setting': True}), ('assistiveAudio', {'path': ['video_list_sorted', 'assistiveAudio', 'None'], - 'lolomo_contexts': None, - 'lolomo_known': False, + 'loco_contexts': None, + 'loco_known': False, 'request_context_name': 'assistiveAudio', 'label_id': 30163, 'description_id': 30164, - 'icon': 'DefaultTVShows.png'}), + 'icon': 'DefaultTVShows.png', + 'has_sort_setting': True, + 'query_without_reference': True}), ('recommendations', {'path': ['recommendations', 'recommendations'], - 'lolomo_contexts': ['similars', 'becauseYouAdded'], - 'lolomo_known': False, + 'loco_contexts': ['similars', 'becauseYouAdded', 'becauseYouLiked', 'watchAgain', + 'bigRow'], + 'loco_known': False, 'label_id': 30001, 'description_id': 30094, - 'icon': 'DefaultUser.png', - 'content_type': CONTENT_FOLDER}), + 'icon': 'DefaultUser.png'}), ('tvshowsGenres', {'path': ['subgenres', 'tvshowsGenres', '83'], - 'lolomo_contexts': None, - 'lolomo_known': False, + 'loco_contexts': None, + 'loco_known': False, 'request_context_name': 'genres', # Used for sub-menus 'label_id': 30174, 'description_id': None, 'icon': 'DefaultTVShows.png', - 'content_type': CONTENT_FOLDER}), + 'has_sort_setting': True}), ('moviesGenres', {'path': ['subgenres', 'moviesGenres', '34399'], - 'lolomo_contexts': None, - 'lolomo_known': False, + 'loco_contexts': None, + 'loco_known': False, 'request_context_name': 'genres', # Used for sub-menus 'label_id': 30175, 'description_id': None, 'icon': 'DefaultMovies.png', - 'content_type': CONTENT_FOLDER}), + 'content_type': CONTENT_MOVIE, + 'has_sort_setting': True}), ('tvshows', {'path': ['genres', 'tvshows', '83'], - 'lolomo_contexts': None, - 'lolomo_known': False, + 'loco_contexts': ['genre'], + 'loco_known': False, 'request_context_name': 'genres', # Used for sub-menus 'label_id': 30095, 'description_id': None, 'icon': 'DefaultTVShows.png', - 'content_type': CONTENT_FOLDER}), + 'has_sort_setting': True}), ('movies', {'path': ['genres', 'movies', '34399'], - 'lolomo_contexts': None, - 'lolomo_known': False, + 'loco_contexts': ['genre'], + 'loco_known': False, 'request_context_name': 'genres', # Used for sub-menus 'label_id': 30096, 'description_id': None, 'icon': 'DefaultMovies.png', - 'content_type': CONTENT_FOLDER}), + 'content_type': CONTENT_MOVIE, + 'has_sort_setting': True}), ('genres', {'path': ['genres', 'genres'], - 'lolomo_contexts': ['genre'], - 'lolomo_known': False, + 'loco_contexts': ['genre'], + 'loco_known': False, 'request_context_name': 'genres', # Used for sub-menus 'label_id': 30010, 'description_id': 30093, 'icon': 'DefaultGenre.png', - 'content_type': CONTENT_FOLDER}), + 'has_sort_setting': True}), ('search', {'path': ['search', 'search'], - 'lolomo_contexts': None, - 'lolomo_known': False, - 'label_id': 30011, + 'loco_contexts': None, + 'loco_known': False, + 'label_id': 30400, 'description_id': 30092, - 'icon': None, - 'view': VIEW_SEARCH}), + 'icon': 'DefaultAddonsSearch.png', + 'view': VIEW_SEARCH, + 'has_sort_setting': True}), ('exported', {'path': ['exported', 'exported'], - 'lolomo_contexts': None, - 'lolomo_known': False, + 'loco_contexts': None, + 'loco_known': False, 'label_id': 30048, 'description_id': 30091, 'icon': 'DefaultHardDisk.png', @@ -168,236 +187,135 @@ class GlobalVariables(object): ]) MODE_DIRECTORY = 'directory' - MODE_HUB = 'hub' MODE_ACTION = 'action' MODE_PLAY = 'play' + MODE_PLAY_STRM = 'play_strm' MODE_LIBRARY = 'library' + MODE_KEYMAPS = 'keymaps' + + SERVICE_STATUS_STARTUP = 'startup' + SERVICE_STATUS_RUNNING = 'running' + SERVICE_STATUS_STOPPED = 'stopped' + SERVICE_STATUS_UPGRADE = 'upgrade' + SERVICE_STATUS_ERROR = 'error' def __init__(self): """Do nothing on constructing the object""" - pass - - def init_globals(self, argv, skip_database_initialize=False): - """Initialized globally used module variables. - Needs to be called at start of each plugin instance! - This is an ugly hack because Kodi doesn't execute statements defined on - module level if reusing a language invoker.""" - self.COOKIES = {} + # The class initialization (GlobalVariables) will only take place at the first initialization of this module + # on subsequent add-on invocations (invoked by reuseLanguageInvoker) will have no effect. + # Define here also any other variables necessary for the correct loading of the other project modules + self.WND_KODI_HOME = Window(10000) # Kodi home window + self.IS_ADDON_FIRSTRUN = None + self.IS_SERVICE = False + self.ADDON = None + self.ADDON_DATA_PATH = None + self.DATA_PATH = None + self.CACHE_MANAGEMENT = None + self.CACHE_TTL = None + self.CACHE_MYLIST_TTL = None + self.CACHE_METADATA_TTL = None + + def init_globals(self, argv): + """Initialized globally used module variables. Needs to be called at start of each plugin instance!""" + # IS_ADDON_FIRSTRUN: specifies if the add-on has been initialized for the first time + # (reuseLanguageInvoker not used yet) + self.IS_ADDON_FIRSTRUN = self.IS_ADDON_FIRSTRUN is None + self.IS_ADDON_EXTERNAL_CALL = False + # xbmcaddon.Addon must be created at every instance otherwise it does not read any new changes to the settings self.ADDON = xbmcaddon.Addon() - self.ADDON_ID = self.ADDON.getAddonInfo('id') - self.PLUGIN = self.ADDON.getAddonInfo('name') - self.VERSION = self.ADDON.getAddonInfo('version') - self.DEFAULT_FANART = self.ADDON.getAddonInfo('fanart') - self.ICON = self.ADDON.getAddonInfo('icon') - self.ADDON_DATA_PATH = self.ADDON.getAddonInfo('path') # Addon folder - self.DATA_PATH = self.ADDON.getAddonInfo('profile') # Addon user data folder - - # Add absolute paths of embedded py modules to python system directory - module_paths = [ - os.path.join(self.ADDON_DATA_PATH, 'modules', 'enum'), - os.path.join(self.ADDON_DATA_PATH, 'modules', 'mysql-connector-python') - ] - for path in module_paths: - path = xbmc.translatePath(path) - if path not in sys.path: - sys.path.insert(0, path) - - self.CACHE_PATH = os.path.join(self.DATA_PATH, 'cache') - self.COOKIE_PATH = os.path.join(self.DATA_PATH, 'COOKIE') - self.CACHE_TTL = self.ADDON.getSettingInt('cache_ttl') * 60 - self.CACHE_METADATA_TTL = ( - self.ADDON.getSettingInt('cache_metadata_ttl') * 24 * 60 * 60) - self.URL = urlparse(argv[0]) - try: - self.PLUGIN_HANDLE = int(argv[1]) - except IndexError: - self.PLUGIN_HANDLE = 0 - self.BASE_URL = '{scheme}://{netloc}'.format(scheme=self.URL[0], - netloc=self.URL[1]) - self.PATH = unquote(self.URL[2][1:]).decode('utf-8') + self.REQUEST_PATH = unquote(self.URL[2][1:]) try: self.PARAM_STRING = argv[2][1:] except IndexError: self.PARAM_STRING = '' self.REQUEST_PARAMS = dict(parse_qsl(self.PARAM_STRING)) - self.reset_time_trace() - self.TIME_TRACE_ENABLED = self.ADDON.getSettingBool('enable_timing') + try: + self.PLUGIN_HANDLE = int(argv[1]) + except IndexError: + self.PLUGIN_HANDLE = 0 + self.IS_SERVICE = True + if self.IS_ADDON_FIRSTRUN: + # Global variables that do not need to be generated at every instance + self.ADDON_ID = self.ADDON.getAddonInfo('id') + self.PLUGIN = self.ADDON.getAddonInfo('name') + self.VERSION_RAW = self.ADDON.getAddonInfo('version') + self.VERSION = remove_ver_suffix(self.VERSION_RAW) + self.ICON = self.ADDON.getAddonInfo('icon') + self.DEFAULT_FANART = self.ADDON.getAddonInfo('fanart') + self.ADDON_DATA_PATH = self.ADDON.getAddonInfo('path') # Add-on folder + self.DATA_PATH = self.ADDON.getAddonInfo('profile') # Add-on user data folder + self.CACHE_PATH = os.path.join(self.DATA_PATH, 'cache') + self.COOKIES_PATH = os.path.join(self.DATA_PATH, 'COOKIES') + if self.IS_SERVICE: + self.BASE_URL = f'plugin://{self.ADDON_ID}' + else: + self.BASE_URL = f'{self.URL[0]}://{self.URL[1]}' + from resources.lib.common.kodi_ops import KodiVersion + self.KODI_VERSION = KodiVersion() + self.IS_OLD_KODI_MODULES = self.KODI_VERSION < '20' + # Initialize the log + from resources.lib.utils.logging import LOG + LOG.initialize(self.ADDON_ID, self.PLUGIN_HANDLE, + self.ADDON.getSettingBool('enable_debug'), + self.ADDON.getSettingBool('enable_timing')) + if self.IS_ADDON_FIRSTRUN: + self.init_database() + # Initialize the cache + if self.IS_SERVICE: + from resources.lib.services.cache_management import CacheManagement + self.CACHE_MANAGEMENT = CacheManagement() + self.CACHE = self.CACHE_MANAGEMENT + from resources.lib.services.settings_monitor import SettingsMonitor + self.SETTINGS_MONITOR = SettingsMonitor() + else: + from resources.lib.common.cache import Cache + self.CACHE = Cache() self.IPC_OVER_HTTP = self.ADDON.getSettingBool('enable_ipc_over_http') - if not skip_database_initialize: - # Initialize local database - import resources.lib.database.db_local as db_local - self.LOCAL_DB = db_local.NFLocalDatabase() - # Initialize shared database - import resources.lib.database.db_shared as db_shared - from resources.lib.database.db_exceptions import MySQLConnectionError - try: - shared_db_class = db_shared.get_shareddb_class() - self.SHARED_DB = shared_db_class() - except MySQLConnectionError: - # The MySQL database cannot be reached, fallback to local SQLite database - # When this code is called from addon, is needed apply the change also in the - # service, so disabling it run the SettingsMonitor - import resources.lib.kodi.ui as ui - self.ADDON.setSettingBool('use_mysql', False) - ui.show_notification(self.ADDON.getLocalizedString(30206), time=10000) - shared_db_class = db_shared.get_shareddb_class(force_sqlite=True) - self.SHARED_DB = shared_db_class() - - self.settings_monitor_suspended(False) # Reset the value in case of addon crash - + def init_database(self): + # Check if SQLite module is thread safe + from resources.lib.database.db_utils import is_sqlite3_threadsafe + self.IS_SQLITE3_THREADSAFE = is_sqlite3_threadsafe() + # Initialize local database + import resources.lib.database.db_local as db_local + self.LOCAL_DB = db_local.NFLocalDatabase() + # Initialize shared database + use_mysql = G.ADDON.getSettingBool('use_mysql') + import resources.lib.database.db_shared as db_shared + from resources.lib.common.exceptions import DBMySQLConnectionError, DBMySQLError try: - os.mkdir(self.DATA_PATH) - except OSError: - pass - - self._init_cache() - - def _init_cache(self): - if not os.path.exists( - xbmc.translatePath(self.CACHE_PATH).decode('utf-8')): - self._init_filesystem_cache() - # This is ugly: Pass the common module into Cache.__init__ to work - # around circular import dependencies. - import resources.lib.common as common - self.CACHE = cache.Cache(common, self.CACHE_PATH, self.CACHE_TTL, - self.CACHE_METADATA_TTL, self.PLUGIN_HANDLE) - - def _init_filesystem_cache(self): - # pylint: disable=broad-except - for bucket in cache.BUCKET_NAMES: - xbmcvfs.mkdirs(xbmc.translatePath(os.path.join(self.CACHE_PATH, bucket))) - - def initial_addon_configuration(self): - """ - Initial addon configuration, - helps users to automatically configure addon parameters for proper viewing of videos - """ - run_initial_config = self.ADDON.getSettingBool('run_init_configuration') - if run_initial_config: - import resources.lib.common as common + shared_db_class = db_shared.get_shareddb_class(use_mysql=use_mysql) + self.SHARED_DB = shared_db_class() + except (DBMySQLConnectionError, DBMySQLError) as exc: import resources.lib.kodi.ui as ui - self.settings_monitor_suspended(True) - - system = common.get_system_platform() - common.debug('Running initial addon configuration dialogs on system: {}'.format(system)) - if system in ['osx', 'ios', 'xbox']: - self.ADDON.setSettingBool('enable_vp9_profiles', False) - self.ADDON.setSettingBool('enable_hevc_profiles', True) - elif system == 'windows': - # Currently inputstream does not support hardware video acceleration on windows, - # there is no guarantee that we will get 4K without video hardware acceleration, - # so no 4K configuration - self.ADDON.setSettingBool('enable_vp9_profiles', True) - self.ADDON.setSettingBool('enable_hevc_profiles', False) - elif system == 'android': - ultrahd_capable_device = False - premium_account = ui.ask_for_confirmation(common.get_local_string(30154), - common.get_local_string(30155)) - if premium_account: - ultrahd_capable_device = ui.ask_for_confirmation(common.get_local_string(30154), - common.get_local_string(30156)) - if ultrahd_capable_device: - ui.show_ok_dialog(common.get_local_string(30154), - common.get_local_string(30157)) - ia_enabled = xbmc.getCondVisibility('System.HasAddon(inputstream.adaptive)') - if ia_enabled: - xbmc.executebuiltin('Addon.OpenSettings(inputstream.adaptive)') - else: - ui.show_ok_dialog(common.get_local_string(30154), - common.get_local_string(30046)) - self.ADDON.setSettingBool('enable_vp9_profiles', False) - self.ADDON.setSettingBool('enable_hevc_profiles', True) - else: - # VP9 should have better performance since there is no need for 4k - self.ADDON.setSettingBool('enable_vp9_profiles', True) - self.ADDON.setSettingBool('enable_hevc_profiles', False) - self.ADDON.setSettingBool('enable_force_hdcp', ultrahd_capable_device) - elif system == 'linux': - # Too many different linux systems, we can not predict all the behaviors - # Some linux distributions have encountered problems with VP9, - # OMSC users complain that hevc creates problems - self.ADDON.setSettingBool('enable_vp9_profiles', False) - self.ADDON.setSettingBool('enable_hevc_profiles', False) - else: - self.ADDON.setSettingBool('enable_vp9_profiles', False) - self.ADDON.setSettingBool('enable_hevc_profiles', False) - self.ADDON.setSettingBool('run_init_configuration', False) - self.settings_monitor_suspended(False) - - def settings_monitor_suspended(self, suspend): - """ - Suspends for the necessary time the settings monitor - that otherwise cause the reinitialization of global settings - and possible consequent actions to settings changes or unnecessary checks - """ - is_suspended = g.LOCAL_DB.get_value('suspend_settings_monitor', False) - if (is_suspended and suspend) or (not is_suspended and not suspend): - return - g.LOCAL_DB.set_value('suspend_settings_monitor', suspend) - - def settings_monitor_is_suspended(self): - """ - Returns True when the setting monitor must be suspended - """ - return g.LOCAL_DB.get_value('suspend_settings_monitor', False) - - def get_esn(self): - """Get the generated esn or if set get the custom esn""" - from resources.lib.database.db_utils import (TABLE_SESSION) - custom_esn = g.ADDON.getSetting('esn') - return custom_esn if custom_esn else g.LOCAL_DB.get_value('esn', table=TABLE_SESSION) - - def get_edge_esn(self): - """Get a previously generated edge ESN from the settings or generate - a new one if none exists""" - return self.ADDON.getSetting('edge_esn') or self.generate_edge_esn() - - def generate_edge_esn(self): - """Generate a random EDGE ESN and save it to the settings""" - import random - esn = ['NFCDIE-02-'] - possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' - for _ in range(0, 30): - esn.append(random.choice(possible)) - edge_esn = ''.join(esn) - self.settings_monitor_suspended(True) - self.ADDON.setSetting('edge_esn', edge_esn) - self.settings_monitor_suspended(False) - return edge_esn + if isinstance(exc, DBMySQLError): + # There is a problem with the database + ui.show_addon_error_info(exc) + # The MySQL database cannot be reached, fallback to local SQLite database + # When this code is called from addon, is needed apply the change also in the + # service, so disabling it run the SettingsMonitor + self.ADDON.setSettingBool('use_mysql', False) + ui.show_notification(self.ADDON.getLocalizedString(30206), time=10000) + shared_db_class = db_shared.get_shareddb_class() + self.SHARED_DB = shared_db_class() def is_known_menu_context(self, context): - """Return true if context are one of the menu with lolomo_known=True""" - for menu_id, data in self.MAIN_MENU_ITEMS.iteritems(): # pylint: disable=unused-variable - if data['lolomo_known']: - if data['lolomo_contexts'][0] == context: + """Return true if context are one of the menu with loco_known=True""" + for _, data in self.MAIN_MENU_ITEMS.items(): + if data['loco_known']: + if data['loco_contexts'][0] == context: return True return False - def flush_settings(self): - """Reload the ADDON""" - # pylint: disable=attribute-defined-outside-init - self.ADDON = xbmcaddon.Addon() - - def reset_time_trace(self): - """Reset current time trace info""" - self.TIME_TRACE = [] - self.time_trace_level = -2 - - def add_time_trace_level(self): - """Add a level to the time trace""" - self.time_trace_level += 2 - def remove_time_trace_level(self): - """Remove a level from the time trace""" - self.time_trace_level -= 2 +def remove_ver_suffix(version): + """Remove the codename suffix from version value""" + import re + pattern = re.compile(r'\+.+$') # Example: +matrix +matrix.1 ... + return re.sub(pattern, '', version) -# pylint: disable=invalid-name -# This will have no effect most of the time, as it doesn't seem to be executed -# on subsequent addon invocations when reuseLanguageInvoker is being used. -# We initialize an empty instance so the instance is importable from addon.py -# and service.py, where g.init_globals(sys.argv) MUST be called before doing -# anything else (even BEFORE OTHER IMPORTS from this addon) -g = GlobalVariables() +# We initialize an instance importable of GlobalVariables from run_addon.py and run_service.py, +# where G.init_globals() MUST be called before you do anything else. +G = GlobalVariables() diff --git a/resources/lib/kodi/__init__.py b/resources/lib/kodi/__init__.py index e69de29bb..c6723a588 100644 --- a/resources/lib/kodi/__init__.py +++ b/resources/lib/kodi/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) +# Copyright (C) 2018 Caphm (original implementation module) + +# SPDX-License-Identifier: MIT +# See LICENSES/MIT.md for more information. diff --git a/resources/lib/kodi/context_menu.py b/resources/lib/kodi/context_menu.py index 40f5905b2..e88544d1d 100644 --- a/resources/lib/kodi/context_menu.py +++ b/resources/lib/kodi/context_menu.py @@ -1,102 +1,135 @@ # -*- coding: utf-8 -*- -"""Helper functions to generating context menu items""" -from __future__ import absolute_import, division, unicode_literals - -from resources.lib.globals import g +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Helper functions to generating context menu items + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" import resources.lib.common as common -import resources.lib.api.shakti as api -import resources.lib.kodi.library as library - - -def ctx_item_url(paths, mode=g.MODE_ACTION): - """Return a function that builds an URL from a videoid - for the predefined path""" - def ctx_url_builder(videoid): - """Build a context menu item URL""" - return common.build_url(paths, videoid, mode=mode) - return ctx_url_builder - - -CONTEXT_MENU_ACTIONS = { - 'export': { - 'label': common.get_local_string(30018), - 'url': ctx_item_url(['export'], g.MODE_LIBRARY)}, - 'remove': { - 'label': common.get_local_string(30030), - 'url': ctx_item_url(['remove'], g.MODE_LIBRARY)}, - 'update': { - 'label': common.get_local_string(30061), - 'url': ctx_item_url(['update'], g.MODE_LIBRARY)}, - 'export_new_episodes': { - 'label': common.get_local_string(30195), - 'url': ctx_item_url(['export_new_episodes'], g.MODE_LIBRARY)}, - 'exclude_from_auto_update': { - 'label': common.get_local_string(30196), - 'url': ctx_item_url(['exclude_from_auto_update'], g.MODE_LIBRARY)}, - 'include_in_auto_update': { - 'label': common.get_local_string(30197), - 'url': ctx_item_url(['include_in_auto_update'], g.MODE_LIBRARY)}, - 'rate': { - 'label': common.get_local_string(30019), - 'url': ctx_item_url(['rate'])}, - 'add_to_list': { - 'label': common.get_local_string(30021), - 'url': ctx_item_url(['my_list', 'add'])}, - 'remove_from_list': { - 'label': common.get_local_string(30020), - 'url': ctx_item_url(['my_list', 'remove'])}, - 'trailer': { - 'label': common.get_local_string(30179), - 'url': ctx_item_url(['trailer'])}, -} - - -def generate_context_menu_items(videoid): +import resources.lib.kodi.library_utils as lib_utils +from resources.lib.globals import G + + +def generate_context_menu_mainmenu(menu_id): + """Generate context menu items for a listitem of the main menu""" + items = [] + if menu_id in ['myList', 'continueWatching']: + items.append(_ctx_item('force_update_list', None, {'menu_id': menu_id})) + return items + + +def generate_context_menu_profile(profile_guid, is_autoselect, is_autoselect_library, is_pin_locked): + """Generate context menu items for a listitem of the profile""" + params = {'profile_guid': profile_guid} + is_remember_pin = G.LOCAL_DB.get_profile_config('addon_remember_pin', False, guid=profile_guid) + items = [ + _ctx_item('profile_autoselect', None, + {**params, 'operation': 'remove' if is_autoselect else 'set'}, + label_format='●' if is_autoselect else '○'), + _ctx_item('profile_autoselect_library', None, + {**params, 'operation': 'remove' if is_autoselect_library else 'set'}, + label_format='●' if is_autoselect_library else '○'), + _ctx_item('profile_parental_control', None, params) + ] + if is_pin_locked: + items.append(_ctx_item('profile_remember_pin', None, params, label_format='●' if is_remember_pin else '○')) + return items + + +def generate_context_menu_searchitem(row_id, search_type): + """Generate context menu items for a listitem of the search menu""" + items = [] + if search_type == 'text': + items.append(_ctx_item('search_edit', None, {'row_id': row_id})) + items.append(_ctx_item('search_remove', None, {'row_id': row_id})) + return items + + +def generate_context_menu_remind_me(videoid, is_set, trackid): + items = [] + if is_set is not None and videoid.mediatype in [common.VideoId.MOVIE, common.VideoId.SHOW]: + operation = 'remove' if is_set else 'add' + items.insert(0, _ctx_item('remind_me', videoid, {'operation': operation, 'trackid': trackid}, + label_format='●' if is_set else '○')) + return items + + +def generate_context_menu_items(videoid, is_in_mylist, perpetual_range_start=None, add_remove_watched_status=False, + trackid=None): """Generate context menu items for a listitem""" - items = _generate_library_ctx_items(videoid) + items = [] + + if videoid.mediatype not in [common.VideoId.SUPPLEMENTAL, common.VideoId.EPISODE]: + # Library operations for supplemental (trailers etc) and single episodes are not allowed + if G.ADDON.getSettingBool('lib_enabled'): + items = _generate_library_ctx_items(videoid) + + # Old rating system + # if videoid.mediatype != common.VideoId.SEASON and \ + # videoid.mediatype != common.VideoId.SUPPLEMENTAL: + # items.insert(0, _ctx_item('rate', videoid)) - if videoid.mediatype != common.VideoId.SEASON and \ - videoid.mediatype != common.VideoId.SUPPLEMENTAL: - items.insert(0, _ctx_item('rate', videoid)) + if videoid.mediatype in [common.VideoId.MOVIE, common.VideoId.SHOW]: + items.insert(0, _ctx_item('rate_thumb', videoid)) + if add_remove_watched_status: + items.insert(0, _ctx_item('remove_watched_status', videoid)) - if videoid.mediatype != common.VideoId.SUPPLEMENTAL and \ - videoid.mediatype in [common.VideoId.MOVIE, common.VideoId.SHOW]: + if (videoid.mediatype != common.VideoId.SUPPLEMENTAL and + videoid.mediatype in [common.VideoId.MOVIE, common.VideoId.SHOW]): items.insert(0, _ctx_item('trailer', videoid)) - if videoid.mediatype in [common.VideoId.MOVIE, common.VideoId.SHOW] \ - and not g.LOCAL_DB.get_profile_config('isKids', False): - list_action = ('remove_from_list' - if videoid.value in api.mylist_items() - else 'add_to_list') - items.insert(0, _ctx_item(list_action, videoid)) + if videoid.mediatype in [common.VideoId.MOVIE, common.VideoId.SHOW] and trackid is not None: + list_action = 'remove_from_list' if is_in_mylist else 'add_to_list' + items.insert(0, _ctx_item(list_action, videoid, {'perpetual_range_start': perpetual_range_start, + 'trackid': trackid})) + + if videoid.mediatype in [common.VideoId.MOVIE, common.VideoId.EPISODE]: + # Add menu to allow change manually the watched status when progress manager is enabled + if G.ADDON.getSettingBool('sync_watched_status'): + items.insert(0, _ctx_item('change_watched_status', videoid)) return items def _generate_library_ctx_items(videoid): library_actions = [] - if videoid.mediatype == common.VideoId.SUPPLEMENTAL: - return library_actions - - is_in_library = library.is_in_library(videoid) - library_actions = ['remove', 'update'] if is_in_library else ['export'] - - if g.ADDON.getSettingInt('auto_update') and \ - videoid.mediatype in [common.VideoId.SEASON, common.VideoId.EPISODE]: - library_actions = [] - - if videoid.mediatype == common.VideoId.SHOW and is_in_library: - library_actions.append('export_new_episodes') - if library.show_excluded_from_auto_update(videoid): - library_actions.append('include_in_auto_update') + allow_lib_operations = True + lib_is_sync_with_mylist = (G.ADDON.getSettingBool('lib_sync_mylist') and + G.ADDON.getSettingInt('lib_auto_upd_mode') in [0, 2]) + + if lib_is_sync_with_mylist: + # If the synchronization of Netflix "My List" with the Kodi library is enabled + # only in the chosen profile allow to do operations in the Kodi library otherwise + # it creates inconsistency to the exported elements and increases the work for sync + sync_mylist_profile_guid = G.SHARED_DB.get_value('sync_mylist_profile_guid', + G.LOCAL_DB.get_guid_owner_profile()) + allow_lib_operations = sync_mylist_profile_guid == G.LOCAL_DB.get_active_profile_guid() + + if allow_lib_operations: + _is_in_library = lib_utils.is_videoid_in_db(videoid) + if lib_is_sync_with_mylist: + if _is_in_library: + library_actions = ['update'] else: - library_actions.append('exclude_from_auto_update') + library_actions = ['remove', 'update'] if _is_in_library else ['export'] + + if videoid.mediatype == common.VideoId.SHOW and _is_in_library: + library_actions.append('export_new_episodes') + if lib_utils.is_show_excluded_from_auto_update(videoid): + library_actions.append('include_in_auto_update') + else: + library_actions.append('exclude_from_auto_update') return [_ctx_item(action, videoid) for action in library_actions] -def _ctx_item(template, videoid): +def _ctx_item(template, videoid, params=None, label_format=''): """Create a context menu item based on the given template and videoid""" - return (CONTEXT_MENU_ACTIONS[template]['label'], - common.run_plugin_action( - CONTEXT_MENU_ACTIONS[template]['url'](videoid))) + # Do not move the import to the top of the module header, see context_menu_utils.py + from resources.lib.kodi.context_menu_utils import CONTEXT_MENU_ACTIONS + label = CONTEXT_MENU_ACTIONS[template]['label'] + if label_format: + label = label.format(label_format) + return label, common.run_plugin_action(CONTEXT_MENU_ACTIONS[template]['url'](videoid, params)) diff --git a/resources/lib/kodi/context_menu_utils.py b/resources/lib/kodi/context_menu_utils.py new file mode 100644 index 000000000..b025fb381 --- /dev/null +++ b/resources/lib/kodi/context_menu_utils.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2020 Stefano Gottardo (original implementation module) + Miscellaneous utility functions for generating context menu items + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +import resources.lib.common as common +from resources.lib.globals import G + + +# Normally it wouldn't be necessary to split a module so small into two files, +# unfortunately use 'get_local_string' on a variable in the module header, makes that method (get_local_string) +# run immediately upon loading of the add-on modules, making it impossible to load the service instance. +# Separating the process of the loading of local strings would cause a huge slowdown in the processing of video lists. + + +def ctx_item_url(paths, mode=G.MODE_ACTION): + """Return a function that builds an URL from a videoid for the predefined path""" + def ctx_url_builder(videoid, params): + """Build a context menu item URL""" + return common.build_url(paths, videoid, params, mode=mode) + return ctx_url_builder + + +CONTEXT_MENU_ACTIONS = { + 'export': { + 'label': common.get_local_string(30018), + 'url': ctx_item_url(['export'], G.MODE_LIBRARY)}, + 'remove': { + 'label': common.get_local_string(30030), + 'url': ctx_item_url(['remove'], G.MODE_LIBRARY)}, + 'update': { + 'label': common.get_local_string(30061), + 'url': ctx_item_url(['update'], G.MODE_LIBRARY)}, + 'export_new_episodes': { + 'label': common.get_local_string(30195), + 'url': ctx_item_url(['export_new_episodes'], G.MODE_LIBRARY)}, + 'exclude_from_auto_update': { + 'label': common.get_local_string(30196), + 'url': ctx_item_url(['exclude_from_auto_update'], G.MODE_LIBRARY)}, + 'include_in_auto_update': { + 'label': common.get_local_string(30197), + 'url': ctx_item_url(['include_in_auto_update'], G.MODE_LIBRARY)}, + 'rate': { + 'label': common.get_local_string(30019), + 'url': ctx_item_url(['rate'])}, + 'rate_thumb': { + 'label': common.get_local_string(30019), + 'url': ctx_item_url(['rate_thumb'])}, + 'add_to_list': { + 'label': common.get_local_string(30021), + 'url': ctx_item_url(['my_list', 'add'])}, + 'remove_from_list': { + 'label': common.get_local_string(30020), + 'url': ctx_item_url(['my_list', 'remove'])}, + 'trailer': { + 'label': common.get_local_string(30179), + 'url': ctx_item_url(['trailer'])}, + 'force_update_list': { + 'label': common.get_local_string(30214), + 'url': ctx_item_url(['force_update_list'])}, + 'change_watched_status': { + 'label': common.get_local_string(30236), + 'url': ctx_item_url(['change_watched_status'])}, + 'search_remove': { + 'label': common.get_local_string(15015), + 'url': ctx_item_url(['search', 'search', 'remove'], G.MODE_DIRECTORY)}, + 'search_edit': { + 'label': common.get_local_string(21450), + 'url': ctx_item_url(['search', 'search', 'edit'], G.MODE_DIRECTORY)}, + 'remove_watched_status': { + 'label': common.get_local_string(15015), + 'url': ctx_item_url(['remove_watched_status'])}, + 'profile_autoselect': { + 'label': common.get_local_string(30055), + 'url': ctx_item_url(['profile_autoselect'])}, + 'profile_autoselect_library': { + 'label': common.get_local_string(30052), + 'url': ctx_item_url(['profile_autoselect_library'])}, + 'profile_remember_pin': { + 'label': common.get_local_string(30057), + 'url': ctx_item_url(['profile_remember_pin'])}, + 'profile_parental_control': { + 'label': common.get_local_string(30062), + 'url': ctx_item_url(['parental_control'])}, + 'remind_me': { + 'label': common.get_local_string(30622), + 'url': ctx_item_url(['remind_me'])}, +} diff --git a/resources/lib/kodi/infolabels.py b/resources/lib/kodi/infolabels.py index 9d5575c73..f6ec0c387 100644 --- a/resources/lib/kodi/infolabels.py +++ b/resources/lib/kodi/infolabels.py @@ -1,242 +1,352 @@ # -*- coding: utf-8 -*- -"""Helper functions for setting infolabels of list items""" -from __future__ import absolute_import, division, unicode_literals - -from resources.lib.globals import g +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Helper functions for setting infolabels of list items + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +import copy +import time + +import resources.lib.utils.api_paths as paths import resources.lib.common as common -import resources.lib.cache as cache -import resources.lib.api.paths as paths -import resources.lib.kodi.library as library - -QUALITIES = [ - {'codec': 'h264', 'width': '960', 'height': '540'}, - {'codec': 'h264', 'width': '1920', 'height': '1080'}, - {'codec': 'h265', 'width': '3840', 'height': '2160'} -] - -JSONRPC_MAPPINGS = { - 'showtitle': 'tvshowtitle', - 'userrating': 'rating' +from resources.lib.common.exceptions import CacheMiss, ItemNotFound +from resources.lib.common.cache_utils import CACHE_BOOKMARKS, CACHE_INFOLABELS, CACHE_ARTINFO +from resources.lib.common.kodi_wrappers import ListItemW +from resources.lib.globals import G +from resources.lib.utils.logging import LOG + + +COLORS = [None, 'blue', 'red', 'green', 'white', 'yellow', 'black', 'gray'] + +# Mapping of videoid type to ListItem.MediaType +MEDIA_TYPE_MAPPINGS = { + common.VideoId.SHOW: 'tvshow', + common.VideoId.SEASON: 'season', + common.VideoId.EPISODE: 'episode', + common.VideoId.MOVIE: 'movie', + common.VideoId.SUPPLEMENTAL: 'video', + common.VideoId.UNSPECIFIED: 'video' } -def add_info(videoid, list_item, item, raw_data): - """Add infolabels to the list_item. The passed in list_item is modified - in place and the infolabels are returned.""" - # pylint: disable=too-many-locals +def get_video_codec_hint(): + """Suggests which codec the video may have""" + # The video lists do not provide the type of codec, it depends on many factors (device/SO/DRM/manifest request) + # but we can rely on which codec is enabled from the settings, if there are more codecs enabled usually + # the most efficient codec has the priority, e.g. HEVC > VP9 > H264 + # This could be not always reliable, depends also on the availability of stream types + codec = 'h264' + if G.ADDON.getSettingBool('enable_hevc_profiles'): + codec = 'hevc' + elif G.ADDON.getSettingBool('enable_vp9_profiles'): + codec = 'vp9' + return codec + + +def get_info(videoid, item, raw_data, profile_language_code='', delayed_db_op=False, common_data=None): + """Get the infolabels data""" + if common_data is None: + common_data = {} + cache_identifier = f'{videoid.value}_{profile_language_code}' try: - cache_entry = g.CACHE.get(cache.CACHE_INFOLABELS, videoid) + cache_entry = G.CACHE.get(CACHE_INFOLABELS, cache_identifier) infos = cache_entry['infos'] quality_infos = cache_entry['quality_infos'] - except cache.CacheMiss: - infos, quality_infos = parse_info(videoid, item, raw_data) - g.CACHE.add(cache.CACHE_INFOLABELS, videoid, - {'infos': infos, 'quality_infos': quality_infos}, - ttl=g.CACHE_METADATA_TTL, to_disk=True) + except CacheMiss: + infos, quality_infos = parse_info(videoid, item, raw_data, common_data) + G.CACHE.add(CACHE_INFOLABELS, cache_identifier, {'infos': infos, 'quality_infos': quality_infos}, + delayed_db_op=delayed_db_op) + # Use a deepcopy of dict to not reflect changes of the dictionary also to the cache + infos_copy = copy.deepcopy(infos) + # Not all skins support PlotOutline, so copy over Plot if it does not exist + if 'Plot' not in infos_copy and 'PlotOutline' in infos_copy: + infos_copy['Plot'] = infos_copy['PlotOutline'] + _add_supplemental_plot_info(infos_copy, item, common_data) + return infos_copy, quality_infos + + +def add_info_list_item(list_item: ListItemW, videoid, item, raw_data, is_in_mylist, common_data, art_item=None, + is_in_remind_me=False): + """Add infolabels and art to a ListItem""" + infos, quality_infos = get_info(videoid, item, raw_data, delayed_db_op=True, common_data=common_data) + list_item.addStreamInfoFromDict(quality_infos) + if is_in_mylist and common_data.get('mylist_titles_color'): + # Highlight ListItem title when the videoid is contained in "My list" + list_item.setLabel(_colorize_text(common_data['mylist_titles_color'], list_item.getLabel())) + elif is_in_remind_me: + # Highlight ListItem title when a video is marked as "Remind me" + list_item.setLabel(_colorize_text(common_data['rememberme_titles_color'], list_item.getLabel())) + infos['Title'] = list_item.getLabel() + if videoid.mediatype == common.VideoId.SHOW and not common_data['marks_tvshow_started']: + infos.pop('PlayCount', None) list_item.setInfo('video', infos) - if videoid.mediatype == common.VideoId.EPISODE or \ - videoid.mediatype == common.VideoId.MOVIE or \ - videoid.mediatype == common.VideoId.SUPPLEMENTAL: - list_item.setProperty('isFolder', 'false') - list_item.setProperty('IsPlayable', 'true') - for stream_type, quality_infos in quality_infos.iteritems(): - list_item.addStreamInfo(stream_type, quality_infos) - return infos - - -def add_art(videoid, list_item, item, raw_data=None): - """Add art infolabels to list_item""" + list_item.setArt(get_art(videoid, art_item or item or {}, common_data['profile_language_code'], + delayed_db_op=True)) + + +def _add_supplemental_plot_info(infos, item, common_data): + """Add supplemental info to plot description""" + suppl_info = [] + suppl_msg = None + suppl_dp = item.get('dpSupplementalMessage', {}) + if suppl_dp.get('$type') != 'error': + suppl_msg = suppl_dp.get('value') + if suppl_msg: + # Short information about future release of tv show episode/season or movie + suppl_info.append(suppl_msg) + else: + # If there is no supplemental message, we provide a possible release date info + avail_data = item.get('availability', {}).get('value', {}) + avail_text = avail_data.get('availabilityDate') + if avail_text: + avail_timestamp = avail_data.get('availabilityStartTime', 0) / 1000 + if avail_timestamp > time.time(): + suppl_info.append(common.get_local_string(30620).format(avail_text)) + # The 'sequiturEvidence' dict can be of type 'hook' or 'watched' + sequitur_evid = item.get('sequiturEvidence', {}).get('value') + if sequitur_evid and sequitur_evid.get('type') == 'hook': + hook_value = sequitur_evid.get('value') + if hook_value: + # Short info about the actors career/awards and similarities/connections with others films or tv shows + suppl_info.append(hook_value['text']) + suppl_text = '[CR][CR]'.join(suppl_info) + if suppl_text: + suppl_text = _colorize_text(common_data.get('supplemental_info_color', + get_color_name(G.ADDON.getSettingInt('supplemental_info_color'))), + suppl_text) + plot = infos.get('Plot', '') + if plot: + plot += '[CR][CR]' + plotoutline = infos.get('PlotOutline', '') + if plotoutline: + plotoutline += '[CR][CR]' + infos.update({'Plot': plot + suppl_text}) + infos.update({'PlotOutline': plotoutline + suppl_text}) + + +def get_art(videoid, item, profile_language_code='', delayed_db_op=False): + """Get art infolabels - NOTE: If 'item' arg is None this method can raise TypeError when there is not cache""" + cache_identifier = f'{videoid.value}_{profile_language_code}' try: - art = g.CACHE.get(cache.CACHE_ARTINFO, videoid) - except cache.CacheMiss: - art = parse_art(videoid, item, raw_data) - g.CACHE.add(cache.CACHE_ARTINFO, videoid, art, - ttl=g.CACHE_METADATA_TTL, to_disk=True) - list_item.setArt(art) + art = G.CACHE.get(CACHE_ARTINFO, cache_identifier) + except CacheMiss: + art = parse_art(videoid, item) + G.CACHE.add(CACHE_ARTINFO, cache_identifier, art, + delayed_db_op=delayed_db_op) return art -@common.time_execution(immediate=False) -def add_info_for_playback(videoid, list_item): - """Retrieve infolabels and art info and add them to the list_item""" +def get_resume_info_from_library(videoid): + """Retrieve the resume value from the Kodi library""" try: - return add_info_from_library(videoid, list_item) - except library.ItemNotFound as exc: - common.debug(exc) - return add_info_from_netflix(videoid, list_item) + return get_info_from_library(videoid)[0].get('resume', {}) + except ItemNotFound: + LOG.warn('Can not get resume value from the library') + return {} -def parse_info(videoid, item, raw_data): +def parse_info(videoid, item, raw_data, common_data): """Parse info from a path request response into Kodi infolabels""" if (videoid.mediatype == common.VideoId.UNSPECIFIED and hasattr(item, 'contained_titles')): # Special handling for VideoLists return { - 'plot': + 'Plot': common.get_local_string(30087).format( ', '.join(item.contained_titles)) if item.contained_titles else common.get_local_string(30111) }, {} - infos = {'mediatype': ('tvshow' - if videoid.mediatype == common.VideoId.SHOW or - videoid.mediatype == common.VideoId.SUPPLEMENTAL - else videoid.mediatype)} + infos = {'MediaType': MEDIA_TYPE_MAPPINGS[videoid.mediatype]} if videoid.mediatype in common.VideoId.TV_TYPES: - infos['tvshowtitle'] = raw_data['videos'][videoid.tvshowid]['title'] - if item.get('watched', False): - infos['playcount'] = 1 - - infos.update(parse_atomic_infos(item)) - infos.update(parse_referenced_infos(item, raw_data)) - infos.update(parse_tags(item)) - - return infos, get_quality_infos(item) - - -def parse_atomic_infos(item): - """Parse those infos into infolabels that are directly accesible from - the item dict""" - return {target: _get_and_transform(source, target, item) - for target, source - in paths.INFO_MAPPINGS.iteritems()} + infos['TVShowTitle'] = raw_data['videos'][videoid.tvshowid]['title'].get('value', '') + if item.get('watched', {}).get('value'): + infos['PlayCount'] = 1 + + infos.update(_parse_atomic_infos(item)) + infos.update(_parse_referenced_infos(item, raw_data)) + infos.update(_parse_tags(item)) + + if videoid.mediatype == common.VideoId.EPISODE: + # 01/12/2022: The 'delivery' info in the episode data are wrong (e.g. wrong resolution) + # as workaround we get the 'delivery' info from tvshow data + delivery_info = raw_data['videos'][videoid.tvshowid]['delivery'].get('value', '') + else: + delivery_info = item.get('delivery', {}).get('value') + return infos, get_quality_infos(delivery_info, common_data.get('video_codec_hint', get_video_codec_hint())) + + +def _parse_atomic_infos(item): + """Parse those infos into infolabels that are directly accessible from the item dict""" + infos = {} + for target, source in paths.INFO_MAPPINGS: + value = common.get_path_safe(source, item) + # The dict check is needed when the info requested is not available + # and jsonGraph return a dict of $type sentinel + if not isinstance(value, dict) and value is not None: + infos[target] = _transform_value(target, value) + return infos -def _get_and_transform(source, target, item): - """Get the value for source and transform it if neccessary""" - value = common.get_path_safe(source, item) - if isinstance(value, dict) or value is None: - return '' +def _transform_value(target, value): + """Transform a target value if necessary""" return (paths.INFO_TRANSFORMATIONS[target](value) if target in paths.INFO_TRANSFORMATIONS else value) -def parse_referenced_infos(item, raw_data): +def _parse_referenced_infos(item, raw_data): """Parse those infos into infolabels that need their references resolved within the raw data""" - return {target: [person['name'] + return {target: [person['name']['value'] for _, person - in paths.resolve_refs(item.get(source, {}), raw_data)] - for target, source in paths.REFERENCE_MAPPINGS.iteritems()} + in paths.resolve_refs(item.get(source, {}), raw_data) + if person['name']['value']] + for target, source in paths.REFERENCE_MAPPINGS.items()} -def parse_tags(item): +def _parse_tags(item): """Parse the tags""" - return {'tag': [tagdef['name'] + return {'Tag': [tagdef['name']['value'] for tagdef - in item.get('tags', {}).itervalues() - if isinstance(tagdef.get('name', {}), unicode)]} + in item.get('tags', {}).values() + if isinstance(tagdef.get('name', {}), str)]} -def get_quality_infos(item): +def get_quality_infos(delivery, video_codec_hint): """Return audio and video quality infolabels""" quality_infos = {} - delivery = item.get('delivery') if delivery: - quality_infos['video'] = QUALITIES[ - min((delivery.get('hasUltraHD', False) << 1 | - delivery.get('hasHD')), 2)] - quality_infos['audio'] = { - 'channels': 2 + 4 * delivery.get('has51Audio', False)} - - if g.ADDON.getSettingBool('enable_dolby_sound'): + if delivery.get('hasUltraHD', False): # 4k only with HEVC codec + quality_infos['video'] = {'codec': 'hevc', 'width': 3840, 'height': 2160} + elif delivery.get('hasHD'): + quality_infos['video'] = {'codec': video_codec_hint, 'width': 1920, 'height': 1080} + else: + quality_infos['video'] = {'codec': video_codec_hint, 'width': 960, 'height': 540} + quality_infos['audio'] = {'channels': 2 + 4 * delivery.get('has51Audio', False)} + if G.ADDON.getSettingBool('enable_dolby_sound'): if delivery.get('hasDolbyAtmos', False): quality_infos['audio']['codec'] = 'truehd' else: quality_infos['audio']['codec'] = 'eac3' else: quality_infos['audio']['codec'] = 'aac' + if delivery.get('hasDolbyVision', False): + quality_infos['video']['hdrtype'] = 'dolbyvision' + elif delivery.get('hasHDR', False): + quality_infos['video']['hdrtype'] = 'hdr10' return quality_infos -def parse_art(videoid, item, raw_data): # pylint: disable=unused-argument +def parse_art(videoid, item): """Parse art info from a path request response to Kodi art infolabels""" boxarts = common.get_multiple_paths( - paths.ART_PARTIAL_PATHS[0] + ['url'], item) + paths.ART_PARTIAL_PATHS[0] + ['url'], item, {}) interesting_moment = common.get_multiple_paths( - paths.ART_PARTIAL_PATHS[1] + ['url'], item, {}).get(paths.ART_SIZE_FHD) + paths.ART_PARTIAL_PATHS[1] + ['url'], item, {}) clearlogo = common.get_path_safe( - paths.ART_PARTIAL_PATHS[3] + ['url'], item) + paths.ART_PARTIAL_PATHS[2] + ['url'], item) fanart = common.get_path_safe( - paths.ART_PARTIAL_PATHS[4] + [0, 'url'], item) - return assign_art(videoid, - boxarts[paths.ART_SIZE_FHD], - boxarts[paths.ART_SIZE_SD], - boxarts[paths.ART_SIZE_POSTER], - interesting_moment, - clearlogo, - fanart) - - -def assign_art(videoid, boxart_large, boxart_small, poster, interesting_moment, - clearlogo, fanart): + paths.ART_PARTIAL_PATHS[3] + [0, 'url'], item) + fallback = common.get_path_safe(['itemSummary', 'value', 'boxArt', 'url'], item) + return _assign_art(videoid, + boxart_large=boxarts.get(paths.ART_SIZE_FHD), + boxart_small=boxarts.get(paths.ART_SIZE_SD), + poster=boxarts.get(paths.ART_SIZE_POSTER), + interesting_moment=interesting_moment.get(paths.ART_SIZE_FHD), + clearlogo=clearlogo, + fanart=fanart, + fallback=fallback) + + +def _assign_art(videoid, **kwargs): """Assign the art available from Netflix to appropriate Kodi art""" - # pylint: disable=too-many-arguments - art = {'poster': _best_art([poster]), - 'fanart': _best_art([fanart, interesting_moment, boxart_large, - boxart_small]), - 'thumb': ((interesting_moment - if videoid.mediatype == common.VideoId.EPISODE or - videoid.mediatype == common.VideoId.SUPPLEMENTAL else '') - or boxart_large or boxart_small)} + art = {'poster': _best_art([kwargs['poster'], kwargs['fallback']]), + 'fanart': _best_art([kwargs['fanart'], + kwargs['interesting_moment'], + kwargs['boxart_large'], + kwargs['boxart_small']]), + 'thumb': ((kwargs['interesting_moment'] + if videoid.mediatype in (common.VideoId.EPISODE, common.VideoId.SUPPLEMENTAL) else '') + or kwargs['boxart_large'] or kwargs['boxart_small'])} art['landscape'] = art['thumb'] if videoid.mediatype != common.VideoId.UNSPECIFIED: - art['clearlogo'] = _best_art([clearlogo]) + art['clearlogo'] = _best_art([kwargs['clearlogo']]) return art def _best_art(arts): - """Return the best art (determined by list order of arts) or - an empty string if none is available""" + """Return the best art (determined by list order of arts) or an empty string if none is available""" return next((art for art in arts if art), '') -def add_info_from_netflix(videoid, list_item): - """Apply infolabels with info from Netflix API""" - try: - infos = add_info(videoid, list_item, None, None) - art = add_art(videoid, list_item, None) - common.debug('Got infolabels and art from cache') - except (AttributeError, TypeError): - common.info('Infolabels or art were not in cache, retrieving from API') - import resources.lib.api.shakti as api - api_data = api.single_info(videoid) - infos = add_info(videoid, list_item, api_data['videos'][videoid.value], - api_data) - art = add_art(videoid, list_item, api_data['videos'][videoid.value]) - return infos, art - - -def add_info_from_library(videoid, list_item): - """Apply infolabels with info from Kodi library""" - details = library.get_item(videoid) - common.debug('Got fileinfo from library: {}'.format(details)) +def get_info_from_library(videoid): + """Get infolabels with info from Kodi library""" + details = common.get_library_item_by_videoid(videoid) + LOG.debug('Got file info from library: {}', details) art = details.pop('art', {}) - # Resuming for strm files in library is currently broken in all kodi versions - # keeping this for reference / in hopes this will get fixed - resume = details.pop('resume', {}) - # if resume: - # start_percent = resume['position'] / resume['total'] * 100.0 - # list_item.setProperty('startPercent', str(start_percent)) infos = { - 'DBID': details.pop('{}id'.format(videoid.mediatype)), - 'mediatype': videoid.mediatype + 'DBID': details.pop(f'{videoid.mediatype}id'), + 'MediaType': MEDIA_TYPE_MAPPINGS[videoid.mediatype] } - # WARNING!! Remove unsupported ListItem.setInfo keys from 'details' reference ListItem.cpp, using _sanitize_infos - _sanitize_infos(details) infos.update(details) - list_item.setInfo('video', infos) - list_item.setArt(art) - # Workaround for resuming strm files from library - infos['resume'] = resume return infos, art -def _sanitize_infos(details): - for source, target in JSONRPC_MAPPINGS.items(): - if source in details: - details[target] = details.pop(source) - for prop in ['file', 'label', 'runtime']: - details.pop(prop, None) +def _colorize_text(color_name, text): + if color_name: + return f'[COLOR {color_name}]{text}[/COLOR]' + return text + + +def get_color_name(color_index): + return COLORS[color_index] + + +def set_watched_status(list_item: ListItemW, video_data, common_data): + """Check and set progress status (watched and resume)""" + if not common_data['set_watched_status']: + return + video_id = str(video_data['summary']['value']['id']) + # Check from db if user has manually changed the watched status + is_watched_user_overrided = G.SHARED_DB.get_watched_status(common_data['active_profile_guid'], video_id, None, bool) + resume_time = 0 + video_runtime = video_data.get('runtime', {}).get('value', 0) + if is_watched_user_overrided is None: + # Note to shakti properties: + # 'watched': unlike the name this value is used to other purposes, so not to set a video as watched + # 'watchedToEndOffset': this value is used to determine if a video is watched but + # is available only with the metadata api and only for "episode" video type + # 'creditsOffset' : this value is used as position where to show the (play) "Next" (episode) button + # on the website, but it may not be always available with the "movie" video type + credits_offset_val = video_data.get('creditsOffset', {}).get('value', 0) + if credits_offset_val > 0: + # To better ensure that a video is marked as watched also when a user do not reach the ending credits + # we generally lower the watched threshold by 50 seconds for 50 minutes of video (3000 secs) + lower_value = video_runtime / 3000 * 50 + watched_threshold = credits_offset_val - lower_value + else: + # When missing the value should be only a video of movie type, + # then we simulate the default Kodi playcount behaviour (playcountminimumpercent) + watched_threshold = video_runtime / 100 * 90 + # To avoid asking to the server again the entire list of titles (after watched a video) + # to get the updated value, we override the value with the value saved in memory (see am_video_events.py) + try: + bookmark_position = G.CACHE.get(CACHE_BOOKMARKS, video_id) + except CacheMiss: + # NOTE shakti 'bookmarkPosition' tag when it is not set have -1 value + bookmark_position = video_data['bookmarkPosition'].get('value', 0) + playcount = 1 if 0 < watched_threshold <= bookmark_position else 0 + if playcount == 0 and bookmark_position > 0: + resume_time = bookmark_position + else: + playcount = 1 if is_watched_user_overrided else 0 + # We have to set playcount with setInfo(), because the setProperty('PlayCount', ) have a bug + # when a item is already watched and you force to set again watched, the override do not work + list_item.updateInfo({'PlayCount': playcount}) + list_item.setProperty('TotalTime', str(video_runtime)) + list_item.setProperty('ResumeTime', str(resume_time)) diff --git a/resources/lib/kodi/library.py b/resources/lib/kodi/library.py index 9b1b2d39d..2de5d819f 100644 --- a/resources/lib/kodi/library.py +++ b/resources/lib/kodi/library.py @@ -1,661 +1,364 @@ # -*- coding: utf-8 -*- -"""Kodi library integration""" -from __future__ import absolute_import, division, unicode_literals - +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Copyright (C) 2020 Stefano Gottardo + Kodi library integration + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" import os -import random -import re -import xml.etree.ElementTree as ET -from datetime import datetime, timedelta -from functools import wraps +from datetime import datetime -import xbmc import xbmcvfs -import resources.lib.api.shakti as api +import resources.lib.utils.api_requests as api import resources.lib.common as common import resources.lib.kodi.nfo as nfo import resources.lib.kodi.ui as ui -from resources.lib.database.db_utils import (VidLibProp) -from resources.lib.globals import g - -LIBRARY_HOME = 'library' -FOLDER_MOVIES = 'movies' -FOLDER_TV = 'shows' -ILLEGAL_CHARACTERS = '[<|>|"|?|$|!|:|#|*]' - - -class ItemNotFound(Exception): - """The requested item could not be found in the Kodi library""" - pass - - -def library_path(): - """Return the full path to the library""" - return (g.ADDON.getSetting('customlibraryfolder') - if g.ADDON.getSettingBool('enablelibraryfolder') - else g.DATA_PATH) - - -@common.time_execution(immediate=False) -def get_item(videoid): - """Find an item in the Kodi library by its Netflix videoid and return - Kodi DBID and mediatype""" - # pylint: disable=broad-except - try: - file_path, media_type = _get_library_entry(videoid) - return _get_item(media_type, file_path) - except (KeyError, AttributeError, IndexError, ItemNotFound): - raise ItemNotFound( - 'The video with id {} is not present in the Kodi library' - .format(videoid)) - - -@common.time_execution(immediate=False) -def _get_library_entry(videoid): - if videoid.mediatype == common.VideoId.MOVIE: - file_path = g.SHARED_DB.get_movie_filepath(videoid.value) - media_type = videoid.mediatype - elif videoid.mediatype == common.VideoId.EPISODE: - file_path = g.SHARED_DB.get_episode_filepath(videoid.tvshowid, - videoid.seasonid, - videoid.episodeid) - media_type = videoid.mediatype - elif videoid.mediatype == common.VideoId.SHOW: - file_path = g.SHARED_DB.get_random_episode_filepath_from_tvshow(videoid.value) - media_type = common.VideoId.EPISODE - elif videoid.mediatype == common.VideoId.SEASON: - file_path = g.SHARED_DB.get_random_episode_filepath_from_season(videoid.tvshowid, - videoid.seasonid) - media_type = common.VideoId.EPISODE - else: - # Items of other mediatype are never in library - raise ItemNotFound - if file_path is None: - raise ItemNotFound - return file_path, media_type - - -@common.time_execution(immediate=False) -def _get_item(mediatype, filename): - # To ensure compatibility with previously exported items, - # make the filename legal - fname = xbmc.makeLegalFilename(filename) - untranslated_path = os.path.dirname(fname).decode("utf-8") - translated_path = os.path.dirname(xbmc.translatePath(fname).decode("utf-8")) - shortname = os.path.basename(xbmc.translatePath(fname).decode("utf-8")) - # We get the data from Kodi library using filters. - # This is much faster than loading all episodes in memory - - # First build the path filter, we may have to search in both special and translated path - path_filter = {'field': 'path', 'operator': 'startswith', 'value': translated_path} \ - if fname[:10] != 'special://' \ - else {'or': [ - {'field': 'path', 'operator': 'startswith', 'value': translated_path}, - {'field': 'path', 'operator': 'startswith', 'value': untranslated_path} - ]} - - # Now build the all request and call the json-rpc function through common.get_library_items - library_item = common.get_library_items( - mediatype, - {'and': [ - path_filter, - {'field': 'filename', 'operator': 'is', 'value': shortname} - ]})[0] - if not library_item: - raise ItemNotFound - return common.get_library_item_details( - mediatype, library_item[mediatype + 'id']) - - -def list_contents(): - """Return a list of all video IDs (movies, shows) - contained in the library""" - return g.SHARED_DB.get_all_video_id_list() - - -def is_in_library(videoid): - """Return True if the video is in the local Kodi library, else False""" - if videoid.mediatype == common.VideoId.MOVIE: - return g.SHARED_DB.movie_id_exists(videoid.value) - elif videoid.mediatype == common.VideoId.SHOW: - return g.SHARED_DB.tvshow_id_exists(videoid.value) - elif videoid.mediatype == common.VideoId.SEASON: - return g.SHARED_DB.season_id_exists(videoid.tvshowid, - videoid.seasonid) - elif videoid.mediatype == common.VideoId.EPISODE: - return g.SHARED_DB.episode_id_exists(videoid.tvshowid, - videoid.seasonid, - videoid.episodeid) - else: - raise common.InvalidVideoId('videoid {} type not implemented'.format(videoid)) - - -def show_excluded_from_auto_update(videoid): - """Return true if the videoid is excluded from auto-update""" - return g.SHARED_DB.get_tvshow_property(videoid.value, VidLibProp.exclude_update, False) - - -@common.time_execution(immediate=False) -def exclude_show_from_auto_update(videoid, exclude): - """Set if a tvshow is excluded from auto-update""" - g.SHARED_DB.set_tvshow_property(videoid.value, VidLibProp.exclude_update, exclude) - - -def update_kodi_library(library_operation): - """Decorator that ensures an update of the Kodi libarary""" - - @wraps(library_operation) - def kodi_library_update_wrapper(videoid, task_handler, *args, **kwargs): - """Either trigger an update of the Kodi library or remove the - items associated with videoid, depending on the invoked task_handler""" - is_remove = task_handler == [remove_item] - if is_remove: - _remove_from_kodi_library(videoid) - library_operation(videoid, task_handler, *args, **kwargs) - if not is_remove: - # Update Kodi library through service - # This prevents a second call to cancel the update - common.debug('Notify service to update the library') - common.send_signal(common.Signals.LIBRARY_UPDATE_REQUESTED) - - return kodi_library_update_wrapper - - -def _remove_from_kodi_library(videoid): - """Remove an item from the Kodi library.""" - common.debug('Removing {} videoid from Kodi library'.format(videoid)) - try: - kodi_library_items = [get_item(videoid)] - if videoid.mediatype == common.VideoId.SHOW or videoid.mediatype == common.VideoId.SEASON: - # Retrieve the all episodes in the export folder - filters = {'and': [ - {'field': 'path', 'operator': 'startswith', - 'value': os.path.dirname(kodi_library_items[0]['file'])}, - {'field': 'filename', 'operator': 'endswith', 'value': '.strm'} - ]} - if videoid.mediatype == common.VideoId.SEASON: - # Add a season filter in case we just want to remove a season - filters['and'].append({'field': 'season', 'operator': 'is', - 'value': str(kodi_library_items[0]['season'])}) - kodi_library_items = common.get_library_items(common.VideoId.EPISODE, filters) - for item in kodi_library_items: - rpc_params = { - 'movie': ['VideoLibrary.RemoveMovie', 'movieid'], - # We should never remove an entire show - # 'show': ['VideoLibrary.RemoveTVShow', 'tvshowid'], - # Instead we delete all episodes listed in the JSON query above - 'show': ['VideoLibrary.RemoveEpisode', 'episodeid'], - 'season': ['VideoLibrary.RemoveEpisode', 'episodeid'], - 'episode': ['VideoLibrary.RemoveEpisode', 'episodeid'] - }[videoid.mediatype] - common.debug(item) - common.json_rpc(rpc_params[0], - {rpc_params[1]: item[rpc_params[1]]}) - except ItemNotFound: - common.debug('Cannot remove {} from Kodi library, item not present' - .format(videoid)) - except KeyError as exc: - ui.show_notification(common.get_local_string(30120), time=7500) - common.warn('Cannot remove {} from Kodi library, ' - 'Kodi does not support this (yet)' - .format(exc)) - - -@common.time_execution(immediate=False) -def purge(): - """Purge all items exported to Kodi library and delete internal library database""" - common.debug('Purging internal database and kodi library') - for videoid_value in g.SHARED_DB.get_movies_id_list(): - videoid = common.VideoId.from_path([common.VideoId.MOVIE, videoid_value]) - execute_library_tasks(videoid, [remove_item], - common.get_local_string(30030), - sync_mylist=False) - for videoid_value in g.SHARED_DB.get_tvshows_id_list(): - videoid = common.VideoId.from_path([common.VideoId.SHOW, videoid_value]) - execute_library_tasks(videoid, [remove_item], - common.get_local_string(30030), - sync_mylist=False) - - -@common.time_execution(immediate=False) -def compile_tasks(videoid, task_handler, nfo_settings=None): - """Compile a list of tasks for items based on the videoid""" - common.debug('Compiling library tasks for {}'.format(videoid)) - - if task_handler == export_item: - metadata = api.metadata(videoid) - if videoid.mediatype == common.VideoId.MOVIE: - return _create_export_movie_task(videoid, metadata[0], nfo_settings) - if videoid.mediatype in common.VideoId.TV_TYPES: - return _create_export_tv_tasks(videoid, metadata, nfo_settings) - raise ValueError('Cannot handle {}'.format(videoid)) - - if task_handler == export_new_item: - metadata = api.metadata(videoid, True) - return _create_new_episodes_tasks(videoid, metadata) - - if task_handler == remove_item: - if videoid.mediatype == common.VideoId.MOVIE: - return _create_remove_movie_task(videoid) - if videoid.mediatype == common.VideoId.SHOW: - return _compile_remove_tvshow_tasks(videoid) - if videoid.mediatype == common.VideoId.SEASON: - return _compile_remove_season_tasks(videoid) - if videoid.mediatype == common.VideoId.EPISODE: - return _create_remove_episode_task(videoid) - - common.debug('compile_tasks: task_handler {} did not match any task for {}'.format(task_handler, videoid)) - return None - - -def _create_export_movie_task(videoid, movie, nfo_settings): - """Create a task for a movie""" - # Reset NFO export to false if we never want movies nfo - name = '{title} ({year})'.format(title=movie['title'], year=movie['year']) - return [_create_export_item_task(name, FOLDER_MOVIES, videoid, name, name, - nfo.create_movie_nfo(movie) if - nfo_settings and nfo_settings.export_movie_enabled else None)] - - -def _create_export_tv_tasks(videoid, metadata, nfo_settings): - """Create tasks for a show, season or episode. - If videoid represents a show or season, tasks will be generated for - all contained seasons and episodes""" - if videoid.mediatype == common.VideoId.SHOW: - tasks = _compile_export_show_tasks(videoid, metadata[0], nfo_settings) - elif videoid.mediatype == common.VideoId.SEASON: - tasks = _compile_export_season_tasks(videoid, - metadata[0], - common.find(int(videoid.seasonid), - 'id', - metadata[0]['seasons']), - nfo_settings) - else: - tasks = [_create_export_episode_task(videoid, *metadata, nfo_settings=nfo_settings)] - - if nfo_settings and nfo_settings.export_full_tvshow: - # Create tvshow.nfo file - # In episode metadata, show data is at 3rd position, - # while it's at first position in show metadata. - # Best is to enumerate values to find the correct key position - key_index = -1 - for i, item in enumerate(metadata): - if item and item.get('type', None) == 'show': - key_index = i - if key_index > -1: - tasks.append(_create_export_item_task('tvshow.nfo', FOLDER_TV, videoid, - metadata[key_index]['title'], - 'tvshow', - nfo.create_show_nfo(metadata[key_index]), - False)) - return tasks - - -def _compile_export_show_tasks(videoid, show, nfo_settings): - """Compile a list of task items for all episodes of all seasons - of a tvshow""" - # This nested comprehension is nasty but necessary. It flattens - # the task lists for each season into one list - return [task for season in show['seasons'] - for task in _compile_export_season_tasks( - videoid.derive_season(season['id']), show, season, nfo_settings)] - - -def _compile_export_season_tasks(videoid, show, season, nfo_settings): - """Compile a list of task items for all episodes in a season""" - return [_create_export_episode_task(videoid.derive_episode(episode['id']), - episode, season, show, nfo_settings) - for episode in season['episodes']] - - -def _create_export_episode_task(videoid, episode, season, show, nfo_settings): - """Export a single episode to the library""" - filename = 'S{:02d}E{:02d}'.format(season['seq'], episode['seq']) - title = ' - '.join((show['title'], filename, episode['title'])) - return _create_export_item_task( - title, FOLDER_TV, videoid, show['title'], filename, - nfo.create_episode_nfo(episode, season, show) - if nfo_settings and nfo_settings.export_tvshow_enabled else None) - - -def _create_export_item_task(title, section, videoid, destination, filename, nfo_data=None, - is_strm=True): - """Create a single task item""" - return { - 'title': title, - 'section': section, - 'videoid': videoid, - 'destination': re.sub(ILLEGAL_CHARACTERS, '', destination), - 'filename': re.sub(ILLEGAL_CHARACTERS, '', filename), - 'nfo_data': nfo_data, - 'is_strm': is_strm - } - - -def _create_new_episodes_tasks(videoid, metadata): - tasks = [] - if metadata and 'seasons' in metadata[0]: - for season in metadata[0]['seasons']: - nfo_export = g.SHARED_DB.get_tvshow_property(videoid.value, - VidLibProp.nfo_export, False) - nfo_settings = nfo.NFOSettings(nfo_export) - - if g.SHARED_DB.season_id_exists(videoid.value, season['id']): - # The season exists, try to find any missing episode - for episode in season['episodes']: - if not g.SHARED_DB.episode_id_exists( - videoid.value, season['id'], episode['id']): - tasks.append(_create_export_episode_task( - videoid=videoid.derive_season( - season['id']).derive_episode(episode['id']), - episode=episode, - season=season, - show=metadata[0], - nfo_settings=nfo_settings - )) - common.debug('Auto exporting episode {}'.format(episode['id'])) - else: - # The season does not exist, build task for the season - tasks += _compile_export_season_tasks( - videoid=videoid.derive_season(season['id']), - show=metadata[0], - season=season, - nfo_settings=nfo_settings - ) - common.debug('Auto exporting season {}'.format(season['id'])) - return tasks - - -def _create_remove_movie_task(videoid): - filepath = g.SHARED_DB.get_movie_filepath(videoid.value) - title = os.path.splitext(os.path.basename(filepath))[0] - return [_create_remove_item_task(title, filepath, videoid)] - - -def _compile_remove_tvshow_tasks(videoid): - row_results = g.SHARED_DB.get_all_episodes_ids_and_filepath_from_tvshow(videoid.value) - return _create_remove_tv_tasks(row_results) - - -def _compile_remove_season_tasks(videoid): - row_results = g.SHARED_DB.get_all_episodes_ids_and_filepath_from_season( - videoid.tvshowid, videoid.seasonid) - return _create_remove_tv_tasks(row_results) - - -def _create_remove_episode_task(videoid): - filepath = g.SHARED_DB.get_episode_filepath( - videoid.tvshowid, videoid.seasonid, videoid.episodeid) - return [_create_remove_item_task( - _episode_title_from_path(filepath), - filepath, videoid)] - - -def _create_remove_tv_tasks(row_results): - return [_create_remove_item_task(_episode_title_from_path(row['FilePath']), - row['FilePath'], - common.VideoId.from_dict( - {'mediatype': common.VideoId.SHOW, - 'tvshowid': row['TvShowID'], - 'seasonid': row['SeasonID'], - 'episodeid': row['EpisodeID']})) - for row in row_results] - - -def _create_remove_item_task(title, filepath, videoid): - """Create a single task item""" - return { - 'title': title, - 'filepath': filepath, - 'videoid': videoid - } - - -def _episode_title_from_path(filepath): - fname = os.path.splitext(os.path.basename(filepath))[0] - path = os.path.split(os.path.split(filepath)[0])[1] - return '{} - {}'.format(path, fname) - - -# We need to differentiate task_handler for task creation, but we use the same export method -def export_new_item(item_task, library_home): - export_item(item_task, library_home) - - -@common.time_execution(immediate=False) -def export_item(item_task, library_home): - """Create strm file for an item and add it to the library""" - # Paths must be legal to ensure NFS compatibility - destination_folder = xbmc.makeLegalFilename('/'.join( - [library_home, item_task['section'], item_task['destination']])).decode('utf-8') - _create_destination_folder(destination_folder) - if item_task['is_strm']: - export_filename = xbmc.makeLegalFilename('/'.join( - [destination_folder, item_task['filename'] + '.strm'])).decode('utf-8') - add_to_library(item_task['videoid'], export_filename, (item_task['nfo_data'] is not None)) - _write_strm_file(item_task, export_filename) - if item_task['nfo_data'] is not None: - nfo_filename = xbmc.makeLegalFilename('/'.join( - [destination_folder, item_task['filename'] + '.nfo'])).decode('utf-8') - _write_nfo_file(item_task['nfo_data'], nfo_filename) - common.debug('Exported {}'.format(item_task['title'])) - - -def _create_destination_folder(destination_folder): - """Create destination folder, ignore error if it already exists""" - destination_folder = xbmc.translatePath(destination_folder) - if not xbmcvfs.exists(destination_folder): - xbmcvfs.mkdirs(destination_folder) - - -def _write_strm_file(item_task, export_filename): - """Write the playable URL to a strm file""" - filehandle = xbmcvfs.File(xbmc.translatePath(export_filename), 'w') - try: - filehandle.write(common.build_url(videoid=item_task['videoid'], - mode=g.MODE_PLAY).encode('utf-8')) - finally: - filehandle.close() - - -def _write_nfo_file(nfo_data, nfo_filename): - """Write the NFO file""" - filehandle = xbmcvfs.File(xbmc.translatePath(nfo_filename), 'w') - try: - filehandle.write(''.encode('utf-8')) - filehandle.write(ET.tostring(nfo_data, encoding='utf-8', method='xml')) - finally: - filehandle.close() - - -def add_to_library(videoid, export_filename, nfo_export, exclude_update=False): - """Add an exported file to the library""" - if videoid.mediatype == common.VideoId.EPISODE: - g.SHARED_DB.set_tvshow(videoid.tvshowid, nfo_export, exclude_update) - g.SHARED_DB.insert_season(videoid.tvshowid, videoid.seasonid) - g.SHARED_DB.insert_episode(videoid.tvshowid, videoid.seasonid, videoid.value, export_filename) - elif videoid.mediatype == common.VideoId.MOVIE: - g.SHARED_DB.set_movie(videoid.value, export_filename, nfo_export) - - -@common.time_execution(immediate=False) -def remove_item(item_task, library_home=None): - """Remove an item from the library and delete if from disk""" - # pylint: disable=unused-argument, broad-except - - common.debug('Removing {} from library'.format(item_task['title'])) - - exported_filename = xbmc.translatePath(item_task['filepath']) - videoid = item_task['videoid'] - common.debug('VideoId: {}'.format(videoid)) - try: - parent_folder = xbmc.translatePath(os.path.dirname(exported_filename)) - xbmcvfs.delete(exported_filename.decode("utf-8")) - # Remove the NFO files if exists - nfo_file = os.path.splitext(exported_filename.decode("utf-8"))[0] + '.nfo' - if xbmcvfs.exists(nfo_file): - xbmcvfs.delete(nfo_file) - dirs, files = xbmcvfs.listdir(parent_folder.decode("utf-8")) - tvshow_nfo_file = xbmc.makeLegalFilename( - '/'.join([parent_folder.decode("utf-8"), 'tvshow.nfo'])).decode("utf-8") - # Remove tvshow_nfo_file only when is the last file - # (users have the option of removing even single seasons) - if xbmcvfs.exists(tvshow_nfo_file) and not dirs and len(files) == 1: - xbmcvfs.delete(tvshow_nfo_file) - # Delete parent folder - xbmcvfs.rmdir(parent_folder.decode("utf-8")) - # Delete parent folder when empty - if not dirs and not files: - xbmcvfs.rmdir(parent_folder.decode("utf-8")) - - _remove_videoid_from_db(videoid) - except ItemNotFound: - common.debug('The video with id {} not exists in the database'.format(videoid)) - except Exception: - common.debug('Cannot delete {}, file does not exist'.format(exported_filename)) - - -def _remove_videoid_from_db(videoid): - """Removes records from database in relation to a videoid""" - if videoid.mediatype == common.VideoId.MOVIE: - g.SHARED_DB.delete_movie(videoid.value) - elif videoid.mediatype == common.VideoId.EPISODE: - g.SHARED_DB.delete_episode(videoid.tvshowid, videoid.seasonid, videoid.episodeid) - - -def _export_all_new_episodes_running(): - update = g.SHARED_DB.get_value('library_export_new_episodes_running', False) - if update: - start_time = g.SHARED_DB.get_value('library_export_new_episode_start_time', - datetime.utcfromtimestamp(0)) - if datetime.now() >= start_time + timedelta(hours=6): - g.SHARED_DB.set_value('library_export_new_episodes_running', False) - common.warn('Canceling previous library update: duration >6 hours') - else: - common.debug('Export all new episodes is already running') - return True - return False - - -def export_all_new_episodes(): +from resources.lib.database.db_utils import VidLibProp +from resources.lib.globals import G +from resources.lib.kodi.library_tasks import LibraryTasks +from resources.lib.kodi.library_utils import (request_kodi_library_update, get_library_path, + FOLDER_NAME_MOVIES, FOLDER_NAME_SHOWS, + is_auto_update_library_running, request_kodi_library_scan_decorator, + get_library_subfolders, delay_anti_ban) +from resources.lib.utils.logging import LOG, measure_exec_time_decorator + + +# Reasons that led to the creation of a class for the library operations: +# - Time-consuming update functionality like "full sync of kodi library", "auto update", "export" (large tv show) +# from context menu or settings, can not be performed within of the service side or will cause IPC timeouts +# and could block IPC access for other actions at same time. +# - The scheduled update operation for the library must be performed within the service, with the goal of: +# - Avoid tons of IPC calls that cause the continuous display of the loading screens while using Kodi +# to do other things at same time +# - Avoid use the IPC can improve the time for completion and so improve a bit the cpu use +# - Allows you to check when Kodi will be closed and avoid the force close of the add-on +# - A class allows you to choice to retrieve the data from Netflix by using IPC or directly nfsession. +# The time needed to initialize the class at each operation (about 30ms) is a small cost compared to the advantages. + + +def get_library_cls(): """ - Update the local Kodi library with new episodes of every exported shows + Get the library class to do library operations + FUNCTION NOT TO BE USED IN ADD-ON SERVICE INSTANCE """ - if not _export_all_new_episodes_running(): - common.log('Starting to export new episodes for all tv shows') - g.SHARED_DB.set_value('library_export_new_episodes_running', True) - g.SHARED_DB.set_value('library_export_new_episode_start_time', datetime.now()) - - for videoid_value in g.SHARED_DB.get_tvshows_id_list(VidLibProp.exclude_update, False): + # This build a instance of library class by assigning access to external functions through IPC + return Library(api.get_metadata, api.get_mylist_videoids_profile_switch, None) + + +class Library(LibraryTasks): + """Kodi library integration""" + + def __init__(self, func_get_metadata, func_get_mylist_videoids_profile_switch, func_req_profiles_info): + # External functions + self.ext_func_get_metadata = func_get_metadata + self.ext_func_get_mylist_videoids_profile_switch = func_get_mylist_videoids_profile_switch + self.ext_func_req_profiles_info = func_req_profiles_info + + @request_kodi_library_scan_decorator + def export_to_library(self, videoid, show_prg_dialog=True): + """ + Export an item to the Kodi library + :param videoid: the videoid + :param show_prg_dialog: if True show progress dialog, otherwise, a background progress bar + """ + LOG.info('Start exporting {} to the library', videoid) + nfo_settings = nfo.NFOSettings() + nfo_settings.show_export_dialog(videoid.mediatype) + self.execute_library_task_gui(videoid, + self.export_item, + title=common.get_local_string(30018), + nfo_settings=nfo_settings, + show_prg_dialog=show_prg_dialog) + + @request_kodi_library_scan_decorator + def export_to_library_new_episodes(self, videoid, show_prg_dialog=True): + """ + Export new episodes for a tv show by it's videoid + :param videoid: The videoid of the tv show to process + :param show_prg_dialog: if True show progress dialog, otherwise, a background progress bar + """ + LOG.info('Start exporting new episodes for {}', videoid) + if videoid.mediatype != common.VideoId.SHOW: + LOG.warn('{} is not a tv show, the operation is cancelled', videoid) + return + nfo_settings = nfo.NFOSettings() + nfo_settings.show_export_dialog(videoid.mediatype) + self.execute_library_task_gui(videoid, + self.export_new_item, + title=common.get_local_string(30198), + nfo_settings=nfo_settings, + show_prg_dialog=show_prg_dialog) + + @request_kodi_library_scan_decorator + def update_library(self, videoid, show_prg_dialog=True): + """ + Update items in the Kodi library + :param videoid: the videoid + :param show_prg_dialog: if True show progress dialog, otherwise, a background progress bar + """ + LOG.info('Start updating {} in the library', videoid) + nfo_settings = nfo.NFOSettings() + nfo_settings.show_export_dialog(videoid.mediatype) + self.execute_library_task_gui(videoid, + self.remove_item, + title=common.get_local_string(30061), + nfo_settings=nfo_settings, + show_prg_dialog=show_prg_dialog) + self.execute_library_task_gui(videoid, + self.export_item, + title=common.get_local_string(30061), + nfo_settings=nfo_settings, + show_prg_dialog=show_prg_dialog) + + def remove_from_library(self, videoid, show_prg_dialog=True): + """ + Remove an item from the Kodi library + :param videoid: the videoid + :param show_prg_dialog: if True show progress dialog, otherwise, a background progress bar + """ + LOG.info('Start removing {} from library', videoid) + common.remove_videoid_from_kodi_library(videoid) + self.execute_library_task_gui(videoid, + self.remove_item, + title=common.get_local_string(30030), + show_prg_dialog=show_prg_dialog) + + def sync_library_with_mylist(self): + """ + Perform a full sync of Kodi library with Netflix "My List", + by deleting everything that was previously exported + """ + LOG.info('Performing sync of Kodi library with My list') + # Clear all the library + self.clear_library() + # Start the sync + self.auto_update_library(True, show_nfo_dialog=True, clear_on_cancel=True) + + @measure_exec_time_decorator(is_immediate=True) + def clear_library(self, show_prg_dialog=True): + """ + Delete all exported items to the library + :param show_prg_dialog: if True, will be show a progress dialog window + """ + LOG.info('Start deleting exported library items') + # This will clear all the add-on library data, to prevents possible inconsistencies when for some reason + # such as improper use of the add-on, unexpected error or other has broken the library database data or files + with ui.ProgressDialog(show_prg_dialog, common.get_local_string(30245), max_value=3) as progress_dlg: + progress_dlg.perform_step() + progress_dlg.set_wait_message() + G.SHARED_DB.purge_library() + for folder_name in [FOLDER_NAME_MOVIES, FOLDER_NAME_SHOWS]: + progress_dlg.perform_step() + progress_dlg.set_wait_message() + section_root_dir = common.join_folders_paths(get_library_path(), folder_name) + common.delete_folder_contents(section_root_dir, delete_subfolders=True) + # Clean the Kodi library database + common.clean_library(show_prg_dialog, get_library_path()) + + def auto_update_library(self, sync_with_mylist, show_prg_dialog=True, show_nfo_dialog=False, clear_on_cancel=False, + update_profiles=False): + """ + Perform an auto update of the exported items in to Kodi library. + - The main purpose is check if there are new seasons/episodes. + - In the case "Sync Kodi library with My list" feature is enabled, will be also synchronized with My List. + :param sync_with_mylist: if True, sync the Kodi library with Netflix My List + :param show_prg_dialog: if True, will be show a progress dialog window and the errors will be notified to user + :param show_nfo_dialog: if True, ask to user if want export NFO files (override custom NFO actions for videoid) + :param clear_on_cancel: if True, when the user cancel the operations will be cleared the entire library + :param update_profiles: if True, before perform sync_with_mylist will be updated the profiles + """ + if is_auto_update_library_running(show_prg_dialog): + return + LOG.info('Start auto-updating of Kodi library {}', '(with sync of My List)' if sync_with_mylist else '') + G.SHARED_DB.set_value('library_auto_update_is_running', True) + G.SHARED_DB.set_value('library_auto_update_start_time', datetime.now()) + try: + # Get the full list of the exported tvshows/movies as id (VideoId.value) + exp_tvshows_videoids_values = G.SHARED_DB.get_tvshows_id_list() + exp_movies_videoids_values = G.SHARED_DB.get_movies_id_list() + + # Get the exported tv shows (to be updated) as dict (key=videoid, value=type of task) + videoids_tasks = { + common.VideoId.from_path([common.VideoId.SHOW, videoid_value]): self.export_new_item + for videoid_value in G.SHARED_DB.get_tvshows_id_list(VidLibProp['exclude_update'], False) + } + if sync_with_mylist and update_profiles: + # Before do the sync with My list try to update the profiles in the database, + # to do a sanity check of the features that are linked to the profiles + self.ext_func_req_profiles_info(update_database=True) # pylint: disable=not-callable + sync_with_mylist = G.ADDON.getSettingBool('lib_sync_mylist') + # If enabled sync the Kodi library with Netflix My List + if sync_with_mylist: + self._sync_my_list_ops(videoids_tasks, exp_tvshows_videoids_values, exp_movies_videoids_values) + + # Show a warning message when there are more than 100 titles to be updated, making too many metadata + # requests may cause blocking of http communication from the server or temporary ban of the account + if show_prg_dialog: + total_titles_upd = sum(task != self.remove_item for task in videoids_tasks.values()) + if total_titles_upd >= 100 and not ui.ask_for_confirmation( + common.get_local_string(30122), + common.get_local_string(30059).format(total_titles_upd)): + return + # Start the update operations + ret = self._update_library(videoids_tasks, exp_tvshows_videoids_values, show_prg_dialog, show_nfo_dialog, + clear_on_cancel) + if not ret: + return + request_kodi_library_update(scan=True, clean=True) + # Save date for completed operation to compute next update schedule (used in library_updater.py) + G.SHARED_DB.set_value('library_auto_update_last_start', datetime.now()) + LOG.info('Auto update of the Kodi library completed') + if not G.ADDON.getSettingBool('lib_auto_upd_disable_notification'): + ui.show_notification(common.get_local_string(30220), time=5000) + except Exception as exc: # pylint: disable=broad-except + import traceback + LOG.error('An error has occurred in the library auto update: {}', exc) + LOG.error(traceback.format_exc()) + finally: + G.SHARED_DB.set_value('library_auto_update_is_running', False) + + def _sync_my_list_ops(self, videoids_tasks, exp_tvshows_videoids_values, exp_movies_videoids_values): + # Get videoids from the My list (of the chosen profile) + # pylint: disable=not-callable + mylist_video_id_list, mylist_video_id_list_type = self.ext_func_get_mylist_videoids_profile_switch() + + # Check if tv shows have been removed from the My List + for videoid_value in exp_tvshows_videoids_values: + if str(videoid_value) in mylist_video_id_list: + continue + # The tv show no more exist in My List so remove it from library videoid = common.VideoId.from_path([common.VideoId.SHOW, videoid_value]) - export_new_episodes(videoid, True) - # add some randomness between show analysis to limit servers load and ban risks - xbmc.sleep(random.randint(1000, 5001)) - - g.SHARED_DB.set_value('library_export_new_episodes_running', False) - common.debug('Notify service to update the library') - common.send_signal(common.Signals.LIBRARY_UPDATE_REQUESTED) - - -def export_new_episodes(videoid, silent=False): - """ - Export new episodes for a tv show by it's video id - :param videoid: The videoid of the tv show to process - :param scan: Whether or not to scan the library after exporting, useful for a single show - :param silent: don't display user interface while exporting - :return: None - """ - - method = execute_library_tasks_silently if silent else execute_library_tasks - - if videoid.mediatype == common.VideoId.SHOW: - common.debug('Exporting new episodes for {}'.format(videoid)) - method(videoid, [export_new_item], - title=common.get_local_string(30198), - sync_mylist=False) - else: - common.debug('{} is not a tv show, no new episodes will be exported'.format(videoid)) - - -@update_kodi_library -def execute_library_tasks(videoid, task_handlers, title, sync_mylist=True, nfo_settings=None): - """Execute library tasks for videoid and show errors in foreground""" - for task_handler in task_handlers: - common.execute_tasks(title=title, - tasks=compile_tasks(videoid, task_handler, nfo_settings), - task_handler=task_handler, - notify_errors=True, - library_home=library_path()) - - # Exclude update operations - if task_handlers != [remove_item, export_item]: - _sync_mylist(videoid, task_handler, sync_mylist) - - -@update_kodi_library -def execute_library_tasks_silently(videoid, task_handlers, title=None, # pylint: disable=unused-argument - sync_mylist=False, nfo_settings=None): - """Execute library tasks for videoid and don't show any GUI feedback""" - # pylint: disable=broad-except - for task_handler in task_handlers: - for task in compile_tasks(videoid, task_handler, nfo_settings): - try: - task_handler(task, library_path()) - except Exception: - import traceback - common.error(traceback.format_exc()) - common.error('{} of {} failed' - .format(task_handler.__name__, task['title'])) - if sync_mylist and (task_handlers != [remove_item, export_item]): - _sync_mylist(videoid, task_handler, sync_mylist) - - -def _sync_mylist(videoid, task_handler, enabled): - """Add or remove exported items to My List, if enabled in settings""" - operation = { - 'export_item': 'add', - 'remove_item': 'remove'}.get(task_handler.__name__) - if enabled and operation and g.ADDON.getSettingBool('mylist_library_sync'): - common.debug('Syncing my list due to change of Kodi library') - api.update_my_list(videoid, operation) - - -def get_previously_exported_items(): - """Return a list of movie or tvshow VideoIds for items that were exported in - the old storage format""" - result = [] - videoid_pattern = re.compile('video_id=(\\d+)') - for folder in _lib_folders(FOLDER_MOVIES) + _lib_folders(FOLDER_TV): - for filename in xbmcvfs.listdir(folder)[1]: - filepath = xbmc.makeLegalFilename('/'.join([folder, filename])).decode('utf-8') - if filepath.endswith('.strm'): - common.debug('Trying to migrate {}'.format(filepath)) + videoids_tasks.update({videoid: self.remove_item}) + + # Check if movies have been removed from the My List + for videoid_value in exp_movies_videoids_values: + if str(videoid_value) in mylist_video_id_list: + continue + # The movie no more exist in My List so remove it from library + videoid = common.VideoId.from_path([common.VideoId.MOVIE, videoid_value]) + videoids_tasks.update({videoid: self.remove_item}) + + # Add to library the missing tv shows / movies of My List + for index, videoid_value in enumerate(mylist_video_id_list): + if (int(videoid_value) not in exp_tvshows_videoids_values and + int(videoid_value) not in exp_movies_videoids_values): + is_movie = mylist_video_id_list_type[index] == 'movie' + videoid = common.VideoId(**{('movieid' if is_movie else 'tvshowid'): videoid_value}) + videoids_tasks.update({videoid: self.export_item if is_movie else self.export_new_item}) + + def _update_library(self, videoids_tasks, exp_tvshows_videoids_values, show_prg_dialog, show_nfo_dialog, + clear_on_cancel): + # If set ask to user if want to export NFO files (override user custom NFO settings for videoids) + nfo_settings_override = None + if show_nfo_dialog: + nfo_settings_override = nfo.NFOSettings() + nfo_settings_override.show_export_dialog() + # Get the exported tvshows, but to be excluded from the updates + excluded_videoids_values = G.SHARED_DB.get_tvshows_id_list(VidLibProp['exclude_update'], True) + # Start the update operations + with ui.ProgressDialog(show_prg_dialog, max_value=len(videoids_tasks)) as progress_bar: + for videoid, task_handler in videoids_tasks.items(): + # Check if current videoid is excluded from updates + if int(videoid.value) in excluded_videoids_values: + continue + # Get the NFO settings for the current videoid + if not nfo_settings_override and int(videoid.value) in exp_tvshows_videoids_values: + # User custom NFO setting + # it is possible that the user has chosen not to export NFO files for a specific tv show + nfo_export = G.SHARED_DB.get_tvshow_property(videoid.value, + VidLibProp['nfo_export'], False) + nfo_settings = nfo.NFOSettings(nfo_export) + else: + nfo_settings = nfo_settings_override or nfo.NFOSettings() + # Execute the task + for index, total_tasks, title in self.execute_library_task(videoid, + task_handler, + nfo_settings=nfo_settings, + notify_errors=show_prg_dialog): + label_partial_op = f' ({index + 1}/{total_tasks})' if total_tasks > 1 else '' + progress_bar.set_message(title + label_partial_op) + if progress_bar.is_cancelled(): + LOG.warn('Auto update of the Kodi library interrupted by User') + if clear_on_cancel: + self.clear_library(True) + return False + if self.monitor.abortRequested(): + LOG.warn('Auto update of the Kodi library interrupted by Kodi') + return False + progress_bar.perform_step() + progress_bar.set_wait_message() + delay_anti_ban() + return True + + def import_library(self, path): + """ + Imports an already existing exported STRM library into the add-on library database, + allows you to restore an existing library, by avoiding to recreate it from scratch. + This operations also update the missing tv shows seasons and episodes, and automatically + converts old STRM format type from add-on version 0.13.x or before 1.7.0 to new format. + """ + # If set ask to user if want to export NFO files + nfo_settings = nfo.NFOSettings() + nfo_settings.show_export_dialog() + LOG.info('Start importing Kodi library') + remove_folders = [] # List of failed imports paths to be optionally removed + remove_titles = [] # List of failed imports titles to be optionally removed + # Start importing STRM files + folders = get_library_subfolders(FOLDER_NAME_MOVIES, path) + get_library_subfolders(FOLDER_NAME_SHOWS, path) + with ui.ProgressDialog(True, max_value=len(folders)) as progress_bar: + for folder_path in folders: + folder_name = os.path.basename(xbmcvfs.translatePath(folder_path)) + progress_bar.set_message(folder_name) try: - # Only get a VideoId from the first file in each folder. - # For shows, all episodes will result in the same VideoId - # and movies only contain one file - result.append( - _get_root_videoid(filepath, videoid_pattern)) - except (AttributeError, IndexError): - common.debug('Item does not conform to old format') - break - return result - - -def _lib_folders(section): - section_dir = xbmc.translatePath( - xbmc.makeLegalFilename('/'.join([library_path(), section]))) - return [xbmc.makeLegalFilename('/'.join([section_dir, folder.decode('utf-8')])) - for folder - in xbmcvfs.listdir(section_dir)[0]] - - -def _get_root_videoid(filename, pattern): - match = re.search(pattern, - xbmcvfs.File(filename, 'r').read().decode('utf-8').split('\n')[-1]) - metadata = api.metadata( - common.VideoId(videoid=match.groups()[0]))[0] - if metadata['type'] == 'show': - return common.VideoId(tvshowid=metadata['id']) - return common.VideoId(movieid=metadata['id']) + videoid = self.import_videoid_from_existing_strm(folder_path, folder_name) + if videoid is None: + # Failed to import, add folder to remove list + remove_folders.append(folder_path) + remove_titles.append(folder_name) + continue + # Successfully imported, Execute the task + for index, total_tasks, title in self.execute_library_task(videoid, + self.export_item, + nfo_settings=nfo_settings, + notify_errors=True): + label_partial_op = f' ({index + 1}/{total_tasks})' if total_tasks > 1 else '' + progress_bar.set_message(title + label_partial_op) + if progress_bar.is_cancelled(): + LOG.warn('Import library interrupted by User') + return + if self.monitor.abortRequested(): + LOG.warn('Import library interrupted by Kodi') + return + except ImportWarning: + # Ignore it, something was wrong in STRM file (see _import_videoid in library_jobs.py) + pass + progress_bar.perform_step() + progress_bar.set_wait_message() + delay_anti_ban() + ret = self._import_library_remove(remove_titles, remove_folders) + request_kodi_library_update(scan=True, clean=ret) + + def _import_library_remove(self, remove_titles, remove_folders): + if not remove_folders: + return False + # If there are STRM files that it was not possible to import them, + # we will ask to user if you want to delete them + tot_folders = len(remove_folders) + if tot_folders > 50: + remove_titles = remove_titles[:50] + ['...'] + message = common.get_local_string(30246).format(tot_folders) + '[CR][CR]' + ', '.join(remove_titles) + if not ui.ask_for_confirmation(common.get_local_string(30140), message): + return False + # Delete all folders + LOG.info('Start deleting folders') + with ui.ProgressDialog(True, max_value=tot_folders) as progress_bar: + for file_path in remove_folders: + progress_bar.set_message(f'{progress_bar.value}/{tot_folders}') + LOG.debug('Deleting folder: {}', file_path) + common.delete_folder(file_path) + progress_bar.perform_step() + return True diff --git a/resources/lib/kodi/library_jobs.py b/resources/lib/kodi/library_jobs.py new file mode 100644 index 000000000..30394f5b3 --- /dev/null +++ b/resources/lib/kodi/library_jobs.py @@ -0,0 +1,171 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Kodi library integration: jobs for a task + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +import os +import re + +import xbmc +import xbmcvfs + +import resources.lib.common as common +import resources.lib.kodi.ui as ui +from resources.lib.common.exceptions import MetadataNotAvailable, ItemNotFound +from resources.lib.globals import G +from resources.lib.kodi.library_utils import remove_videoid_from_db, insert_videoid_to_db +from resources.lib.utils.logging import LOG + + +class LibraryJobs: + """Type of jobs for a task in order to execute library operations""" + + is_abort_requested = False + """Will be True when Kodi will be closed""" + + # External functions + ext_func_get_metadata = None + ext_func_get_mylist_videoids_profile_switch = None + ext_func_req_profiles_info = None + + monitor = xbmc.Monitor() + + def export_item(self, job_data, library_home): + """Create strm file for an item and add it to the library""" + # Paths must be legal to ensure NFS compatibility + destination_folder = common.join_folders_paths(library_home, + job_data['root_folder_name'], + job_data['folder_name']) + common.create_folder(destination_folder) + if job_data['create_strm_file']: + strm_file_path = common.join_folders_paths(destination_folder, job_data['filename'] + '.strm') + insert_videoid_to_db(job_data['videoid'], strm_file_path, job_data['nfo_data'] is not None) + common.write_strm_file(job_data['videoid'], strm_file_path) + if job_data['create_nfo_file']: + nfo_file_path = common.join_folders_paths(destination_folder, job_data['filename'] + '.nfo') + common.write_nfo_file(job_data['nfo_data'], nfo_file_path) + LOG.debug('Exported {}: {}', job_data['videoid'], job_data['title']) + + def export_new_item(self, job_data, library_home): + """Used to export new episodes, but it is same operation of task_export_item""" + # We need to differentiate this task handler from task_export_item + # in order to manage the compilation of data separately + self.export_item(job_data, library_home) + + def remove_item(self, job_data, library_home=None): # pylint: disable=unused-argument + """Remove an item from the Kodi library, delete it from disk, remove add-on database references""" + videoid = job_data['videoid'] + LOG.debug('Removing {} ({}) from add-on library', videoid, job_data['title']) + try: + # Remove the STRM file exported + exported_file_path = xbmcvfs.translatePath(job_data['file_path']) + common.delete_file_safe(exported_file_path) + + parent_folder = xbmcvfs.translatePath(os.path.dirname(exported_file_path)) + + # Remove the NFO file of the related STRM file + nfo_file = os.path.splitext(exported_file_path)[0] + '.nfo' + common.delete_file_safe(nfo_file) + + dirs, files = common.list_dir(parent_folder) + + # Remove the tvshow NFO file (only when it is the last file in the folder) + tvshow_nfo_file = common.join_folders_paths(parent_folder, 'tvshow.nfo') + + # (users have the option of removing even single seasons) + if xbmcvfs.exists(tvshow_nfo_file) and not dirs and len(files) == 1: + xbmcvfs.delete(tvshow_nfo_file) + # Delete parent folder + xbmcvfs.rmdir(parent_folder) + + # Delete parent folder when empty + if not dirs and not files: + xbmcvfs.rmdir(parent_folder) + + # Remove videoid records from add-on database + remove_videoid_from_db(videoid) + except ItemNotFound: + LOG.warn('The videoid {} not exists in the add-on library database', videoid) + except Exception as exc: # pylint: disable=broad-except + import traceback + LOG.error(traceback.format_exc()) + ui.show_addon_error_info(exc) + + # -------------------------- The follow functions not concern jobs for tasks + + def import_videoid_from_existing_strm(self, folder_path, folder_name): + """ + Get a VideoId from an existing STRM file that was exported + """ + for filename in common.list_dir(folder_path)[1]: + if not filename.endswith('.strm'): + continue + file_path = common.join_folders_paths(folder_path, filename) + # Only get a VideoId from the first file in each folder. + # For tv shows all episodes will result in the same VideoId, the movies only contain one file. + file_content = common.load_file(file_path) + if not file_content: + LOG.warn('Import error: folder "{}" skipped, STRM file empty or corrupted', folder_name) + return None + if 'action=play_video' in file_content: + LOG.debug('Trying to import (v0.13.x): {}', file_path) + return self._import_videoid_old(file_content, folder_name) + LOG.debug('Trying to import: {}', file_path) + return self._import_videoid(file_content, folder_name) + + def _import_videoid_old(self, file_content, folder_name): + try: + # The STRM file in add-on v13.x is different and can contains two lines, example: + # #EXTINF:-1,Tv show title - "meta data ..." + # plugin://plugin.video.netflix/?video_id=12345678&action=play_video + # Get last line and extract the videoid value + match = re.search(r'video_id=(\d+)', file_content.split('\n')[-1]) + # Create a videoid of UNSPECIFIED type (we do not know the real type of videoid) + videoid = common.VideoId(videoid=match.groups()[0]) + # Try to get the videoid metadata: + # - To know if the videoid still exists on netflix + # - To get the videoid type + # - To get the Tv show videoid, in the case of STRM of an episode + metadata = self.ext_func_get_metadata(videoid)[0] # pylint: disable=not-callable + # Generate the a good videoid + if metadata['type'] == 'show': + return common.VideoId(tvshowid=metadata['id']) + return common.VideoId(movieid=metadata['id']) + except MetadataNotAvailable: + LOG.warn('Import error: folder {} skipped, metadata not available', folder_name) + return None + except (AttributeError, IndexError): + LOG.warn('Import error: folder {} skipped, STRM not conform to v0.13.x format', folder_name) + return None + + def _import_videoid(self, file_content, folder_name): + file_content = file_content.strip('\t\n\r') + if G.BASE_URL not in file_content: + LOG.warn('Import error: folder "{}" skipped, unrecognized plugin name in STRM file', folder_name) + raise ImportWarning + file_content = file_content.replace(G.BASE_URL, '') + # file_content should result as, example: + # - Old STRM path: '/play/show/xxxxxxxx/season/xxxxxxxx/episode/xxxxxxxx/' (used before ver 1.7.0) + # - New STRM path: '/play_strm/show/xxxxxxxx/season/xxxxxxxx/episode/xxxxxxxx/' (used from ver 1.7.0) + pathitems = file_content.strip('/').split('/') + if G.MODE_PLAY not in pathitems and G.MODE_PLAY_STRM not in pathitems: + LOG.warn('Import error: folder "{}" skipped, unsupported play path in STRM file', folder_name) + raise ImportWarning + pathitems = pathitems[1:] + try: + if pathitems[0] == common.VideoId.SHOW: + # Get always VideoId of tvshow type (not season or episode) + videoid = common.VideoId.from_path(pathitems[:2]) + else: + videoid = common.VideoId.from_path(pathitems) + # Try to get the videoid metadata, to know if the videoid still exists on netflix + self.ext_func_get_metadata(videoid) # pylint: disable=not-callable + return videoid + except MetadataNotAvailable: + LOG.warn('Import error: folder {} skipped, metadata not available for videoid {}', + folder_name, pathitems[1]) + return None diff --git a/resources/lib/kodi/library_tasks.py b/resources/lib/kodi/library_tasks.py new file mode 100644 index 000000000..ec1f24229 --- /dev/null +++ b/resources/lib/kodi/library_tasks.py @@ -0,0 +1,297 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Copyright (C) 2019 Stefano Gottardo - @CastagnaIT + Kodi library integration: task management + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +import os +import re + +import resources.lib.common as common +import resources.lib.kodi.nfo as nfo +from resources.lib.common.exceptions import MetadataNotAvailable +from resources.lib.database.db_utils import VidLibProp +from resources.lib.globals import G +from resources.lib.kodi import ui +from resources.lib.kodi.library_jobs import LibraryJobs +from resources.lib.kodi.library_utils import (get_episode_title_from_path, get_library_path, + ILLEGAL_CHARACTERS, FOLDER_NAME_MOVIES, FOLDER_NAME_SHOWS) +from resources.lib.kodi.ui import show_library_task_errors +from resources.lib.utils.logging import LOG, measure_exec_time_decorator + + +class LibraryTasks(LibraryJobs): + """Compile the jobs for a videoid task and execute them""" + + def execute_library_task(self, videoid, task_type, nfo_settings=None, notify_errors=False): + """ + Execute a library task for a videoid + :param videoid: the videoid + :param task_type: the type of task for the jobs (same used to execute the jobs) + :param nfo_settings: the NFOSettings object containing the user's NFO settings + :param notify_errors: if True a dialog box will be displayed at each error + """ + list_errors = [] + # Preparation of jobs data for the task + jobs_data = self.compile_jobs_data(videoid, task_type, nfo_settings) + if not jobs_data: + return + total_jobs = len(jobs_data) + # Execute the jobs for the task + for index, job_data in enumerate(jobs_data): + self._execute_job(task_type, job_data, list_errors) + yield index, total_jobs, job_data['title'] + show_library_task_errors(notify_errors, list_errors) + + def execute_library_task_gui(self, videoid, task_type, title, nfo_settings=None, show_prg_dialog=True): + """ + Execute a library task for a videoid, by showing a GUI progress bar/dialog + :param videoid: the videoid + :param task_type: the type of task for the jobs (same used to execute the jobs) + :param title: title for the progress dialog/background progress bar + :param nfo_settings: the NFOSettings object containing the user's NFO settings + :param show_prg_dialog: if True show progress dialog, otherwise, a background progress bar + """ + list_errors = [] + # Preparation of jobs data for the task + jobs_data = self.compile_jobs_data(videoid, task_type, nfo_settings) + # Set a progress bar + progress_class = ui.ProgressDialog if show_prg_dialog else ui.ProgressBarBG + with progress_class(show_prg_dialog, title, max_value=len(jobs_data)) as progress_bar: + # Execute the jobs for the task + for job_data in jobs_data: + self._execute_job(task_type, job_data, list_errors) + progress_bar.perform_step() + progress_bar.set_message(f'{job_data["title"]} ({progress_bar.value}/{progress_bar.max_value})') + if progress_bar.is_cancelled(): + LOG.warn('Library operations interrupted by User') + break + if self.monitor.abortRequested(): + LOG.warn('Library operations interrupted by Kodi') + break + show_library_task_errors(show_prg_dialog, list_errors) + + def _execute_job(self, job_handler, job_data, list_errors): + if not job_data: # No metadata or unexpected job case + return + try: + job_handler(job_data, get_library_path()) + except Exception as exc: # pylint: disable=broad-except + import traceback + LOG.error(traceback.format_exc()) + LOG.error('{} of {} ({}) failed', job_handler.__name__, job_data['videoid'], job_data['title']) + list_errors.append({'title': job_data['title'], + 'error': f'{type(exc).__name__}: {exc}'}) + + @measure_exec_time_decorator(is_immediate=True) + def compile_jobs_data(self, videoid, task_type, nfo_settings=None): + """Compile a list of jobs data based on the videoid""" + LOG.debug('Compiling list of jobs data for task handler "{}" and videoid "{}"', + task_type.__name__, videoid) + jobs_data = None + try: + if task_type == self.export_item: + metadata = self.ext_func_get_metadata(videoid) # pylint: disable=not-callable + if videoid.mediatype == common.VideoId.MOVIE: + jobs_data = [self._create_export_movie_job(videoid, metadata[0], nfo_settings)] + if videoid.mediatype in common.VideoId.TV_TYPES: + jobs_data = self._create_export_tvshow_jobs(videoid, metadata, nfo_settings) + + if task_type == self.export_new_item: + metadata = self.ext_func_get_metadata(videoid, True) # pylint: disable=not-callable + jobs_data = self._create_export_new_episodes_jobs(videoid, metadata, nfo_settings) + + if task_type == self.remove_item: + if videoid.mediatype == common.VideoId.MOVIE: + jobs_data = [self._create_remove_movie_job(videoid)] + if videoid.mediatype == common.VideoId.SHOW: + jobs_data = self._create_remove_tvshow_jobs(videoid) + if videoid.mediatype == common.VideoId.SEASON: + jobs_data = self._create_remove_season_jobs(videoid) + if videoid.mediatype == common.VideoId.EPISODE: + jobs_data = [self._create_remove_episode_job(videoid)] + except MetadataNotAvailable: + LOG.warn('Unavailable metadata for videoid "{}", list of jobs not compiled', videoid) + return None + if jobs_data is None: + LOG.error('Unexpected job compile case for task type "{}" and videoid "{}", list of jobs not compiled', + task_type.__name__, videoid) + return jobs_data + + def _create_export_movie_job(self, videoid, movie, nfo_settings): + """Create job data to export a movie""" + # Reset NFO export to false if we never want movies nfo + filename = f'{movie["title"]} ({movie["year"]})' + create_nfo_file = nfo_settings and nfo_settings.export_movie_enabled + nfo_data = nfo.create_movie_nfo(movie) if create_nfo_file else None + return self._build_export_job_data(True, create_nfo_file, + videoid=videoid, title=movie['title'], + root_folder_name=FOLDER_NAME_MOVIES, + folder_name=filename, + filename=filename, + nfo_data=nfo_data) + + def _create_export_tvshow_jobs(self, videoid, metadata, nfo_settings): + """ + Create jobs data to export a: tv show, season or episode. + The data for the jobs will be generated by extrapolating every single episode. + """ + if videoid.mediatype == common.VideoId.SHOW: + tasks = self._get_export_tvshow_jobs(videoid, metadata[0], nfo_settings) + elif videoid.mediatype == common.VideoId.SEASON: + tasks = self._get_export_season_jobs(videoid, + metadata[0], + common.find(int(videoid.seasonid), + 'id', + metadata[0]['seasons']), + nfo_settings) + else: + tasks = [self._create_export_episode_job(videoid, *metadata, nfo_settings=nfo_settings)] + + if nfo_settings and nfo_settings.export_full_tvshow: + job = self._create_export_tvshow_nfo_job(videoid, metadata) + if job: + tasks.append(job) + return tasks + + def _create_export_tvshow_nfo_job(self, videoid, metadata): + # Create tvshow.nfo file + # 'get_metadata' return metadata as tuple that can have more items according to type requested, e.g. + # for episode metadata: (episode_meta, season_meta, tvshow_meta) + # for tvshow/season metadata: (tvshow_meta,) + # We need to get the tvshow metadata for each case, then we try to find it on each tuple item + key_index = -1 + for i, item in enumerate(metadata): + if item and item.get('type', None) == 'show': + key_index = i + if key_index == -1: + return None + return self._build_export_job_data(False, True, + videoid=videoid, title='tvshow.nfo', + root_folder_name=FOLDER_NAME_SHOWS, + folder_name=metadata[key_index]['title'], + filename='tvshow', + nfo_data=nfo.create_show_nfo(metadata[key_index])) + + def _get_export_tvshow_jobs(self, videoid, show, nfo_settings): + """Get jobs data to export a tv show (join all jobs data of the seasons)""" + tasks = [] + for season in show['seasons']: + tasks += self._get_export_season_jobs(videoid.derive_season(season['id']), show, season, nfo_settings) + return tasks + + def _get_export_season_jobs(self, videoid, show, season, nfo_settings): + """Get jobs data to export a season (join all jobs data of the episodes)""" + return [self._create_export_episode_job(videoid.derive_episode(episode['id']), + episode, season, show, nfo_settings) + for episode in season['episodes']] + + def _create_export_episode_job(self, videoid, episode, season, show, nfo_settings): + """Create job data to export a single episode""" + filename = f'S{season["seq"]:02d}E{episode["seq"]:02d}' + title = ' - '.join((show['title'], filename)) + create_nfo_file = nfo_settings and nfo_settings.export_tvshow_enabled + nfo_data = nfo.create_episode_nfo(episode, season, show) if create_nfo_file else None + return self._build_export_job_data(True, create_nfo_file, + videoid=videoid, title=title, + root_folder_name=FOLDER_NAME_SHOWS, + folder_name=show['title'], + filename=filename, + nfo_data=nfo_data) + + def _build_export_job_data(self, create_strm_file, create_nfo_file, **kwargs): + """Build the data used to execute an "export" job""" + return { + 'create_strm_file': create_strm_file, # True/False + 'create_nfo_file': create_nfo_file, # True/False + 'videoid': kwargs['videoid'], + 'title': kwargs['title'], # Progress dialog and debug purpose + 'root_folder_name': kwargs['root_folder_name'], + 'folder_name': re.sub(ILLEGAL_CHARACTERS, '', kwargs['folder_name']), + 'filename': re.sub(ILLEGAL_CHARACTERS, '', kwargs['filename']), + 'nfo_data': kwargs['nfo_data'] + } + + def _create_export_new_episodes_jobs(self, videoid, metadata, nfo_settings=None): + """Create jobs data to export missing seasons and episodes""" + tasks = [] + if metadata and 'seasons' in metadata[0]: + for season in metadata[0]['seasons']: + if not nfo_settings: + nfo_export = G.SHARED_DB.get_tvshow_property(videoid.value, VidLibProp['nfo_export'], False) + nfo_settings = nfo.NFOSettings(nfo_export) + # Check and add missing seasons and episodes + self._add_missing_items(tasks, season, videoid, metadata, nfo_settings) + if nfo_settings and nfo_settings.export_full_tvshow: + job = self._create_export_tvshow_nfo_job(videoid, metadata) + if job: + tasks.append(job) + return tasks + + def _add_missing_items(self, tasks, season, videoid, metadata, nfo_settings): + if G.SHARED_DB.season_id_exists(videoid.value, season['id']): + # The season exists, try to find any missing episode + for episode in season['episodes']: + if not G.SHARED_DB.episode_id_exists(videoid.value, season['id'], episode['id']): + tasks.append(self._create_export_episode_job( + videoid=videoid.derive_season(season['id']).derive_episode(episode['id']), + episode=episode, + season=season, + show=metadata[0], + nfo_settings=nfo_settings + )) + LOG.debug('Exporting missing new episode {}', episode['id']) + else: + # The season does not exist, build task for the season + tasks += self._get_export_season_jobs( + videoid=videoid.derive_season(season['id']), + show=metadata[0], + season=season, + nfo_settings=nfo_settings + ) + LOG.debug('Exporting missing new season {}', season['id']) + + def _create_remove_movie_job(self, videoid): + """Create a job data to remove a movie""" + file_path = G.SHARED_DB.get_movie_filepath(videoid.value) + title = os.path.splitext(os.path.basename(file_path))[0] + return self._build_remove_job_data(title, file_path, videoid) + + def _create_remove_tvshow_jobs(self, videoid): + """Create jobs data to remove a tv show (will result jobs data of all the episodes)""" + row_results = G.SHARED_DB.get_all_episodes_ids_and_filepath_from_tvshow(videoid.value) + return self._create_remove_jobs_from_rows(row_results) + + def _create_remove_season_jobs(self, videoid): + """Create jobs data to remove a season (will result jobs data of all the episodes)""" + row_results = G.SHARED_DB.get_all_episodes_ids_and_filepath_from_season( + videoid.tvshowid, videoid.seasonid) + return self._create_remove_jobs_from_rows(row_results) + + def _create_remove_episode_job(self, videoid): + """Create a job data to remove an episode""" + file_path = G.SHARED_DB.get_episode_filepath( + videoid.tvshowid, videoid.seasonid, videoid.episodeid) + return self._build_remove_job_data(get_episode_title_from_path(file_path), + file_path, videoid) + + def _create_remove_jobs_from_rows(self, row_results): + """Create jobs data to remove episodes, from the rows results of the database""" + return [self._build_remove_job_data(get_episode_title_from_path(row['FilePath']), + row['FilePath'], + common.VideoId(tvshowid=row['TvShowID'], + seasonid=row['SeasonID'], + episodeid=row['EpisodeID'])) + for row in row_results] + + def _build_remove_job_data(self, title, file_path, videoid): + """Build the data used to execute an "remove" job""" + return { + 'title': title, # Progress dialog and debug purpose + 'file_path': file_path, + 'videoid': videoid + } diff --git a/resources/lib/kodi/library_utils.py b/resources/lib/kodi/library_utils.py new file mode 100644 index 000000000..f1b63b717 --- /dev/null +++ b/resources/lib/kodi/library_utils.py @@ -0,0 +1,171 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Kodi library integration: helper utils + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +import os +import random +from datetime import datetime, timedelta +from functools import wraps + +import xbmc + +from resources.lib import common +from resources.lib.common.exceptions import InvalidVideoId, ErrorMsg +from resources.lib.database.db_utils import VidLibProp +from resources.lib.globals import G +from resources.lib.kodi import nfo, ui +from resources.lib.utils.api_paths import PATH_REQUEST_SIZE_STD +from resources.lib.utils.logging import LOG, measure_exec_time_decorator + +LIBRARY_HOME = 'library' +FOLDER_NAME_MOVIES = 'movies' +FOLDER_NAME_SHOWS = 'shows' +ILLEGAL_CHARACTERS = '[<|>|"|?|$|!|:|#|*|/|\\\\]' + + +def get_library_path(): + """Return the full path to the library""" + return (G.ADDON.getSetting('customlibraryfolder') + if G.ADDON.getSettingBool('enablelibraryfolder') + else G.DATA_PATH) + + +def get_library_subfolders(folder_name, custom_lib_path=None): + """Returns all the subfolders contained in a folder of library path""" + section_path = common.join_folders_paths(custom_lib_path or get_library_path(), folder_name) + return [common.join_folders_paths(section_path, folder) + for folder + in common.list_dir(section_path)[0]] + + +def insert_videoid_to_db(videoid, export_filename, nfo_export, exclude_update=False): + """Add records to the database in relation to a videoid""" + if videoid.mediatype == common.VideoId.EPISODE: + G.SHARED_DB.set_tvshow(videoid.tvshowid, nfo_export, exclude_update) + G.SHARED_DB.insert_season(videoid.tvshowid, videoid.seasonid) + G.SHARED_DB.insert_episode(videoid.tvshowid, videoid.seasonid, + videoid.value, export_filename) + elif videoid.mediatype == common.VideoId.MOVIE: + G.SHARED_DB.set_movie(videoid.value, export_filename, nfo_export) + + +def remove_videoid_from_db(videoid): + """Removes records from database in relation to a videoid""" + if videoid.mediatype == common.VideoId.MOVIE: + G.SHARED_DB.delete_movie(videoid.value) + elif videoid.mediatype == common.VideoId.EPISODE: + G.SHARED_DB.delete_episode(videoid.tvshowid, videoid.seasonid, videoid.episodeid) + + +def is_videoid_in_db(videoid): + """Return True if the video is in the database, else False""" + if videoid.mediatype == common.VideoId.MOVIE: + return G.SHARED_DB.movie_id_exists(videoid.value) + if videoid.mediatype == common.VideoId.SHOW: + return G.SHARED_DB.tvshow_id_exists(videoid.value) + if videoid.mediatype == common.VideoId.SEASON: + return G.SHARED_DB.season_id_exists(videoid.tvshowid, + videoid.seasonid) + if videoid.mediatype == common.VideoId.EPISODE: + return G.SHARED_DB.episode_id_exists(videoid.tvshowid, + videoid.seasonid, + videoid.episodeid) + raise InvalidVideoId(f'videoid {videoid} type not implemented') + + +def get_episode_title_from_path(file_path): + filename = os.path.splitext(os.path.basename(file_path))[0] + path = os.path.split(os.path.split(file_path)[0])[1] + return f'{path} - {filename}' + + +def get_nfo_settings(): + """Get the NFO settings, confirmations may be requested to the user if necessary""" + return nfo.NFOSettings() + + +def is_auto_update_library_running(show_prg_dialog): + if G.SHARED_DB.get_value('library_auto_update_is_running', False): + start_time = G.SHARED_DB.get_value('library_auto_update_start_time', + datetime.utcfromtimestamp(0)) + if datetime.now() >= start_time + timedelta(hours=6): + G.SHARED_DB.set_value('library_auto_update_is_running', False) + LOG.warn('Canceling previous library update: duration >6 hours') + else: + if show_prg_dialog: + ui.show_notification(common.get_local_string(30063)) + LOG.debug('Library auto update is already running') + return True + return False + + +@measure_exec_time_decorator(is_immediate=True) +def request_kodi_library_update(**kwargs): + """Request to scan and/or clean the Kodi library database""" + # Particular way to start Kodi library scan/clean (details on request_kodi_library_update in library_updater.py) + if not kwargs: + raise ErrorMsg('request_kodi_library_update: you must specify kwargs "scan=True" and/or "clean=True"') + common.send_signal(common.Signals.REQUEST_KODI_LIBRARY_UPDATE, data=kwargs, non_blocking=True) + + +def request_kodi_library_scan_decorator(func): + """ + A decorator to request the scan of Kodi library database (at the end of the operations) + """ + @wraps(func) + def wrapper(*args, **kwargs): + ret = func(*args, **kwargs) + request_kodi_library_update(scan=True) + return ret + return wrapper + + +def is_show_excluded_from_auto_update(videoid): + """Return true if the videoid is excluded from auto-update""" + return G.SHARED_DB.get_tvshow_property(videoid.value, VidLibProp['exclude_update'], False) + + +def set_show_excluded_from_auto_update(videoid, is_excluded): + """Set if a tvshow is excluded from auto-update""" + G.SHARED_DB.set_tvshow_property(videoid.value, VidLibProp['exclude_update'], is_excluded) + + +def list_contents(perpetual_range_start): + """Return a chunked list of all video IDs (movies, shows) contained in the add-on library database""" + perpetual_range_start = int(perpetual_range_start) if perpetual_range_start else 0 + number_of_requests = 2 + video_id_list = G.SHARED_DB.get_all_video_id_list() + count = 0 + chunked_video_list = [] + perpetual_range_selector = {} + + for index, chunk in enumerate(common.chunked_list(video_id_list, PATH_REQUEST_SIZE_STD)): + if index >= perpetual_range_start: + if number_of_requests == 0: + if len(video_id_list) > count: + # Exists others elements + perpetual_range_selector['_perpetual_range_selector'] = {'next_start': perpetual_range_start + 1} + break + chunked_video_list.append(chunk) + number_of_requests -= 1 + count += len(chunk) + + if perpetual_range_start > 0: + previous_start = perpetual_range_start - 1 + if '_perpetual_range_selector' in perpetual_range_selector: + perpetual_range_selector['_perpetual_range_selector']['previous_start'] = previous_start + else: + perpetual_range_selector['_perpetual_range_selector'] = {'previous_start': previous_start} + return chunked_video_list, perpetual_range_selector + + +def delay_anti_ban(): + """Adds some random delay between operations to limit servers load and ban risks""" + # Not so reliable workaround NF has strict control over the number/type of requests in a short space of time + # More than 100~ of requests could still cause HTTP errors by blocking requests to the server + xbmc.sleep(random.randint(1000, 4001)) diff --git a/resources/lib/kodi/listings.py b/resources/lib/kodi/listings.py deleted file mode 100644 index 36ec5306e..000000000 --- a/resources/lib/kodi/listings.py +++ /dev/null @@ -1,444 +0,0 @@ -# -*- coding: utf-8 -*- -"""Helper functions to build plugin listings for Kodi""" -from __future__ import absolute_import, division, unicode_literals - -from functools import wraps - -import os -import xbmc -import xbmcgui -import xbmcplugin - -from resources.lib.database.db_utils import (TABLE_MENU_DATA) -from resources.lib.globals import g -import resources.lib.common as common - -from .infolabels import add_info, add_art -from .context_menu import generate_context_menu_items - - -def custom_viewmode(partial_setting_id): - """Decorator that sets a custom viewmode if currently in - a listing of the plugin""" - # pylint: disable=missing-docstring - def decorate_viewmode(func): - @wraps(func) - def set_custom_viewmode(*args, **kwargs): - # pylint: disable=no-member - override_partial_setting_id = func(*args, **kwargs) - _activate_view(override_partial_setting_id - if override_partial_setting_id else - partial_setting_id) - return set_custom_viewmode - return decorate_viewmode - - -def _activate_view(partial_setting_id): - """Activate the given view if the plugin is run in the foreground""" - if 'plugin://{}'.format(g.ADDON_ID) in xbmc.getInfoLabel('Container.FolderPath'): - if g.ADDON.getSettingBool('customview'): - view_mode = int(g.ADDON.getSettingInt('viewmode' + partial_setting_id)) - if view_mode == 0: - # Leave the management to kodi - return - # Force a custom view, get the id from settings - view_id = int(g.ADDON.getSettingInt('viewmode' + partial_setting_id + 'id')) - if view_id > 0: - xbmc.executebuiltin('Container.SetViewMode({})'.format(view_id)) - - -@custom_viewmode(g.VIEW_PROFILES) -@common.time_execution(immediate=False) -def build_profiles_listing(): - """Builds the profiles list Kodi screen""" - try: - from HTMLParser import HTMLParser - except ImportError: - from html.parser import HTMLParser - html_parser = HTMLParser() - directory_items = [] - active_guid_profile = g.LOCAL_DB.get_active_profile_guid() - for guid in g.LOCAL_DB.get_guid_profiles(): - directory_items.append(_create_profile_item(guid, - (guid == active_guid_profile), - html_parser)) - # The standard kodi theme does not allow to change view type if the content is "files" type, - # so here we use "images" type, visually better to see - finalize_directory(directory_items, g.CONTENT_IMAGES) - - -def _create_profile_item(profile_guid, is_active, html_parser): - """Create a tuple that can be added to a Kodi directory that represents - a profile as listed in the profiles listing""" - profile_name = g.LOCAL_DB.get_profile_config('profileName', '', guid=profile_guid) - unescaped_profile_name = html_parser.unescape(profile_name) - enc_profile_name = profile_name.encode('utf-8') - list_item = list_item_skeleton( - label=unescaped_profile_name, - icon=g.LOCAL_DB.get_profile_config('avatar', '', guid=profile_guid)) - list_item.select(is_active) - autologin_url = common.build_url( - pathitems=['save_autologin', profile_guid], - params={'autologin_user': enc_profile_name}, - mode=g.MODE_ACTION) - list_item.addContextMenuItems( - [(common.get_local_string(30053), - 'RunPlugin({})'.format(autologin_url))]) - url = common.build_url(pathitems=['home'], - params={'profile_id': profile_guid}, - mode=g.MODE_DIRECTORY) - return (url, list_item, True) - - -@custom_viewmode(g.VIEW_MAINMENU) -@common.time_execution(immediate=False) -def build_main_menu_listing(lolomo): - """ - Builds the video lists (my list, continue watching, etc.) Kodi screen - """ - directory_items = [] - - for menu_id, data in g.MAIN_MENU_ITEMS.iteritems(): - show_in_menu = g.ADDON.getSettingBool('_'.join(('show_menu', menu_id))) - if show_in_menu: - menu_title = 'Missing menu title' - if data['lolomo_known']: - for list_id, user_list in lolomo.lists_by_context(data['lolomo_contexts'], - break_on_first=True): - directory_items.append(_create_videolist_item(list_id, - user_list, - data, - static_lists=True)) - menu_title = user_list['displayName'] - else: - if data['label_id']: - menu_title = common.get_local_string(data['label_id']) - menu_description = common.get_local_string(data['description_id']) \ - if data['description_id'] is not None else '' - directory_items.append( - (common.build_url(data['path'], mode=g.MODE_DIRECTORY), - list_item_skeleton(menu_title, - icon=data['icon'], - description=menu_description), - True)) - g.LOCAL_DB.set_value(menu_id, {'title': menu_title}, TABLE_MENU_DATA) - finalize_directory(directory_items, g.CONTENT_FOLDER, title=common.get_local_string(30097)) - - -@custom_viewmode(g.VIEW_FOLDER) -@common.time_execution(immediate=False) -def build_lolomo_listing(lolomo, menu_data, force_videolistbyid=False, exclude_lolomo_known=False): - """Build a listing of video lists (LoLoMo). Only show those - lists with a context specified context if contexts is set.""" - contexts = menu_data['lolomo_contexts'] - lists = (lolomo.lists_by_context(contexts) - if contexts - else lolomo.lists.iteritems()) - directory_items = [] - for video_list_id, video_list in lists: - menu_parameters = common.MenuIdParameters(id_values=video_list_id) - if exclude_lolomo_known: - # Keep only the menus genre - if menu_parameters.type_id != '28': - continue - if menu_parameters.is_menu_id: - # Create a new submenu info in MAIN_MENU_ITEMS - # for reference when 'directory' find the menu data - sel_video_list_id = menu_parameters.context_id\ - if menu_parameters.context_id and not force_videolistbyid else video_list_id - sub_menu_data = menu_data.copy() - sub_menu_data['path'] = [menu_data['path'][0], sel_video_list_id, sel_video_list_id] - sub_menu_data['lolomo_known'] = False - sub_menu_data['lolomo_contexts'] = None - sub_menu_data['content_type'] = g.CONTENT_SHOW - sub_menu_data['force_videolistbyid'] = force_videolistbyid - sub_menu_data['main_menu'] = menu_data['main_menu']\ - if menu_data.get('main_menu') else menu_data.copy() - sub_menu_data.update({'title': video_list['displayName']}) - g.LOCAL_DB.set_value(sel_video_list_id, sub_menu_data, TABLE_MENU_DATA) - directory_items.append(_create_videolist_item(sel_video_list_id, - video_list, - sub_menu_data)) - parent_menu_data = g.LOCAL_DB.get_value(menu_data['path'][1], - table=TABLE_MENU_DATA, data_type=dict) - finalize_directory(directory_items, menu_data.get('content_type', g.CONTENT_SHOW), - title=parent_menu_data['title'], - sort_type='sort_label') - return menu_data.get('view') - - -@common.time_execution(immediate=False) -def _create_videolist_item(video_list_id, video_list, menu_data, static_lists=False): - """Create a tuple that can be added to a Kodi directory that represents - a videolist as listed in a LoLoMo""" - if static_lists and g.is_known_menu_context(video_list['context']): - pathitems = menu_data['path'] - pathitems.append(video_list['context']) - else: - # Has a dynamic video list-menu context - if menu_data.get('force_videolistbyid', False): - path = 'video_list' - else: - path = 'video_list_sorted' - pathitems = [path, menu_data['path'][1], video_list_id] - list_item = list_item_skeleton(video_list['displayName']) - add_info(video_list.id, list_item, video_list, video_list.data) - if video_list.artitem: - add_art(video_list.id, list_item, video_list.artitem) - url = common.build_url(pathitems, - params={'genre_id': unicode(video_list.get('genreId'))}, - mode=g.MODE_DIRECTORY) - return (url, list_item, True) - - -@custom_viewmode(g.VIEW_FOLDER) -@common.time_execution(immediate=False) -def build_subgenre_listing(subgenre_list, menu_data): - """Build a listing of subgenre lists.""" - directory_items = [] - for index, subgenre_data in subgenre_list.lists: # pylint: disable=unused-variable - # Create a new submenu info in MAIN_MENU_ITEMS - # for reference when 'directory' find the menu data - sel_video_list_id = unicode(subgenre_data['id']) - sub_menu_data = menu_data.copy() - sub_menu_data['path'] = [menu_data['path'][0], sel_video_list_id, sel_video_list_id] - sub_menu_data['lolomo_known'] = False - sub_menu_data['lolomo_contexts'] = None - sub_menu_data['content_type'] = g.CONTENT_SHOW - sub_menu_data['main_menu'] = menu_data['main_menu']\ - if menu_data.get('main_menu') else menu_data.copy() - sub_menu_data.update({'title': subgenre_data['name']}) - g.LOCAL_DB.set_value(sel_video_list_id, sub_menu_data, TABLE_MENU_DATA) - directory_items.append(_create_subgenre_item(sel_video_list_id, - subgenre_data, - sub_menu_data)) - parent_menu_data = g.LOCAL_DB.get_value(menu_data['path'][1], - table=TABLE_MENU_DATA, data_type=dict) - finalize_directory(directory_items, menu_data.get('content_type', g.CONTENT_SHOW), - title=parent_menu_data['title'], - sort_type='sort_label') - return menu_data.get('view') - - -@common.time_execution(immediate=False) -def _create_subgenre_item(video_list_id, subgenre_data, menu_data): - """Create a tuple that can be added to a Kodi directory that represents - a videolist as listed in a subgenre listing""" - pathitems = ['video_list_sorted', menu_data['path'][1], video_list_id] - list_item = list_item_skeleton(subgenre_data['name']) - url = common.build_url(pathitems, mode=g.MODE_DIRECTORY) - return (url, list_item, True) - - -@custom_viewmode(g.VIEW_SHOW) -@common.time_execution(immediate=False) -def build_video_listing(video_list, menu_data, pathitems=None, genre_id=None): - """Build a video listing""" - directory_items = [_create_video_item(videoid_value, video, video_list) - for videoid_value, video - in video_list.videos.iteritems()] - # If genre_id exists add possibility to browse lolomos subgenres - if genre_id and genre_id != 'None': - menu_id = 'subgenre_' + genre_id - sub_menu_data = menu_data.copy() - sub_menu_data['path'] = [menu_data['path'][0], menu_id, genre_id] - sub_menu_data['lolomo_known'] = False - sub_menu_data['lolomo_contexts'] = None - sub_menu_data['content_type'] = g.CONTENT_SHOW - sub_menu_data['main_menu'] = menu_data['main_menu']\ - if menu_data.get('main_menu') else menu_data.copy() - sub_menu_data.update({'title': common.get_local_string(30089)}) - g.LOCAL_DB.set_value(menu_id, sub_menu_data, TABLE_MENU_DATA) - directory_items.insert(0, - (common.build_url(['genres', menu_id, genre_id], - mode=g.MODE_DIRECTORY), - list_item_skeleton(common.get_local_string(30089), - icon='DefaultVideoPlaylists.png', - description=common.get_local_string(30088)), - True)) - add_items_previous_next_page(directory_items, pathitems, - video_list.perpetual_range_selector, genre_id) - # At the moment it is not possible to make a query with results sorted for the 'mylist', - # so we adding the sort order of kodi - sort_type = 'sort_nothing' - if menu_data['path'][1] == 'myList': - sort_type = 'sort_label_ignore_folders' - parent_menu_data = g.LOCAL_DB.get_value(menu_data['path'][1], - table=TABLE_MENU_DATA, data_type=dict) - finalize_directory(directory_items, menu_data.get('content_type', g.CONTENT_SHOW), - title=parent_menu_data['title'], - sort_type=sort_type) - return menu_data.get('view') - - -@common.time_execution(immediate=False) -def _create_video_item(videoid_value, video, video_list): - """Create a tuple that can be added to a Kodi directory that represents - a video as listed in a videolist""" - is_movie = video['summary']['type'] == 'movie' - videoid = common.VideoId( - **{('movieid' if is_movie else 'tvshowid'): videoid_value}) - list_item = list_item_skeleton(video['title']) - add_info(videoid, list_item, video, video_list.data) - add_art(videoid, list_item, video) - url = common.build_url(videoid=videoid, - mode=(g.MODE_PLAY - if is_movie - else g.MODE_DIRECTORY)) - list_item.addContextMenuItems(generate_context_menu_items(videoid)) - return (url, list_item, not is_movie) - - -@custom_viewmode(g.VIEW_SEASON) -@common.time_execution(immediate=False) -def build_season_listing(tvshowid, season_list, pathitems=None): - """Build a season listing""" - directory_items = [_create_season_item(tvshowid, seasonid_value, season, - season_list) - for seasonid_value, season - in season_list.seasons.iteritems()] - add_items_previous_next_page(directory_items, pathitems, season_list.perpetual_range_selector) - finalize_directory(directory_items, g.CONTENT_SEASON, 'sort_only_label', - title=' - '.join((season_list.tvshow['title'], - common.get_local_string(20366)[2:]))) - - -@common.time_execution(immediate=False) -def _create_season_item(tvshowid, seasonid_value, season, season_list): - """Create a tuple that can be added to a Kodi directory that represents - a season as listed in a season listing""" - seasonid = tvshowid.derive_season(seasonid_value) - list_item = list_item_skeleton(season['summary']['name']) - add_info(seasonid, list_item, season, season_list.data) - add_art(tvshowid, list_item, season_list.tvshow) - list_item.addContextMenuItems(generate_context_menu_items(seasonid)) - url = common.build_url(videoid=seasonid, mode=g.MODE_DIRECTORY) - return (url, list_item, True) - - -@custom_viewmode(g.VIEW_EPISODE) -@common.time_execution(immediate=False) -def build_episode_listing(seasonid, episode_list, pathitems=None): - """Build a season listing""" - directory_items = [_create_episode_item(seasonid, episodeid_value, episode, - episode_list) - for episodeid_value, episode - in episode_list.episodes.iteritems()] - add_items_previous_next_page(directory_items, pathitems, episode_list.perpetual_range_selector) - finalize_directory(directory_items, g.CONTENT_EPISODE, 'sort_episodes', - title=' - '.join( - (episode_list.tvshow['title'], - episode_list.season['summary']['name']))) - - -@common.time_execution(immediate=False) -def _create_episode_item(seasonid, episodeid_value, episode, episode_list): - """Create a tuple that can be added to a Kodi directory that represents - an episode as listed in an episode listing""" - episodeid = seasonid.derive_episode(episodeid_value) - list_item = list_item_skeleton(episode['title']) - add_info(episodeid, list_item, episode, episode_list.data) - add_art(episodeid, list_item, episode) - list_item.addContextMenuItems(generate_context_menu_items(episodeid)) - url = common.build_url(videoid=episodeid, mode=g.MODE_PLAY) - return (url, list_item, False) - - -@custom_viewmode(g.VIEW_SHOW) -@common.time_execution(immediate=False) -def build_supplemental_listing(video_list, pathitems=None): # pylint: disable=unused-argument - """Build a supplemental listing (eg. trailers)""" - directory_items = [_create_supplemental_item(videoid_value, video, video_list) - for videoid_value, video - in video_list.videos.iteritems()] - finalize_directory(directory_items, g.CONTENT_SHOW, 'sort_label', - title='Trailers') - - -@common.time_execution(immediate=False) -def _create_supplemental_item(videoid_value, video, video_list): - """Create a tuple that can be added to a Kodi directory that represents - a video as listed in a videolist""" - videoid = common.VideoId( - **{'supplementalid': videoid_value}) - list_item = list_item_skeleton(video['title']) - add_info(videoid, list_item, video, video_list.data) - add_art(videoid, list_item, video) - url = common.build_url(videoid=videoid, - mode=g.MODE_PLAY) - # replaceItems still look broken because it does not remove the default ctx menu - # i hope in the future Kodi fix this - list_item.addContextMenuItems(generate_context_menu_items(videoid), replaceItems=True) - return (url, list_item, False) - - -def list_item_skeleton(label, icon=None, fanart=None, description=None, customicon=None): - """Create a rudimentary list item skeleton with icon and fanart""" - # pylint: disable=unexpected-keyword-arg - list_item = xbmcgui.ListItem(label=label, offscreen=True) - list_item.setContentLookup(False) - art_values = {} - if customicon: - addon_dir = xbmc.translatePath(g.ADDON.getAddonInfo('path')) - icon = os.path.join(addon_dir, 'resources', 'media', customicon) - art_values['thumb'] = icon - if icon: - art_values['icon'] = icon - if fanart: - art_values['fanart'] = fanart - if art_values: - list_item.setArt(art_values) - info = {'title': label} - if description: - info['plot'] = description - list_item.setInfo('video', info) - return list_item - - -def add_items_previous_next_page(directory_items, pathitems, perpetual_range_selector, - genre_id=None): - if pathitems and perpetual_range_selector: - if 'previous_start' in perpetual_range_selector: - params = {'perpetual_range_start': perpetual_range_selector.get('previous_start'), - 'genre_id': - genre_id if perpetual_range_selector.get('previous_start') == 0 else None} - previous_page_url = common.build_url(pathitems=pathitems, - params=params, - mode=g.MODE_DIRECTORY) - directory_items.insert(0, (previous_page_url, - list_item_skeleton(common.get_local_string(30148), - customicon='FolderPagePrevious.png'), - True)) - if 'next_start' in perpetual_range_selector: - params = {'perpetual_range_start': perpetual_range_selector.get('next_start')} - next_page_url = common.build_url(pathitems=pathitems, - params=params, - mode=g.MODE_DIRECTORY) - directory_items.append((next_page_url, - list_item_skeleton(common.get_local_string(30147), - customicon='FolderPageNext.png'), - True)) - - -def finalize_directory(items, content_type=g.CONTENT_FOLDER, sort_type='sort_nothing', - title=None): - """Finalize a directory listing. - Add items, set available sort methods and content type""" - if title: - xbmcplugin.setPluginCategory(g.PLUGIN_HANDLE, title) - xbmcplugin.setContent(g.PLUGIN_HANDLE, content_type) - add_sort_methods(sort_type) - xbmcplugin.addDirectoryItems(g.PLUGIN_HANDLE, items) - - -def add_sort_methods(sort_type): - if sort_type == 'sort_nothing': - xbmcplugin.addSortMethod(g.PLUGIN_HANDLE, xbmcplugin.SORT_METHOD_NONE) - if sort_type == 'sort_label': - xbmcplugin.addSortMethod(g.PLUGIN_HANDLE, xbmcplugin.SORT_METHOD_LABEL) - if sort_type == 'sort_label_ignore_folders': - xbmcplugin.addSortMethod(g.PLUGIN_HANDLE, xbmcplugin.SORT_METHOD_LABEL_IGNORE_FOLDERS) - if sort_type == 'sort_episodes': - xbmcplugin.addSortMethod(g.PLUGIN_HANDLE, xbmcplugin.SORT_METHOD_EPISODE) - xbmcplugin.addSortMethod(g.PLUGIN_HANDLE, xbmcplugin.SORT_METHOD_LABEL) - xbmcplugin.addSortMethod(g.PLUGIN_HANDLE, xbmcplugin.SORT_METHOD_VIDEO_TITLE) diff --git a/resources/lib/kodi/nfo.py b/resources/lib/kodi/nfo.py index ebc30f962..b16614626 100644 --- a/resources/lib/kodi/nfo.py +++ b/resources/lib/kodi/nfo.py @@ -1,28 +1,34 @@ # -*- coding: utf-8 -*- -"""Functions for Kodi library NFO creation""" -from __future__ import absolute_import, division, unicode_literals - +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2019 Smeulf (original implementation module) + Functions for Kodi library NFO creation + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" import xml.etree.ElementTree as ET -from resources.lib.globals import g +from resources.lib.globals import G import resources.lib.common as common import resources.lib.kodi.ui as ui +from resources.lib.utils.logging import LOG class NFOSettings: def __init__(self, enforce=None): """ - :param force: Used for export new episode, to force the nfo export status + :param enforce: Used for export new episode, to force the nfo export status """ if enforce is None: - self._enabled = g.ADDON.getSettingBool('enable_nfo_export') - self._export_tvshow_id = g.ADDON.getSettingInt('export_tvshow_nfo') + self._enabled = G.ADDON.getSettingBool('enable_nfo_export') + self._export_tvshow_id = G.ADDON.getSettingInt('export_tvshow_nfo') else: - common.debug('Export NFO enforced to {}'.format(enforce)) + LOG.debug('Export NFO enforced to {}', enforce) self._enabled = enforce self._export_tvshow_id = enforce - self._export_movie_id = g.ADDON.getSettingInt('export_movie_nfo') - self._export_full_tvshow = g.ADDON.getSettingBool('export_full_tvshow_nfo') + self._export_movie_id = G.ADDON.getSettingInt('export_movie_nfo') + self._export_full_tvshow = G.ADDON.getSettingBool('export_full_tvshow_nfo') @property def export_enabled(self): @@ -58,7 +64,7 @@ def tvshow_prompt_dialog(self): """Ask to user when export TvShow NFO""" return self._enabled and self._export_tvshow_id == 2 - def show_export_dialog(self, mediatype=None, dialog_message=common.get_local_string(30183)): + def show_export_dialog(self, mediatype=None): """Ask the user if he wants to export NFO for movies and/or tvshows, this override the default settings""" if not self.export_enabled or (not self.movie_prompt_dialog and not self.tvshow_prompt_dialog): return @@ -74,8 +80,8 @@ def show_export_dialog(self, mediatype=None, dialog_message=common.get_local_str if self.tvshow_prompt_dialog: ask_message_typelist.append(common.get_local_string(30190)) if ask_message_typelist: - message = ' {} '.format(common.get_local_string(1397)).join(ask_message_typelist) - message = dialog_message.format(message) + common.get_local_string(30192) + message = f' {common.get_local_string(1397)} '.join(ask_message_typelist) + message = common.get_local_string(30183).format(message) + common.get_local_string(30192) user_choice = ui.ask_for_confirmation(common.get_local_string(30182), message) if len(ask_message_typelist) == 2 and not user_choice: self._export_movie_id = 0 @@ -95,10 +101,12 @@ def create_episode_nfo(episode, season, show): 'episode': episode.get('seq'), 'plot': episode.get('synopsis'), 'runtime': episode.get('runtime', 0) / 60, - 'year': season.get('year'), 'id': episode.get('id') } - + year = episode.get('year') + if year: + # Since we have the year only, so we hardcode the month/day + tags['premiered'] = f'{year}-01-01' root = _build_root_node('episodedetails', tags) _add_episode_thumb(root, episode) return root @@ -113,6 +121,11 @@ def create_show_nfo(show): 'id': show['id'], 'mpaa': show.get('rating') } + # Try get the year from the first season + year = show.get('seasons', [{}])[0].get('year') + if year: + # Since we have the year only, so we hardcode the month/day + tags['premiered'] = f'{year}-01-01' root = _build_root_node('tvshow', tags) _add_poster(root, show) _add_fanart(root, show) @@ -125,13 +138,15 @@ def create_movie_nfo(movie): 'plot': movie.get('synopsis'), 'id': movie.get('id'), 'mpaa': movie.get('rating'), - 'year': movie.get('year'), 'runtime': movie.get('runtime', 0) / 60, } + year = movie.get('year') + if year: + # Since we have the year only, so we hardcode the month/day + tags['premiered'] = f'{year}-01-01' root = _build_root_node('movie', tags) _add_poster(root, movie) _add_fanart(root, movie) - common.debug(root) return root @@ -162,8 +177,8 @@ def _add_fanart(root, data): def _build_root_node(root_name, tags): root = ET.Element(root_name) - for (k, v) in tags.items(): + for (k, v) in list(tags.items()): if v: tag = ET.SubElement(root, k) - tag.text = unicode(v) + tag.text = str(v) return root diff --git a/resources/lib/kodi/ui/__init__.py b/resources/lib/kodi/ui/__init__.py index 3790483d4..4ba54506e 100644 --- a/resources/lib/kodi/ui/__init__.py +++ b/resources/lib/kodi/ui/__init__.py @@ -1,7 +1,12 @@ # -*- coding: utf-8 -*- -"""Kodi GUI stuff""" -# pylint: disable=wildcard-import -from __future__ import absolute_import, division, unicode_literals +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Kodi GUI stuff + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +# pylint: disable=wildcard-import from .dialogs import * from .xmldialogs import * diff --git a/resources/lib/kodi/ui/dialogs.py b/resources/lib/kodi/ui/dialogs.py index fa5d4eb58..a5da3e942 100644 --- a/resources/lib/kodi/ui/dialogs.py +++ b/resources/lib/kodi/ui/dialogs.py @@ -1,20 +1,22 @@ # -*- coding: utf-8 -*- -"""Various simple dialogs""" -# pylint: disable=wildcard-import -from __future__ import absolute_import, division, unicode_literals - +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Various simple dialogs + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" import xbmc import xbmcgui -from resources.lib.globals import g +from resources.lib.globals import G import resources.lib.common as common def show_notification(msg, title='Netflix', time=3000): """Show a notification""" - xbmc.executebuiltin('Notification({}, {}, {}, {})' - .format(title, msg, time, g.ICON) - .encode('utf-8')) + xbmc.executebuiltin(f'Notification({title}, {msg}, {time}, {G.ICON})') def ask_credentials(): @@ -25,22 +27,25 @@ def ask_credentials(): heading=common.get_local_string(30005), type=xbmcgui.INPUT_ALPHANUM) or None common.verify_credentials(email) - password = xbmcgui.Dialog().input( - heading=common.get_local_string(30004), - type=xbmcgui.INPUT_ALPHANUM, - option=xbmcgui.ALPHANUM_HIDE_INPUT) or None + password = ask_for_password() common.verify_credentials(password) - common.set_credentials(email, password) return { - 'email': email, - 'password': password + 'email': email.strip(), + 'password': password.strip() } +def ask_for_password(): + """Ask the user for the password""" + return xbmcgui.Dialog().input( + heading=common.get_local_string(30004), + type=xbmcgui.INPUT_ALPHANUM, + option=xbmcgui.ALPHANUM_HIDE_INPUT) or None + + def ask_for_rating(): """Ask the user for a rating""" - heading = '{} {}'.format(common.get_local_string(30019), - common.get_local_string(30022)) + heading = f'{common.get_local_string(30019)} {common.get_local_string(30022)}' try: return int(xbmcgui.Dialog().numeric(heading=heading, type=0, defaultt='')) @@ -48,42 +53,30 @@ def ask_for_rating(): return None -def ask_for_pin(): - """Ask the user for the adult pin""" - return xbmcgui.Dialog().numeric( - heading=common.get_local_string(30002), - type=0, - defaultt='') or None +def show_dlg_input_numeric(message, mask_input=True): + """Ask the user to enter numbers""" + args = {'heading': message, + 'type': 0, + 'defaultt': '', + 'bHiddenInput': mask_input} + return xbmcgui.Dialog().numeric(**args) or None -def ask_for_search_term(): +def ask_for_search_term(default_text=None): """Ask the user for a search term""" - return _ask_for_input(common.get_local_string(30003)) + return ask_for_input(common.get_local_string(30402), default_text) -def _ask_for_input(heading): +def ask_for_input(heading, default_text=None): return xbmcgui.Dialog().input( + defaultt=default_text, heading=heading, - type=xbmcgui.INPUT_ALPHANUM).decode('utf-8') or None - - -def ask_for_custom_title(original_title): - """Ask the user for a custom title (for library export)""" - if g.ADDON.getSettingBool('customexportname'): - return original_title - return _ask_for_input(common.get_local_string(30031)) or original_title - - -def ask_for_removal_confirmation(): - """Ask the user to finally remove title from the Kodi library""" - return ask_for_confirmation( - common.get_local_string(30047), - common.get_local_string(30124)) + type=xbmcgui.INPUT_ALPHANUM) or None def ask_for_confirmation(title, message): - """Ask the user to finally remove title from the Kodi library""" - return xbmcgui.Dialog().yesno(heading=title, line1=message) + """Ask the user to confirm an operation""" + return xbmcgui.Dialog().yesno(title, message) def ask_for_resume(resume_position): @@ -91,35 +84,155 @@ def ask_for_resume(resume_position): return xbmcgui.Dialog().contextmenu( [ common.get_local_string(12022).format(common.convert_seconds_to_hms_str(resume_position)), - common.get_local_string(12023) + common.get_local_string(12021) ]) -def show_backend_not_ready(): - return xbmcgui.Dialog().ok(common.get_local_string(30105), - line1=common.get_local_string(30138)) +def show_backend_not_ready(error_details=None): + message = common.get_local_string(30138) + if error_details: + message += f'[CR][CR]Error details:[CR]{error_details}' + return xbmcgui.Dialog().ok(common.get_local_string(30105), message) def show_ok_dialog(title, message): return xbmcgui.Dialog().ok(title, message) +def show_yesno_dialog(title, message, yeslabel=None, nolabel=None, default_yes_button=False): + if G.KODI_VERSION < '20': + return xbmcgui.Dialog().yesno(title, message, yeslabel=yeslabel, nolabel=nolabel) + # pylint: disable=no-member,unexpected-keyword-arg + default_button = xbmcgui.DLG_YESNO_YES_BTN if default_yes_button else xbmcgui.DLG_YESNO_NO_BTN + return xbmcgui.Dialog().yesno(title, message, + yeslabel=yeslabel, nolabel=nolabel, defaultbutton=default_button) + + def show_error_info(title, message, unknown_error=False, netflix_error=False): """Show a dialog that displays the error message""" prefix = (30104, 30102, 30101)[unknown_error + netflix_error] - return xbmcgui.Dialog().ok(title, - line1=common.get_local_string(prefix), - line2=message, - line3=common.get_local_string(30103)) + return xbmcgui.Dialog().ok(title, (common.get_local_string(prefix) + '[CR]' + + message + '[CR][CR]' + + common.get_local_string(30103))) def show_addon_error_info(exc): """Show a dialog to notify of an addon internal error""" - if g.ADDON.getSettingBool('disable_modal_error_display'): - show_notification(title=common.get_local_string(30105), - msg=common.get_local_string(30131)) - else: - show_error_info(title=common.get_local_string(30105), - message=': '.join((exc.__class__.__name__, - exc.message)), - netflix_error=False) + show_error_info(title=common.get_local_string(30105), + message=': '.join((exc.__class__.__name__, str(exc))), + netflix_error=False) + + +def show_library_task_errors(notify_errors, errors): + if notify_errors and errors: + xbmcgui.Dialog().ok(common.get_local_string(0), + '[CR]'.join([f'{err["title"]} ({err["error"]})' + for err in errors])) + + +def show_browse_dialog(title, browse_type=0, default_path=None, multi_selection=False, extensions=None): + """ + Show a browse dialog to select files or folders + :param title: The window title + :param browse_type: Type of dialog as int value (0 = ShowAndGetDirectory, 1 = ShowAndGetFile, ..see doc) + :param default_path: The initial path + :param multi_selection: Allow multi selection + :param extensions: extensions allowed e.g. '.jpg|.png' + :return: The selected path as string (or tuple of selected items) if user pressed 'Ok', else None + """ + ret = xbmcgui.Dialog().browse(browse_type, title, shares='', useThumbs=False, treatAsFolder=False, + defaultt=default_path, enableMultiple=multi_selection, mask=extensions) + # Note: when defaultt is set and the user cancel the action (when enableMultiple is False), + # will be returned the defaultt value again, so we avoid this strange behavior... + return None if not ret or ret == default_path else ret + + +def show_dlg_select(title, item_list): + """ + Show a select dialog for a list of objects + + :return index of selected item, or -1 when cancelled + """ + return xbmcgui.Dialog().select(title, item_list) + + +class ProgressDialog(xbmcgui.DialogProgress): + """Context manager to handle a progress dialog window""" + # Keep the same arguments for all progress bar classes + def __init__(self, is_enabled, title=None, initial_value=0, max_value=1): + super().__init__() + self.is_enabled = is_enabled + self.max_value = max_value + self.value = initial_value + self._percent = int(initial_value * 100 / max_value) if max_value else 0 + if is_enabled: + self.create(title or common.get_local_string(30047)) + + def __enter__(self): + if self.is_enabled: + self.update(self._percent, common.get_local_string(261)) # "Waiting for start..." + return self + + def set_message(self, message): + if self.is_enabled: + self.update(self._percent, message) + + def set_wait_message(self): + if self.is_enabled: + self.update(self._percent, common.get_local_string(20186)) # "Please wait" + + def is_cancelled(self): + """Return True when the user has pressed cancel button""" + return self.is_enabled and self.iscanceled() + + def perform_step(self): + self.value += 1 + self._percent = int(self.value * 100 / self.max_value) + + def __exit__(self, exc_type, exc_value, exc_traceback): + if self.is_enabled: + self.close() + + +class ProgressBarBG(xbmcgui.DialogProgressBG): + """Context manager to handle a progress bar in background""" + # Keep the same arguments for all progress bar classes + def __init__(self, is_enabled, title, initial_value=None, max_value=None): + super().__init__() + self.is_enabled = is_enabled + self.max_value = max_value + self.value = 0 if max_value and initial_value is None else initial_value + self._percent = int(initial_value * 100 / max_value) if initial_value and max_value else None + if is_enabled: + self.create(title) + + def __enter__(self): + if self.is_enabled: + self._update(common.get_local_string(261)) # "Waiting for start..." + return self + + def set_message(self, message): + if self.is_enabled: + self._update(message) + + def set_wait_message(self): + if self.is_enabled: + self._update(common.get_local_string(20186)) # "Please wait" + + def perform_step(self): + self.value += 1 + self._percent = int(self.value * 100 / self.max_value) + + def _update(self, message): + kwargs = {'message': message} + if self._percent is not None: + kwargs['percent'] = self._percent + self.update(**kwargs) # Here all the arguments are optionals + + def is_cancelled(self): + # Not supported - only need to ensure consistency in dynamic class management + return False + + def __exit__(self, exc_type, exc_value, exc_traceback): + if self.is_enabled: + self.close() diff --git a/resources/lib/kodi/ui/xmldialog_esnwidevine.py b/resources/lib/kodi/ui/xmldialog_esnwidevine.py new file mode 100644 index 000000000..e1e5e81ba --- /dev/null +++ b/resources/lib/kodi/ui/xmldialog_esnwidevine.py @@ -0,0 +1,283 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2020 Stefano Gottardo (original implementation module) + XML based dialog + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +import time + +import xbmc +import xbmcgui +import xbmcvfs + +from resources.lib import common +from resources.lib.common import IPC_ENDPOINT_MSL +from resources.lib.common.exceptions import ErrorMsg +from resources.lib.database.db_utils import TABLE_SESSION +from resources.lib.globals import G +from resources.lib.kodi import ui +from resources.lib.utils.esn import (generate_android_esn, WidevineForceSecLev, get_esn, set_esn, set_website_esn, + get_website_esn) +from resources.lib.utils.logging import LOG + +ACTION_PREVIOUS_MENU = 10 +ACTION_PLAYER_STOP = 13 +ACTION_NAV_BACK = 92 + + +# pylint: disable=invalid-name,no-member +class ESNWidevine(xbmcgui.WindowXMLDialog): + """Dialog for ESN and Widevine settings""" + + WV_SECLEV_MAP_BTN = { # Map sec. Lev. type to button id + WidevineForceSecLev.DISABLED: 40000, + WidevineForceSecLev.L3: 40001, + WidevineForceSecLev.L3_4445: 40002 + } + + def __init__(self, *args, **kwargs): # pylint: disable=unused-argument + self.changes_applied = False + self.esn = get_esn() + self.esn_new = None + self.wv_force_sec_lev = G.LOCAL_DB.get_value('widevine_force_seclev', + WidevineForceSecLev.DISABLED, + table=TABLE_SESSION) + self.wv_sec_lev_new = None + self.is_android = common.get_system_platform() == 'android' + self.action_exit_keys_id = [ACTION_PREVIOUS_MENU, + ACTION_PLAYER_STOP, + ACTION_NAV_BACK] + super().__init__(*args) + + def onInit(self): + # Set label to Widevine sec. lev. radio buttons + self.getControl(40001).setLabel(common.get_local_string(30605).format(WidevineForceSecLev.L3)) + self.getControl(40002).setLabel(common.get_local_string(30605).format(WidevineForceSecLev.L3_4445)) + # Set the current ESN to Label + self.getControl(30000).setLabel(self.esn) + # Set [Auto generate ESN] radio button value + self.getControl(40100).setSelected(G.LOCAL_DB.get_value('esn_auto_generate', True)) + # Set the current Widevine security level to the radio buttons + self.getControl(self.WV_SECLEV_MAP_BTN[self.wv_force_sec_lev]).setSelected(True) + # Hide force L3 on non-android systems (L1 is currently supported only to android) + if not self.is_android: + for _sec_lev, _id in self.WV_SECLEV_MAP_BTN.items(): + if _sec_lev != WidevineForceSecLev.DISABLED: + self.getControl(_id).setVisible(False) + + def onClick(self, controlId): + # [Widevine sec. lev.] radio buttons - this setting can affect the ESN so make a preview of the change + if controlId in self.WV_SECLEV_MAP_BTN.values(): + self._ensure_wv_btn_check(controlId) + self.wv_sec_lev_new = list( + self.WV_SECLEV_MAP_BTN.keys())[list(self.WV_SECLEV_MAP_BTN.values()).index(controlId)] + self._refresh_esn() + self._update_esn_label() + # [Change ESN] button + if controlId == 30010: + self._change_esn() + # [Reset] button - reset all settings to default + if controlId == 30011: + self._reset() + # [Apply changes] button + if controlId == 40020: + self._apply_changes() + # [OK] button - close and keep changes + if controlId == 40021: + if not self.changes_applied: + # The changes to MSL (key handshake) will be done when will be played a video + set_esn(self.esn_new or self.esn) + G.LOCAL_DB.set_value('widevine_force_seclev', + self.wv_sec_lev_new or self.wv_force_sec_lev, + TABLE_SESSION) + # Reset ESN timestamp to prevent to replace the stored ESN immediately + G.LOCAL_DB.set_value('esn_timestamp', int(time.time())) + # Update value for auto generate ESN + is_checked = self.getControl(40100).isSelected() + G.LOCAL_DB.set_value('esn_auto_generate', is_checked) + self.close() + # [X or Cancel] button - close and cancel changes + if controlId in [40099, 40022]: + self._revert_changes() + self.close() + # [Save system info] button + if controlId == 30012: + _save_system_info() + + def onAction(self, action): + if action.getId() in self.action_exit_keys_id: + self._revert_changes() + self.close() + + def _esn_checks(self, esn): + """Sanity checks for ESN""" + if self.is_android: + if not esn.startswith(('NFANDROID1-PRV-', 'NFANDROID2-PRV-')) or esn.count('-') < 5: + return False + else: + if esn.count('-') != 2 or len(esn) != 40: + return False + return True + + def _ensure_wv_btn_check(self, _chosen_id): + """Ensure that only the chosen Widevine sec. lev. radio button is checked""" + for _id in self.WV_SECLEV_MAP_BTN.values(): + is_checked = self.getControl(_id).isSelected() + if _id == _chosen_id: + if not is_checked: + self.getControl(_id).setSelected(True) + elif is_checked: + self.getControl(_id).setSelected(False) + + def _refresh_esn(self): + """Refresh the ESN based on Widevine security level (ANDROID ONLY)""" + # Refresh only is when there is no a full-length ESN + if self.is_android and len(self.esn_new or self.esn) < 50: + self.esn_new = generate_android_esn(wv_force_sec_lev=self.wv_sec_lev_new or self.wv_force_sec_lev) + + def _update_esn_label(self): + self.getControl(30000).setLabel(self.esn_new or self.esn) + + def _change_esn(self): + esn_custom = ui.ask_for_input(common.get_local_string(30602), self.esn_new or self.esn) + if esn_custom: + if not self._esn_checks(esn_custom): + # Wrong custom ESN format type + ui.show_ok_dialog(common.get_local_string(30600), common.get_local_string(30608)) + else: + self.esn_new = esn_custom + self._update_esn_label() + + def _reset(self): + if not ui.ask_for_confirmation(common.get_local_string(13007), # 13007=Reset + common.get_local_string(30609)): + return + with common.show_busy_dialog(): + # Set WV Sec. Lev. to Disabled + self._ensure_wv_btn_check(self.WV_SECLEV_MAP_BTN[WidevineForceSecLev.DISABLED]) + self.wv_sec_lev_new = WidevineForceSecLev.DISABLED + if self.is_android: + # Generate the ESN + self.esn_new = generate_android_esn(wv_force_sec_lev=self.wv_sec_lev_new) + else: + # To retrieve the ESN from the website, + # to avoid possible problems we refresh the nf session data to get a new ESN + set_website_esn('') + common.make_call('refresh_session_data', {'update_profiles': False}) + self.esn_new = get_website_esn() + if not self.esn_new: + raise ErrorMsg('It was not possible to obtain the ESN, try restarting the add-on') + self._update_esn_label() + + def _apply_changes(self): + with common.show_busy_dialog(): + set_esn(self.esn_new or self.esn) + G.LOCAL_DB.set_value('widevine_force_seclev', self.wv_sec_lev_new or self.wv_force_sec_lev, TABLE_SESSION) + # Try apply the changes by performing the MSL key handshake right now + try: + common.make_call('perform_key_handshake', endpoint=IPC_ENDPOINT_MSL) + # When the MSL not raise errors not always means that the device can play the videos + # because the MSL manifest/license request may not be granted (you have to play a video to know it). + ui.show_ok_dialog(common.get_local_string(30600), common.get_local_string(30606)) + except Exception as exc: # pylint: disable=broad-except + ui.show_ok_dialog(common.get_local_string(30600), common.get_local_string(30607).format(exc)) + self.changes_applied = True + + def _revert_changes(self): + if self.changes_applied: + # Revert the saved changes + # The changes to MSL (key handshake) will be done when will be played a video + set_esn(self.esn) + G.LOCAL_DB.set_value('widevine_force_seclev', self.wv_force_sec_lev, TABLE_SESSION) + + +def _save_system_info(): + # Ask to save to a file + filename = 'NFSystemInfo.txt' + path = ui.show_browse_dialog(f'{common.get_local_string(30603)} - {filename}') + if not path: + return + # This collect the main data to allow verification checks for problems + data = f'Netflix add-on version: {G.VERSION}' + data += f'\nDebug enabled: {LOG.is_enabled}' + data += f'\nSystem platform: {common.get_system_platform()}' + data += f'\nMachine architecture: {common.get_machine()}' + data += f'\nUser agent string: {common.get_user_agent()}' + data += '\n\n#### Widevine info ####\n' + if common.get_system_platform() == 'android': + data += f'\nSystem ID: {G.LOCAL_DB.get_value("drm_system_id", "--not obtained--", TABLE_SESSION)}' + data += f'\nSecurity level: {G.LOCAL_DB.get_value("drm_security_level", "--not obtained--", TABLE_SESSION)}' + data += f'\nHDCP level: {G.LOCAL_DB.get_value("drm_hdcp_level", "--not obtained--", TABLE_SESSION)}' + wv_force_sec_lev = G.LOCAL_DB.get_value('widevine_force_seclev', WidevineForceSecLev.DISABLED, + TABLE_SESSION) + data += f'\nForced security level setting is: {wv_force_sec_lev}' + else: + try: + from ctypes import (CDLL, c_char_p) + cdm_lib_file_path = _get_cdm_file_path() + try: + lib = CDLL(cdm_lib_file_path) + data += '\nLibrary status: Correctly loaded' + try: + lib.GetCdmVersion.restype = c_char_p + data += f'\nVersion: {lib.GetCdmVersion().decode("utf-8")}' + except Exception: # pylint: disable=broad-except + # This can happen if the endpoint 'GetCdmVersion' is changed + data += '\nVersion: Reading error' + except Exception as exc: # pylint: disable=broad-except + # This should not happen but currently InputStream Helper does not perform any verification checks on + # downloaded and installed files, so if due to an problem it installs a CDM for a different architecture + # or the files are corrupted, the user can no longer play videos and does not know what to do + data += '\nLibrary status: Error loading failed' + data += '\n>>> It is possible that is installed a CDM of a wrong architecture or is corrupted' + data += '\n>>> Suggested solutions:' + data += '\n>>> - Restore a previous version of Widevine library from InputStream Helper add-on settings' + data += '\n>>> - Report the problem to the GitHub of InputStream Helper add-on' + data += f'\n>>> Error details: {exc}' + except Exception as exc: # pylint: disable=broad-except + data += f'\nThe data could not be obtained. Error details: {exc}' + data += '\n\n#### ESN ####\n' + esn = get_esn() or '--not obtained--' + data += f'\nUsed ESN: {common.censure(esn) if len(esn) > 50 else esn}' + data += f'\nWebsite ESN: {get_website_esn() or "--not obtained--"}' + data += f'\nAndroid generated ESN: {(generate_android_esn() or "--not obtained--")}' + if common.get_system_platform() == 'android': + data += '\n\n#### Device system info ####\n' + try: + import subprocess + info = subprocess.check_output(['/system/bin/getprop']).decode('utf-8') + data += f'\n{info}' + except Exception as exc: # pylint: disable=broad-except + data += f'\nThe data could not be obtained. Error: {exc}' + data += '\n' + try: + common.save_file(common.join_folders_paths(path, filename), data.encode('utf-8')) + ui.show_notification(f'{xbmc.getLocalizedString(35259)}: {filename}') # 35259=Saved + except Exception as exc: # pylint: disable=broad-except + LOG.error('save_file error: {}', exc) + ui.show_notification('Error! Try another path') + + +def _get_cdm_file_path(): + if common.get_system_platform() == 'linux': + lib_filename = 'libwidevinecdm.so' + elif common.get_system_platform() in ['windows', 'uwp']: + lib_filename = 'widevinecdm.dll' + elif common.get_system_platform() == 'osx': + lib_filename = 'libwidevinecdm.dylib' + # import ctypes.util + # lib_filename = util.find_library('libwidevinecdm.dylib') + else: + lib_filename = None + if not lib_filename: + raise ErrorMsg('Widevine library filename not mapped for this operative system') + # Get the CDM path from inputstream.adaptive (such as: ../.kodi/cdm) + from xbmcaddon import Addon + addon = Addon('inputstream.adaptive') + cdm_path = xbmcvfs.translatePath(addon.getSetting('DECRYPTERPATH')) + if not common.folder_exists(cdm_path): + raise ErrorMsg(f'The CDM path {cdm_path} not exists') + return common.join_folders_paths(cdm_path, lib_filename) diff --git a/resources/lib/kodi/ui/xmldialog_parental.py b/resources/lib/kodi/ui/xmldialog_parental.py new file mode 100644 index 000000000..2938b187a --- /dev/null +++ b/resources/lib/kodi/ui/xmldialog_parental.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2020 Stefano Gottardo (original implementation module) + XML based dialog + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +import xbmcgui + +from resources.lib.globals import G + +ACTION_PREVIOUS_MENU = 10 +ACTION_PLAYER_STOP = 13 +ACTION_NAV_BACK = 92 +XBFONT_LEFT = 0x00000000 +XBFONT_RIGHT = 0x00000001 +XBFONT_CENTER_X = 0x00000002 +XBFONT_CENTER_Y = 0x00000004 +XBFONT_TRUNCATED = 0x00000008 +XBFONT_JUSTIFY = 0x00000010 + + +# pylint: disable=invalid-name,no-member +class ParentalControl(xbmcgui.WindowXMLDialog): + """ + Dialog for parental control settings + """ + def __init__(self, *args, **kwargs): + # Keep pin option, there is still some reference in the netflix code + # self.current_pin = kwargs.get('pin') + self.data = kwargs['data'] + self.rating_levels = kwargs['rating_levels'] + self.current_maturity = self.data['maturity'] + self.current_level_index = kwargs['current_level_index'] + self.profile_info = self.data['profileInfo'] + self.levels_count = len(self.rating_levels) + self.status_base_desc = G.ADDON.getLocalizedString(30233) + self.controls = {} + self.action_exit_keys_id = [ACTION_PREVIOUS_MENU, + ACTION_PLAYER_STOP, + ACTION_NAV_BACK] + super().__init__(*args) + + def onInit(self): + self._generate_levels_labels() + # Set maturity level status description + self._update_status_desc(self.current_level_index) + # Set profile name to label description + self.getControl(10003).setLabel(G.ADDON.getLocalizedString(30232).format(self.profile_info['profileName'])) + # PIN input + # edit_control = self.getControl(10002) + # edit_control.setType(xbmcgui.INPUT_TYPE_NUMBER, G.ADDON.getLocalizedString(30002)) + # edit_control.setText(self.current_pin) + # Maturity level slider + slider_control = self.getControl(10004) + # setInt(value, min, delta, max) + slider_control.setInt(self.current_level_index, 0, 1, self.levels_count - 1) + + def onClick(self, controlId): + if controlId == 10028: # Save and close dialog + # pin = self.getControl(10002).getText() + # # Validate pin length + # if not self._validate_pin(pin): + # return + import resources.lib.utils.api_requests as api + data = {'guid': self.data['profileInfo']['guid'], + 'experience': self.data['experience'], + 'maturity': self.rating_levels[self.current_level_index]['value'], + 'token': self.data['token']} + # Send changes to the service + api.set_parental_control_data(data) + + # The selection of the maturity level affects the lists data as a filter, + # so you need to clear the lists in the cache in order not to create inconsistencies + from resources.lib.common.cache_utils import CACHE_COMMON, CACHE_GENRES, CACHE_MYLIST, CACHE_SEARCH + G.CACHE.clear([CACHE_COMMON, CACHE_GENRES, CACHE_MYLIST, CACHE_SEARCH]) + self.close() + if controlId in [10029, 100]: # Close dialog + self.close() + + def onAction(self, action): + if action.getId() in self.action_exit_keys_id: + self.close() + return + # Bad thing to check for changes in this way, but i have not found any other ways + slider_value = self.getControl(10004).getInt() + if slider_value != self.current_level_index: + self._update_status_desc(slider_value) + + def _update_status_desc(self, new_level_index=None): + self.current_level_index = self.getControl(10004).getInt() if new_level_index is None else new_level_index + # Update labels color of slider steps + for index in range(0, self.levels_count): + maturity_name = f'[{self.rating_levels[index]["label"]}]' + ml_label = f'[COLOR red]{maturity_name}[/COLOR]' if index <= self.current_level_index else maturity_name + self.controls[index].setLabel(ml_label) + # Update status description + hint = self.rating_levels[self.current_level_index]['description'] + ml_labels_included = [self.rating_levels[index]['label'] for index in range(0, self.current_level_index + 1)] + status_desc = self.status_base_desc.format(', '.join(ml_labels_included)) + f'[CR]{hint}' + self.getControl(10009).setLabel(status_desc) + + # def _validate_pin(self, pin_value): + # if len(pin_value or '') != 4: + # show_ok_dialog('PIN', G.ADDON.getLocalizedString(30106)) + # return False + # return True + + def _generate_levels_labels(self): + """Generate descriptions for the levels dynamically""" + # Limit to 1200 px max (should be longer than slider) + width = int(1200 / self.levels_count) + height = 100 + pos_x = 175 + pos_y = 508 # 668 + for index, rating_level in enumerate(self.rating_levels): + current_x = pos_x + (width * index) + maturity_name = f'[{rating_level["label"]}]' + lbl = xbmcgui.ControlLabel(current_x, pos_y, width, height, maturity_name, + font='font10', + alignment=XBFONT_CENTER_X) + self.controls.update({index: lbl}) + self.addControl(lbl) diff --git a/resources/lib/kodi/ui/xmldialog_profiles.py b/resources/lib/kodi/ui/xmldialog_profiles.py new file mode 100644 index 000000000..298e71a78 --- /dev/null +++ b/resources/lib/kodi/ui/xmldialog_profiles.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2020 Stefano Gottardo (original implementation module) + XML based dialog + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +import xbmcgui + +ACTION_PREVIOUS_MENU = 10 +ACTION_PLAYER_STOP = 13 +ACTION_NAV_BACK = 92 + + +# pylint: disable=invalid-name,no-member +class Profiles(xbmcgui.WindowXMLDialog): + """Dialog for profile selection""" + + def __init__(self, *args, **kwargs): + self.ctrl_list = None + self.return_value = None + self.title = kwargs['title'] + self.dir_items = kwargs['dir_items'] + self.preselect_guid = kwargs.get('preselect_guid') + self.action_exit_keys_id = [ACTION_PREVIOUS_MENU, + ACTION_PLAYER_STOP, + ACTION_NAV_BACK] + super().__init__(*args) + + def onInit(self): + # Keep only the ListItem objects + list_items = [list_item for (_, list_item, _) in self.dir_items] + self.getControl(99).setLabel(self.title) + self.ctrl_list = self.getControl(10001) + self.ctrl_list.addItems(list_items) + # Preselect the ListItem by guid + self.ctrl_list.selectItem(0) + if self.preselect_guid: + for index, list_item in enumerate(list_items): + if list_item.getProperty('nf_guid') == self.preselect_guid: + self.ctrl_list.selectItem(index) + break + self.setFocusId(10001) + + def onClick(self, controlId): + if controlId == 10001: # Save and close dialog + sel_list_item = self.ctrl_list.getSelectedItem() + # 'nf_guid' property is set to Listitems from _create_profile_item of dir_builder_items.py + self.return_value = sel_list_item.getProperty('nf_guid') + self.close() + if controlId in [10029, 100]: # Close + self.close() + + def onAction(self, action): + if action.getId() in self.action_exit_keys_id: + self.close() diff --git a/resources/lib/kodi/ui/xmldialog_ratingthumb.py b/resources/lib/kodi/ui/xmldialog_ratingthumb.py new file mode 100644 index 000000000..616737263 --- /dev/null +++ b/resources/lib/kodi/ui/xmldialog_ratingthumb.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2020 Stefano Gottardo (original implementation module) + XML based dialog + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +import xbmcgui + +ACTION_PREVIOUS_MENU = 10 +ACTION_PLAYER_STOP = 13 +ACTION_NAV_BACK = 92 +ACTION_NOOP = 999 + + +# pylint: disable=invalid-name,no-member +class RatingThumb(xbmcgui.WindowXMLDialog): + """Dialog for rating a tv show or movie""" + + def __init__(self, *args, **kwargs): + self.videoid = kwargs['videoid'] + self.track_id_jaw = kwargs['track_id_jaw'] + self.title = kwargs.get('title', '--') + self.user_rating = kwargs.get('user_rating', 0) + # Netflix user rating thumb values + # 0 = No rated + # 1 = thumb down + # 2 = thumb up + self.action_exit_keys_id = [ACTION_PREVIOUS_MENU, + ACTION_PLAYER_STOP, + ACTION_NAV_BACK] + super().__init__(*args) + + def onInit(self): + self.getControl(10000).setLabel(self.title) + # Kodi does not allow to change button textures in runtime + # and you can not add nested controls via code, + # so the only alternative is to create double XML buttons + # and eliminate those that are not needed + focus_id = 10010 + if self.user_rating == 0: # No rated + self.removeControl(self.getControl(10012)) + self.removeControl(self.getControl(10022)) + if self.user_rating == 1: # Thumb down set + self.removeControl(self.getControl(10012)) + self.removeControl(self.getControl(10020)) + self.getControl(10010).controlRight(self.getControl(10022)) + self.getControl(10040).controlLeft(self.getControl(10022)) + if self.user_rating == 2: # Thumb up set + focus_id = 10012 + self.removeControl(self.getControl(10010)) + self.removeControl(self.getControl(10022)) + self.getControl(10020).controlLeft(self.getControl(10012)) + self.setFocusId(focus_id) + + def onClick(self, controlId): + if controlId in [10010, 10020, 10012, 10022]: # Rating and close + rating_map = {10010: 2, 10020: 1, 10012: 0, 10022: 0} + rating_value = rating_map[controlId] + from resources.lib.utils.api_requests import rate_thumb + rate_thumb(self.videoid, rating_value, self.track_id_jaw) + self.close() + if controlId in [10040, 100]: # Close + self.close() + + def onAction(self, action): + if action.getId() in self.action_exit_keys_id: + self.close() diff --git a/resources/lib/kodi/ui/xmldialogs.py b/resources/lib/kodi/ui/xmldialogs.py index 29cd7e10c..f66d5e86c 100644 --- a/resources/lib/kodi/ui/xmldialogs.py +++ b/resources/lib/kodi/ui/xmldialogs.py @@ -1,31 +1,47 @@ # -*- coding: utf-8 -*- -# pylint: disable=invalid-name,missing-docstring,attribute-defined-outside-init -"""XML based dialogs""" -from __future__ import absolute_import, division, unicode_literals - +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + XML based dialogs + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" import time -from platform import machine import xbmc import xbmcgui +from resources.lib.common import run_threaded, make_call +from resources.lib.globals import G + + ACTION_PREVIOUS_MENU = 10 ACTION_PLAYER_STOP = 13 ACTION_NAV_BACK = 92 ACTION_NOOP = 999 -OS_MACHINE = machine() - CMD_CLOSE_DIALOG_BY_NOOP = 'AlarmClock(closedialog,Action(noop),{},silent)' -def show_modal_dialog(dlg_class, xml, path, **kwargs): +# @time_execution(immediate=True) +def show_modal_dialog(non_blocking, dlg_class, xml_filename, **kwargs): """ Show a modal Dialog in the UI. Pass kwargs minutes and/or seconds to have the dialog automatically close after the specified time. + + :return if exists return self.return_value value of dlg_class (if non_blocking=True return always None) """ - dlg = dlg_class(xml, path, 'default', '1080i', **kwargs) + # WARNING: doModal when invoked does not release the function immediately! + # it seems that doModal waiting for all window operations to be completed before return, + # for example the "Skip" dialog takes about 30 seconds to release the function (probably for the included animation) + # To be taken into account because it can do very big delays in the execution of the invoking code + return run_threaded(non_blocking, _show_modal_dialog, dlg_class, xml_filename, **kwargs) + + +def _show_modal_dialog(dlg_class, xml_filename, **kwargs): + dlg = dlg_class(xml_filename, G.ADDON.getAddonInfo('path'), 'default', '1080i', **kwargs) minutes = kwargs.get('minutes', 0) seconds = kwargs.get('seconds', 0) if minutes > 0 or seconds > 0: @@ -34,56 +50,95 @@ def show_modal_dialog(dlg_class, xml, path, **kwargs): if seconds > 59 and minutes == 0: alarm_time = time.strftime('%M:%S', time.gmtime(seconds)) else: - alarm_time = '{:02d}:{:02d}'.format(minutes, seconds) + alarm_time = f'{minutes:02d}:{seconds:02d}' xbmc.executebuiltin(CMD_CLOSE_DIALOG_BY_NOOP.format(alarm_time)) dlg.doModal() + if hasattr(dlg, 'return_value'): + return dlg.return_value + return None +# pylint: disable=invalid-name class Skip(xbmcgui.WindowXMLDialog): - """ - Dialog for skipping video parts (intro, recap, ...) - """ + """Dialog for skipping video parts (intro, recap, ...)""" + def __init__(self, *args, **kwargs): - self.skip_to = kwargs['skip_to'] + self.seek_time = kwargs['seek_time'] self.label = kwargs['label'] - - self.action_exitkeys_id = [ACTION_PREVIOUS_MENU, - ACTION_PLAYER_STOP, - ACTION_NAV_BACK, - ACTION_NOOP] - - if OS_MACHINE[0:5] == 'armv7': - xbmcgui.WindowXMLDialog.__init__(self) - else: - xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) + self.action_exit_keys_id = [ACTION_PREVIOUS_MENU, + ACTION_PLAYER_STOP, + ACTION_NAV_BACK, + ACTION_NOOP] + super().__init__(*args) def onInit(self): - self.getControl(6012).setLabel(self.label) + self.getControl(6012).setLabel(self.label) # pylint: disable=no-member - def onClick(self, controlID): - if controlID == 6012: - xbmc.Player().seekTime(self.skip_to) + def onClick(self, controlId): + if controlId == 6012: + xbmc.Player().seekTime(self.seek_time) self.close() def onAction(self, action): - if action.getId() in self.action_exitkeys_id: + if action.getId() in self.action_exit_keys_id: self.close() -class SaveStreamSettings(xbmcgui.WindowXMLDialog): - """ - Dialog for skipping video parts (intro, recap, ...) - """ - def __init__(self, *args, **kwargs): # pylint: disable=super-on-old-class - super(SaveStreamSettings, self).__init__(*args, **kwargs) - self.new_show_settings = kwargs['new_show_settings'] - self.tvshowid = kwargs['tvshowid'] - self.storage = kwargs['storage'] +def show_skip_dialog(dialog_duration, seek_time, label): + """Show a dialog for ESN and Widevine settings""" + show_modal_dialog(True, + Skip, + "plugin-video-netflix-Skip.xml", + seconds=dialog_duration, + seek_time=seek_time, + label=label) - def onInit(self): - self.action_exitkeys_id = [10, 13] - def onClick(self, controlID): - if controlID == 6012: - self.storage[self.tvshowid] = self.new_show_settings - self.close() +def show_parental_dialog(**kwargs): + """Show a dialog for parental control settings""" + from resources.lib.kodi.ui.xmldialog_parental import ParentalControl + show_modal_dialog(False, + ParentalControl, + 'plugin-video-netflix-ParentalControl.xml', + **kwargs) + + +def show_rating_thumb_dialog(**kwargs): + """Show a dialog for rating with thumb""" + from resources.lib.kodi.ui.xmldialog_ratingthumb import RatingThumb + show_modal_dialog(False, + RatingThumb, + 'plugin-video-netflix-RatingThumb.xml', + **kwargs) + + +def show_profiles_dialog(title=None, title_prefix=None, preselect_guid=None): + """ + Show a dialog to select a profile + :return guid of selected profile or None + """ + if not title: + title = G.ADDON.getLocalizedString(30128) + if title_prefix: + title = f'{title_prefix} - {title}' + # Get profiles data + # pylint: disable=unused-variable + dir_items, extra_data = make_call('get_profiles', + {'request_update': True, + 'preselect_guid': preselect_guid, + 'detailed_info': False}) + from resources.lib.kodi.ui.xmldialog_profiles import Profiles + return show_modal_dialog(False, + Profiles, + 'plugin-video-netflix-Profiles.xml', + title=title, + dir_items=dir_items, + preselect_guid=preselect_guid) + + +def show_esn_widevine_dialog(): + """Show a dialog for ESN and Widevine settings""" + from resources.lib.kodi.ui.xmldialog_esnwidevine import ESNWidevine + return show_modal_dialog(False, + ESNWidevine, + 'plugin-video-netflix-ESN-Widevine.xml') diff --git a/resources/lib/navigation/__init__.py b/resources/lib/navigation/__init__.py index d1f84a8c7..c6723a588 100644 --- a/resources/lib/navigation/__init__.py +++ b/resources/lib/navigation/__init__.py @@ -1,21 +1,6 @@ # -*- coding: utf-8 -*- -"""Navigation handling""" -from __future__ import absolute_import, division, unicode_literals +# Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) +# Copyright (C) 2018 Caphm (original implementation module) -import resources.lib.common as common - - -class InvalidPathError(Exception): - """The requested path is invalid and could not be routed""" - pass - - -def execute(executor_type, pathitems, params): - """Execute an action as specified by the path""" - try: - executor = executor_type(params).__getattribute__( - pathitems[0] if pathitems else 'root') - except AttributeError: - raise InvalidPathError('Unknown action {}'.format('/'.join(pathitems))) - common.debug('Invoking action executor {}'.format(executor.__name__)) - executor(pathitems=pathitems) +# SPDX-License-Identifier: MIT +# See LICENSES/MIT.md for more information. diff --git a/resources/lib/navigation/actions.py b/resources/lib/navigation/actions.py index 11f4c52aa..75ecf5a78 100644 --- a/resources/lib/navigation/actions.py +++ b/resources/lib/navigation/actions.py @@ -1,112 +1,295 @@ # -*- coding: utf-8 -*- -"""Navigation handler for actions""" -from __future__ import absolute_import, division, unicode_literals +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Navigation handler for actions + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" import xbmc +import xbmcgui -from resources.lib.globals import g import resources.lib.common as common -import resources.lib.api.shakti as api import resources.lib.kodi.ui as ui +import resources.lib.utils.api_requests as api +from resources.lib.common import cache_utils +from resources.lib.common.cache_utils import CACHE_BOOKMARKS +from resources.lib.common.exceptions import MissingCredentialsError, CacheMiss +from resources.lib.globals import G +from resources.lib.kodi.library import get_library_cls +from resources.lib.utils.api_paths import VIDEO_LIST_RATING_THUMB_PATHS, SUPPLEMENTAL_TYPE_TRAILERS +from resources.lib.utils.logging import LOG, measure_exec_time_decorator -class AddonActionExecutor(object): +class AddonActionExecutor: """Executes actions""" # pylint: disable=no-self-use def __init__(self, params): - common.debug('Initializing AddonActionExecutor: {}' - .format(params)) + LOG.debug('Initializing "AddonActionExecutor" with params: {}', params) self.params = params - def logout(self, pathitems=None): + def logout(self, pathitems=None): # pylint: disable=unused-argument """Perform account logout""" - # pylint: disable=unused-argument - api.logout() + if ui.ask_for_confirmation(common.get_local_string(30017), + common.get_local_string(30016)): + api.logout() - def save_autologin(self, pathitems): - """Save autologin data""" - try: - g.settings_monitor_suspended(True) - g.ADDON.setSetting('autologin_user', - self.params['autologin_user']) - g.ADDON.setSetting('autologin_id', pathitems[1]) - g.ADDON.setSetting('autologin_enable', 'true') - g.settings_monitor_suspended(False) - except (KeyError, IndexError): - common.error('Cannot save autologin - invalid params') - g.CACHE.invalidate() - common.refresh_container() - - def toggle_adult_pin(self, pathitems=None): - """Toggle adult PIN verification""" - # pylint: disable=no-member, unused-argument - pin = ui.ask_for_pin() - if pin is None: - return - if api.verify_pin(pin): - current_setting = {'true': True, 'false': False}.get( - g.ADDON.getSetting('adultpin_enable').lower()) - g.settings_monitor_suspended(True) - g.ADDON.setSetting('adultpin_enable', str(not current_setting)) - g.settings_monitor_suspended(False) - g.flush_settings() - ui.show_notification( - common.get_local_string(30107 if current_setting else 30108)) + def profile_autoselect(self, pathitems): # pylint: disable=unused-argument + """Set or remove the GUID for profile auto-selection when addon start-up""" + if self.params['operation'] == 'set': + G.LOCAL_DB.set_value('autoselect_profile_guid', self.params['profile_guid']) + else: + G.LOCAL_DB.set_value('autoselect_profile_guid', '') + common.container_refresh() + + def profile_autoselect_library(self, pathitems): # pylint: disable=unused-argument + """Set or remove the GUID for profile auto-selection for playback from Kodi library""" + if self.params['operation'] == 'set': + G.LOCAL_DB.set_value('library_playback_profile_guid', self.params['profile_guid']) else: - ui.show_notification(common.get_local_string(30106)) + G.LOCAL_DB.set_value('library_playback_profile_guid', '') + common.container_refresh() + + def profile_remember_pin(self, pathitems): # pylint: disable=unused-argument + """Set whether to remember the profile PIN""" + is_remember_pin = not G.LOCAL_DB.get_profile_config('addon_remember_pin', False, + guid=self.params['profile_guid']) + if is_remember_pin: + from resources.lib.navigation.directory_utils import verify_profile_pin + if not verify_profile_pin(self.params['profile_guid'], is_remember_pin): + ui.show_notification(common.get_local_string(30106), time=8000) + return + G.LOCAL_DB.set_profile_config('addon_remember_pin', is_remember_pin, guid=self.params['profile_guid']) + if not is_remember_pin: + G.LOCAL_DB.set_profile_config('addon_pin', '', guid=self.params['profile_guid']) + common.container_refresh() + + def parental_control(self, pathitems=None): # pylint: disable=unused-argument + """Open parental control settings dialog""" + password = ui.ask_for_password() + if not password: + return + try: + parental_control_data = api.get_parental_control_data(self.params['profile_guid'], + password) + ui.show_parental_dialog(**parental_control_data) + except MissingCredentialsError: + ui.show_ok_dialog('Netflix', common.get_local_string(30009)) @common.inject_video_id(path_offset=1) - @common.time_execution(immediate=False) - def rate(self, videoid): - """Rate an item on Netflix. Ask for a rating if there is none supplied - in the path.""" - rating = self.params.get('rating') or ui.ask_for_rating() - if rating is not None: - api.rate(videoid, rating) + @measure_exec_time_decorator() + def rate_thumb(self, videoid): + """Rate an item on Netflix. Ask for a thumb rating""" + # Get updated user rating info for this videoid + raw_data = api.get_video_raw_data([videoid], VIDEO_LIST_RATING_THUMB_PATHS) + if raw_data.get('videos', {}).get(videoid.value): + video_data = raw_data['videos'][videoid.value] + title = video_data.get('title', {}).get('value', '--') + # Is intended throw error when missing some dict data + track_id_jaw = video_data['trackIds']['value']['trackId_jaw'] + is_thumb_rating = video_data['userRating'].get('value', {}).get('type') == 'thumb' + user_rating = video_data['userRating']['value']['userRating'] if is_thumb_rating else None + ui.show_rating_thumb_dialog(videoid=videoid, + title=title, + track_id_jaw=track_id_jaw, + user_rating=user_rating) + else: + LOG.warn('Rating thumb video list api request no got results for {}', videoid) + + # Old rating system + # @common.inject_video_id(path_offset=1) + # @common.time_execution(immediate=False) + # def rate(self, videoid): + # """Rate an item on Netflix. Ask for a rating if there is none supplied + # in the path.""" + # rating = self.params.get('rating') or ui.ask_for_rating() + # if rating is not None: + # api.rate(videoid, rating) @common.inject_video_id(path_offset=2, inject_remaining_pathitems=True) - @common.time_execution(immediate=False) + @measure_exec_time_decorator() def my_list(self, videoid, pathitems): """Add or remove an item from my list""" operation = pathitems[1] - api.update_my_list(videoid, operation) - _sync_library(videoid, operation) - common.refresh_container() + api.update_my_list(videoid, operation, self.params) + sync_library(videoid, operation) + if operation == 'remove' and common.WndHomeProps[common.WndHomeProps.CURRENT_DIRECTORY_MENU_ID] == 'myList': + common.json_rpc('Input.Down') # Avoids selection back to the top + common.container_refresh() + + @common.inject_video_id(path_offset=1) + def remind_me(self, videoid): + """Add or remove an item to 'remind me' feature""" + # This functionality is used with videos that are not available, + # allows you to automatically add the title to my list as soon as it becomes available. + operation = self.params['operation'] + G.CACHE.add(CACHE_BOOKMARKS, f'is_in_remind_me_{videoid}', bool(operation == 'add')) + api.update_remindme(operation, videoid, self.params['trackid']) + common.container_refresh() @common.inject_video_id(path_offset=1) - @common.time_execution(immediate=False) + @measure_exec_time_decorator() def trailer(self, videoid): """Get the trailer list""" - video_list = api.supplemental_video_list(videoid, 'trailers') - if video_list.videos: - url = common.build_url(['supplemental', videoid.value, videoid.mediatype, 'trailers'], - mode=g.MODE_DIRECTORY) - xbmc.executebuiltin('Container.Update({})'.format(url)) + from json import dumps + menu_data = {'path': ['is_context_menu_item', 'is_context_menu_item'], # Menu item do not exists + 'title': common.get_local_string(30179)} + video_id_dict = videoid.to_dict() + list_data, extra_data = common.make_call('get_video_list_supplemental', # pylint: disable=unused-variable + { + 'menu_data': menu_data, + 'video_id_dict': video_id_dict, + 'supplemental_type': SUPPLEMENTAL_TYPE_TRAILERS + }) + if list_data: + url = common.build_url(['supplemental'], + params={'video_id_dict': dumps(video_id_dict), + 'supplemental_type': SUPPLEMENTAL_TYPE_TRAILERS}, + mode=G.MODE_DIRECTORY) + common.container_update(url) + else: + ui.show_notification(common.get_local_string(30111)) + + @measure_exec_time_decorator() + def purge_cache(self, pathitems=None): # pylint: disable=unused-argument + """Clear the cache. If on_disk param is supplied, also clear cached items from disk""" + _on_disk = self.params.get('on_disk', False) + G.CACHE.clear(clear_database=_on_disk) + if _on_disk: + G.SHARED_DB.clear_stream_continuity() + ui.show_notification(common.get_local_string(30135)) + + def force_update_list(self, pathitems=None): # pylint: disable=unused-argument + """Clear the cache of my list to force the update""" + if self.params['menu_id'] == 'myList': + G.CACHE.clear([cache_utils.CACHE_MYLIST], clear_database=False) + if self.params['menu_id'] == 'continueWatching': + # Delete the cache of continueWatching list + # pylint: disable=unused-variable + is_exists, list_id = common.make_call('get_continuewatching_videoid_exists', {'video_id': ''}) + if list_id: + G.CACHE.delete(cache_utils.CACHE_COMMON, list_id, including_suffixes=True) + # When the continueWatching context is invalidated from a refreshListByContext call + # the LoCo need to be updated to obtain the new list id, so we delete the cache to get new data + G.CACHE.delete(cache_utils.CACHE_COMMON, 'loco_list') + + def show_esn_widevine_options(self, pathitems=None): # pylint: disable=unused-argument + # Deny opening of the dialog when the user is not logged + if not common.check_credentials(): + ui.show_notification(common.get_local_string(30112)) + return + ui.show_esn_widevine_dialog() + + @common.inject_video_id(path_offset=1) + def change_watched_status(self, videoid): + """Change the watched status of a video, only when sync of watched status with NF is enabled""" + change_watched_status_locally(videoid) + + def configuration_wizard(self, pathitems=None): # pylint: disable=unused-argument + """Run the add-on configuration wizard""" + from resources.lib.config_wizard import run_addon_configuration + run_addon_configuration(restore=True) + + @common.inject_video_id(path_offset=1) + def remove_watched_status(self, videoid): + """Remove the watched status from the Netflix service""" + if not ui.ask_for_confirmation(common.get_local_string(30168), + common.get_local_string(30300).format(xbmc.getInfoLabel('ListItem.Label'))): + return + api.remove_watched_status(videoid) + # Check if item is in the cache + videoid_exists, list_id = common.make_call('get_continuewatching_videoid_exists', + {'video_id': str(videoid.value)}) + if videoid_exists: + # Try to remove the videoid from the list in the cache + try: + video_list_sorted_data = G.CACHE.get(cache_utils.CACHE_COMMON, list_id) + del video_list_sorted_data.videos[videoid.value] + G.CACHE.add(cache_utils.CACHE_COMMON, list_id, video_list_sorted_data) + common.json_rpc('Input.Down') # Avoids selection back to the top + except CacheMiss: + pass + common.container_refresh() + + @common.inject_video_id(path_offset=1) + def show_availability_message(self, videoid): # pylint: disable=unused-argument + """Show a message to the user to show the date of availability of a video""" + # Try get the promo trailer path + trailer_path = xbmc.getInfoLabel('ListItem.Trailer') + msg = common.get_local_string(30620).format( + xbmc.getInfoLabel('ListItem.Property(nf_availability_message)') or '--') + if trailer_path: + if ui.show_yesno_dialog(xbmc.getInfoLabel('ListItem.Label'), + f'{msg}[CR]{common.get_local_string(30621)}', + default_yes_button=True): + # Create a basic trailer ListItem (all other info if available are set on Play callback) + list_item = xbmcgui.ListItem(xbmc.getInfoLabel('ListItem.Title'), + path=trailer_path, + offscreen=True) + list_item.setInfo('video', {'Title': xbmc.getInfoLabel('ListItem.Title')}) + list_item.setProperty('isPlayable', 'true') + if G.KODI_VERSION < 21: + xbmc.Player().play(trailer_path, list_item) + else: + # Due to a kodi bug if you use xbmc.Player().play Kodi crashes + # this due to Kodi GUI dialog bug, details on issue: https://github.com/xbmc/xbmc/issues/24526 + # To reproduce: set ISAdaptive setting "Stream selection type" to "Ask quality" and play + from xbmcplugin import setResolvedUrl + setResolvedUrl(handle=G.PLUGIN_HANDLE, succeeded=True, listitem=list_item) + else: + from xbmcplugin import endOfDirectory + endOfDirectory(handle=G.PLUGIN_HANDLE, succeeded=True) else: - ui.show_notification(common.get_local_string(30180)) - - @common.time_execution(immediate=False) - def purge_cache(self, pathitems=None): - """Clear the cache. If on_disk param is supplied, also clear cached - items from disk""" - # pylint: disable=unused-argument - g.CACHE.invalidate(self.params.get('on_disk', False)) - if self.params.get('on_disk', False): - common.delete_file('resources.lib.services.playback.stream_continuity.ndb') - if not self.params.get('no_notification', False): - ui.show_notification(common.get_local_string(30135)) - - -def _sync_library(videoid, operation): - operation = { - 'add': 'export_silent', - 'remove': 'remove_silent'}.get(operation) - if operation and g.ADDON.getSettingBool('mylist_library_sync'): - common.debug('Syncing library due to change of my list') - # We need to wait a little before syncing the library to prevent race - # conditions with the Container refresh - common.schedule_builtin( - '00:03', - common.run_plugin_action( - common.build_url([operation], videoid, mode=g.MODE_LIBRARY)), - name='NetflixLibrarySync') + ui.show_ok_dialog(xbmc.getInfoLabel('ListItem.Label'), msg) + + def profile_options(self, pathitems=None): # pylint: disable=unused-argument + ui.show_ok_dialog('Netflix', common.get_local_string(30013)) + + def show_support_info(self, pathitems=None): # pylint: disable=unused-argument + text = f'[B]Netflix add-on[/B] version {G.VERSION}\n\n' + text += ('This is an unofficial Netflix platform for watching Netflix content on Kodi, ' + 'this project is neither commissioned nor supported by the Netflix company, ' + 'so we invest our free time to provide constant updates for your entertainment.\n\n' + 'If you enjoy using this add-on, please consider supporting our efforts with a donation.\n\n') + text += ('[B]Homepage:[/B] http://tiny.one/netflix-kodi-addon\n' + '[B]Wiki:[/B] http://tiny.one/netflix-addon-wiki\n' + '[B]Forum:[/B] http://tiny.one/netflix-addon-forum\n' + '[B]For donations:[/B] See [I]Sponsor links[/I] on homepage or open https://beacons.ai/castagnait') + from xbmcgui import Dialog + Dialog().textviewer(heading=common.get_local_string(30735), text=text) + + +def sync_library(videoid, operation): + if (operation + and G.ADDON.getSettingBool('lib_enabled') + and G.ADDON.getSettingBool('lib_sync_mylist') + and G.ADDON.getSettingInt('lib_auto_upd_mode') in [0, 2]): + sync_mylist_profile_guid = G.SHARED_DB.get_value('sync_mylist_profile_guid', + G.LOCAL_DB.get_guid_owner_profile()) + # Allow to sync library with My List only by chosen profile + if sync_mylist_profile_guid != G.LOCAL_DB.get_active_profile_guid(): + return + LOG.debug('Syncing library due to change of My list') + if operation == 'add': + get_library_cls().export_to_library(videoid, False) + elif operation == 'remove': + get_library_cls().remove_from_library(videoid, False) + + +def change_watched_status_locally(videoid): + """Change the watched status locally""" + # Todo: how get resumetime/playcount of selected item for calculate current watched status? + profile_guid = G.LOCAL_DB.get_active_profile_guid() + current_value = G.SHARED_DB.get_watched_status(profile_guid, videoid.value, None, bool) + if current_value: + txt_index = 1 + G.SHARED_DB.set_watched_status(profile_guid, videoid.value, False) + elif current_value is not None and not current_value: + txt_index = 2 + G.SHARED_DB.delete_watched_status(profile_guid, videoid.value) + else: + txt_index = 0 + G.SHARED_DB.set_watched_status(profile_guid, videoid.value, True) + ui.show_notification(common.get_local_string(30237).split('|')[txt_index]) + common.container_refresh() diff --git a/resources/lib/navigation/directory.py b/resources/lib/navigation/directory.py index 63c1e1420..95a3ec741 100644 --- a/resources/lib/navigation/directory.py +++ b/resources/lib/navigation/directory.py @@ -1,211 +1,312 @@ # -*- coding: utf-8 -*- -"""Navigation for classic plugin directory listing mode""" -from __future__ import absolute_import, division, unicode_literals +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Navigation for classic plugin directory listing mode -import xbmc + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" import xbmcplugin -from resources.lib.database.db_utils import (TABLE_MENU_DATA) -from resources.lib.globals import g import resources.lib.common as common -import resources.lib.api.shakti as api -import resources.lib.kodi.listings as listings +import resources.lib.kodi.library_utils as lib_utils import resources.lib.kodi.ui as ui -import resources.lib.kodi.library as library +from resources.lib.database.db_utils import TABLE_MENU_DATA +from resources.lib.globals import G +from resources.lib.navigation.directory_utils import (finalize_directory, custom_viewmode, + end_of_directory, get_title, activate_profile, auto_scroll) +from resources.lib.utils.logging import LOG, measure_exec_time_decorator -class DirectoryBuilder(object): - """Builds directory listings""" - # pylint: disable=no-self-use +# What means dynamic menus (and dynamic id): +# Are considered dynamic menus all menus which context name do not exists in the 'loco_contexts' of +# MAIN_MENU_ITEMS items in globals.py. +# These menus are generated on the fly (they are not hardcoded) and their data references are saved in TABLE_MENU_DATA +# as menu item (with same structure of MAIN_MENU_ITEMS items in globals.py) + +# The same TABLE_MENU_DATA table is used to temporary store the title of menus of the main menu which can change +# dynamically according to the language set by the profile, and it is the most practical way to get the title +# when opening a menu + +# The 'pathitems': +# It should match the 'path' key in MAIN_MENU_ITEMS of globals.py (or when not listed the dynamic menu item) +# the indexes are: 0 the function name of this 'Directory' class, 1 the menu id, 2 an optional id + + +class Directory: + """Directory listings""" + def __init__(self, params): - common.debug('Initializing directory builder: {}'.format(params)) + LOG.debug('Initializing "Directory" with params: {}', params) self.params = params # After build url the param value is converted as string - self.perpetual_range_start = None \ - if self.params.get('perpetual_range_start') == 'None' else self.params.get('perpetual_range_start') - self.dir_update_listing = True if self.perpetual_range_start else False + self.perpetual_range_start = (None if self.params.get('perpetual_range_start') == 'None' + else self.params.get('perpetual_range_start')) + if 'dir_update_listing' in self.params: + self.dir_update_listing = self.params['dir_update_listing'] == 'True' + else: + self.dir_update_listing = bool(self.perpetual_range_start) if self.perpetual_range_start == '0': # For cache identifier purpose self.perpetual_range_start = None - profile_id = params.get('profile_id') - if profile_id: - api.activate_profile(profile_id) - - def root(self, pathitems=None): - """Show profiles or home listing is autologin es enabled""" - # pylint: disable=unused-argument - autologin = g.ADDON.getSettingBool('autologin_enable') - profile_id = g.ADDON.getSetting('autologin_id') - if autologin and profile_id: - common.debug('Performing auto-login for selected profile {}' - .format(profile_id)) - api.activate_profile(profile_id) - self.home(None, False) - else: - self.profiles() - def profiles(self, pathitems=None): - """Show profiles listing""" - # pylint: disable=unused-argument - common.debug('Showing profiles listing') - if not api.update_profiles_data(): - xbmcplugin.endOfDirectory(g.PLUGIN_HANDLE, succeeded=False) - return - listings.build_profiles_listing() - _handle_endofdirectory(False, False) + def root(self, pathitems=None): # pylint: disable=unused-argument + """Show profiles or home listing when profile auto-selection is enabled""" + # Fetch initial page to refresh all session data + current_directory = common.WndHomeProps[common.WndHomeProps.CURRENT_DIRECTORY] + if not current_directory: + # Note when the profiles are updated to the database (by fetch_initial_page call), + # the update sanitize also relative settings to profiles (see _delete_non_existing_profiles in website.py) + common.make_call('fetch_initial_page') + # When the add-on is used in a browser window, we do not have to execute the auto profile selection + if not G.IS_ADDON_EXTERNAL_CALL: + autoselect_profile_guid = G.LOCAL_DB.get_value('autoselect_profile_guid', '') + if autoselect_profile_guid and not common.WndHomeProps[common.WndHomeProps.IS_CONTAINER_REFRESHED]: + if not current_directory: + LOG.info('Performing auto-selection of profile {}', autoselect_profile_guid) + self.params['switch_profile_guid'] = autoselect_profile_guid + self.home(None) + return + dir_items, extra_data = common.make_call('get_profiles', {'request_update': False}) + self._profiles(dir_items, extra_data) - @common.time_execution(immediate=False) - def home(self, pathitems=None, cache_to_disc=True): - """Show home listing""" - # pylint: disable=unused-argument - common.debug('Showing root video lists') - listings.build_main_menu_listing(api.root_lists()) - _handle_endofdirectory(False, cache_to_disc) + def profiles(self, pathitems=None): # pylint: disable=unused-argument + """Show profiles listing""" + LOG.debug('Showing profiles listing') + dir_items, extra_data = common.make_call('get_profiles', {'request_update': True}) + self._profiles(dir_items, extra_data) - @common.time_execution(immediate=False) - def video_list(self, pathitems): - """Show a video list with a listid request""" - menu_data = g.MAIN_MENU_ITEMS.get(pathitems[1]) - if not menu_data: - menu_data = g.LOCAL_DB.get_value(pathitems[1], table=TABLE_MENU_DATA, data_type=dict) - if g.is_known_menu_context(pathitems[2]): - list_id = api.list_id_for_type(menu_data['lolomo_contexts'][0]) - listings.build_video_listing(api.video_list(list_id), menu_data) - else: - # Dynamic IDs from generated sub-menu - list_id = pathitems[2] - listings.build_video_listing(api.video_list(list_id), menu_data) - _handle_endofdirectory(False) + @custom_viewmode(G.VIEW_PROFILES) + def _profiles(self, dir_items, extra_data): # pylint: disable=unused-argument + # The standard kodi theme does not allow to change view type if the content is "files" type, + # so here we use "images" type, visually better to see + finalize_directory(dir_items, G.CONTENT_IMAGES) + end_of_directory(True) - @common.time_execution(immediate=False) - def video_list_sorted(self, pathitems): - """Show a video list with a sorted request""" - menu_data = g.MAIN_MENU_ITEMS.get(pathitems[1]) - if not menu_data: - menu_data = g.LOCAL_DB.get_value(pathitems[1], table=TABLE_MENU_DATA, data_type=dict) - mainmenu_data = menu_data.copy() - # If the menu is a sub-menu, we get the parameters of the main menu - if menu_data.get('main_menu'): - mainmenu_data = menu_data['main_menu'] - if menu_data.get('request_context_name', None) and g.is_known_menu_context(pathitems[2]): - listings.build_video_listing( - api.video_list_sorted(context_name=menu_data['request_context_name'], - perpetual_range_start=self.perpetual_range_start, - menu_data=mainmenu_data), - menu_data, pathitems) - else: - # Dynamic IDs for common video lists - list_id = None if pathitems[2] == 'None' else pathitems[2] - listings.build_video_listing( - api.video_list_sorted(context_name=menu_data['request_context_name'], - context_id=list_id, - perpetual_range_start=self.perpetual_range_start, - menu_data=mainmenu_data), - menu_data, pathitems, self.params.get('genre_id')) - _handle_endofdirectory(self.dir_update_listing) + @measure_exec_time_decorator() + @custom_viewmode(G.VIEW_MAINMENU) + def home(self, pathitems=None): # pylint: disable=unused-argument + """Show home listing""" + if 'switch_profile_guid' in self.params: + if G.IS_ADDON_EXTERNAL_CALL: + # Profile switch/ask PIN only once + ret = not self.params['switch_profile_guid'] == G.LOCAL_DB.get_active_profile_guid() + else: + # Profile switch/ask PIN every time you come from ... + ret = common.WndHomeProps[common.WndHomeProps.CURRENT_DIRECTORY] in ['', 'root', 'profiles'] + if ret and not activate_profile(self.params['switch_profile_guid']): + xbmcplugin.endOfDirectory(G.PLUGIN_HANDLE, succeeded=False) + return + LOG.debug('Showing home listing') + dir_items, extra_data = common.make_call('get_mainmenu') # pylint: disable=unused-variable + finalize_directory(dir_items, G.CONTENT_FOLDER, + title=(G.LOCAL_DB.get_profile_config('profileName', '???') + + ' - ' + common.get_local_string(30097))) + end_of_directory(True) + @measure_exec_time_decorator() @common.inject_video_id(path_offset=0, inject_full_pathitems=True) - @common.time_execution(immediate=False) def show(self, videoid, pathitems): - """Show seasons of a tvshow""" if videoid.mediatype == common.VideoId.SEASON: - self.season(videoid, pathitems) + self._episodes(videoid, pathitems) else: - listings.build_season_listing(videoid, api.seasons(videoid), pathitems) - _handle_endofdirectory(self.dir_update_listing) + self._seasons(videoid, pathitems) - def season(self, videoid, pathitems): - """Show episodes of a season""" - listings.build_episode_listing(videoid, api.episodes(videoid), pathitems) - _handle_endofdirectory(self.dir_update_listing) + def _seasons(self, videoid, pathitems): + """Show the seasons list of a tv show""" + call_args = { + 'pathitems': pathitems, + 'tvshowid_dict': videoid.to_dict(), + 'perpetual_range_start': self.perpetual_range_start, + } + dir_items, extra_data = common.make_call('get_seasons', call_args) + if len(dir_items) == 1: + # If there is only one season, load and show the episodes now + pathitems = dir_items[0][0].replace(G.BASE_URL, '').strip('/').split('/')[1:] + videoid = common.VideoId.from_path(pathitems) + self._episodes(videoid, pathitems) + return + self._seasons_directory(dir_items, extra_data) - @common.time_execution(immediate=False) - def genres(self, pathitems): + @custom_viewmode(G.VIEW_SEASON) + def _seasons_directory(self, dir_items, extra_data): + finalize_directory(dir_items, G.CONTENT_SEASON, 'sort_only_label', + title=extra_data.get('title', '')) + end_of_directory(self.dir_update_listing) + + @custom_viewmode(G.VIEW_EPISODE) + def _episodes(self, videoid, pathitems): + """Show the episodes list of a season""" + call_args = { + 'pathitems': pathitems, + 'seasonid_dict': videoid.to_dict(), + 'perpetual_range_start': self.perpetual_range_start, + } + dir_items, extra_data = common.make_call('get_episodes', call_args) + finalize_directory(dir_items, G.CONTENT_EPISODE, 'sort_episodes', + title=extra_data.get('title', '')) + end_of_directory(self.dir_update_listing) + auto_scroll(dir_items) + + @measure_exec_time_decorator() + @custom_viewmode(G.VIEW_SHOW) + def video_list(self, pathitems): + """Show a video list of a list ID""" + menu_data = G.MAIN_MENU_ITEMS.get(pathitems[1]) + if not menu_data: # Dynamic menus + menu_data = G.LOCAL_DB.get_value(pathitems[1], table=TABLE_MENU_DATA, data_type=dict) + call_args = { + 'list_id': pathitems[2], + 'menu_data': menu_data, + 'is_dynamic_id': not G.is_known_menu_context(pathitems[2]) + } + dir_items, extra_data = common.make_call('get_video_list', call_args) + + finalize_directory(dir_items, menu_data.get('content_type', G.CONTENT_SHOW), + title=get_title(menu_data, extra_data)) + end_of_directory(False) + return menu_data.get('view') + + @measure_exec_time_decorator() + @custom_viewmode(G.VIEW_SHOW) + def video_list_sorted(self, pathitems): + """Show a video list sorted of a 'context' name""" + menu_data = G.MAIN_MENU_ITEMS.get(pathitems[1]) + if not menu_data: # Dynamic menus + menu_data = G.LOCAL_DB.get_value(pathitems[1], table=TABLE_MENU_DATA, data_type=dict) + call_args = { + 'pathitems': pathitems, + 'menu_data': menu_data, + 'sub_genre_id': self.params.get('sub_genre_id'), # Used to show the sub-genre folder when sub-genres exists + 'perpetual_range_start': self.perpetual_range_start, + 'is_dynamic_id': not G.is_known_menu_context(pathitems[2]) + } + dir_items, extra_data = common.make_call('get_video_list_sorted', call_args) + sort_type = 'sort_nothing' + if menu_data['path'][1] == 'myList' and int(G.ADDON.getSettingInt('menu_sortorder_mylist')) == 0: + # At the moment it is not possible to make a query with results sorted for the 'mylist', + # so we adding the sort order of kodi + sort_type = 'sort_label_ignore_folders' + + finalize_directory(dir_items, menu_data.get('content_type', G.CONTENT_SHOW), + title=get_title(menu_data, extra_data), sort_type=sort_type) + end_of_directory(self.dir_update_listing) + return menu_data.get('view') + + @measure_exec_time_decorator() + @custom_viewmode(G.VIEW_FOLDER) + def category_list(self, pathitems): + """Show a list of folders of a LoLoMo category""" + menu_data = G.MAIN_MENU_ITEMS.get(pathitems[1]) + call_args = { + 'menu_data': menu_data + } + dir_items, extra_data = common.make_call('get_category_list', call_args) + + finalize_directory(dir_items, menu_data.get('content_type', G.CONTENT_FOLDER), + title=get_title(menu_data, extra_data), sort_type='sort_label') + end_of_directory(self.dir_update_listing) + return menu_data.get('view') + + @measure_exec_time_decorator() + @custom_viewmode(G.VIEW_FOLDER) + def recommendations(self, pathitems): """Show video lists for a genre""" - menu_data = g.MAIN_MENU_ITEMS.get(pathitems[1]) - if not menu_data: - menu_data = g.LOCAL_DB.get_value(pathitems[1], table=TABLE_MENU_DATA, data_type=dict) - # pathitems indexes: 0 function name, 1 menu id, 2 optional id - if len(pathitems) < 3: - lolomo = api.root_lists() - listings.build_lolomo_listing(lolomo, menu_data) - else: - # Here is provided the id of the genre, eg. get sub-menus of tvshows (all tv show) - lolomo = api.genre(pathitems[2]) - listings.build_lolomo_listing(lolomo, menu_data, exclude_lolomo_known=True) - _handle_endofdirectory(False) + menu_data = G.MAIN_MENU_ITEMS.get(pathitems[1]) + call_args = { + 'menu_data': menu_data, + 'genre_id': None, + 'force_use_videolist_id': True, + } + dir_items, extra_data = common.make_call('get_genres', call_args) + + finalize_directory(dir_items, G.CONTENT_FOLDER, + title=get_title(menu_data, extra_data), sort_type='sort_label') + end_of_directory(False) + return menu_data.get('view') + @measure_exec_time_decorator() + @custom_viewmode(G.VIEW_SHOW) + def supplemental(self, pathitems): # pylint: disable=unused-argument + """Show supplemental video list (eg. trailers) of a tv show / movie""" + menu_data = {'path': ['is_context_menu_item', 'is_context_menu_item'], # Menu item do not exists + 'title': common.get_local_string(30179)} + from json import loads + call_args = { + 'menu_data': menu_data, + 'video_id_dict': loads(self.params['video_id_dict']), + 'supplemental_type': self.params['supplemental_type'] + } + dir_items, extra_data = common.make_call('get_video_list_supplemental', call_args) + + finalize_directory(dir_items, menu_data.get('content_type', G.CONTENT_SHOW), + title=get_title(menu_data, extra_data)) + end_of_directory(self.dir_update_listing) + return menu_data.get('view') + + @measure_exec_time_decorator() + @custom_viewmode(G.VIEW_FOLDER) + def genres(self, pathitems): + """Show loco list of a genre or from loco root the list of contexts specified in the menu data""" + menu_data = G.MAIN_MENU_ITEMS.get(pathitems[1]) + if not menu_data: # Dynamic menus + menu_data = G.LOCAL_DB.get_value(pathitems[1], table=TABLE_MENU_DATA, data_type=dict) + call_args = { + 'menu_data': menu_data, + # When genre_id is None is loaded the loco root the list of contexts specified in the menu data + 'genre_id': None if len(pathitems) < 3 else int(pathitems[2]), + 'force_use_videolist_id': False, + } + dir_items, extra_data = common.make_call('get_genres', call_args) + + finalize_directory(dir_items, G.CONTENT_FOLDER, + title=get_title(menu_data, extra_data), sort_type='sort_label') + end_of_directory(False) + return menu_data.get('view') + + @custom_viewmode(G.VIEW_FOLDER) def subgenres(self, pathitems): - """Show a lists of subgenres""" - # pathitems indexes: 0 function name, 1 menu id, 2 genre id - menu_data = g.MAIN_MENU_ITEMS[pathitems[1]] - listings.build_subgenre_listing(api.subgenre(pathitems[2]), menu_data) - _handle_endofdirectory(False) - - @common.time_execution(immediate=False) - def recommendations(self, pathitems=None): - """Show video lists for a genre""" - # pylint: disable=unused-argument - listings.build_lolomo_listing( - api.root_lists(), - g.MAIN_MENU_ITEMS['recommendations'], force_videolistbyid=True) - _handle_endofdirectory(False) + """Show a lists of sub-genres of a 'genre id'""" + menu_data = G.MAIN_MENU_ITEMS[pathitems[1]] + call_args = { + 'menu_data': menu_data, + 'genre_id': pathitems[2] + } + dir_items, extra_data = common.make_call('get_subgenres', call_args) + + finalize_directory(dir_items, menu_data.get('content_type', G.CONTENT_SHOW), + title=get_title(menu_data, extra_data), + sort_type='sort_label') + end_of_directory(False) + return menu_data.get('view') def search(self, pathitems): - """Ask for a search term if none is given via path, query API - and display search results""" - if len(pathitems) == 2: - _ask_search_term_and_redirect() - else: - _display_search_results(pathitems, self.perpetual_range_start, self.dir_update_listing) + from resources.lib.navigation.directory_search import route_search_nav + route_search_nav(pathitems, self.perpetual_range_start, self.dir_update_listing, self.params) - @common.time_execution(immediate=False) + @measure_exec_time_decorator() def exported(self, pathitems=None): """List all items that are exported to the Kodi library""" - # pylint: disable=unused-argument - library_contents = library.list_contents() - if library_contents: - listings.build_video_listing(api.custom_video_list(library_contents), g.MAIN_MENU_ITEMS['exported']) - _handle_endofdirectory(self.dir_update_listing) + chunked_video_list, perpetual_range_selector = lib_utils.list_contents(self.perpetual_range_start) + if chunked_video_list: + self._exported_directory(pathitems, chunked_video_list, perpetual_range_selector) else: - ui.show_notification(common.get_local_string(30013)) - xbmcplugin.endOfDirectory(g.PLUGIN_HANDLE, succeeded=False) - - @common.time_execution(immediate=False) - def supplemental(self, pathitems): - """Show supplemental videos (eg. trailers) of a tvshow/movie""" - # pathitems indexes: 0 function name, 1 videoid value, 2 videoid mediatype, 3 supplemental_type - videoid = common.VideoId.from_path([pathitems[2], pathitems[1]]) - listings.build_supplemental_listing(api.supplemental_video_list(videoid, pathitems[3]), - pathitems) - _handle_endofdirectory(self.dir_update_listing) - - -def _ask_search_term_and_redirect(): - search_term = ui.ask_for_search_term() - if search_term: - url = common.build_url(['search', 'search', search_term], mode=g.MODE_DIRECTORY) - xbmcplugin.endOfDirectory(g.PLUGIN_HANDLE, succeeded=True) - xbmc.executebuiltin('Container.Update({})'.format(url)) - else: - xbmcplugin.endOfDirectory(g.PLUGIN_HANDLE, succeeded=False) - - -@common.time_execution(immediate=False) -def _display_search_results(pathitems, perpetual_range_start, dir_update_listing): - search_term = pathitems[2] - search_results = api.search(search_term, perpetual_range_start) - if search_results.videos: - listings.build_video_listing(search_results, g.MAIN_MENU_ITEMS['search'], pathitems) - _handle_endofdirectory(dir_update_listing) - else: - ui.show_notification(common.get_local_string(30013)) - xbmcplugin.endOfDirectory(g.PLUGIN_HANDLE, succeeded=False) - - -def _handle_endofdirectory(dir_update_listing, cache_to_disc=True): - # If dir_update_listing=True overwrite the history list, so we can get back to the main page - xbmcplugin.endOfDirectory(g.PLUGIN_HANDLE, - succeeded=True, - updateListing=dir_update_listing, - cacheToDisc=cache_to_disc) + ui.show_notification(common.get_local_string(30111)) + xbmcplugin.endOfDirectory(G.PLUGIN_HANDLE, succeeded=False) + + @custom_viewmode(G.VIEW_SHOW) + def _exported_directory(self, pathitems, chunked_video_list, perpetual_range_selector): + menu_data = G.MAIN_MENU_ITEMS['exported'] + call_args = { + 'pathitems': pathitems, + 'menu_data': menu_data, + 'chunked_video_list': chunked_video_list, + 'perpetual_range_selector': perpetual_range_selector + } + dir_items, extra_data = common.make_call('get_video_list_chunked', call_args) + + finalize_directory(dir_items, menu_data.get('content_type', G.CONTENT_SHOW), + title=get_title(menu_data, extra_data)) + end_of_directory(self.dir_update_listing) + return menu_data.get('view') diff --git a/resources/lib/navigation/directory_search.py b/resources/lib/navigation/directory_search.py new file mode 100644 index 000000000..f9b243e56 --- /dev/null +++ b/resources/lib/navigation/directory_search.py @@ -0,0 +1,273 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2020 Stefano Gottardo (original implementation module) + Navigation for search menu + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +from copy import deepcopy + +import xbmcgui +import xbmcplugin + +import resources.lib.utils.api_requests as api +from resources.lib import common +from resources.lib.globals import G +from resources.lib.kodi import ui +from resources.lib.kodi.context_menu import generate_context_menu_searchitem +from resources.lib.navigation.directory_utils import (finalize_directory, end_of_directory, + custom_viewmode, get_title) +from resources.lib.utils.logging import LOG, measure_exec_time_decorator + +# The search types allows you to provide a modular structure to the search feature, +# in this way you can add new/remove types of search in a simple way. +# To add a new type: add the new type name to SEARCH_TYPES, then implement the new type to search_add/search_query. + + +SEARCH_TYPES = ['text', 'audio_lang', 'subtitles_lang', 'genre_id'] +SEARCH_TYPES_DESC = { + 'text': common.get_local_string(30410), + 'audio_lang': common.get_local_string(30411), + 'subtitles_lang': common.get_local_string(30412), + 'genre_id': common.get_local_string(30413) +} + + +def route_search_nav(pathitems, perpetual_range_start, dir_update_listing, params): + if 'query' in params: + path = 'query' + else: + path = pathitems[2] if len(pathitems) > 2 else 'list' + LOG.debug('Routing "search" navigation to: {}', path) + ret = True + if path == 'list': + search_list() + elif path == 'add': + ret = search_add() + elif path == 'edit': + search_edit(params['row_id']) + elif path == 'remove': + search_remove(params['row_id']) + elif path == 'clear': + ret = search_clear() + elif path == 'query': + # Used to make a search by text from a JSON-RPC request + # without save the item to the add-on database + # Endpoint: plugin://plugin.video.netflix/directory/search/search/?query=something + ret = exec_query(None, 'text', None, params['query'], perpetual_range_start, dir_update_listing, + {'query': params['query']}) + else: + ret = search_query(path, perpetual_range_start, dir_update_listing) + if not ret: + xbmcplugin.endOfDirectory(G.PLUGIN_HANDLE, succeeded=False) + + +def search_list(dir_update_listing=False): + """Show the list of search item (main directory)""" + dir_items = [_create_diritem_from_row(row) for row in G.LOCAL_DB.get_search_list()] + dir_items.insert(0, _get_diritem_add()) + dir_items.append(_get_diritem_clear()) + sort_type = 'sort_nothing' + if G.ADDON.getSettingInt('menu_sortorder_search_history') == 1: + sort_type = 'sort_label_ignore_folders' + finalize_directory(dir_items, G.CONTENT_FOLDER, sort_type, + common.get_local_string(30400)) + end_of_directory(dir_update_listing) + + +def search_add(): + """Perform actions to add and execute a new research""" + # Ask to user the type of research + search_types_desc = [SEARCH_TYPES_DESC.get(stype, 'Unknown') for stype in SEARCH_TYPES] + type_index = ui.show_dlg_select(common.get_local_string(30401), search_types_desc) + if type_index == -1: # Cancelled + return False + # If needed ask to user other info, then save the research to the database + search_type = SEARCH_TYPES[type_index] + row_id = None + if search_type == 'text': + search_term = ui.ask_for_search_term() + if search_term and search_term.strip(): + row_id = G.LOCAL_DB.insert_search_item(SEARCH_TYPES[type_index], search_term.strip()) + elif search_type == 'audio_lang': + row_id = _search_add_bylang(SEARCH_TYPES[type_index], api.get_available_audio_languages()) + elif search_type == 'subtitles_lang': + row_id = _search_add_bylang(SEARCH_TYPES[type_index], api.get_available_subtitles_languages()) + elif search_type == 'genre_id': + genre_id = ui.show_dlg_input_numeric(search_types_desc[type_index], mask_input=False) + if genre_id: + row_id = _search_add_bygenreid(SEARCH_TYPES[type_index], genre_id) + else: + raise NotImplementedError(f'Search type index {type_index} not implemented') + # Redirect to "search" endpoint (otherwise no results in JSON-RPC) + # Rewrite path history using dir_update_listing + container_update + # (otherwise will retrigger input dialog on Back or Container.Refresh) + if row_id is not None and search_query(row_id, 0, False): + url = common.build_url(['search', 'search', row_id], mode=G.MODE_DIRECTORY, params={'dir_update_listing': True}) + from time import sleep + # The forced sleep its needed because seem that change the container path too fast + # make problems in Kodi core and the GUI fails to update, when this happens cause side effects to context menus + # like "add/remove from my list" that when used ask again to make a new search because re-open the initial path + sleep(1) + common.container_update(url, False) + return True + return False + + +def _search_add_bylang(search_type, dict_languages): + search_type_desc = SEARCH_TYPES_DESC.get(search_type, 'Unknown') + title = f'{search_type_desc} - {common.get_local_string(30405)}' + index = ui.show_dlg_select(title, list(dict_languages.values())) + if index == -1: # Cancelled + return None + lang_code = list(dict_languages.keys())[index] + lang_desc = list(dict_languages.values())[index] + # In this case the 'value' is used only as title for the ListItem and not for the query + value = f'{search_type_desc}: {lang_desc}' + row_id = G.LOCAL_DB.insert_search_item(search_type, value, {'lang_code': lang_code}) + return row_id + + +def _search_add_bygenreid(search_type, genre_id): + # If the genre ID exists, the title of the list will be returned + title = api.get_genre_title(genre_id) + if not title: + ui.show_notification(common.get_local_string(30407)) + return None + # In this case the 'value' is used only as title for the ListItem and not for the query + title += f' [{genre_id}]' + row_id = G.LOCAL_DB.insert_search_item(search_type, title, {'genre_id': genre_id}) + return row_id + + +def search_edit(row_id): + """Edit a search item""" + search_item = G.LOCAL_DB.get_search_item(row_id) + search_type = search_item['Type'] + ret = False + if search_type == 'text': + search_term = ui.ask_for_search_term(search_item['Value']) + if search_term and search_term.strip(): + G.LOCAL_DB.update_search_item_value(row_id, search_term.strip()) + ret = True + if not ret: + return + common.container_update(common.build_url(['search', 'search', row_id], mode=G.MODE_DIRECTORY)) + + +def search_remove(row_id): + """Remove a search item""" + LOG.debug('Removing search item with ID {}', row_id) + G.LOCAL_DB.delete_search_item(row_id) + common.json_rpc('Input.Down') # Avoids selection back to the top + common.container_refresh() + + +def search_clear(): + """Clear all search items""" + if not ui.ask_for_confirmation(common.get_local_string(30404), common.get_local_string(30406)): + return False + G.LOCAL_DB.clear_search_items() + common.container_refresh() + return True + + +@measure_exec_time_decorator() +def search_query(row_id, perpetual_range_start, dir_update_listing): + """Perform the research""" + # Get item from database + search_item = G.LOCAL_DB.get_search_item(row_id) + if not search_item: + ui.show_error_info('Search error', 'Item not found in the database.') + return False + # Update the last access data (move on top last used items) + if not perpetual_range_start: + G.LOCAL_DB.update_search_item_last_access(row_id) + return exec_query(row_id, search_item['Type'], search_item['Parameters'], search_item['Value'], + perpetual_range_start, dir_update_listing) + + +def exec_query(row_id, search_type, search_params, search_value, perpetual_range_start, dir_update_listing, + path_params=None): + menu_data = deepcopy(G.MAIN_MENU_ITEMS['search']) + if search_type == 'text': + call_args = { + 'menu_data': menu_data, + 'search_term': search_value, + 'pathitems': ['search', 'search', row_id] if row_id else ['search', 'search'], + 'path_params': path_params, + 'perpetual_range_start': perpetual_range_start + } + dir_items, extra_data = common.make_call('get_video_list_search', call_args) + elif search_type == 'audio_lang': + call_args = { + 'menu_data': menu_data, + 'pathitems': ['search', 'search', row_id], + 'perpetual_range_start': perpetual_range_start, + 'context_name': 'genres', + 'context_id': common.convert_from_string(search_params, dict)['lang_code'] + } + dir_items, extra_data = common.make_call('get_video_list_sorted_sp', call_args) + elif search_type == 'subtitles_lang': + call_args = { + 'menu_data': menu_data, + 'pathitems': ['search', 'search', row_id], + 'perpetual_range_start': perpetual_range_start, + 'context_name': 'genres', + 'context_id': common.convert_from_string(search_params, dict)['lang_code'] + } + dir_items, extra_data = common.make_call('get_video_list_sorted_sp', call_args) + elif search_type == 'genre_id': + call_args = { + 'menu_data': menu_data, + 'pathitems': ['search', 'search', row_id], + 'perpetual_range_start': perpetual_range_start, + 'context_name': 'genres', + 'context_id': common.convert_from_string(search_params, dict)['genre_id'] + } + dir_items, extra_data = common.make_call('get_video_list_sorted_sp', call_args) + else: + raise NotImplementedError(f'Search type {search_type} not implemented') + # Show the results + if not dir_items: + ui.show_notification(common.get_local_string(30407)) + return False + _search_results_directory(search_value, menu_data, dir_items, extra_data, dir_update_listing) + return True + + +@custom_viewmode(G.VIEW_SHOW) +def _search_results_directory(search_value, menu_data, dir_items, extra_data, dir_update_listing): + extra_data['title'] = f'{common.get_local_string(30400)} - {search_value}' + finalize_directory(dir_items, menu_data.get('content_type', G.CONTENT_SHOW), + title=get_title(menu_data, extra_data)) + end_of_directory(dir_update_listing) + return menu_data.get('view') + + +def _get_diritem_add(): + """Generate the "add" menu item""" + list_item = xbmcgui.ListItem(label=common.get_local_string(30403), offscreen=True) + list_item.setArt({'icon': 'DefaultAddSource.png'}) + list_item.setProperty('specialsort', 'top') # Force an item to stay on top + return common.build_url(['search', 'search', 'add'], mode=G.MODE_DIRECTORY), list_item, True + + +def _get_diritem_clear(): + """Generate the "clear" menu item""" + list_item = xbmcgui.ListItem(label=common.get_local_string(30404), offscreen=True) + list_item.setArt({'icon': 'icons\\infodialogs\\uninstall.png'}) + list_item.setProperty('specialsort', 'bottom') # Force an item to stay on bottom + # This ListItem is not set as folder so that the executed command is not added to the history + return common.build_url(['search', 'search', 'clear'], mode=G.MODE_DIRECTORY), list_item, False + + +def _create_diritem_from_row(row): + row_id = str(row['ID']) + search_desc = common.get_local_string(30401) + ': ' + SEARCH_TYPES_DESC.get(row['Type'], 'Unknown') + list_item = xbmcgui.ListItem(label=row['Value'], offscreen=True) + list_item.setInfo('video', {'Plot': search_desc}) + list_item.addContextMenuItems(generate_context_menu_searchitem(row_id, row['Type'])) + return common.build_url(['search', 'search', row_id], mode=G.MODE_DIRECTORY), list_item, True diff --git a/resources/lib/navigation/directory_utils.py b/resources/lib/navigation/directory_utils.py new file mode 100644 index 000000000..39a158f34 --- /dev/null +++ b/resources/lib/navigation/directory_utils.py @@ -0,0 +1,188 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2020 Stefano Gottardo (original implementation module) + Miscellaneous utility functions for directory handling + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +from functools import wraps + +import xbmc +import xbmcplugin + +import resources.lib.common as common +from resources.lib.utils.api_requests import verify_profile_lock +from resources.lib.database.db_utils import TABLE_MENU_DATA +from resources.lib.globals import G +from resources.lib.kodi import ui + + +def custom_viewmode(content_type): + """Decorator that sets a custom viewmode (skin viewtype) if currently in a listing of the plugin""" + # pylint: disable=missing-docstring + def decorate_viewmode(func): + @wraps(func) + def set_custom_viewmode(*args, **kwargs): + override_content_type = func(*args, **kwargs) + _content_type = override_content_type if override_content_type else content_type + if (G.ADDON.getSettingBool('customview') + and f'plugin://{G.ADDON_ID}' in xbmc.getInfoLabel('Container.FolderPath')): + # Activate the given skin viewtype if the plugin is run in the foreground + view_id = G.ADDON.getSettingInt(f'viewmode{_content_type}id') + if view_id > 0: + xbmc.executebuiltin(f'Container.SetViewMode({view_id})') + return set_custom_viewmode + return decorate_viewmode + + +def add_sort_methods(sort_type): + if sort_type == 'sort_nothing': + xbmcplugin.addSortMethod(G.PLUGIN_HANDLE, xbmcplugin.SORT_METHOD_NONE) + if sort_type == 'sort_label': + xbmcplugin.addSortMethod(G.PLUGIN_HANDLE, xbmcplugin.SORT_METHOD_LABEL) + if sort_type == 'sort_label_ignore_folders': + xbmcplugin.addSortMethod(G.PLUGIN_HANDLE, xbmcplugin.SORT_METHOD_LABEL_IGNORE_FOLDERS) + if sort_type == 'sort_episodes': + xbmcplugin.addSortMethod(G.PLUGIN_HANDLE, xbmcplugin.SORT_METHOD_EPISODE) + xbmcplugin.addSortMethod(G.PLUGIN_HANDLE, xbmcplugin.SORT_METHOD_LABEL) + xbmcplugin.addSortMethod(G.PLUGIN_HANDLE, xbmcplugin.SORT_METHOD_VIDEO_TITLE) + + +def finalize_directory(dir_items, content_type=G.CONTENT_FOLDER, sort_type='sort_nothing', title=None): + """Finalize a directory listing. Add items, set available sort methods and content type""" + if title: + xbmcplugin.setPluginCategory(G.PLUGIN_HANDLE, title) + xbmcplugin.setContent(G.PLUGIN_HANDLE, content_type) + add_sort_methods(sort_type) + xbmcplugin.addDirectoryItems(G.PLUGIN_HANDLE, dir_items) + + +def end_of_directory(dir_update_listing): + # If dir_update_listing=True overwrite the history list, so we can get back to the main page + xbmcplugin.endOfDirectory(G.PLUGIN_HANDLE, + succeeded=True, + updateListing=dir_update_listing, + cacheToDisc=False) + + +def get_title(menu_data, extra_data): + """Get title for the directory""" + # Try to get the title from 'extra_data', if not exists then try fallback to the title contained in the 'menu_data' + # But 'menu_data' do not have the title if: + # - Is a main-menu, menu data in 'globals' do not have the titles (are saved from build_main_menu_listing) + # - In case of dynamic menu + # So get the menu title from TABLE_MENU_DATA of the database + return extra_data.get('title', + menu_data.get('title', + G.LOCAL_DB.get_value(menu_data['path'][1], + {}, + table=TABLE_MENU_DATA).get('title', ''))) + + +def activate_profile(guid): + """Activate a profile and ask the PIN if required""" + pin_result = verify_profile_pin(guid, G.LOCAL_DB.get_profile_config('addon_remember_pin', False, guid=guid)) + if not pin_result: + if pin_result is not None: + G.LOCAL_DB.set_profile_config('addon_pin', '', guid=guid) + ui.show_notification(common.get_local_string(30106), time=8000) + return False + common.make_call('activate_profile', guid) + return True + + +def verify_profile_pin(guid, is_remember_pin): + """Verify if the profile is locked by a PIN and ask the PIN""" + if not G.LOCAL_DB.get_profile_config('isPinLocked', False, guid=guid): + return True + stored_pin = '' + if is_remember_pin: + try: + stored_pin = common.decrypt_string(G.LOCAL_DB.get_profile_config('addon_pin', '', guid=guid)) + except Exception: # pylint: disable=broad-except + pass + if stored_pin: + pin = stored_pin + else: + pin = ui.show_dlg_input_numeric(common.get_local_string(30006)) + if not pin: + return None + if verify_profile_lock(guid, pin): + if is_remember_pin: + G.LOCAL_DB.set_profile_config('addon_pin', common.encrypt_string(pin), guid=guid) + return True + return False + + +def auto_scroll(dir_items): + """ + Auto scroll the current viewed list to select the last partial watched or next episode to be watched, + works only with Sync of watched status with netflix + """ + # A sad implementation to a Kodi feature available only for the Kodi library + if G.ADDON.getSettingBool('sync_watched_status') and G.ADDON.getSettingBool('select_first_unwatched'): + total_items = len(dir_items) + if total_items: + # Delay a bit to wait for the completion of the screen update + xbmc.sleep(200) + if not _auto_scroll_init_checks(): + return + # Check if all items are already watched + watched_items = 0 + to_resume_items = 0 + for _, list_item, _ in dir_items: + watched_items += list_item.getVideoInfoTag().getPlayCount() != 0 + if G.IS_OLD_KODI_MODULES: + resume_time = list_item.getProperty('ResumeTime') + else: + resume_time = list_item.getVideoInfoTag().getResumeTime() + to_resume_items += float(resume_time) != 0 + if total_items == watched_items or (watched_items + to_resume_items) == 0: + return + steps = _find_index_last_watched(total_items, dir_items) + # Get the sort order of the view + is_sort_descending = xbmc.getCondVisibility('Container.SortDirection(descending)') + if is_sort_descending: + steps = (total_items - 1) - steps + gui_sound_mode = common.json_rpc('Settings.GetSettingValue', + {'setting': 'audiooutput.guisoundmode'})['value'] + if gui_sound_mode != 0: + # Disable GUI sounds to avoid squirting sound with item selections + common.json_rpc('Settings.SetSettingValue', + {'setting': 'audiooutput.guisoundmode', 'value': 0}) + # Auto scroll the list + for _ in range(0, steps + 1): + common.json_rpc('Input.Down') + if gui_sound_mode != 0: + # Restore GUI sounds + common.json_rpc('Settings.SetSettingValue', + {'setting': 'audiooutput.guisoundmode', 'value': gui_sound_mode}) + + +def _auto_scroll_init_checks(): + # Check if view sort method is "Episode" (ID 23 = SortByEpisodeNumber) + if not xbmc.getCondVisibility('Container.SortMethod(23)'): + return False + # Check if a selection is already done (CurrentItem return the index) + if int(xbmc.getInfoLabel('ListItem.CurrentItem') or 2) > 1: + return False + return True + + +def _find_index_last_watched(total_items, dir_items): + """Find last watched item""" + for index in range(total_items - 1, -1, -1): + list_item = dir_items[index][1] + if list_item.getVideoInfoTag().getPlayCount() != 0: + # Last watched item + return index + 1 + if G.IS_OLD_KODI_MODULES: + resume_time = list_item.getProperty('ResumeTime') + else: + resume_time = list_item.getVideoInfoTag().getResumeTime() + if float(resume_time) != 0: + # Last partial watched item + return index + return 0 diff --git a/resources/lib/navigation/hub.py b/resources/lib/navigation/hub.py deleted file mode 100644 index e29d66a46..000000000 --- a/resources/lib/navigation/hub.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -"""Navigation for hub mode - needs skin support!""" -from __future__ import absolute_import, division, unicode_literals - -import resources.lib.common as common -import resources.lib.api.shakti as api - - -class HubBrowser(object): - """Fills window properties for browsing the Netflix style Hub""" - # pylint: disable=no-self-use - def __init__(self, params): - common.debug('Initializing hub browser: {}'.format(params)) - self.params = params - - profile_id = params.get('profile_id') - if profile_id: - api.activate_profile(profile_id) - - def browse(self, pathitems): - """Browse the hub at a given location""" - pass diff --git a/resources/lib/navigation/keymaps.py b/resources/lib/navigation/keymaps.py new file mode 100644 index 000000000..63174ca11 --- /dev/null +++ b/resources/lib/navigation/keymaps.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2020 Stefano Gottardo (original implementation module) + Navigation handler for keyboard shortcut keys + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +from functools import wraps + +import xbmc + +from resources.lib import common +from resources.lib.globals import G +from resources.lib.kodi import ui +from resources.lib.kodi.library import get_library_cls +from resources.lib.navigation.actions import change_watched_status_locally, sync_library +from resources.lib.utils.logging import LOG +import resources.lib.utils.api_requests as api +import resources.lib.kodi.library_utils as lib_utils + + +def allow_execution_decorator(check_addon=True, check_lib=False, inject_videoid=False): + def allow_execution(func): + """Decorator that catch exceptions""" + @wraps(func) + def wrapper(*args, **kwargs): + if check_addon and not _is_addon_opened(): + return + if check_lib and not _is_library_ops_allowed(): + return + if inject_videoid: + videoid = _get_selected_videoid() + if not videoid: + return + kwargs['videoid'] = videoid + func(*args, **kwargs) + return wrapper + return allow_execution + + +class KeymapsActionExecutor: + """Executes keymaps actions""" + + def __init__(self, params): + LOG.debug('Initializing "KeymapsActionExecutor" with params: {}', params) + self.params = params + + @allow_execution_decorator(check_lib=True, inject_videoid=True) + def lib_export(self, pathitems, videoid=None): # pylint: disable=unused-argument + """Export or update an item to the Kodi library""" + if videoid.mediatype in [common.VideoId.SUPPLEMENTAL, common.VideoId.EPISODE]: + return + if lib_utils.is_videoid_in_db(videoid): + get_library_cls().update_library(videoid) + else: + get_library_cls().export_to_library(videoid) + common.container_refresh() + + @allow_execution_decorator(check_lib=True, inject_videoid=True) + def lib_remove(self, pathitems, videoid=None): # pylint: disable=unused-argument + """Remove an item to the Kodi library""" + if videoid.mediatype in [common.VideoId.SUPPLEMENTAL, common.VideoId.EPISODE]: + return + if not ui.ask_for_confirmation(common.get_local_string(30030), + common.get_local_string(30124)): + return + get_library_cls().remove_from_library(videoid) + common.container_refresh(use_delay=True) + + @allow_execution_decorator(inject_videoid=True) + def change_watched_status(self, pathitems, videoid=None): # pylint: disable=unused-argument + """Change the watched status of a video, only when sync of watched status with NF is enabled""" + if videoid.mediatype not in [common.VideoId.MOVIE, common.VideoId.EPISODE]: + return + if G.ADDON.getSettingBool('sync_watched_status'): + change_watched_status_locally(videoid) + + @allow_execution_decorator(inject_videoid=True) + def my_list(self, pathitems, videoid=None): # pylint: disable=unused-argument + """Add or remove an item from my list""" + if videoid.mediatype not in [common.VideoId.MOVIE, common.VideoId.SHOW]: + return + perpetual_range_start = xbmc.getInfoLabel('ListItem.Property(nf_perpetual_range_start)') + is_in_mylist = xbmc.getInfoLabel('ListItem.Property(nf_is_in_mylist)') == 'True' + operation = 'remove' if is_in_mylist else 'add' + api.update_my_list(videoid, operation, {'perpetual_range_start': perpetual_range_start}) + sync_library(videoid, operation) + common.container_refresh() + + +def _get_selected_videoid(): + """Return the videoid from the current selected ListItem""" + videoid_str = xbmc.getInfoLabel('ListItem.Property(nf_videoid)') + if not videoid_str: + return None + return common.VideoId.from_path(videoid_str.split('/')) + + +def _is_library_ops_allowed(): + """Check if library operations are allowed""" + allow_lib_operations = True + if not G.ADDON.getSettingBool('lib_enabled'): + return False + is_lib_sync_with_mylist = (G.ADDON.getSettingBool('lib_sync_mylist') and + G.ADDON.getSettingInt('lib_auto_upd_mode') in [0, 2]) + if is_lib_sync_with_mylist: + # If the synchronization of Netflix "My List" with the Kodi library is enabled + # only in the chosen profile allow to do operations in the Kodi library otherwise + # it creates inconsistency to the exported elements and increases the work for sync + sync_mylist_profile_guid = G.SHARED_DB.get_value('sync_mylist_profile_guid', + G.LOCAL_DB.get_guid_owner_profile()) + allow_lib_operations = sync_mylist_profile_guid == G.LOCAL_DB.get_active_profile_guid() + return allow_lib_operations + + +def _is_addon_opened(): + """Check if the add-on is opened on screen""" + return xbmc.getInfoLabel('Container.PluginName') == G.ADDON.getAddonInfo('id') diff --git a/resources/lib/navigation/library.py b/resources/lib/navigation/library.py index 51eaff0ae..6e4cd3a28 100644 --- a/resources/lib/navigation/library.py +++ b/resources/lib/navigation/library.py @@ -1,134 +1,117 @@ # -*- coding: utf-8 -*- -"""Navigation handler for library actions""" -from __future__ import absolute_import, division, unicode_literals +""" + Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix) + Copyright (C) 2018 Caphm (original implementation module) + Navigation handler for library actions + + SPDX-License-Identifier: MIT + See LICENSES/MIT.md for more information. +""" +import xbmcvfs -import resources.lib.api.shakti as api import resources.lib.common as common -import resources.lib.kodi.library as library -import resources.lib.kodi.nfo as nfo import resources.lib.kodi.ui as ui -from resources.lib.globals import g +import resources.lib.kodi.library_utils as lib_utils +from resources.lib.common.exceptions import ErrorMsgNoReport +from resources.lib.globals import G +from resources.lib.kodi.library import get_library_cls +from resources.lib.utils.logging import LOG -class LibraryActionExecutor(object): +# pylint: disable=no-self-use +class LibraryActionExecutor: """Executes actions""" - # pylint: disable=no-self-use + def __init__(self, params): - common.debug('Initializing LibraryActionExecutor: {}' - .format(params)) + LOG.debug('Initializing "LibraryActionExecutor" with params: {}', params) self.params = params @common.inject_video_id(path_offset=1) def export(self, videoid): """Export an item to the Kodi library""" - nfo_settings = nfo.NFOSettings() - nfo_settings.show_export_dialog(videoid.mediatype) - library.execute_library_tasks(videoid, - [library.export_item], - common.get_local_string(30018), - nfo_settings=nfo_settings) + get_library_cls().export_to_library(videoid) + common.container_refresh() @common.inject_video_id(path_offset=1) def remove(self, videoid): """Remove an item from the Kodi library""" - if ui.ask_for_removal_confirmation(): - library.execute_library_tasks(videoid, - [library.remove_item], - common.get_local_string(30030)) - common.refresh_container() + if not ui.ask_for_confirmation(common.get_local_string(30030), + common.get_local_string(30124)): + return + get_library_cls().remove_from_library(videoid) + common.container_refresh(use_delay=True) @common.inject_video_id(path_offset=1) def update(self, videoid): """Update an item in the Kodi library""" - nfo_settings = nfo.NFOSettings() - nfo_settings.show_export_dialog(videoid.mediatype) - library.execute_library_tasks(videoid, - [library.remove_item, library.export_item], - common.get_local_string(30061), - nfo_settings=nfo_settings) - common.refresh_container() - - @common.inject_video_id(path_offset=1) - def export_silent(self, videoid): - """Silently export an item to the Kodi library - (without GUI feedback). This will ignore the setting for syncing my - list and Kodi library and do no sync, if not explicitly asked to. - Will only ask for NFO export based on user settings""" - # pylint: disable=broad-except - nfo_settings = nfo.NFOSettings() - nfo_settings.show_export_dialog(videoid.mediatype, common.get_local_string(30191)) - library.execute_library_tasks_silently( - videoid, [library.export_item], - sync_mylist=self.params.get('sync_mylist', False), - nfo_settings=nfo_settings) + get_library_cls().update_library(videoid) + common.container_refresh() + + def sync_mylist(self, pathitems): # pylint: disable=unused-argument + """ + Perform a full sync of Netflix "My List" with the Kodi library + """ + if not ui.ask_for_confirmation(common.get_local_string(30122), + common.get_local_string(30123)): + return + get_library_cls().sync_library_with_mylist() + + def auto_upd_run_now(self, pathitems): # pylint: disable=unused-argument + """ + Perform an auto update of Kodi library to add new seasons/episodes of tv shows + """ + if not ui.ask_for_confirmation(common.get_local_string(30065), + common.get_local_string(30231)): + return + get_library_cls().auto_update_library(False) - @common.inject_video_id(path_offset=1) - def remove_silent(self, videoid): - """Silently remove an item from the Kodi library - (without GUI feedback). This will ignore the setting for syncing my - list and Kodi library and do no sync, if not explicitly asked to.""" - library.execute_library_tasks_silently( - videoid, [library.remove_item], - sync_mylist=self.params.get('sync_mylist', False)) - - # Not used for now - # @common.inject_video_id(path_offset=1) - # def update_silent(self, videoid): - # """Silently update an item in the Kodi library - # (without GUI feedback). This will ignore the setting for syncing my - # list and Kodi library and do no sync, if not explicitly asked to.""" - # library.execute_library_tasks_silently( - # videoid, [library.remove_item, library.export_item], - # sync_mylist=self.params.get('sync_mylist', False)) - - def initial_mylist_sync(self, pathitems): - """Perform an initial sync of My List and the Kodi library""" - # pylint: disable=unused-argument - do_it = ui.ask_for_confirmation(common.get_local_string(30122), - common.get_local_string(30123)) - if not do_it or not g.ADDON.getSettingBool('mylist_library_sync'): + def sync_mylist_sel_profile(self, pathitems): # pylint: disable=unused-argument + """ + Select a profile for the synchronization of Kodi library with Netflix "My List" + """ + if _check_auto_update_running(): return - common.debug('Performing full sync from My List to Kodi library') - library.purge() - nfo_settings = nfo.NFOSettings() - nfo_settings.show_export_dialog() - for videoid in api.video_list( - api.list_id_for_type('queue')).videoids: - library.execute_library_tasks(videoid, [library.export_item], - common.get_local_string(30018), - sync_mylist=False, - nfo_settings=nfo_settings) - - def purge(self, pathitems): - """Delete all previously exported items from the Kodi library""" - # pylint: disable=unused-argument - if ui.ask_for_confirmation(common.get_local_string(30125), - common.get_local_string(30126)): - library.purge() + preselect_guid = G.SHARED_DB.get_value('sync_mylist_profile_guid', + G.LOCAL_DB.get_guid_owner_profile()) + selected_guid = ui.show_profiles_dialog(title=common.get_local_string(30228), + preselect_guid=preselect_guid) + if not selected_guid: + return + G.SHARED_DB.set_value('sync_mylist_profile_guid', selected_guid) - def migrate(self, pathitems): # pylint: disable=unused-argument - """Migrate exported items from old library format to the new format""" - for videoid in library.get_previously_exported_items(): - library.execute_library_tasks(videoid, [library.export_item], - common.get_local_string(30018), - sync_mylist=False) + def purge(self, pathitems): # pylint: disable=unused-argument + """Delete all previously exported items from the Kodi library""" + if _check_auto_update_running(): + return + if not ui.ask_for_confirmation(common.get_local_string(30125), + common.get_local_string(30126)): + return + get_library_cls().clear_library() - def export_all_new_episodes(self, pathitems): # pylint: disable=unused-argument - library.export_all_new_episodes() + def import_library(self, pathitems): # pylint: disable=unused-argument + """Import previous exported STRM files to add-on and/or convert them to the current STRM format type""" + if _check_auto_update_running(): + return + path = ui.show_browse_dialog(common.get_local_string(651), default_path=G.DATA_PATH) + if path: + if not ui.ask_for_confirmation(common.get_local_string(30140), + common.get_local_string(20135)): + return + get_library_cls().import_library(path) @common.inject_video_id(path_offset=1) def export_new_episodes(self, videoid): - library.export_new_episodes(videoid) + get_library_cls().export_to_library_new_episodes(videoid) @common.inject_video_id(path_offset=1) def exclude_from_auto_update(self, videoid): - library.exclude_show_from_auto_update(videoid, True) - common.refresh_container() + lib_utils.set_show_excluded_from_auto_update(videoid, True) + common.container_refresh() @common.inject_video_id(path_offset=1) def include_in_auto_update(self, videoid): - library.exclude_show_from_auto_update(videoid, False) - common.refresh_container() + lib_utils.set_show_excluded_from_auto_update(videoid, False) + common.container_refresh() def mysql_test(self, pathitems): """Perform a MySQL database connection test""" @@ -136,22 +119,96 @@ def mysql_test(self, pathitems): # to initialize the database and then the test is also performed # in addition, you must also wait for the timeout to obtain any connection error # Perhaps creating a particular modal dialog with connection parameters can help - pass def set_autoupdate_device(self, pathitems): # pylint: disable=unused-argument """Set the current device to manage auto-update of the shared-library (MySQL)""" + if _check_auto_update_running(): + return random_uuid = common.get_random_uuid() - g.LOCAL_DB.set_value('client_uuid', random_uuid) - g.SHARED_DB.set_value('auto_update_device_uuid', random_uuid) + G.LOCAL_DB.set_value('client_uuid', random_uuid) + G.SHARED_DB.set_value('auto_update_device_uuid', random_uuid) ui.show_notification(common.get_local_string(30209), time=8000) def check_autoupdate_device(self, pathitems): # pylint: disable=unused-argument """Check if the current device manage the auto-updates of the shared-library (MySQL)""" - uuid = g.SHARED_DB.get_value('auto_update_device_uuid') + uuid = G.SHARED_DB.get_value('auto_update_device_uuid') if uuid is None: msg = common.get_local_string(30212) else: - client_uuid = g.LOCAL_DB.get_value('client_uuid') - msg = common.get_local_string(30210) \ - if client_uuid == uuid else common.get_local_string(30211) + client_uuid = G.LOCAL_DB.get_value('client_uuid') + msg = common.get_local_string(30210 if client_uuid == uuid else 30211) ui.show_notification(msg, time=8000) + + def add_folders_to_library(self, pathitems): # pylint: disable=unused-argument + from xml.dom import minidom + from xbmcvfs import translatePath + sources_xml_path = translatePath('special://userdata/sources.xml') + if common.file_exists(sources_xml_path): + try: + xml_doc = minidom.parse(sources_xml_path) + except Exception as exc: # pylint: disable=broad-except + raise ErrorMsgNoReport('Cannot open "sources.xml" the file could be corrupted. ' + 'Please check manually on your Kodi userdata folder or reinstall Kodi.') from exc + else: + xml_doc = minidom.Document() + source_node = xml_doc.createElement("sources") + for content_type in ['programs', 'video', 'music', 'pictures', 'files']: + node_type = xml_doc.createElement(content_type) + element_default = xml_doc.createElement('default') + element_default.setAttribute('pathversion', '1') + node_type.appendChild(element_default) + source_node.appendChild(node_type) + xml_doc.appendChild(source_node) + + lib_path_movies = common.check_folder_path(common.join_folders_paths(lib_utils.get_library_path(), + lib_utils.FOLDER_NAME_MOVIES)) + lib_path_shows = common.check_folder_path(common.join_folders_paths(lib_utils.get_library_path(), + lib_utils.FOLDER_NAME_SHOWS)) + lib_path_movies = xbmcvfs.makeLegalFilename(xbmcvfs.translatePath(lib_path_movies)) + lib_path_shows = xbmcvfs.makeLegalFilename(xbmcvfs.translatePath(lib_path_shows)) + # Check if the paths already exists in source tags of video content type + is_movies_source_exist = False + is_shows_source_exist = False + video_node = xml_doc.childNodes[0].getElementsByTagName('video')[0] + source_nodes = video_node.getElementsByTagName('source') + for source_node in source_nodes: + path_nodes = source_node.getElementsByTagName('path') + if not path_nodes: + continue + source_path = common.get_xml_nodes_text(path_nodes[0].childNodes) + if source_path == lib_path_movies: + is_movies_source_exist = True + elif source_path == lib_path_shows: + is_shows_source_exist = True + + # Add to the parent