diff --git a/script.plexmod/LICENSE.txt b/script.plexmod/LICENSE.txt index 3163ab4046..96a08db343 100644 --- a/script.plexmod/LICENSE.txt +++ b/script.plexmod/LICENSE.txt @@ -593,6 +593,723 @@ products or services of Licensee, or any third party. agrees to be bound by the terms and conditions of this License Agreement. + +------------------------------------------------------------------------- + +https://salsa.debian.org/python-debian-team/python-debian/-/blob/0.1.36/lib/debian/debian_support.py + +Copyright (C) 2005 Florian Weimer +Copyright (C) 2010 John Wright + +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 + +------------------------------------------------------------------------- + +https://github.com/noumar/iso639/blob/main/LICENSE.txt + +GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 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 Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. + +------------------------------------------------------------------------- +requests-cache + +Copyright (c) 2012, Roman Haritonov +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. + +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. + ------------------------------------------------------------------------- https://github.com/dmulholl/ibis/blob/master/license.txt @@ -610,5 +1327,7 @@ CC BY 4.0 License: https://creativecommons.org/licenses/by/4.0/# Play queue by Sébastien Robaszkiewicz from Noun Project (CC BY 3.0) fast forward by Adiyogi from Noun Project (CC BY 3.0) subtitle by YANDI RS from Noun Project (CC BY 3.0) +hourglass by shariful islam from Noun Project (CC BY 3.0) +Wait for Calendar by Smashicons from Noun Project (CC BY 3.0) Other unlisted icons under CC BY 3.0, https://creativecommons.org/licenses/by/3.0/ \ No newline at end of file diff --git a/script.plexmod/README.md b/script.plexmod/README.md index cd33b49671..45f8bca5bc 100644 --- a/script.plexmod/README.md +++ b/script.plexmod/README.md @@ -19,7 +19,7 @@ Master branch is based off of the official plex-for-kodi master branch. ## Installation ### Via repository (recommended) -* Add `https://pm4k.eu` to your Kodi installation as a file source +* Add `https://pm4k.eu` to your Kodi installation as a file source (Hit Add source in File Manager, click on the selected "``" in the list, enter `https://pm4k.eu`, OK, down, enter a name, hit OK) * Go to Settings->Addons, choose "Install from zip file", choose the file source you added and install the repository * Install Plex via Settings->Addons->Install from repository->Don't Panic->Video add-ons->Plex * Optional, recommended: Install Plextuary via Settings->Addons->Install from repository->Don't Panic->Look and Feel->Skin->Plextuary @@ -29,7 +29,10 @@ Master branch is based off of the official plex-for-kodi master branch. * Optional, recommended: Install Plextuary skin using the above ### Manual -* Checkout any branch of this GitHub repository, rename to `script.plexmod` and use as an addon +* Checkout any branch of this GitHub repository, rename to `script.plexmod` and use as an addon (for it to work with "Install from zip", the contents of the zip should be the folder `script.plexmod`. + +### Installing to a read-only or write-protected location +Set the environment variable `INSTALLATION_DIR_AVOID_WRITE` to any value before starting Kodi to prevent the addon from trying to write to its installation directory. Useful for package managers. ## Translation You can help! Join the translation effort at [POEditor](https://poeditor.com/join/project/ASOl50YAXg) (thanks for the free open source license, guys). @@ -39,3 +42,7 @@ https://forums.plex.tv/t/plexmod-for-kodi-18-19-20-21/481208 ## License [LICENSE](https://github.com/plexinc/plex-for-kodi/blob/master/LICENSE.txt) + +## Powered by + +[![JetBrains logo.](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg)](https://jb.gg/OpenSource) diff --git a/script.plexmod/addon.xml b/script.plexmod/addon.xml index 5f60499c98..650e454f15 100644 --- a/script.plexmod/addon.xml +++ b/script.plexmod/addon.xml @@ -1,7 +1,7 @@ @@ -15,6 +15,7 @@ executable + PlexMod for Kodi @@ -33,7 +34,7 @@ https://github.com/pannal/plex-for-kodi all -- Based on 0.7.9-rev4 +- Based on 1.0.5e icon2.png diff --git a/script.plexmod/changelog.txt b/script.plexmod/changelog.txt index b8920e287a..0b3758d429 100644 --- a/script.plexmod/changelog.txt +++ b/script.plexmod/changelog.txt @@ -1,8 +1,189 @@ -[-0.7.9-rev4-] -- Episodes: Clear VIDEO_PROGRESS onFirstInit; Only redirect to episode from progress if shows match -- Collections: Fix shuffle play -- Core/Playback/CoreELEC: Increase CoreELEC resume seek wait fix to 350ms (was 250ms) -- Playlists: Fix shuffle/play button behaviour +[-1.0.5d-] +- Core/Plex.direct: Only check native resolving of hostnames containing plex.direct +- Core/Plex.direct: Reset mapped hosts if the user skips the confirmation dialog +- Core/Plex.direct: Fix long-existing issue with multiple servers in certain circumstances not triggering plex.direct host mapping check +- Core/Plex.direct: Force mapping if requested +- Core/GDM: GDM could still be reported as active under certain circumstances +- Core/MyPlex: Consider a device that is product="Plex Media Server" as a server as well, even if it doesn't provide the correct attributes (can happen; very rare) +- Core/PlexNet/CoreELEC: Properly report device and platform parameters to Plex.tv +- Player: Don't throw away progress data during consecutive watch sessions; properly represent watched state in current video playlist during those +- Player: Honor "Never show Post Play" properly even when an episode has ended +- Player/Timeline: Never allow timeline reporting below 0 seconds +- Player: SeekBehind: Never allow seek target to be < 0 +- Player/SeekDialog/AltSeekFix: Never react to seek attempts while we're waiting for a seek to happen +- Player/MDE: If we don't want a subtitle, report it properly to the Plex server (still bugged, probably server-side) +- Libraries: Move filter options out of the header area to fix overlapping music player and long filter descriptions +- Libraries: Unify and optimize play/filter/jumplist/scrollbar navigation +- Libraries/Listviews/Squares: Autoscroll long description texts +- Libraries/Photos: Fix PhotoDirectory browsing +- Library/Movies: Add filters for Writer, Producer, Editions, Audio Language, Subtitle Language, Folder Location (only if admin) +- Library/Shows: Add filters for Director, Writer, Producer; correctly separate Studio and Network filters +- Libraries: Store subfilter states +- Libraries: Re-select currently selected items in filter dropdowns +- Libraries: When re-opening applied filters, re-open all subfilter dropdowns until we reach our current one +- Libraries: Cache subfilter content +- Libraries: Depending on which "side" of the library view we are, when pressing "up", select either the left buttons or the right buttons +- Libraries: Enable fast navigation through subfilter lists via scrollbar +- Library/Movies: Allow filtering by HDR and DOVI; allow multiple boolean filters at the same time +- Library/Photos: Fix filtering +- Libraries: Allow filter clearing for boolean filters +- Libraries/Movies: Add "year" and "progress" sort types +- Libraries: Shows/Movies use sort order from PlexWeb +- Libraries: Movies: Show current sort reference value if possible in addition to movie year +- Libraries: Photos: Properly implement PlexWeb sort; fix display +- Libraries/PosterSmall: Fix vertical cutoff +- Libraries/Posters/PostersSmall: Scale and position scrollbar appropriately when moving down +- Libraries: Re-select currently used item type and sort attribute in dropdowns +- Dropdown: Allow opening a sublist by pressing RIGHT +- Dropdown: Show scrollbar if a (child-)dropdown needs scrolling +- Dropdown: Fix shadow size; perfect shadow positioning +- PostPlay: Remove leftover bifurcation line +- SeekDialog: Be smarter about playerprocessinfo/playerdebug/PM4K stream info (press left/right) +- Settings: Make the combined continue watching/ondeck hub default enabled +- Settings: Add setting "Force plex.direct mapping" (default: False) +- Settings: Sanitize defaults ("Loop Theme Music" off vs on, "Home: Resume in-progress items" off vs on) +- Settings: Temporarily remove "custom" theme setting as it's been broken for a while now +- Settings: Rename "Episodes: Skip Post Play screen" to "Episodes: Continuous playback" to make functionality clearer +- AddonSettings: widen maximum for "Use alternate seek" wait for seek to 5000 (was 2500) + + +[-1.0.0-] +CoreELEC +- Fix issues with delayed audio playback via the alternate seek method +- (Re-) Implemented sophisticated alternate seek method for problematic devices (brought its own quirks) +- Gradually improved alternate seek to a stable state with community feedback +- When switching audio track don’t trigger progress events + +Watchlist +- Implement first version of the watchlist feature +- Auto-remove watched items (movies, shows) from the watchlist (adjustable via setting) +- Allow adding any media item to the watchlist from any media view +- (blindly) remove item from watchlist if manually marked watched via Home or Library context menus +- Allow visiting and playing back items on any available server with the item available, without having to manually switch servers +- Allow selection between multiple versions, if the case, instead of only one/the first per server (ordered by bitrate) + +Subtitles +- Add support for subtitle Auto-Sync (based on audio analysis, PlexServer-side) +- Allow live-toggling of subtitle Auto-Sync in the player (overrides global setting temporarily, or per-TV-show setting) +- Fix embedded subtitle selection when path mapping enabled +- Add subtitle language denylist (disable subtitles for native languages) +- Live-download subtitles via Plex (or Kodi, or both; pre-play and during playback) +- Parse, use and store Plex account subtitle preferences + +Episodes +- Fix episode progress handling +- Try using PlexServer settings for episode completion handling +- Improve background music handling +- Fix play from beginning not working for context menu of episode list items + +Libraries +- Massive potential speedup (5-10x) for huge libraries +- Adhere to the library collections setting of Plex +- Use library title in title header instead of library type +- listview-16x9: Reimplement item type filter +- General fixes + +Collections +- Fix collections repeating themselves after CHUNKSIZE hit (240 by default); Improve performance tenfold for big collections; Implement chunking in Collection.all() + +Playlist +- Fix playlist subsequent chunking (was broken) (thanks @BrendanMyers88) +- Select the currently relevant item once it’s been created properly +- Massively improve resource usage on big video playlists when returning from playback by only refreshing affected items +- Correctly show and scale video progress + +UI +- Mark any item as played/unplayed on Home (even in-progress ones), Libraries +- Allow removing individual items from Continue Watching/On Deck (same as in official clients) +- Implement fast server and user switch in Home (via long-press/context menu) +- Visual changes to adhere more to the 2024 style (player, dropdowns, dialogs) +- Add the ability to hide episode and movie spoilers defined by multiple user settings (titles, blur images, summaries, ratings) +- Add the ability to always resume in progress media (also directly from home if wanted); can be overriden using long-press/context menu +- Settings: Allow navigation using MOVE_RIGHT/LEFT; fixes +- Unify dropdown behaviour: always allow round-robin on any dropdown; default close behaviour is pressing left or BACK/PREVIOUS_MENU +- Core/Poster dimensions: Use 268x402 (0.66 ratio) for poster base resolution instead of 268x397 (0.675 ratio); unifies vertical scaling for posters and collection art +- Unify bound Home key (should now work everywhere) +- Home/Core: Don’t retain background image when switching home users +- Home/Hubs: Use season thumbnail for episodes if possible (adjustable via setting) +- Home: Add context menu option to start playback from beginning for in progress items when resume in progress items on home is enabled +- Home: Make moving sections more resilient (esp. when moving fast/holding left/right) +- Home: Add “Minimize” and “Exit” options to user menu (touch based devices can’t easily hit the exit menu by using BACK) +- Episodes: Use season art as background if available +- Episodes: Show correct cast per episode, not from parent show +- Movies/Shows/Episodes: Add Directors (up to two) to the beginning of the cast list; support searching for director’s movies (currently not possible with shows/episodes) +- Episodes/Shows/Movies: Make “go to section” action actually visit the section instead of the home hub +- Remove most obsolete dividers in sub views +- Movies/Episodes: Properly vertically align audio/video/subtitle labels within textures +- Movies: Show duration for extras +- Postplay: Scroll titles (instead of cutting them off + ellipsis); scroll multiline summaries +- Item/AudioSettings: Don’t trim secondary title, scroll it + +Videoplayer +- Modernize the usage of the Plex Media Decision Engine +- Use bitrate instead of resolution when asking the Plex Server for a decision (better transcode behaviour, especially with HEVC) +- Any transcode quality selection is now bitrate based, as the resolution can (and should) vary +- Add the ability to not spoil anything during playback +- Add fast resume/pause behaviour (use OK/ENTER to quickly resume when paused or pause when playing, instead of showing the OSD) +- When showing stream info, pressing RIGHT will show playerprocessinfo, pressing LEFT will show playerdebug +- Don’t skip to next episode after final credits marker was skipped if binge mode or skip postplay for TV isn’t active +- Never show postplay for movie extras, regardless of any postplay setting +- Allow for immediate skipping of a counting down marker (before you had to wait 1 second for that to work) (needs the respective AddonSetting enabled as well) +- PlayerVideoCurrentPlaylist: Scroll titles if they’re too long; allow vertical round-robining at the top boundary; allow closing the playlist by pressing LEFT +- Use uuid4() as session ID; keep session ID when switching streams; report session ID when sending timeline requests +- Remove artificial percentage-based virtual chapters (intro/outro are still generated if necessary) +- When stopping playback after idle, don’t start playback of next episode/playlist item +- Fix rare occasion where immediately going to next item in playlist/season would keep the “skip prev” button disabled, when the playlist/season was started at episode 1 +- Ensure correct audio track was set after a handler was reused (skip to next video was pressed in player) +- SeekDialog/CurrentPlaylist: Mark previous episodes/items as watched in current playlist during a multi-item playback session +- SeekDialog/CurrentPlaylist: Show progress indicators for items in playlist; “live” snapshot of current video progress when opened as well + +Core: +- Add Autoupdater (for beta releases) +- VideoPlayer/Timeline event improvements +- Ship certificate bundle and allow manual certificates +- Improve background resolution by allowing a manual scale value +- Fix Collections +- Fix Playlists +- Improve VideoPlaylists +- Fix Postplay issues +- Fix MusicPlayer +- Migrate to InfoTags for Kodi >= 20, removes deprecation warning for setInfo +- Fix HTTPConnection was never async. Unify Asynchronicity between HTTPConnection and HTTPSConnection via AsyncConnectionMixin +- Add requests cache for Plex data (can also be persisted), to speed up re-visiting resource-heavy views +- Fix core networking issues +- Basic support for external players. Does not support seeking or resuming inside external players but tries to keep the playback and resume states (for resuming an item with an external player you’ll have to seek there yourself); only supports path mapping if the external player can access the same path PM4K sees +- Core/Transcode/Audio: Allow audio transcode targets ‘mp3’, ‘ac3’, ‘aac’, ‘opus’, ‘vorbis’, ‘eac3’, ‘flac’, ‘alac’ (new: eac3, vorbis, flac, alac) +- Improve audio stream type detection +- Improve correct audio stream selection during certain events fixing Kodi quirks +- Add setting to loop theme music (default: on) +- Harden VideoPlayer +- Implement cleaner shutdown functionality +- Support custom theme.mp3 theme music for TV shows and movies (additional extensions: 'ac3', 'aac', 'opus', 'vorbis', 'eac3', 'flac', 'alac') +- Watchlist/Core/ThemeMusic: While traversing items of multiple types (movies and shows), properly stop or start theme music if necessary +- Support Plex markers as watched indicators +- Check plex.direct dns rebind issues more thoroughly and don’t completely rely on the protection flags from the Plex server registry (often false); can lead to a slightly longer bootup time +- Fix plex.direct host mapping not being triggered in certain situations (local docker IP mapped to VM for example) +- Don’t consider a resolved plex.direct domain with IP 0.0.0.0 as “successfully resolved” +- Force plex.direct connectivity check if the current connection has hostname issues (should fix certain setups with manual connections/LAN connections without allowing insecure local connections, falling back to plex.direct) +- Home/Movies/Shows/Episodes/Libraries: Implement generic watched toggle; toggle watched via keyboard mapping “w” (buttonCode: 61527) +- Home: Don’t fail hard when no currently selected server is available +- Home: After selecting an unavailable server from the dropdown, shutdown wasn’t possible +- Write templates to user directory instead of plugin directory when env var INSTALLATION_DIR_AVOID_WRITE is set (useful when plugin directory isn’t writable) +- AddonSettings: Use 850ms as new default for the Alternate seek fix (was: 500ms) +- Bring back screensaver. +- Optionally allow direct play for resolutions above 4K + +Translations +- Update translations for Spanish (100%), French (75%), Chinese (90%), Hungarian (100%), Italian (63%) + +Addons: +- Provide the Plextuary skin with optimizations for CoreELEC and CoreELEC CPM +- Plextuary: Allow customization of Exit menu +- Add "McFontFace" Kodi font management plugin +- Add "PM4K Log Uploader" plugin + + +[-0.7.9-rev3-] - Player: Only send timeline events if playstate has changed or we haven't sent one for 15 seconds; fixes #137 - AddonSettings: Fix default cert bundle setting - Core/plexnet/http: Fix session cert bundle usage by using the correct attribute (verify vs cert); should fix kodi 18 connectivity diff --git a/script.plexmod/default.py b/script.plexmod/default.py index 2d642052bb..a9b02622b1 100644 --- a/script.plexmod/default.py +++ b/script.plexmod/default.py @@ -4,68 +4,106 @@ import tempfile import sys -from lib.logging import log +from lib.logging import log, KodiLogProxyHandler # noinspection PyUnresolvedReferences -from lib.kodi_util import translatePath, xbmc, setGlobalProperty, getGlobalProperty +from lib.kodi_util import translatePath, xbmc, xbmcgui +from lib.properties import getGlobalProperty, setGlobalProperty from tendo_singleton import SingleInstance, SingleInstanceException - # tempfile's standard temp dirs won't work on specific OS's (android) tempfile.tempdir = translatePath("special://temp/") - -class KodiLogProxyHandler(logging.Handler): - def emit(self, record): - try: - log(self.format(record)) - except: - self.handleError(record) - - # add custom logger for tendo.singleton, so we can capture its messages logger = logging.getLogger("tendo.singleton") -logger.addHandler(KodiLogProxyHandler()) +logger.addHandler(KodiLogProxyHandler(level=logging.DEBUG)) logger.setLevel(logging.DEBUG) + +from_kiosk = False +kiosk_always = False boot_delay = False -if len(sys.argv) > 1: - boot_delay = int(sys.argv[1]) +argvlen = len(sys.argv) +if argvlen > 1: + from_kiosk = int(sys.argv[1]) > 0 + kiosk_always = int(sys.argv[1]) > 1 + if argvlen > 2: + boot_delay = int(sys.argv[2]) + if argvlen > 3: + update_successful = bool(int(sys.argv[3])) started = False set_waiting_for_start = False try: + # reactivate/maximize if getGlobalProperty('running'): try: + log('Main: script.plexmod: Trying to reactivate minimized addon') xbmc.executebuiltin('NotifyAll({0},{1},{2})'.format('script.plexmod', 'RESTORE', '{}')) except: - log('Main: script.plex: Already running, couldn\'t reactivate other instance, exiting.') + log('Main: script.plexmod: Already running or faulty, couldn\'t reactivate other instance, exiting.') + else: + sys.exit(0) else: + # addon not started if not getGlobalProperty('started'): + # we're waiting for the addon to start, immediate start was requested if getGlobalProperty('waiting_for_start'): - setGlobalProperty('waiting_for_start', '') - log('Main: script.plex: Currently waiting for start, immediate start was requested.') + setGlobalProperty('waiting_for_start', '', wait=True) + log('Main: script.plexmod: Currently waiting for start, immediate start was requested.') sys.exit(0) + # only allow a single instance with SingleInstance("pm4k"): started = True + skip_ensure_home = False from lib import main - waited = 0 - if boot_delay: - set_waiting_for_start = True - setGlobalProperty('waiting_for_start', '1') - log('Main: script.plex: Delaying start for {}s.', boot_delay) - while (not main.util.MONITOR.abortRequested() and waited < boot_delay - and getGlobalProperty('waiting_for_start')): - waited += 0.1 - main.util.MONITOR.waitForAbort(0.1) - if waited < boot_delay: - log('Main: script.plex: Forced start before auto-start delay ({:.1f}/{} s).', waited, boot_delay) - setGlobalProperty('waiting_for_start', '') - setGlobalProperty('started', '1') + + # called from service.py? + if from_kiosk: + waited = 0 + if boot_delay: + set_waiting_for_start = True + setGlobalProperty('waiting_for_start', '1', wait=True) + log('Main: script.plexmod: Delaying start for {}s.', boot_delay) + while (not main.util.MONITOR.abortRequested() and waited < boot_delay + and getGlobalProperty('waiting_for_start')): + waited += 0.1 + main.util.MONITOR.waitForAbort(0.1) + + # boot delay canceled by immediate start + if waited < boot_delay: + log('Main: script.plexmod: Forced start before auto-start delay ({:.1f}/{} s).', + waited, boot_delay) + skip_ensure_home = True + + if kiosk_always: + skip_ensure_home = True + + waited = 0 + # wait 120s if we're not at home right now or have an active dialog until starting the addon + if not skip_ensure_home and \ + (xbmcgui.getCurrentWindowId() > 10000 or xbmcgui.getCurrentWindowDialogId() > 9999): + setGlobalProperty('waiting_for_start', '1', wait=True) + + while getGlobalProperty('waiting_for_start') and not main.util.MONITOR.abortRequested() and \ + waited < 120 and ( + xbmcgui.getCurrentWindowId() > 10000 or xbmcgui.getCurrentWindowDialogId() > 9999): + if waited == 0: + log('Main: script.plexmod: Waiting for auto-start; we\'re not home or have an ' + 'active dialog.') + waited += 0.1 + main.util.MONITOR.waitForAbort(0.1) + + if from_kiosk: + # no immediate start requested + main.util.MONITOR.waitForAbort(0.5) + + setGlobalProperty('waiting_for_start', '', wait=True) + setGlobalProperty('started', '1', wait=True) main.main() else: - log('Main: script.plex: Already running, exiting') + log('Main: script.plexmod: Already running, exiting') except SingleInstanceException: pass diff --git a/script.plexmod/icon2.png b/script.plexmod/icon2.png index 31265d3f7d..7ab0a86d75 100644 Binary files a/script.plexmod/icon2.png and b/script.plexmod/icon2.png differ diff --git a/script.plexmod/lib/_included_packages/icmplib/utils.py b/script.plexmod/lib/_included_packages/icmplib/utils.py index 00e2664dbb..c9836a9f87 100644 --- a/script.plexmod/lib/_included_packages/icmplib/utils.py +++ b/script.plexmod/lib/_included_packages/icmplib/utils.py @@ -74,7 +74,7 @@ def unique_identifier(): return _current_id -def resolve(name, family=None): +def resolve(name, family=None, use_orig=False): ''' Resolve a hostname or FQDN to an IP address. Depending on the name specified in parameters, several IP addresses may be returned. @@ -105,7 +105,9 @@ def resolve(name, family=None): else: _family = socket.AF_INET - lookup = socket.getaddrinfo( + func = socket.getaddrinfo if use_orig else socket.getaddrinfo_orig + + lookup = func( host=name, port=None, family=_family, diff --git a/script.plexmod/lib/_included_packages/plexnet/asyncadapter.py b/script.plexmod/lib/_included_packages/plexnet/asyncadapter.py index 798c7eae4f..bec6c3f4ea 100644 --- a/script.plexmod/lib/_included_packages/plexnet/asyncadapter.py +++ b/script.plexmod/lib/_included_packages/plexnet/asyncadapter.py @@ -2,10 +2,10 @@ import time import socket import six +import os +import datetime -import requests -import six - +from kodi_six import xbmc from requests.packages.urllib3 import HTTPConnectionPool, HTTPSConnectionPool from requests.packages.urllib3.connection import HTTPConnection from requests.packages.urllib3.poolmanager import PoolManager, proxy_from_url @@ -16,8 +16,9 @@ # urllib3 >= 2.1.0 from requests.packages.urllib3.connection import HTTPSConnection as VerifiedHTTPSConnection -from requests.adapters import HTTPAdapter +from requests.adapters import HTTPAdapter, Retry from requests.compat import urlparse +from requests_cache import CachedSession #from six.moves.http_client import HTTPConnection import errno @@ -26,6 +27,9 @@ SSL_KEYWORDS = ('key_file', 'cert_file', 'cert_reqs', 'ca_certs', 'ssl_version') +DEBUG_REQUESTS = False +TEMP_PATH = None + WIN_WSAEINVAL = 10022 WIN_EWOULDBLOCK = 10035 WIN_ECONNRESET = 10054 @@ -34,10 +38,13 @@ WIN_EHOSTUNREACH = 10065 MAX_RETRIES = 3 +REQUESTS_CACHE_EXPIRY = 168 def ABORT_FLAG_FUNCTION(): - return False + if STOP_RETRYING_REQUESTS and DEBUG_REQUESTS: + xbmc.log('AsyncVerifiedHTTPSConnection: Abort flag set!', xbmc.LOGINFO) + return STOP_RETRYING_REQUESTS class CanceledException(Exception): @@ -76,21 +83,18 @@ def getConnectTimeout(self): return self -DEFAULT_TIMEOUT = AsyncTimeout(10).setConnectTimeout(10) - +DEFAULT_TIMEOUT = AsyncTimeout(5).setConnectTimeout(5) -class AsyncVerifiedHTTPSConnection(VerifiedHTTPSConnection): - __slots__ = ("_canceled", "deadline", "_timeout") +class AsyncConnectionMixin: + def __str__(self): + return '{0}({1})'.format(self.__class__.__name__, self.identifier) - def __init__(self, *args, **kwargs): - VerifiedHTTPSConnection.__init__(self, *args, **kwargs) - self._canceled = False - self.deadline = 0 - self._timeout = AsyncTimeout(DEFAULT_TIMEOUT) + def __repr__(self): + return str(self) def _check_timeout(self): if time.time() > self.deadline: - raise ConnectTimeoutError('connection timed out') + raise ConnectTimeoutError('connection timed out: {0}'.format(str(self))) def create_connection(self, address, timeout=None, source_address=None): """Connect to *address* and return the socket object. @@ -104,8 +108,13 @@ def create_connection(self, address, timeout=None, source_address=None): for the socket to bind as a source address before making the connection. An host of '' or port 0 tells the OS to use the default. """ + if DEBUG_REQUESTS: + xbmc.log( + '{3}.create_connection: {0} {1} {2}'.format(address, timeout, repr(timeout), self.__class__.__name__), + xbmc.LOGINFO) timeout = AsyncTimeout.fromTimeout(timeout) self._timeout = timeout + self.identifier = address host, port = address err = None @@ -122,14 +131,14 @@ def create_connection(self, address, timeout=None, source_address=None): sock.bind(source_address) for msg in self._connect(sock, sa): if self._canceled or ABORT_FLAG_FUNCTION(): - raise CanceledException('Request canceled') + raise CanceledException('Request canceled: {0}'.format(str(self))) sock.setblocking(True) return sock except socket.error as _: err = _ if sock is not None: - sock.shutdown(socket.SHUT_RDWR) + #sock.shutdown(socket.SHUT_RDWR) sock.close() if err is not None: @@ -145,13 +154,19 @@ def _connect(self, sock, sa): if not status or status in (errno.EISCONN, WIN_EISCONN): break elif status in (errno.EINPROGRESS, WIN_EWOULDBLOCK): - self.deadline = time.time() + self._timeout.getConnectTimeout() + # extend the deadline once at most, otherwise count towards our connect timeout + if not self.deadline_extended: + self.deadline = time.time() + self._timeout.getConnectTimeout() + self.deadline_extended = True + # elif status in (errno.EWOULDBLOCK, errno.EALREADY) or (os.name == 'nt' and status == errno.WSAEINVAL): # pass yield if self._canceled or ABORT_FLAG_FUNCTION(): - raise CanceledException('Request canceled') + if DEBUG_REQUESTS: + xbmc.log('{1}._connect: Canceled: {0}'.format(self.identifier, self.__class__.__name__), xbmc.LOGINFO) + raise CanceledException('Request canceled: {0}'.format(str(self))) error = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) if error: @@ -170,15 +185,29 @@ def cancel(self): self._canceled = True -class AsyncHTTPConnection(HTTPConnection): - __slots__ = ("_canceled", "deadline") +class AsyncVerifiedHTTPSConnection(AsyncConnectionMixin, VerifiedHTTPSConnection): + __slots__ = ("_canceled", "deadline", "deadline_extended", "identifier", "_timeout") + def __init__(self, *args, **kwargs): - HTTPConnection.__init__(self, *args, **kwargs) + super(AsyncVerifiedHTTPSConnection, self).__init__(*args, **kwargs) self._canceled = False self.deadline = 0 + self.identifier = None + self.deadline_extended = False + self._timeout = AsyncTimeout(DEFAULT_TIMEOUT) + + +class AsyncHTTPConnection(AsyncConnectionMixin, HTTPConnection): + __slots__ = ("_canceled", "deadline", "deadline_extended", "identifier", "_timeout") + + def __init__(self, *args, **kwargs): + super(AsyncHTTPConnection, self).__init__(*args, **kwargs) + self._canceled = False + self.deadline = 0 + self.identifier = None + self.deadline_extended = False + self._timeout = AsyncTimeout(DEFAULT_TIMEOUT) - def cancel(self): - self._canceled = True class AsyncHTTPConnectionPool(HTTPConnectionPool): @@ -335,12 +364,33 @@ def get_connection(self, url, proxies=None): self.connections.append(conn) return conn +STOP_RETRYING_REQUESTS = False + + +class StoppableRetry(Retry): + def increment(self, *args, **kwargs): + if STOP_RETRYING_REQUESTS: + self.total = 0 + return super(StoppableRetry, self).increment(*args, **kwargs) + -class Session(requests.Session): +class Session(CachedSession): def __init__(self, *args, **kwargs): - requests.Session.__init__(self, *args, **kwargs) - self.mount('https://', AsyncHTTPAdapter(max_retries=MAX_RETRIES)) - self.mount('http://', AsyncHTTPAdapter(max_retries=MAX_RETRIES)) + kwargs['cache_name'] = os.path.join(TEMP_PATH, "pm4k_requests_cache") + kwargs['backend'] = "sqlite" + kwargs['fast_save'] = True + if REQUESTS_CACHE_EXPIRY: + kwargs['expire_after'] = datetime.timedelta(hours=REQUESTS_CACHE_EXPIRY) # 7 days + CachedSession.__init__(self, *args, **kwargs) + + self.mount('https://', AsyncHTTPAdapter(max_retries=StoppableRetry(MAX_RETRIES))) + self.mount('http://', AsyncHTTPAdapter(max_retries=StoppableRetry(MAX_RETRIES))) + + def request(self, method, url, *args, **kwargs): + self._is_cache_disabled = not kwargs.pop('with_cache', False) + if DEBUG_REQUESTS: + xbmc.log("Session.request: (cache enabled: %s) %s %s" % (not self._is_cache_disabled, method, url), xbmc.LOGINFO) + return CachedSession.request(self, method, url, *args, **kwargs) def cancel(self): for v in self.adapters.values(): diff --git a/script.plexmod/lib/_included_packages/plexnet/gdm.py b/script.plexmod/lib/_included_packages/plexnet/gdm.py index ccc540aed0..a82f761602 100644 --- a/script.plexmod/lib/_included_packages/plexnet/gdm.py +++ b/script.plexmod/lib/_included_packages/plexnet/gdm.py @@ -26,9 +26,9 @@ def __init__(self): def isActive(self): from . import plexapp - return util.INTERFACE.getPreference("gdm_discovery", True) and self.thread and self.thread.is_alive() + return util.INTERFACE.getPreference("gdm_discovery", False) and self.thread and self.thread.is_alive() - ''' + r''' def discover(self): # Only allow discovery if enabled and not currently running self._close = False @@ -122,7 +122,7 @@ def discover(self): def discover(self): from . import plexapp - if not util.INTERFACE.getPreference("gdm_discovery", True) or self.isActive(): + if not util.INTERFACE.getPreference("gdm_discovery", False) or self.isActive(): return self.thread = threading.Thread(target=self._discover) diff --git a/script.plexmod/lib/_included_packages/plexnet/http.py b/script.plexmod/lib/_included_packages/plexnet/http.py index e77d5adc5b..941abada42 100644 --- a/script.plexmod/lib/_included_packages/plexnet/http.py +++ b/script.plexmod/lib/_included_packages/plexnet/http.py @@ -55,6 +55,7 @@ def pgetaddrinfo(host, port, *args, **kwargs): socket.getaddrinfo = pgetaddrinfo +socket.getaddrinfo_orig = _getaddrinfo def GET(*args, **kwargs): diff --git a/script.plexmod/lib/_included_packages/plexnet/media.py b/script.plexmod/lib/_included_packages/plexnet/media.py index ec5a77c72a..f88da8f6ef 100644 --- a/script.plexmod/lib/_included_packages/plexnet/media.py +++ b/script.plexmod/lib/_included_packages/plexnet/media.py @@ -32,7 +32,11 @@ def getIdentifier(self): identifier = self.get('identifier') or None if identifier is None: - identifier = self.container.identifier + try: + identifier = self.container.identifier + except AttributeError: + util.DEBUG_LOG("Couldn't get media identifier for {}", self) + pass # HACK # PMS doesn't return an identifier for playlist items. If we haven't found @@ -144,12 +148,12 @@ class MediaPartStream(plexstream.PlexStream): TYPE = None STREAMTYPE = None - def __init__(self, data, initpath=None, server=None, part=None): - plexobjects.PlexObject.__init__(self, data, initpath, server) + def __init__(self, data, initpath=None, server=None, part=None, **kwargs): + plexobjects.PlexObject.__init__(self, data, initpath, server, **kwargs) self.part = part @staticmethod - def parse(data, initpath=None, server=None, part=None): + def parse(data, initpath=None, server=None, part=None, **kwargs): STREAMCLS = { 1: VideoStream, 2: AudioStream, @@ -157,7 +161,11 @@ def parse(data, initpath=None, server=None, part=None): } stype = int(data.attrib.get('streamType')) cls = STREAMCLS.get(stype, MediaPartStream) - return cls(data, initpath=initpath, server=server, part=part) + return cls(data, initpath=initpath, server=server, part=part, **kwargs) + + @staticmethod + def rebuild(s): + return MediaPartStream.parse(s.data, initpath=s.initpath, server=s.server, part=s.part) def __repr__(self): return '<%s:%s>' % (self.__class__.__name__, self.id) @@ -177,6 +185,29 @@ class SubtitleStream(MediaPartStream): TYPE = 'subtitlestream' STREAMTYPE = plexstream.PlexStream.TYPE_SUBTITLE + def __init__(self, data, initpath=None, server=None, part=None): + super(MediaPartStream, self).__init__(data, initpath=initpath, server=server, part=part) + self.force_auto_sync = None + self.init_auto_sync(part=part) + + def init_auto_sync(self, part=None, video=None): + if not (part or video): + return + self._should_auto_sync = self.canAutoSync.asBool() and util.INTERFACE.playbackManager( + part.media).auto_sync if part and part.media else video.playbackSettings.auto_sync if video else util.INTERFACE.getPreference('auto_sync', user=True) + + @property + def should_auto_sync(self): + return self.force_auto_sync if self.force_auto_sync is not None else self._should_auto_sync + + @property + def should_auto_sync_unforced(self): + return self._should_auto_sync + + @should_auto_sync.setter + def should_auto_sync(self, value): + self._should_auto_sync = value + class TranscodeSession(plexobjects.PlexObject): TYPE = 'TranscodeSession' @@ -216,12 +247,6 @@ class Country(MediaTag): FILTER = 'country' -class Director(MediaTag): - TYPE = 'Director' - FILTER = 'director' - ID = '4' - - class Genre(MediaTag): TYPE = 'Genre' FILTER = 'genre' @@ -233,20 +258,17 @@ class Mood(MediaTag): FILTER = 'mood' -class Producer(MediaTag): - TYPE = 'Producer' - FILTER = 'producer' - class Role(MediaTag): TYPE = 'Role' FILTER = 'actor' ID = '6' + translated_role = '' def sectionRoles(self): hubs = self.server.hubs(count=10, search_query=self.tag) for hub in hubs: - if hub.type == 'actor': + if hub.type == self.FILTER: break else: return None @@ -265,9 +287,23 @@ class Similar(MediaTag): FILTER = 'similar' -class Writer(MediaTag): +class Director(Role): + TYPE = 'Director' + FILTER = 'director' + ID = '4' + translated_role = 'Director' + + +class Producer(Role): + TYPE = 'Producer' + FILTER = 'producer' + translated_role = 'Producer' + + +class Writer(Role): TYPE = 'Writer' FILTER = 'writer' + translated_role = 'Writer' class Guid(MediaTag): @@ -307,13 +343,19 @@ def ratingImage(self): return img.split('rottentomatoes://')[1] +class Studio(MediaTag): + TYPE = 'Studio' + FILTER = 'Studio' + + class RelatedMixin(object): _relatedCount = None + related_source = "similar" @property def relatedCount(self): if self._relatedCount is None: - related = self.getRelated(0, 0) + related = self.getRelated(0, 0 if self.related_source == "similar" else 36) if related is not None: self._relatedCount = related.totalSize else: @@ -326,9 +368,10 @@ def related(self): return self.getRelated(0, 8) def getRelated(self, offset=None, limit=None, _max=36): - path = '/library/metadata/%s/similar' % self.ratingKey + path = '/library/metadata/{}/{}'.format(self.ratingKey, self.related_source) try: - return plexobjects.listItems(self.server, path, offset=offset, limit=limit, params={"count": _max}) + return plexobjects.listItems(self.server, path, offset=offset, limit=limit, params={"count": _max}, + cachable=self.cachable, cache_ref=self.cacheRef, not_cachable=self._not_cachable) except exceptions.BadRequest: util.DEBUG_LOG("Invalid related items response returned for {}", self) return None diff --git a/script.plexmod/lib/_included_packages/plexnet/mediachoice.py b/script.plexmod/lib/_included_packages/plexnet/mediachoice.py index 4afb73b200..6bd506b7da 100644 --- a/script.plexmod/lib/_included_packages/plexnet/mediachoice.py +++ b/script.plexmod/lib/_included_packages/plexnet/mediachoice.py @@ -2,7 +2,6 @@ from . import plexstream from . import util - class MediaChoice(object): SUBTITLES_DEFAULT = 0 SUBTITLES_BURN = 1 @@ -12,6 +11,7 @@ class MediaChoice(object): def __init__(self, media=None, partIndex=0): self.media = media self.part = None + self.partIndex = partIndex self.forceTranscode = False self.isDirectPlayable = False self.videoStream = None @@ -37,6 +37,7 @@ def __init__(self, media=None, partIndex=0): self.videoStream = self.part.getSelectedStreamOfType(plexstream.PlexStream.TYPE_VIDEO) self.audioStream = self.part.getSelectedStreamOfType(plexstream.PlexStream.TYPE_AUDIO) self.subtitleStream = self.part.getSelectedStreamOfType(plexstream.PlexStream.TYPE_SUBTITLE) + else: util.WARN_LOG("Media does not contain a valid part") diff --git a/script.plexmod/lib/_included_packages/plexnet/mediadecisionengine.py b/script.plexmod/lib/_included_packages/plexnet/mediadecisionengine.py index 9d5adcd4bf..446eb4ceb4 100644 --- a/script.plexmod/lib/_included_packages/plexnet/mediadecisionengine.py +++ b/script.plexmod/lib/_included_packages/plexnet/mediadecisionengine.py @@ -1,7 +1,6 @@ from __future__ import absolute_import from . import mediachoice from . import serverdecision -from . import plexapp from . import util import six from six.moves import range @@ -112,7 +111,8 @@ def evaluateMediaVideo(self, item, media, partIndex=0): choice.isSelected = media.isSelected() choice.protocol = media.protocol("http") - maxResolution = item.settings.getMaxResolution(item.getQualityType(), self.isSupported4k(media, choice.videoStream)) + #maxResolution = item.settings.getMaxResolution(item.getQualityType(), self.isSupported4k(media, choice.videoStream)) + maxResolution = self.isSupported4k(media, choice.videoStream) and item.settings.maxVerticalDPRes or 1088 maxBitrate = item.settings.getMaxBitrate(item.getQualityType()) choice.resolution = media.getVideoResolution() @@ -249,6 +249,13 @@ def evaluateMediaVideo(self, item, media, partIndex=0): "user settings ({} ch)".format(ach, choice.audioStream.codec, ch)) choice.isDirectPlayable = False + # check for disabled audio codecs + if choice.audioStream is not None \ + and choice.audioStream.codec in item.settings.getPreference("audio_disabled_codecs", []): + choice.isDirectPlayable = False + util.LOG("MDE: {} can't be direct played due " + "to user settings".format(choice.audioStream.codec)) + choice.sorts.videoDS = not ( choice.sorts.videoDS is None or choice.forceTranscode is True) and choice.sorts.videoDS or 0 choice.sorts.resolution = choice.resolution @@ -300,6 +307,16 @@ def canDirectPlay(self, item, choice): util.LOG("MDE: (DP) Codec is VC1, which is disabled") return False + # HDR + if item.settings.getPreference('disable_hdr', False): + if choice.videoStream.DOVIProfile == "8" and choice.videoStream.DOVIBLCompatID == "1" or \ + choice.videoStream.DOVIProfile == "8" and choice.videoStream.DOVIBLCompatID == "4" or \ + choice.videoStream.DOVIProfile == "7" or \ + choice.videoStream.colorTrc in ("smpte2084", "arib-std-b67"): + choice.forceTranscode = True + choice.sorts.videoDS = 0 + return False + return True # container = choice.media.get('container') diff --git a/script.plexmod/lib/_included_packages/plexnet/mixins.py b/script.plexmod/lib/_included_packages/plexnet/mixins.py index 3cb4bb68a5..9676f97555 100644 --- a/script.plexmod/lib/_included_packages/plexnet/mixins.py +++ b/script.plexmod/lib/_included_packages/plexnet/mixins.py @@ -21,6 +21,7 @@ def translateAudioCodec(self, codec=None): codec = (codec or (streamBase.codec or '')).lower() title = streamBase.title.lower() + fn = ((mc and mc.part.file) or (self.part and getattr(self.part, "file")) or '').lower() if codec == "dca-ma" or (codec == "dca" and streamBase.profile == "ma"): codec = "DTS-HD MA" @@ -38,10 +39,17 @@ def translateAudioCodec(self, codec=None): codec = "TrueHD" if EAC3JOC_STR in title: codec = "TrueHD {}".format(EAC3JOC_STR.capitalize()) + elif EAC3JOC_STR in fn: + codec = "TrueHD {}?".format(EAC3JOC_STR.capitalize()) return codec elif codec == "eac3": - if streamBase and streamBase.bitrate.asInt() >= 768: - codec = "DD+ {}".format(EAC3JOC_STR.capitalize()) + definitely_ertmers = (streamBase and streamBase.bitrate.asInt() >= 768) or EAC3JOC_STR in title + possible_ertmers = False + if not definitely_ertmers: + possible_ertmers = EAC3JOC_STR in fn + + if definitely_ertmers or possible_ertmers: + codec = "DD+ {}".format(EAC3JOC_STR.capitalize() + (possible_ertmers and "?" or "")) return codec return codec.upper() diff --git a/script.plexmod/lib/_included_packages/plexnet/myplexaccount.py b/script.plexmod/lib/_included_packages/plexnet/myplexaccount.py index 326be51353..0e2e34e11e 100644 --- a/script.plexmod/lib/_included_packages/plexnet/myplexaccount.py +++ b/script.plexmod/lib/_included_packages/plexnet/myplexaccount.py @@ -54,6 +54,12 @@ def __init__(self): self.revalidatePlexPass = False self.homeUsers = [] + # defaultSubtitleAccessibility: 0 = prefer non SDH, 1 = prefer SDH, 2 = only SDH, 3 = only non SDH + # defaultSubtitleForced: 0 = prefer non forced, 1 = prefer forced, 2 = only forced, 3 = only non forced + self.subtitlesSDH = 0 + self.subtitlesForced = 0 + self.subtitlesLanguage = 'en' + def init(self): self.loadState() @@ -71,7 +77,10 @@ def saveState(self): 'isSecure': self.isSecure, 'adminHasPlexPass': self.adminHasPlexPass, 'thumb': self.thumb, - 'lastHomeUserUpdate': self.lastHomeUserUpdate + 'lastHomeUserUpdate': self.lastHomeUserUpdate, + 'subtitlesSDH': self.subtitlesSDH, + 'subtitlesForced': self.subtitlesForced, + 'subtitlesLanguage': self.subtitlesLanguage, } if self.cacheHomeUsers: @@ -109,6 +118,9 @@ def loadState(self): self.adminHasPlexPass = obj.get('adminHasPlexPass') or self.adminHasPlexPass self.thumb = obj.get('thumb') self.lastHomeUserUpdate = obj.get('lastHomeUserUpdate') + self.subtitlesSDH = obj.get('subtitlesSDH', 0) + self.subtitlesForced = obj.get('subtitlesForced', 0) + self.subtitlesLanguage = obj.get('subtitlesLanguage', 'en') if self.cacheHomeUsers: self.homeUsers = [HomeUser(data) for data in obj.get('homeUsers', [])] self.setAdminByCHU() @@ -141,6 +153,9 @@ def logState(self): util.LOG("Protected: {0}", self.isProtected) util.LOG("Admin: {0}", self.isAdmin) util.LOG("AdminPlexPass: {0}", self.adminHasPlexPass) + util.LOG("subtitlesSDH: {0}", self.subtitlesSDH) + util.LOG("subtitlesForced: {0}", self.subtitlesForced) + util.LOG("subtitlesLanguage: {0}", self.subtitlesLanguage) def getHomeSubscription(self): """ @@ -184,6 +199,12 @@ def onAccountResponse(self, request, response, context): self.isSecure = data.attrib.get('secure') == '1' self.hasQueue = bool(data.attrib.get('queueEmail')) + # profile settings + prof = data.find('profile_settings') + self.subtitlesSDH = int(prof.attrib.get('default_subtitle_accessibility', 0)) + self.subtitlesForced = int(prof.attrib.get('default_subtitle_forced', 0)) + self.subtitlesLanguage = str(prof.attrib.get('default_subtitle_language', 'en')) + # PIN if data.attrib.get('pin'): self.pin = data.attrib.get('pin') @@ -367,6 +388,13 @@ def onHomeUsersUpdateResponse(self, request, response, context): self.lastHomeUserUpdate = time.time() self.saveState() + def getHomeUser(self, userId): + if not self.homeUsers: + return None + for user in self.homeUsers: + if user.id == userId: + return user + def switchHomeUser(self, userId, pin=''): if userId == self.ID and self.isAuthenticated: return True diff --git a/script.plexmod/lib/_included_packages/plexnet/myplexmanager.py b/script.plexmod/lib/_included_packages/plexnet/myplexmanager.py index d96aa50a04..9471af725c 100644 --- a/script.plexmod/lib/_included_packages/plexnet/myplexmanager.py +++ b/script.plexmod/lib/_included_packages/plexnet/myplexmanager.py @@ -73,7 +73,7 @@ def onResourcesResponse(self, request, response, context): for conn in resource.connections: util.DEBUG_LOG(' {0}', conn) - if 'server' in resource.provides: + if 'server' in resource.provides or resource.product == "Plex Media Server": server = plexserver.createPlexServerForResource(resource) util.DEBUG_LOG(' {0}', server) servers.append(server) diff --git a/script.plexmod/lib/_included_packages/plexnet/myplexserver.py b/script.plexmod/lib/_included_packages/plexnet/myplexserver.py index 48c9bcd769..e47973d513 100644 --- a/script.plexmod/lib/_included_packages/plexnet/myplexserver.py +++ b/script.plexmod/lib/_included_packages/plexnet/myplexserver.py @@ -1,9 +1,16 @@ from __future__ import absolute_import + from . import plexapp from . import plexconnection from . import plexserver from . import plexresource from . import plexservermanager +from . import plexobjects +from . import plexlibrary +from . import compat +from . import util + +from lib.i18n import T class MyPlexServer(plexserver.PlexServer): @@ -35,3 +42,88 @@ def buildUrl(self, path, includeToken=False): return url return plexserver.PlexServer.buildUrl(self, path, includeToken) + + +class PlexDiscoverServer(MyPlexServer): + TYPE = 'PLEXDISCOVERSERVER' + DEFER_HUBS = True + + def __init__(self): + MyPlexServer.__init__(self) + self.uuid = 'plexdiscover' + self.name = 'discover.plex.tv' + + # tell the server manager we're a synced server and it should try to use a more fitting server for photo + # encoding + self.synced = True + conn = plexconnection.PlexConnection(plexresource.ResourceConnection.SOURCE_MYPLEX, + "https://discover.provider.plex.tv", False, + None, skipLocalCheck=True) + self.connections.append(conn) + self.activeConnection = conn + + # inject our plextv timeout + #self.session.request = functools.partial(self.session.request, timeout=plexserver.util.PLEXTV_TIMEOUT) + + + def hubs(self, section=None, count=None, search_query=None, section_ids=None, ignore_hubs=None): + hubs = [] + + self.currentHubs = {} if self.currentHubs is None else self.currentHubs + + wanted_hubs = [ + ("/hubs/sections/watchlist/continueWatching", {'contentDirectoryID': 'watchlist'}), + ("/hubs/sections/watchlist/recently-added", {'contentDirectoryID': 'watchlist'}), + ("/hubs/sections/watchlist/coming-soon", {'contentDirectoryID': 'watchlist'}), + ("/hubs/sections/home/top_watchlisted", {'contentDirectoryID': 'home'}), + ("/hubs/sections/home/coming-soon", {'contentDirectoryID': 'home'}), + ("/hubs/sections/home/trending-friends", {'contentDirectoryID': 'home'}), + ("/hubs/sections/home/trending-for-you", {'contentDirectoryID': 'home'}), + ("/hubs/sections/home/new-for-you", {'contentDirectoryID': 'home'}), + ] + wanted_hubs_dict = dict(wanted_hubs) + + # discover hubs + params = { + 'includeMeta': "1", + 'includeExternalMetadata': '1', + 'excludeFields': 'summary' + } + + for q in ('/hubs/sections/watchlist', '/hubs/sections/home'): + data = self.query(q, params=params) + container = plexobjects.PlexContainer(data, initpath=q, server=self, address=q) + + if data: + for elem in data: + if elem.attrib.get('key') not in wanted_hubs_dict: + continue + hubIdent = elem.attrib.get('hubIdentifier') + hubTitle = T(34019, "Discover {}").format(elem.attrib.get('title')) if q == '/hubs/sections/home' else elem.attrib.get('title') + self.currentHubs["{}:{}".format(section, hubIdent)] = hubTitle + + if ignore_hubs and "{}:{}".format(section, hubIdent) in ignore_hubs: + continue + + hub = plexlibrary.WatchlistHub(elem, server=self, container=container) + hub.title = hubTitle + hubs.append(hub) + + base_params = { + 'includeMeta': "1", + 'includeExternalMetadata': '1', + } + + # fetch hub data + for hub in hubs: + params = base_params.copy() + params.update(wanted_hubs_dict[hub.key]) + data = self.query(hub.key, params=params) +# + if data: + hubTitle = hub.title + hub.init(data) + # re-inject our modified hubtitle + hub.title = hubTitle + + return hubs \ No newline at end of file diff --git a/script.plexmod/lib/_included_packages/plexnet/nowplayingmanager.py b/script.plexmod/lib/_included_packages/plexnet/nowplayingmanager.py index 95f9ebdb8c..fa87ab4694 100644 --- a/script.plexmod/lib/_included_packages/plexnet/nowplayingmanager.py +++ b/script.plexmod/lib/_included_packages/plexnet/nowplayingmanager.py @@ -14,7 +14,7 @@ class ServerTimeline(util.AttributeDict): def reset(self): - self.expires = time.time() + 15 + self.expires = time.time() + 10 def isExpired(self): return time.time() > self.get('expires', 0) @@ -71,10 +71,6 @@ def __init__(self): self.TIMELINE_TYPES = ["video", "music", "photo"] # Members - self.serverTimelines = util.AttributeDict() - self.subscribers = util.AttributeDict() - self.pollReplies = util.AttributeDict() - self.timelines = util.AttributeDict() self.location = self.NAVIGATION self.textFieldName = None @@ -82,12 +78,23 @@ def __init__(self): self.textFieldSecure = None # Initialization + self.reset() + + def reset(self): + self.serverTimelines = util.AttributeDict() + self.subscribers = util.AttributeDict() + self.pollReplies = util.AttributeDict() + self.timelines = util.AttributeDict() for timelineType in self.TIMELINE_TYPES: self.timelines[timelineType] = TimelineData(timelineType) def updatePlaybackState(self, timelineType, itemData, state, t, playQueue=None, duration=0, force=False, - force_time=False): + continuing=False, force_time=False, server=None): timeline = self.timelines[timelineType] + old_item_data = None + if timeline.itemData: + old_item_data = timeline.itemData.copy() + timeline.itemData = itemData timeline.playQueue = playQueue old_time = timeline.attrs.get("time") @@ -96,7 +103,8 @@ def updatePlaybackState(self, timelineType, itemData, state, t, playQueue=None, if state != "stopped" or force_time: timeline.attrs["time"] = str(t) time_updated = True - elif old_time: + + elif old_time and (not old_item_data or old_item_data.ratingKey == itemData.ratingKey): # the second part might be unnecessary, check if old_state != "stopped": # use old timeline state's time for stopped states util.DEBUG_LOG("Using previous timeline state as we're stopped now: {}", old_time) @@ -113,11 +121,11 @@ def updatePlaybackState(self, timelineType, itemData, state, t, playQueue=None, timeline.state = state timeline.duration = duration - self.sendTimelineToServer(timelineType, timeline, t, force=force) + self.sendTimelineToServer(timelineType, timeline, t, force=force, continuing=continuing, server=server) return time_updated - def sendTimelineToServer(self, timelineType, timeline, t, force=False): - server = util.APP.serverManager.selectedServer + def sendTimelineToServer(self, timelineType, timeline, t, force=False, continuing=False, server=None): + server = server or util.APP.serverManager.selectedServer if not server: return @@ -148,6 +156,11 @@ def sendTimelineToServer(self, timelineType, timeline, t, force=False): params["url"] = timeline.itemData.url params["key"] = timeline.itemData.key params["containerKey"] = timeline.itemData.containerKey + params["playbackTime"] = timeline.itemData.playbackTime + params["continuing"] = continuing and "1" or "0" + if timeline.itemData.additional_params: + params.update(timeline.itemData.additional_params) + if timeline.playQueue: params["playQueueItemID"] = timeline.playQueue.selectedId diff --git a/script.plexmod/lib/_included_packages/plexnet/photo.py b/script.plexmod/lib/_included_packages/plexnet/photo.py index b611a0238d..c569aef67b 100644 --- a/script.plexmod/lib/_included_packages/plexnet/photo.py +++ b/script.plexmod/lib/_included_packages/plexnet/photo.py @@ -45,6 +45,10 @@ def isPhotoOrDirectoryItem(self): class PhotoDirectory(media.MediaItem): TYPE = 'photodirectory' + ALLOWED_FILTERS = () + ALLOWED_SORT = () + DEFAULT_SORT = 'titleSort' + DEFAULT_SORT_DESC = False def all(self, *args, **kwargs): path = self.key @@ -56,7 +60,7 @@ def isPhotoOrDirectoryItem(self): @plexobjects.registerLibFactory('photo') @plexobjects.registerLibFactory('image') -def PhotoFactory(data, initpath=None, server=None, container=None): +def PhotoFactory(data, initpath=None, server=None, container=None, **kwargs): if data.tag == 'Photo': return Photo(data, initpath=initpath, server=server, container=container) else: diff --git a/script.plexmod/lib/_included_packages/plexnet/playlist.py b/script.plexmod/lib/_included_packages/plexnet/playlist.py index b06358dd7e..e670e02eab 100644 --- a/script.plexmod/lib/_included_packages/plexnet/playlist.py +++ b/script.plexmod/lib/_included_packages/plexnet/playlist.py @@ -80,6 +80,8 @@ def next(self): if self.pos >= len(self._items): self.pos = 0 + self.trigger("current.changed") + return True __next__ = next @@ -95,6 +97,8 @@ def prev(self): if self.pos < 0: self.pos = len(self._items) - 1 + self.trigger("current.changed") + return True def getPosFromItem(self, item): @@ -112,6 +116,7 @@ def setCurrent(self, pos): return False self.pos = pos + self.trigger("current.changed") return True def current(self): diff --git a/script.plexmod/lib/_included_packages/plexnet/playqueue.py b/script.plexmod/lib/_included_packages/plexnet/playqueue.py index 5af80c5bc8..181e9c755f 100644 --- a/script.plexmod/lib/_included_packages/plexnet/playqueue.py +++ b/script.plexmod/lib/_included_packages/plexnet/playqueue.py @@ -505,6 +505,7 @@ def next(self): return None self.selectedId = item.playQueueItemID.asInt() + self.trigger("current.changed") return item __next__ = next @@ -518,6 +519,7 @@ def prev(self): return None self.selectedId = item.playQueueItemID.asInt() + self.trigger("current.changed") return item def getPrev(self): @@ -546,10 +548,12 @@ def setCurrent(self, pos): item = list(self.items())[pos] self.selectedId = item.playQueueItemID.asInt() + self.trigger("current.changed") return item def setCurrentItem(self, item): self.selectedId = item.playQueueItemID.asInt() + self.trigger("current.changed") def __eq__(self, other): if not other: diff --git a/script.plexmod/lib/_included_packages/plexnet/plexapp.py b/script.plexmod/lib/_included_packages/plexnet/plexapp.py index 8a6b23e410..f706bfd4c7 100644 --- a/script.plexmod/lib/_included_packages/plexnet/plexapp.py +++ b/script.plexmod/lib/_included_packages/plexnet/plexapp.py @@ -165,15 +165,18 @@ def setQualities(self): maxResolution = "1080p" self._globals['qualities'] = [ - simpleobjects.AttributeDict({'title': "Original", 'index': 13, 'maxBitrate': 1000000}), - simpleobjects.AttributeDict({'title': "20 Mbps " + maxResolution, 'index': 12, 'maxBitrate': 20000}), - simpleobjects.AttributeDict({'title': "12 Mbps " + maxResolution, 'index': 11, 'maxBitrate': 12000}), - simpleobjects.AttributeDict({'title': "10 Mbps " + maxResolution, 'index': 10, 'maxBitrate': 10000}), - simpleobjects.AttributeDict({'title': "8 Mbps " + maxResolution, 'index': 9, 'maxBitrate': 8000}), - simpleobjects.AttributeDict({'title': "4 Mbps 720p", 'index': 8, 'maxBitrate': 4000, 'maxHeight': 720}), - simpleobjects.AttributeDict({'title': "3 Mbps 720p", 'index': 7, 'maxBitrate': 3000, 'maxHeight': 720}), - simpleobjects.AttributeDict({'title': "2 Mbps 720p", 'index': 6, 'maxBitrate': 2000, 'maxHeight': 720}), - simpleobjects.AttributeDict({'title': "1.5 Mbps 480p", 'index': 5, 'maxBitrate': 1500, 'maxHeight': 480}), + simpleobjects.AttributeDict({'title': "Original", 'index': 16, 'maxBitrate': 1000000}), + simpleobjects.AttributeDict({'title': "26 Mbps", 'index': 15, 'maxBitrate': 26000}), + simpleobjects.AttributeDict({'title': "20 Mbps", 'index': 14, 'maxBitrate': 20000}), + simpleobjects.AttributeDict({'title': "16 Mbps", 'index': 13, 'maxBitrate': 16000}), + simpleobjects.AttributeDict({'title': "12 Mbps", 'index': 12, 'maxBitrate': 12000}), + simpleobjects.AttributeDict({'title': "10 Mbps", 'index': 11, 'maxBitrate': 10000}), + simpleobjects.AttributeDict({'title': "8 Mbps", 'index': 10, 'maxBitrate': 8000}), + simpleobjects.AttributeDict({'title': "6 Mbps", 'index': 9, 'maxBitrate': 8000}), + simpleobjects.AttributeDict({'title': "4 Mbps", 'index': 8, 'maxBitrate': 4000, 'maxHeight': 720}), + simpleobjects.AttributeDict({'title': "3 Mbps", 'index': 7, 'maxBitrate': 3000, 'maxHeight': 720}), + simpleobjects.AttributeDict({'title': "2 Mbps", 'index': 6, 'maxBitrate': 2000, 'maxHeight': 720}), + simpleobjects.AttributeDict({'title': "1.5 Mbps", 'index': 5, 'maxBitrate': 1500, 'maxHeight': 480}), simpleobjects.AttributeDict({'title': "720 Kbps", 'index': 4, 'maxBitrate': 720, 'maxHeight': 360}), simpleobjects.AttributeDict({'title': "320 Kbps", 'index': 3, 'maxBitrate': 320, 'maxHeight': 360}), maxQuality @@ -183,7 +186,7 @@ def setQualities(self): if quality.index is not None and quality.index >= 9: quality.update(maxQuality) - def getPreference(self, pref, default=None): + def getPreference(self, pref, default=None, **kw): raise NotImplementedError def setPreference(self, pref, value): @@ -233,11 +236,11 @@ def getMaxResolution(self, quality_type, allow4k=False): def getQualityIndex(self, qualityType): if qualityType == self.QUALITY_LOCAL: - return self.getPreference("local_quality", 13) + return self.getPreference("local_quality2", 16) elif qualityType == self.QUALITY_ONLINE: - return self.getPreference("online_quality", 13) + return self.getPreference("online_quality2", 16) else: - return self.getPreference("remote_quality", 13) + return self.getPreference("remote_quality2", 16) def settingsGetMaxResolution(self, qualityType, allow4k): qualityIndex = self.getQualityIndex(qualityType) @@ -277,17 +280,17 @@ def getPrefOverride(self, key, default=None): def getQualityIndex(self, qualityType): if qualityType == util.INTERFACE.QUALITY_LOCAL: - return self.getPreference("local_quality", 13) + return self.getPreference("local_quality2", 16) elif qualityType == util.INTERFACE.QUALITY_ONLINE: - return self.getPreference("online_quality", 13) + return self.getPreference("online_quality2", 16) else: - return self.getPreference("remote_quality", 13) + return self.getPreference("remote_quality2", 16) - def getPreference(self, key, default=None): + def getPreference(self, key, default=None, **kw): if key in self.prefOverrides: return self.prefOverrides[key] else: - return util.INTERFACE.getPreference(key, default) + return util.INTERFACE.getPreference(key, default, **kw) def getPlaybackFeatures(self): return util.INTERFACE.getPlaybackFeatures() @@ -299,7 +302,7 @@ def getMaxResolution(self, quality_type, allow4k=False): qualityIndex = self.getQualityIndex(quality_type) if qualityIndex >= 9: - return allow4k and 2160 or 1088 + return allow4k and util.INTERFACE.maxVerticalDPRes or 1088 elif qualityIndex >= 6: return 720 elif qualityIndex >= 5: @@ -332,11 +335,12 @@ class DumbInterface(AppInterface): 'provides': 'player', 'device': platform.uname()[0], 'model': 'Unknown', + 'vendor': '', 'friendlyName': 'PlexNet.API', 'deviceInfo': DeviceInfo() } - def getPreference(self, pref, default=None): + def getPreference(self, pref, default=None, **kw): return self._prefs.get(pref, default) def setPreference(self, pref, value): diff --git a/script.plexmod/lib/_included_packages/plexnet/plexconnection.py b/script.plexmod/lib/_included_packages/plexnet/plexconnection.py index 7fbcc437c8..9f58ba12e1 100644 --- a/script.plexmod/lib/_included_packages/plexnet/plexconnection.py +++ b/script.plexmod/lib/_included_packages/plexnet/plexconnection.py @@ -15,7 +15,7 @@ try: from icmplib import ping, resolve, ICMPLibError except: - pass + from urlparse import urlparse else: HAS_ICMPLIB = True from urllib.parse import urlparse @@ -82,6 +82,7 @@ def __init__(self, source, address, isLocal, token, isFallback=False, skipLocalC self.refreshed = True self.score = 0 self.request = None + self.pdHostnameResolved = ".plex.direct:" not in address self.lastTestedAt = 0 self.hasPendingRequest = False @@ -95,6 +96,9 @@ def __init__(self, source, address, isLocal, token, isFallback=False, skipLocalC if HAS_ICMPLIB and util.CHECK_LOCAL and not skipLocalCheck: self.checkLocal() + if not util.NO_HOST_CHECK and not self.pdHostnameResolved: + self.checkNativeResolve() + self.getScore(True) def __eq__(self, other): @@ -128,6 +132,18 @@ def ipInLocalNet(self, ip): return network return False + def checkNativeResolve(self): + pUrl = urlparse(self.address) + hostname = pUrl.hostname + if hostname.endswith("plex.direct") and hostname not in util.SKIP_HOST_CHECK: + try: + ips = util.resolve(hostname, use_orig=True) + self.pdHostnameResolved = ips[0] != "0.0.0.0" + util.DEBUG_LOG("Natively resolved hostname: {} to {}", hostname, ips) + except Exception as e: + util.DEBUG_LOG("Couldn't resolve hostname: {}, {}", hostname, e) + self.pdHostnameResolved = False + def checkLocal(self): pUrl = urlparse(self.address) hostname = pUrl.hostname diff --git a/script.plexmod/lib/_included_packages/plexnet/plexlibrary.py b/script.plexmod/lib/_included_packages/plexnet/plexlibrary.py index 9dce2276b0..e1e44e24ac 100644 --- a/script.plexmod/lib/_included_packages/plexnet/plexlibrary.py +++ b/script.plexmod/lib/_included_packages/plexnet/plexlibrary.py @@ -45,7 +45,7 @@ def onDeck(self): def recentlyAdded(self): return plexobjects.listItems(self.server, '/library/recentlyAdded') - def get(self, title): + def getByTitle(self, title): return plexobjects.findItem(self.server, '/library/all', title) def getByKey(self, key): @@ -90,11 +90,16 @@ class LibrarySection(plexobjects.PlexObject): ALLOWED_SORT = () BOOLEAN_FILTERS = ('unwatched', 'duplicate') + DEFAULT_URL_ARGS = None + DEFAULT_SORT = 'titleSort' + DEFAULT_SORT_DESC = False + isLibraryPQ = True def __init__(self, data, initpath=None, server=None, container=None): self.locations = [] self._isMapped = None + self._settings = None super(LibrarySection, self).__init__(data, initpath=initpath, server=server, container=container) def __repr__(self): @@ -107,14 +112,26 @@ def _setData(self, data): sep = norm_sep(loc.path) self.locations.append(loc.path if loc.path.endswith(sep) else loc.path + sep) + @property + def cachable(self): + return 'libraries' in util.INTERFACE.getPreference('cache_requests') + + def getCacheRef(self, always_return=False): + if (hasattr(self, "TYPE") and self.TYPE and self.key + and ('libraries' in util.INTERFACE.getPreference('cache_requests') or always_return)): + return "_".join(('section', self.key)) + + def clearCache(self, override_type=None, **kwargs): + super(LibrarySection, self).clearCache(override_type="section") + @staticmethod def fromFilter(filter_): - cls = SECTION_IDS.get(filter_.getLibrarySectionType()) + cls = SECTION_IDS.get(filter_.getLibrarySectionType(), SECTION_TYPES.get(filter_.TYPE, None)) if not cls: return section = cls(None, initpath=filter_.initpath, server=filter_.server, container=filter_.container) section.key = filter_.getLibrarySectionId() - section.title = filter_.reasonTitle + section.title = filter_.reasonTitle or filter_.getLibrarySectionTitle() section.type = cls.TYPE return section @@ -123,7 +140,7 @@ def reload(self, **kwargs): initpath = '/library/sections/{0}'.format(self.key) key = self.key try: - data = self.server.query(initpath, params=kwargs) + data = self.server.query(initpath, params=kwargs, cachable=self.cachable, cache_ref=self.cacheRef) except Exception as e: import traceback traceback.print_exc() @@ -144,7 +161,7 @@ def getMappedPath(self, loc=None): if not self.locations: return None, None - return pmm.getMappedPathFor(loc or self.locations[0], self.server) + return pmm.getMappedPathFor(loc or self.locations[0], self.server)[:-1] def deleteMapping(self, target): pmm.deletePathMapping(target, server=self.getServer()) @@ -170,13 +187,30 @@ def getAbsolutePath(self, key): return plexobjects.PlexObject.getAbsolutePath(self, key) - def all(self, start=None, size=None, filter_=None, sort=None, unwatched=False, type_=None): + def all(self, start=None, size=None, filter_=None, sort=None, unwatched=False, type_=None, hdr=False, dovi=False): if self.key.startswith('/'): path = '{0}/all'.format(self.key) else: path = '/library/sections/{0}/all'.format(self.key) - return self.items(path, start, size, filter_, sort, unwatched, type_, False) + return self.items(path, start, size, filter_, sort, unwatched, type_, False, hdr=hdr, dovi=dovi) + + @property + def settings(self): + if self._settings is None: + if self.key.startswith('/'): + path = '{0}/prefs'.format(self.key) + else: + path = '/library/sections/{0}/prefs'.format(self.key) + + try: + self._settings = {setting.id: {"default": setting.default, "value": setting.value} + for setting in plexobjects.listItems(self.server, path, bytag=True)} + except: + util.LOG("Couldn't get settings for {0}".format(self.key)) + self._settings = {} + + return self._settings def folder(self, start=None, size=None, subDir=False): if self.key.startswith('/'): @@ -189,16 +223,22 @@ def folder(self, start=None, size=None, subDir=False): return self.items(path, start, size, None, None, False, None, True) - def items(self, path, start, size, filter_, sort, unwatched, type_, tag_fallback): + def items(self, path, start, size, filter_, sort, unwatched, type_, tag_fallback, hdr=False, dovi=False): args = {} + if self.DEFAULT_URL_ARGS: + args.update(self.DEFAULT_URL_ARGS) if size is not None: args['X-Plex-Container-Start'] = start args['X-Plex-Container-Size'] = size if filter_: - args[filter_[0]] = filter_[1] + # filter might've been returned with a full path (e.g. watchlist) + if filter_[1].startswith('/'): + path = filter_[1] + else: + args[filter_[0]] = filter_[1] else: args['includeCollections'] = 1 @@ -209,14 +249,27 @@ def items(self, path, start, size, filter_, sort, unwatched, type_, tag_fallback args['type'] = str(type_) if unwatched: - args[self.TYPE == 'movie' and 'unwatched' or 'unwatchedLeaves'] = 1 + if self.TYPE == 'movie': + args['unwatched'] = 1 + elif type_ == 4: + args['episode.unwatched'] = 1 + elif self.TYPE == 'show': + args['show.unwatchedLeaves'] = 1 + else: + # might not apply anywhere + args['unwatchedLeaves'] = 1 + if hdr: + args['hdr'] = 1 + if dovi: + args['dovi'] = 1 if args: path += util.joinArgs(args, '?' not in path) - return plexobjects.listItems(self.server, path, tag_fallback=tag_fallback) + return plexobjects.listItems(self.server, path, tag_fallback=tag_fallback, not_cachable=not self.cachable, + cache_ref=self.cacheRef) - def jumpList(self, filter_=None, sort=None, unwatched=False, type_=None): + def jumpList(self, filter_=None, sort=None, unwatched=False, type_=None, hdr=False, dovi=False): if self.key.startswith('/'): path = '{0}/firstCharacter'.format(self.key) else: @@ -236,20 +289,34 @@ def jumpList(self, filter_=None, sort=None, unwatched=False, type_=None): args['type'] = str(type_) if unwatched: - args[self.TYPE == 'movie' and 'unwatched' or 'unwatchedLeaves'] = 1 + if self.TYPE == 'movie': + args['unwatched'] = 1 + elif type_ == 4: + args['episode.unwatched'] = 1 + elif self.TYPE == 'show': + args['show.unwatchedLeaves'] = 1 + else: + # might not apply anywhere + args['unwatchedLeaves'] = 1 + if hdr: + args['hdr'] = 1 + if dovi: + args['dovi'] = 1 if args: - path += util.joinArgs(args) + path += util.joinArgs(args, '?' not in path) try: - return plexobjects.listItems(self.server, path, bytag=True) + return plexobjects.listItems(self.server, path, bytag=True, cachable=self.cachable, + cache_ref=self.cacheRef) except exceptions.BadRequest: util.ERROR('jumpList() request error for path: {0}'.format(repr(path))) return None @property def onDeck(self): - return plexobjects.listItems(self.server, '/library/sections/%s/onDeck' % self.key) + return plexobjects.listItems(self.server, '/library/sections/%s/onDeck' % self.key, cachable=self.cachable, + cache_ref=self.cacheRef) def analyze(self): self.server.query('/library/sections/%s/analyze' % self.key, method=self.server.session.put) @@ -272,7 +339,12 @@ def listChoices(self, category, libtype=None, **kwargs): args[category] = self._cleanSearchFilter(subcategory, value) if libtype is not None: args['type'] = plexobjects.searchType(libtype) - query = '/library/sections/%s/%s%s' % (self.key, category, util.joinArgs(args)) + + if self.key.startswith('/'): + base = '{0}/'.format(self.key) + else: + base = '/library/sections/{0}/'.format(self.key) + query = '{0}{1}{2}'.format(base, category, util.joinArgs(args)) return plexobjects.listItems(self.server, query, bytag=True) @@ -398,6 +470,48 @@ def searchEpisodes(self, **kwargs): return self.search(libtype='episode', **kwargs) +class WatchlistSection(LibrarySection): + ALLOWED_FILTERS = ( + 'year', 'decade', 'genre', 'released' + ) + ALLOWED_SORT = ( + 'watchlistedAt', 'firstAvailableAt', 'titleSort', 'rating', 'audienceRating', + ) + DEFAULT_SORT = 'watchlistedAt' + DEFAULT_SORT_DESC = True + + TYPE = 'movies_shows' + ID = 'watchlist' + _key = '/library/sections/watchlist' + + cachable = False + + DEFAULT_URL_ARGS = { + "includeAdvanced": 1, + "includeMeta": 1 + } + + def __init__(self, data, initpath=None, server=None, container=None): + self.locations = [] + self._settings = {} + data = server.query(self.key+"/all", offset=0, limit=0, type=99, **self.DEFAULT_URL_ARGS) # type: ignore + self.type = "mixed" + super(LibrarySection, self).__init__(data, initpath=initpath, server=server, container=self) + self.server = server + + def has_data(self): + return self.totalSize and self.totalSize > 0 + + @property + def key(self): + return self._key + + @key.setter + def key(self, value): + return + + + class MusicSection(LibrarySection): ALLOWED_FILTERS = ('genre', 'country', 'collection') ALLOWED_SORT = ('addedAt', 'lastViewedAt', 'viewCount', 'titleSort') @@ -427,14 +541,16 @@ def isPhotoOrDirectoryItem(self): @plexobjects.registerLibType class Collection(media.MediaItem): TYPE = 'collection' + DEFAULT_SORT = 'titleSort' + DEFAULT_SORT_DESC = False def __repr__(self): title = self.title.replace(' ', '.')[0:20] return '<{0}:{1}:{2}>'.format(self.__class__.__name__, self.key, title) - def all(self, *args, **kwargs): - items = plexobjects.listItems(self.server, self.key) - items.totalSize = items.size + def all(self, start=None, size=None, filter_=None, sort=None, unwatched=False, type_=None, **kwargs): + items = plexobjects.listItems(self.server, self.key, offset=start, limit=size) + items.totalSize = items.get("size") if items.get("size").asInt() else items.get("totalSize") if items.get("totalSize").asInt() else 0 return items @property @@ -472,6 +588,12 @@ def __repr__(self): #class Collection(Generic): # TYPE = 'collection' + +@plexobjects.registerLibType +class Setting(plexobjects.PlexObject): + TYPE = 'Setting' + + @plexobjects.registerLibType class Playlist(playlist.BasePlaylist, signalsmixin.SignalsMixin): TYPE = 'playlist' @@ -547,6 +669,9 @@ def buildComposite(self, **kwargs): class BaseHub(plexobjects.PlexObject): + is_external = False + is_watchlist = False + def __init__(self, *args, **kwargs): super(BaseHub, self).__init__(*args, **kwargs) self._identifier = None @@ -574,7 +699,7 @@ def getCleanHubIdentifier(self, is_home=False): class Hub(BaseHub): TYPE = "Hub" - def init(self, data): + def init(self, data, not_cachable=False): self.items = [] self._totalSize = None @@ -591,8 +716,10 @@ def init(self, data): self.items = [media.Role(elem, initpath='/hubs', server=self.server, container=container) for elem in data] else: for elem in data: + if elem.tag == "Meta": + continue try: - self.items.append(plexobjects.buildItem(self.server, elem, '/hubs', container=container, tag_fallback=True)) + self.items.append(plexobjects.buildItem(self.server, elem, '/hubs', container=container, tag_fallback=True, not_cachable=not_cachable or self.is_external)) except exceptions.UnknownType: util.DEBUG_LOG('Unkown hub item type({1}): {0}', elem, elem.attrib.get('type')) @@ -662,6 +789,19 @@ def extend(self, start=None, size=None, **kwargs): return items +class ExternalHub(Hub): + TYPE = "Hub" + is_external = True + + def init(self, data, not_cachable=False): + self._setData(data) + super(ExternalHub, self).init(data, not_cachable=not_cachable) + + +class WatchlistHub(ExternalHub): + is_watchlist = True + + class PlaylistHub(BaseHub): TYPE = "Hub" type = None @@ -713,12 +853,14 @@ class VideoPlaylistHub(PlaylistHub): MovieSection.TYPE: MovieSection, ShowSection.TYPE: ShowSection, MusicSection.TYPE: MusicSection, - PhotoSection.TYPE: PhotoSection + PhotoSection.TYPE: PhotoSection, + WatchlistSection.TYPE: WatchlistSection, } SECTION_IDS = { MovieSection.ID: MovieSection, ShowSection.ID: ShowSection, MusicSection.ID: MusicSection, - PhotoSection.ID: PhotoSection + PhotoSection.ID: PhotoSection, + WatchlistSection.ID: WatchlistSection, } diff --git a/script.plexmod/lib/_included_packages/plexnet/plexmedia.py b/script.plexmod/lib/_included_packages/plexnet/plexmedia.py index 882dd2044c..e158c4dd5f 100644 --- a/script.plexmod/lib/_included_packages/plexnet/plexmedia.py +++ b/script.plexmod/lib/_included_packages/plexnet/plexmedia.py @@ -106,7 +106,7 @@ def __str__(self): if attr and not attr.NA: extra.append("{0}={1}".format(astr, attr)) - return self.versionString(log_safe=True) + " " + ' '.join(extra) + return str(self.container.ratingKey) + ": " + self.versionString(log_safe=True) + " " + ' '.join(extra) def versionString(self, log_safe=False): details = [] diff --git a/script.plexmod/lib/_included_packages/plexnet/plexobjects.py b/script.plexmod/lib/_included_packages/plexnet/plexobjects.py index 1b34878cee..94b6cac890 100644 --- a/script.plexmod/lib/_included_packages/plexnet/plexobjects.py +++ b/script.plexmod/lib/_included_packages/plexnet/plexobjects.py @@ -15,7 +15,10 @@ 'episode': 4, 'artist': 8, 'album': 9, - 'track': 10 + 'track': 10, + 'photo': 13, + 'collection': 18, + 'movies_shows': 99, } LIBRARY_TYPES = {} @@ -51,8 +54,14 @@ def __copy__(self): def __deepcopy__(self, memodict=None): return self.__class__(self) + def __gt__(self, other): + return self.asInt() > other + + def __lt__(self, other): + return self.asInt() < other + def asBool(self): - return self == '1' + return self == '1' or self == 'true' def asInt(self, default=0): return int(self or default) @@ -120,7 +129,10 @@ def isOnlineItem(self): return self.isChannelItem() or self.isMyPlexItem() or self.isVevoItem() or self.isIvaItem() def isMyPlexItem(self): - return self.container.server.TYPE == 'MYPLEXSERVER' or self.container.identifier == 'com.plexapp.plugins.myplex' + try: + return self.container.server.TYPE == 'MYPLEXSERVER' or self.container.identifier == 'com.plexapp.plugins.myplex' + except AttributeError: + return def isChannelItem(self): identifier = self.getIdentifier() or "com.plexapp.plugins.library" @@ -155,9 +167,13 @@ def isSettings(self): class PlexObject(Checks): - __slots__ = ("initpath", "key", "server", "container", "mediaChoice", "titleSort", "deleted", "_reloaded", "data") + __slots__ = ("initpath", "key", "server", "container", "mediaChoice", "titleSort", "deleted", "_reloaded", "data", + "_not_cachable") + TYPE = None + cachable = False + is_watchlist = False - def __init__(self, data, initpath=None, server=None, container=None): + def __init__(self, data, initpath=None, server=None, container=None, **kwargs): self.initpath = initpath self.key = None self.server = server @@ -166,6 +182,10 @@ def __init__(self, data, initpath=None, server=None, container=None): self.titleSort = PlexValue('') self.deleted = False self._reloaded = False + + # items initialized by containers that shouldn't be cached get this special attribute set, which overrides + # cachable + self._not_cachable = kwargs.get('not_cachable', False) self.data = data if data is None: @@ -211,6 +231,80 @@ def set(self, attr, value): def init(self, data): pass + @property + def cacheRef(self): + return self.getCacheRef() + + def getCacheRef(self, always_return=False): + if (hasattr(self, "TYPE") and self.TYPE and self.get('ratingKey') + and ('items' in util.INTERFACE.getPreference('cache_requests') or always_return)): + return "_".join((self.TYPE, self.ratingKey)) + + def _clearCache(self, cks, urls): + if not urls: + return + + if util.DEBUG_REQUESTS: + util.DEBUG_LOG("Clearing cache for: {0}, {1}".format(self, urls)) + + from .asyncadapter import Session + s = Session() + for url in urls: + try: + s.cache.delete_url(url) + except Exception as e: + util.LOG('Failed to delete cached URL {0}: {1}', url, e) + + # compact DB + s.cache.vacuum() + + base = util.CACHED_PLEX_URLS.get(util.INTERFACE.getRCBaseKey(), {}) + + # delete cache keys for collected urls + for ck in cks: + try: + del base[ck] + except: + pass + + def clearCache(self, override_type=None, return_urls=False): + # fixme: cache handling should be in a separate manager class + _type = override_type or self.TYPE + if self.cachable: + # get cache key no matter what, even if the specific type isn't cached, we still want to clear the library + # cache regardless + ck = self.getCacheRef(always_return=True) + urls = [] + if ck: + base = util.CACHED_PLEX_URLS.get(util.INTERFACE.getRCBaseKey(), {}) + cks = [] + if base: + urls = base.get(ck, []) + cks.append(ck) + + if _type in ("movie", "episode", "show", "season"): + # library cache + libID = self.getLibrarySectionId() + if libID: + urls += base.get("section_%s" % libID, []) + cks.append("section_%s" % libID) + + # parents caches + if _type == "episode": + urls += base.get("season_%s" % self.parentRatingKey, []) + urls += base.get("show_%s" % self.grandparentRatingKey, []) + cks += ["season_%s" % self.parentRatingKey, "show_%s" % self.grandparentRatingKey] + + if _type == "season": + urls += base.get("show_%s" % self.parentRatingKey, []) + cks.append("show_%s" % self.parentRatingKey) + + if return_urls: + return cks, urls + + self._clearCache(cks, urls) + + def isFullObject(self): return self.initpath is None or self.key is None or self.initpath == self.key @@ -230,17 +324,20 @@ def defaultArt(self): return self.__dict__.get('art') and self.art or PlexValue('', self) def refresh(self): - import requests - self.server.query('%s/refresh' % self.key, method=requests.put) + self.server.query('%s/refresh' % self.key, method="put") + self.clearCache() - def reload(self, _soft=False, **kwargs): + def reload(self, _soft=False, skip_cache=False, **kwargs): """ Reload the data for this object from PlexServer XML. """ if _soft and self._reloaded: return self try: if self.get('ratingKey'): - data = self.server.query('/library/metadata/{0}'.format(self.ratingKey), params=kwargs) + data = self.server.query('/library/metadata/{0}'.format(self.ratingKey), + cachable=self.cachable and not skip_cache, + cache_ref=self.cacheRef, + params=kwargs) else: data = self.server.query(self.key, params=kwargs) self._reloaded = True @@ -353,7 +450,13 @@ def getAbsolutePath(self, attr): if path is None: return None else: - return self.container._getAbsolutePath(path) + try: + return self.container._getAbsolutePath(path) + except AttributeError: + try: + return self._getAbsolutePath(path) + except AttributeError: + raise def _getAbsolutePath(self, path): if path.startswith('/'): @@ -536,14 +639,14 @@ def findItem(server, path, title): raise exceptions.NotFound('Unable to find item: {0}'.format(title)) -def buildItem(server, elem, initpath, bytag=False, container=None, tag_fallback=False): +def buildItem(server, elem, initpath, bytag=False, container=None, tag_fallback=False, not_cachable=False): libtype = elem.tag if bytag else elem.attrib.get('type') if not libtype and tag_fallback: libtype = elem.tag if libtype in LIBRARY_TYPES: cls = LIBRARY_TYPES[libtype] - return cls(elem, initpath=initpath, server=server, container=container) + return cls(elem, initpath=initpath, server=server, container=container, not_cachable=not_cachable) raise exceptions.UnknownType('Unknown library type: {0}'.format(libtype)) @@ -560,6 +663,7 @@ def init(self, container): def listItems(server, path, libtype=None, watched=None, bytag=False, data=None, container=None, offset=None, limit=None, tag_fallback=False, **kwargs): + not_cachable = kwargs.pop('not_cachable', False) data = data if data is not None else server.query(path, offset=offset, limit=limit, **kwargs) container = container or PlexContainer(data, path, server, path) items = ItemContainer().init(container) @@ -568,12 +672,12 @@ def listItems(server, path, libtype=None, watched=None, bytag=False, data=None, for elem in data: if libtype and elem.attrib.get('type') != libtype: continue - if watched is True and elem.attrib.get('viewCount', 0) == 0: + if watched is True and PlexValue(elem.attrib.get('viewCount', "0")).asInt() == 0: continue - if watched is False and elem.attrib.get('viewCount', 0) >= 1: + if watched is False and PlexValue(elem.attrib.get('viewCount', "0")).asInt() >= 1: continue try: - items.append(buildItem(server, elem, path, bytag, container, tag_fallback)) + items.append(buildItem(server, elem, path, bytag, container, tag_fallback, not_cachable=not_cachable)) except exceptions.UnknownType: pass @@ -581,9 +685,6 @@ def listItems(server, path, libtype=None, watched=None, bytag=False, data=None, def searchType(libtype): - searchtypesstrs = [str(k) for k in SEARCHTYPES.keys()] - if libtype in SEARCHTYPES + searchtypesstrs: - return libtype stype = SEARCHTYPES.get(libtype.lower()) if not stype: raise exceptions.NotFound('Unknown libtype: %s' % libtype) diff --git a/script.plexmod/lib/_included_packages/plexnet/plexpart.py b/script.plexmod/lib/_included_packages/plexnet/plexpart.py index 8ded244f3d..c5ef5f79f1 100644 --- a/script.plexmod/lib/_included_packages/plexnet/plexpart.py +++ b/script.plexmod/lib/_included_packages/plexnet/plexpart.py @@ -4,6 +4,7 @@ from . import plexstream from . import plexrequest from . import util +from .media import MediaPartStream from lib.util import addonSettings from lib.path_mapping import pmm, norm_sep @@ -21,7 +22,7 @@ def __init__(self, data, initpath=None, server=None, media=None): # If we weren't given any data, this is a synthetic part if data is not None: - self.streams = [plexstream.PlexStream(e, initpath=self.initpath, server=self.server) for e in data if e.tag == 'Stream'] + self.streams = [MediaPartStream.parse(e, initpath=self.initpath, server=self.server, part=self) for e in data if e.tag == 'Stream'] if self.indexes: indexKeys = self.indexes('').split(",") self.indexes = util.AttributeDict() @@ -105,7 +106,7 @@ def getSelectedStreamOfType(self, streamType): return default - def setSelectedStream(self, streamType, streamId, _async, from_session=False, video=None): + def setSelectedStream(self, streamType, streamId, _async, from_session=False, session_id=None, video=None): if streamType == plexstream.PlexStream.TYPE_AUDIO: typeString = "audio" elif streamType == plexstream.PlexStream.TYPE_SUBTITLE: @@ -121,7 +122,7 @@ def setSelectedStream(self, streamType, streamId, _async, from_session=False, vi path = path + "&allParts=1" # if from_session: - path = path + "&X-Plex-Session-Id=%s" % video.settings.getGlobal("clientIdentifier") + path = path + "&X-Plex-Session-Identifier={}&X-Plex-Session-Id={}".format(session_id, session_id) request = plexrequest.PlexRequest(self.getServer(), path, "PUT") @@ -166,7 +167,7 @@ def hasStreams(self): def getPathMappedUrl(self, return_only_folder=False): verify = addonSettings.verifyMappedFiles - map_path, pms_path = pmm.getMappedPathFor(self.file, self.getServer()) + map_path, pms_path, _ = pmm.getMappedPathFor(self.file, self.getServer()) if map_path and pms_path: if return_only_folder: return map_path @@ -198,7 +199,8 @@ def getPathMappedProto(self): return "" def __str__(self): - return "PlexPart {0} {1}".format(self.id("NaN"), self.key) + return "PlexPart {0}:{1}:{2} {3}".format(self.container.container.ratingKey, + self.container.id, self.id("NaN"), self.key) def __eq__(self, other): if other is None: diff --git a/script.plexmod/lib/_included_packages/plexnet/plexplayer.py b/script.plexmod/lib/_included_packages/plexnet/plexplayer.py index 0fbe5c6dd1..c8dc366307 100644 --- a/script.plexmod/lib/_included_packages/plexnet/plexplayer.py +++ b/script.plexmod/lib/_included_packages/plexnet/plexplayer.py @@ -36,10 +36,11 @@ def setupObj(self, obj, part, server, force_request_to_server=False): class PlexPlayer(BasePlayer): DECISION_ENDPOINT = "/video/:/transcode/universal/decision" - def __init__(self, item, seekValue=0, forceUpdate=False): + def __init__(self, item, seekValue=0, forceUpdate=False, session_id=None): self.decision = None self.seekValue = seekValue self.metadata = None + self.sessionID = session_id self.init(item, forceUpdate) def init(self, item, forceUpdate=False): @@ -81,9 +82,9 @@ def build(self, forceTranscode=False): else: directPlay = directPlayPref == "forced" and True or None - return self._build(directPlay, "playback_remux" in features) + return self._build(directPlay, "playback_remux" in features, features=features) - def _build(self, directPlay=None, directStream=True, currentPartIndex=None): + def _build(self, directPlay=None, directStream=True, currentPartIndex=None, features=None): isForced = directPlay is not None if isForced: util.LOG(directPlay and "Forced Direct Play" or "Forced Transcode; allowDirectStream={0}".format(directStream)) @@ -109,10 +110,14 @@ def _build(self, directPlay=None, directStream=True, currentPartIndex=None): obj.frameRate = 30 # Add soft subtitle info - if self.choice.subtitleDecision == self.choice.SUBTITLES_SOFT_ANY: - obj.subtitleUrl = server.buildUrl(self.choice.subtitleStream.getSubtitlePath(), True) - elif self.choice.subtitleDecision == self.choice.SUBTITLES_SOFT_DP: - obj.subtitleConfig = {'TrackName': "mkv/" + str(self.choice.subtitleStream.index.asInt() + 1)} + if not self.choice.audioStream or self.choice.audioStream.languageCode not in self.item.settings.getPreference( + "disable_subtitle_languages", []): + if self.choice.subtitleDecision == self.choice.SUBTITLES_SOFT_ANY: + # add sub autosync settings per item + auto_sync = self.item.playbackSettings.auto_sync + obj.subtitleUrl = server.buildUrl(self.choice.subtitleStream.getSubtitlePath(auto_sync=auto_sync), True) + elif self.choice.subtitleDecision == self.choice.SUBTITLES_SOFT_DP: + obj.subtitleConfig = {'TrackName': "mkv/" + str(self.choice.subtitleStream.index.asInt() + 1)} # Create one content metadata object for each part and store them as a # linked list. We probably want a doubly linked list, except that it @@ -158,7 +163,8 @@ def _build(self, directPlay=None, directStream=True, currentPartIndex=None): transcodeServer = self.item.getTranscodeServer(True, "video") if transcodeServer is None: return None - partObj = self.buildTranscode(transcodeServer, partObj, partIndex, directStream, isCurrentPart) + partObj = self.buildTranscode(transcodeServer, partObj, partIndex, directStream, isCurrentPart, + features=features) # Set up our linked list references. If we couldn't build an actual # object: fail fast. Otherwise, see if we're at our start offset @@ -259,6 +265,7 @@ def getServerDecision(self): newDecision = decision.getDecision(False) else: util.WARN_LOG("MDE: Server failed to provide a decision") + raise DecisionFailure("0", "Can't play back media") else: util.WARN_LOG("MDE: Server or item does not support decisions") @@ -268,10 +275,16 @@ def getDecisionPath(self, directPlay=False): if not self.item or not self.metadata: return None + features = self.item.settings.getPlaybackFeatures() + decisionPath = self.metadata.decisionPath if not decisionPath: server = self.metadata.transcodeServer or self.item.getServer() - decisionPath = self.buildTranscode(server, util.AttributeDict(), self.metadata.partIndex, True, False).decisionPath + decisionPath = self.buildTranscode(server, util.AttributeDict(), + self.metadata.partIndex, + True, + False, + features=features).decisionPath # Modify the decision params based on the transcode url if decisionPath: @@ -283,7 +296,12 @@ def getDecisionPath(self, directPlay=False): # sidecar subs, burn or embed w/ an optional transcode. for key in ("subtitles", "advancedSubtitles"): decisionPath = re.sub(r'([?&]{0}=)\w+'.format(key), '', decisionPath) + subType = 'sidecar' # AppSettings().getBoolPreference("custom_video_player"), "embedded", "sidecar") + # deselect subtitles if we don't need them + if not self.choice.audioStream or self.choice.audioStream.languageCode in self.item.settings.getPreference( + "disable_subtitle_languages", []): + subType = "none" decisionPath = http.addUrlParam(decisionPath, "subtitles=" + subType) # Global variables for all decisions @@ -386,14 +404,24 @@ def buildTranscodeMkv(self, obj, directStream=True): if not forceAC3: if directStream: - audioCodecs = "eac3,ac3,dca,aac,mp3,mp2,pcm,flac,alac,wmav2,wmapro,wmavoice,opus,vorbis,truehd" + audioCodecs = util.AUDIO_CODECS else: - audioCodecs = "mp3,ac3,dca,aac,opus" + audioCodecs = util.AUDIO_CODECS_TC + + # filter audio codecs + disACodecs = self.item.settings.getPreference("audio_disabled_codecs", []) + if disACodecs: + audioCodecs = list(set(audioCodecs) - set(disACodecs)) + + # force transcode audio codec + tcACodec = self.item.settings.getPreference("audio_transcode_codec", "default") + if tcACodec != "default": + audioCodecs = [tcACodec] else: if dtsIsAC3: - audioCodecs = "ac3,dca" + audioCodecs = ["ac3", "dca"] else: - audioCodecs = "ac3" + audioCodecs = ["ac3",] subtitleCodecs = "srt,ssa,ass,mov_text,tx3g,ttxt,text,pgs,vobsub,smi,subrip,eia_608_embedded," \ "eia_708_embedded,dvb_subtitle" + (",webvtt" if KODI_VERSION_MAJOR > 19 else '') @@ -412,7 +440,7 @@ def buildTranscodeMkv(self, obj, directStream=True): builder.extras.append( "add-transcode-target(type=videoProfile&videoCodec=" "h264,mpeg1video,mpeg2video,mpeg4,msmpeg4v2,msmpeg4v3,wmv3&container=mkv&" - "audioCodec={}&subtitleCodec={}&protocol=http&context=streaming)".format(audioCodecs, subtitleCodecs)) + "audioCodec={}&subtitleCodec={}&protocol=http&context=streaming)".format(",".join(audioCodecs), subtitleCodecs)) # builder.extras.append( # "append-transcode-target-audio-codec(type=videoProfile&context=streaming&protocol=http&audioCodec=" + @@ -541,6 +569,12 @@ def buildTranscodeMkv(self, obj, directStream=True): "append-transcode-target-codec(type=videoProfile&context=streaming&container=mkv&" "protocol=http&videoCodec=vc1)") + if self.item.settings.getPreference("disable_hdr", False): + builder.extras.append( + "add-limitation(scope=videoTranscodeTarget&scopeName=*&scopeType=videoCodec&context=streaming&protocol=http&" + "type=match&name=video.colorTrc&list=bt709|bt470m|bt470bg|smpte170m|smpte240m|bt2020-10|bt2020-10" + "&isRequired=true)") + return builder def buildDirectPlay(self, obj, partIndex): @@ -555,7 +589,7 @@ def buildDirectPlay(self, obj, partIndex): if self.media.protocol == "hls": obj.streamFormat = "hls" obj.switchingStrategy = "full-adaptation" - obj.live = self.isLiveHLS(obj.streamUrls[0], self.media.indirectHeaders) + #obj.live = self.isLiveHls(obj.streamUrls[0], self.media.indirectHeaders) else: obj.streamFormat = self.media.get('container', 'mp4') if obj.streamFormat == "mov" or obj.streamFormat == "m4v": @@ -605,7 +639,7 @@ def getBifUrl(self, offset=0): return None - def buildTranscode(self, server, obj, partIndex, directStream, isCurrentPart): + def buildTranscode(self, server, obj, partIndex, directStream, isCurrentPart, features=None): util.DEBUG_LOG('buildTranscode()') obj.transcodeServer = server obj.isTranscoded = True @@ -623,6 +657,11 @@ def buildTranscode(self, server, obj, partIndex, directStream, isCurrentPart): builder.addParam("path", path) + if self.choice.subtitleStream and self.choice.subtitleStream.should_auto_sync: + # add sub autosync settings per item + auto_sync = self.item.playbackSettings.auto_sync + builder.addParam("autoAdjustSubtitle", auto_sync and '1' or '0') + part = self.media.parts[partIndex] seekOffset = int(self.seekValue / 1000) startOffset = obj.get("startOffset", 0) @@ -658,16 +697,26 @@ def buildTranscode(self, server, obj, partIndex, directStream, isCurrentPart): builder.addParam("offset", str(startOffset)) - builder.addParam("session", self.item.settings.getGlobal("clientIdentifier")) + builder.addParam("session", self.sessionID) builder.addParam("directStream", directStream and "1" or "0") #builder.addParam("directStreamAudio", directStream and "1" or "0") builder.addParam("directPlay", "0") qualityIndex = self.item.settings.getQualityIndex(self.item.getQualityType(server)) - builder.addParam("videoQuality", self.item.settings.getGlobal("transcodeVideoQualities")[qualityIndex]) - builder.addParam("videoResolution", str(self.item.settings.getGlobal("transcodeVideoResolutions")[qualityIndex])) + #builder.addParam("videoQuality", self.item.settings.getGlobal("transcodeVideoQualities")[qualityIndex]) + maxVideoResolution = "allow_4k" in features and (self.item.settings.maxVerticalDPRes == 99999 and "99999x99999" or "3840x2160") or "1920x1080" + builder.addParam("videoResolution", str(maxVideoResolution)) builder.addParam("maxVideoBitrate", self.item.settings.getGlobal("transcodeVideoBitrates")[qualityIndex]) + builder.addParam("X-Plex-Session-Id", self.sessionID) + builder.addParam("X-Plex-Session-Identifier", self.sessionID) + builder.addParam('X-Plex-Client-Identifier', self.item.settings.getGlobal('clientIdentifier')) + + builder.extras.append( + "add-limitation(scope=videoCodec&scopeName=*&context=streaming&protocol=http&" + "type=upperBound&name=video.height&value={}&isRequired=true)".format("allow_4k" in features and str(self.item.settings.maxVerticalDPRes) or + "1088")) + if self.media.mediaIndex is not None: builder.addParam("mediaIndex", str(self.media.mediaIndex)) @@ -702,9 +751,10 @@ def buildTranscode(self, server, obj, partIndex, directStream, isCurrentPart): class PlexAudioPlayer(BasePlayer): - def __init__(self, item=None): + def __init__(self, item=None, session_id=None): self.item = item self.choice = None + self.sessionID = session_id self.containerFormats = { 'aac': "es.aac-adts" } @@ -740,7 +790,7 @@ def buildTranscode(self, item, choice, obj): builder = http.HttpRequest(transcodeServer.buildUrl(obj.transcodeEndpoint, True)) builder.addParam("protocol", "http") builder.addParam("path", item.getAbsolutePath("key")) - builder.addParam("session", item.getGlobal("clientIdentifier")) + builder.addParam("session", self.sessionID and self.sessionID or item.getGlobal("clientIdentifier")) builder.addParam("directPlay", "0") builder.addParam("directStream", "0") diff --git a/script.plexmod/lib/_included_packages/plexnet/plexresource.py b/script.plexmod/lib/_included_packages/plexnet/plexresource.py index 419084daf1..26318313e3 100644 --- a/script.plexmod/lib/_included_packages/plexnet/plexresource.py +++ b/script.plexmod/lib/_included_packages/plexnet/plexresource.py @@ -30,7 +30,7 @@ def __init__(self, data): self.sourceType = data.attrib.get('sourceType') self.uuid = self.clientIdentifier - if 'server' not in self.provides: + if 'server' not in self.provides and self.product != "Plex Media Server": return hasSecureConn = False @@ -64,7 +64,8 @@ def __init__(self, data): # If the connection is one of our plex.direct secure connections, add # the nonsecure variant as well, unless https is required. # - if self.httpsRequired and conn.attrib.get('protocol') == "https" and conn.attrib.get('address') not in conn.attrib.get('uri'): + if (self.httpsRequired and conn.attrib.get('protocol') == "https" and + conn.attrib.get('address') not in conn.attrib.get('uri') and conn.attrib.get('port')): self.connections.append( plexconnection.PlexConnection( plexconnection.PlexConnection.SOURCE_MYPLEX, @@ -78,6 +79,9 @@ def __init__(self, data): # add discovered local cons if necessary for ipPort, origAddress in addLocalConsFound: ip, port = ipPort + if not port: + continue + address = "http://" + ip + ":" + str(port) for conn in self.connections: if conn.address == address: diff --git a/script.plexmod/lib/_included_packages/plexnet/plexserver.py b/script.plexmod/lib/_included_packages/plexnet/plexserver.py index 77bead34ea..76b226d94b 100644 --- a/script.plexmod/lib/_included_packages/plexnet/plexserver.py +++ b/script.plexmod/lib/_included_packages/plexnet/plexserver.py @@ -4,6 +4,8 @@ import time import re import json + +import six import urllib3.exceptions from . import http @@ -18,6 +20,7 @@ from . import plexlibrary from . import asyncadapter from six.moves import range + # from plexapi.client import Client # from plexapi.playqueue import PlayQueue @@ -25,9 +28,12 @@ TOTAL_QUERIES = 0 DEFAULT_BASEURI = 'http://localhost:32400' +CACHE_MAP = {} + class PlexServer(plexresource.PlexResource, signalsmixin.SignalsMixin): TYPE = 'PLEXSERVER' + DEFER_HUBS = False def __init__(self, data=None): signalsmixin.SignalsMixin.__init__(self) @@ -67,6 +73,7 @@ def __init__(self, data=None): self.transcodeSupport = False self.currentHubs = None self.dnsRebindingProtection = False + self.prefs = {} if data is None: return @@ -79,7 +86,8 @@ def __init__(self, data=None): self.name = data.attrib.get('name') self.platform = data.attrib.get('platform') self.rawVersion = data.attrib.get('productVersion') - self.versionNorm = util.normalizedVersion(self.rawVersion) + if self.rawVersion and "server" in data.attrib.get('provides', 'server'): + self.versionNorm = util.normalizedVersion(self.rawVersion) self.transcodeSupport = data.attrib.get('transcodeSupport') == '1' self.dnsRebindingProtection = data.attrib.get('dnsRebindingProtection') == '1' @@ -121,9 +129,20 @@ def isLocal(self): def anyLANConnection(self): return any(c.localVerified for c in self.connections) - def getObject(self, key): + @property + def anyPDHostNotResolvable(self): + return any(".plex.direct:" in c.address and not c.pdHostnameResolved for c in self.connections) + + def getObject(self, key, assume_container=False): data = self.query(key) - return plexobjects.buildItem(self, data[0], key, container=self) + + container = self + if not assume_container: + container = plexobjects.PlexContainer(data, initpath=key, server=self, address=key) + return plexobjects.buildItem(self, data[0], key, container=container) + + def getPrefs(self): + return plexobjects.listItems(self, "/:/prefs", bytag=True, cachable=False, not_cachable=True) def hubs(self, section=None, count=None, search_query=None, section_ids=None, ignore_hubs=None): hubs = [] @@ -177,19 +196,20 @@ def hubs(self, section=None, count=None, search_query=None, section_ids=None, ig self.currentHubs[cdata[0].attrib.get('hubIdentifier')] = cdata[0].attrib.get('title') hubs.append(plexlibrary.Hub(cdata[0], server=self, container=ccontainer)) - for elem in data: - hubIdent = elem.attrib.get('hubIdentifier') - self.currentHubs["{}:{}".format(section, hubIdent)] = elem.attrib.get('title') + if data: + for elem in data: + hubIdent = elem.attrib.get('hubIdentifier') + self.currentHubs["{}:{}".format(section, hubIdent)] = elem.attrib.get('title') - # if we've added continueWatching, which combines continue and ondeck, skip those two hubs - if newCW and hubIdent and \ - (hubIdent.startswith('home.continue') or hubIdent.startswith('home.ondeck')): - continue + # if we've added continueWatching, which combines continue and ondeck, skip those two hubs + if newCW and hubIdent and \ + (hubIdent.startswith('home.continue') or hubIdent.startswith('home.ondeck')): + continue - if ignore_hubs and "{}:{}".format(section, hubIdent) in ignore_hubs: - continue + if ignore_hubs and "{}:{}".format(section, hubIdent) in ignore_hubs: + continue - hubs.append(plexlibrary.Hub(elem, server=self, container=container)) + hubs.append(plexlibrary.Hub(elem, server=self, container=container)) if section_ids: # when we have hidden sections, apply the filter to the hubs keys for subsequent queries @@ -218,9 +238,9 @@ def sessions(self): return plexobjects.listItems(self, '/status/sessions') raise exceptions.ServerNotOwned - def findVideoSession(self, client_id, rating_key): + def findVideoSession(self, session_id, rating_key): for item in self.sessions: - if item.session and item.session.id == client_id and item.ratingKey == rating_key: + if item.session and item.session.id == session_id and item.ratingKey == rating_key: return item def buildUrl(self, path, includeToken=False): @@ -230,11 +250,16 @@ def buildUrl(self, path, includeToken=False): util.WARN_LOG("Server connection is None, returning an empty url") return "" - def query(self, path, method=None, **kwargs): - method = method or self.session.get + def query(self, path, method=None, raw=False, **kwargs): + if method and isinstance(method, six.string_types): + method = getattr(self.session, method) + else: + method = method or self.session.get limit = kwargs.pop("limit", None) params = kwargs.pop("params", None) + cachable = kwargs.pop("cachable", False) + cache_ref = kwargs.pop("cache_ref", None) if params: if limit is None: limit = params.get("limit", None) @@ -260,12 +285,40 @@ def query(self, path, method=None, **kwargs): url = http.addUrlParam(url, "X-Plex-Container-Start=%s" % offset) url = http.addUrlParam(url, "X-Plex-Container-Size=%s" % limit) - util.LOG('{0} {1}', method.__name__.upper(), re.sub('X-Plex-Token=[^&]+', 'X-Plex-Token=****', url)) + with_cache = False + if cachable and cache_ref: + kwargs['with_cache'] = with_cache = True + + util.LOG('{0} (cache enabled: {2}) {1}', method.__name__.upper(), re.sub('X-Plex-Token=[^&]+', 'X-Plex-Token=****', url), with_cache) try: response = method(url, **kwargs) if response.status_code not in (200, 201): codename = http.status_codes.get(response.status_code, ['Unknown'])[0] raise exceptions.BadRequest('({0}) {1}'.format(response.status_code, codename)) + + # caching + if hasattr(response, "from_cache") and with_cache: + if util.DEBUG_REQUESTS: + util.LOG('{0} (from cache: {2}) {1}', method.__name__.upper(), + re.sub('X-Plex-Token=[^&]+', 'X-Plex-Token=****', url), response.from_cache) + + # scope for server+user; URLs itself don't need to be scoped as they differ on X-Plex-Token and domain + base_key = util.INTERFACE.getRCBaseKey() + + if base_key not in util.CACHED_PLEX_URLS: + util.CACHED_PLEX_URLS[base_key] = {} + + base = util.CACHED_PLEX_URLS[base_key] + + if cache_ref not in base: + base[cache_ref] = [] + + # fixme: this could be faster with a dict + if url not in base[cache_ref]: + base[cache_ref].append(url) + if util.DEBUG_REQUESTS: + util.DEBUG_LOG('Storing URL for cached response in {0}: {1}: {2}'.format(base_key, cache_ref, url)) + data = response.text.encode('utf8') except asyncadapter.TimeoutException: util.ERROR() @@ -277,13 +330,15 @@ def query(self, path, method=None, **kwargs): except asyncadapter.CanceledException: return None + if raw: + return data return ElementTree.fromstring(data) if data else None def getImageTranscodeURL(self, path, width, height, **extraOpts): if not path: return '' - eOpts = {"minSize": 1} + eOpts = {"minSize": 1, "upscale": 1} eOpts.update(extraOpts) params = ("&width=%s&height=%s" % (width, height)) + ''.join(["&%s=%s" % (key, eOpts[key]) for key in eOpts]) @@ -666,6 +721,9 @@ def deSerialize(cls, jstring): for i in range(len(serverObj.get('connections', []))): conn = serverObj['connections'][i] + if conn['address'].endswith(":None"): + continue + isFallback = hasSecureConn and conn['address'][:5] != "https" and not util.LOCAL_OVER_SECURE sources = plexconnection.PlexConnection.SOURCE_BY_VAL[conn['sources']] connection = plexconnection.PlexConnection(sources, conn['address'], conn['isLocal'], conn['token'], isFallback) diff --git a/script.plexmod/lib/_included_packages/plexnet/plexservermanager.py b/script.plexmod/lib/_included_packages/plexnet/plexservermanager.py index e6f8b94daf..c0f3b91184 100644 --- a/script.plexmod/lib/_included_packages/plexnet/plexservermanager.py +++ b/script.plexmod/lib/_included_packages/plexnet/plexservermanager.py @@ -64,14 +64,29 @@ def setSelectedServer(self, server, force=False): util.LOG("Setting selected server to {0}", server) self.selectedServer = server - # Update our saved state. - self.saveState(setPreferred=True) + if server: + if server.owned: + util.LOG("Getting and storing server prefs for {0}", server.name) + prefs = server.getPrefs() + for pref in prefs: + if pref.get("id") in ("LibraryVideoPlayedThreshold", "LibraryVideoPlayedAtBehaviour"): + server.prefs[str(pref.get("id"))] = pref.get("value").asInt() + util.INTERFACE.setRegistry("PlexServerPrefs", json.dumps(server.prefs), sec=server.uuid[-8:]) + else: + util.LOG("Server isn't owned by the current user. Trying to reuse cached server prefs for {0}", server.name) + try: + server.prefs = json.loads(util.INTERFACE.getRegistry("PlexServerPrefs", sec=server.uuid[-8:])) + util.DEBUG_LOG("Cached server prefs loaded for {0}", server.name) + except: + pass - # Notify anyone who might care. - util.APP.trigger("change:selectedServer", server=server) + # Update our saved state. + self.saveState(setPreferred=True) - return True + # Notify anyone who might care. + util.APP.trigger("change:selectedServer", server=server) + return True return False def getServer(self, uuid=None): @@ -80,9 +95,15 @@ def getServer(self, uuid=None): elif uuid == "myplex": from . import myplexserver return myplexserver.MyPlexServer() + elif uuid == "plexdiscover": + return self.getDiscoverServer() else: return self.serversByUuid[uuid] + def getDiscoverServer(self): + from . import myplexserver + return myplexserver.PlexDiscoverServer() + def getServers(self): servers = [] for uuid in list(self.serversByUuid.keys()): @@ -91,6 +112,10 @@ def getServers(self): return servers + @property + def connectedServers(self): + return filter(lambda s: s.activeConnection, self.getServers()) + def hasPendingRequests(self): for server in self.getServers(): if server.pendingReachabilityRequests: @@ -121,9 +146,8 @@ def updateFromConnectionType(self, servers, source): for server in servers: self.mergeServer(server) - if self.searchContext and source in self.searchContext.waitingForResources: - #self.searchContext.waitingForResources = False - self.searchContext.waitingForResources.remove(source) + if self.searchContext and source == plexresource.ResourceConnection.SOURCE_MYPLEX: + self.searchContext.waitingForResources = False if not self.searchContext.waitingForResources: self.deviceRefreshComplete(source) @@ -348,6 +372,9 @@ def loadState(self): for i in range(len(serverObj.get('connections', []))): conn = serverObj['connections'][i] + if conn['address'].endswith(":None"): + continue + isFallback = hasSecureConn and conn['address'][:5] != "https" and not util.LOCAL_OVER_SECURE sources = plexconnection.PlexConnection.SOURCE_BY_VAL[conn['sources']] connection = plexconnection.PlexConnection(sources, conn['address'], conn['isLocal'], conn['token'], isFallback) @@ -476,17 +503,10 @@ def startSelectedServerSearch(self, reset=False, ID=None): util.DEBUG_LOG("Preferred server for {0} is: {1}", ID, pServ) # Keep track of some information during our search - waitFor = [] - if plexapp.ACCOUNT.isSignedIn: - waitFor.append(plexresource.ResourceConnection.SOURCE_MYPLEX) - - if util.LOCAL_OVER_SECURE and self.getManualConnections(): - waitFor.append(plexresource.ResourceConnection.SOURCE_MANUAL) - self.searchContext = SearchContext({ 'bestServer': None, 'preferredServer': pServ, - 'waitingForResources': waitFor + 'waitingForResources': plexapp.ACCOUNT.isSignedIn }) util.LOG("Starting selected server search, hoping for {0}", self.searchContext.preferredServer) @@ -615,7 +635,9 @@ def refreshManualConnections(self): serverAddress = "{0}://{1}:{2}".format(proto, conn.connection, port) request = http.HttpRequest(serverAddress + "/identity") - context = request.createRequestContext("manual_connections", callback.Callable(self.onManualConnectionsResponse)) + context = request.createRequestContext("manual_connections", + callback.Callable(self.onManualConnectionsResponse), + timeout=util.CONN_CHECK_TIMEOUT) context.serverAddress = serverAddress context.address = conn.connection context.proto = proto diff --git a/script.plexmod/lib/_included_packages/plexnet/plexstream.py b/script.plexmod/lib/_included_packages/plexnet/plexstream.py index 495620be6b..7c348a124c 100644 --- a/script.plexmod/lib/_included_packages/plexnet/plexstream.py +++ b/script.plexmod/lib/_included_packages/plexnet/plexstream.py @@ -1,7 +1,9 @@ +# encoding: utf-8 from __future__ import absolute_import from . import plexobjects from . import util from .mixins import AudioCodecMixin +from six import ensure_str class PlexStream(plexobjects.PlexObject, AudioCodecMixin): @@ -12,6 +14,8 @@ class PlexStream(plexobjects.PlexObject, AudioCodecMixin): TYPE_SUBTITLE = 3 TYPE_LYRICS = 4 + should_auto_sync = False + streamTypeNames = ( "Unknown", "VideoStream", "AudioStream", "SubtitleStream", "LyricsStream" ) @@ -39,6 +43,10 @@ class PlexStream(plexobjects.PlexObject, AudioCodecMixin): 'yid': "Yiddish" } + def __init__(self, data, initpath=None, server=None, container=None, part=None): + super(PlexStream, self).__init__(data, initpath, server, container) + self.part = part + def reload(self): pass @@ -110,19 +118,22 @@ def getLanguageName(self, translate_func=util.dummyTranslate): return self.SAFE_LANGUAGE_NAMES.get(code) or self.language or "Unknown" - def getSubtitlePath(self): + def getSubtitlePath(self, auto_sync=None): query = "?encoding=utf-8" if self.codec == "smi": query += "&format=srt" + if self.should_auto_sync and auto_sync in (True, None): + query += "&autoAdjustSubtitle=1" + return self.key + query - def getSubtitleServerPath(self): + def getSubtitleServerPath(self, auto_sync=None): if not self.key: return None - return self.getServer().buildUrl(self.getSubtitlePath(), True) + return self.getServer().buildUrl(self.getSubtitlePath(auto_sync=auto_sync), True) @property def embedded(self): @@ -150,7 +161,7 @@ def videoCodecRendering(self): elif self.DOVIProfile == "8" and self.DOVIBLCompatID == "4": render = "dv p8.4/hlg" elif self.DOVIProfile == "7": - render = "dv p7" + render = "dv p7/hdr" elif self.DOVIProfile == "5": render = "dv p5" elif self.DOVIProfile: @@ -163,7 +174,7 @@ def videoCodecRendering(self): return render.upper() def __str__(self): - return self.getTitle() + return ensure_str(self.getTitle()) def __repr__(self): return '<{}: {}>'.format(self.streamTypeNames[self.streamType.asInt()], str(self)) diff --git a/script.plexmod/lib/_included_packages/plexnet/signalsmixin.py b/script.plexmod/lib/_included_packages/plexnet/signalsmixin.py index 6c96d32061..d2456de68d 100644 --- a/script.plexmod/lib/_included_packages/plexnet/signalsmixin.py +++ b/script.plexmod/lib/_included_packages/plexnet/signalsmixin.py @@ -11,6 +11,8 @@ def on(self, signalName, callback): self._signals[signalName] = signalslot.Signal(threadsafe=True) signal = self._signals[signalName] + if self.has_signal(signalName, callback): + return signal.connect(callback) diff --git a/script.plexmod/lib/_included_packages/plexnet/simpleobjects.py b/script.plexmod/lib/_included_packages/plexnet/simpleobjects.py index 16b7b8a784..3ef62e225d 100644 --- a/script.plexmod/lib/_included_packages/plexnet/simpleobjects.py +++ b/script.plexmod/lib/_included_packages/plexnet/simpleobjects.py @@ -1,3 +1,5 @@ +import copy + class Res(tuple): def __str__(self): return '{0}x{1}'.format(*self[:2]) @@ -19,3 +21,6 @@ def __setattr__(self, attr, value): def __repr__(self): return '<{0}:{1}:{2}>'.format(self.__class__.__name__, self.id, self.get('title', 'None').encode('utf8')) + + def copy(self): + return AttributeDict(copy.deepcopy(super(AttributeDict, self).copy())) diff --git a/script.plexmod/lib/_included_packages/plexnet/util.py b/script.plexmod/lib/_included_packages/plexnet/util.py index a9fdee9d46..b40a3bb733 100644 --- a/script.plexmod/lib/_included_packages/plexnet/util.py +++ b/script.plexmod/lib/_included_packages/plexnet/util.py @@ -11,6 +11,7 @@ import threading import six import math +import socket from copy import copy from kodi_six import xbmcaddon @@ -44,6 +45,7 @@ def resetBaseHeaders(): 'X-Plex-Version': ADDON.getAddonInfo('version'), 'X-Plex-Device': X_PLEX_DEVICE, 'X-Plex-Client-Identifier': X_PLEX_IDENTIFIER, + 'X-Plex-Language': LANGUAGE_CODE, 'Accept-Encoding': 'gzip,deflate', 'Accept-Language': ACCEPT_LANGUAGE, 'User-Agent': '{0}/{1}'.format("PM4K", ADDON.getAddonInfo('version')) @@ -51,11 +53,11 @@ def resetBaseHeaders(): # Core Settings -PROJECT = 'PlexNet' # name provided to plex server +PROJECT = 'PM4K' # name provided to plex server VERSION = '0.0.0a1' # version of this api -TIMEOUT = 10 # request timeout +TIMEOUT = 5 # request timeout TIMEOUT_CONNECT = 5 # connect timeout -DEFAULT_TIMEOUT = 10 +DEFAULT_TIMEOUT = 5 LONG_TIMEOUT = 20 PLEXTV_TIMEOUT = None # set me later PLEXTV_TIMEOUT_READ = 20 # s @@ -64,9 +66,13 @@ def resetBaseHeaders(): LAN_REACHABILITY_TIMEOUT = 0.01 # s CHECK_LOCAL = False LOCAL_OVER_SECURE = False +DEBUG_REQUESTS = False +CACHED_PLEX_URLS = {} +REQUESTS_CACHE_EXPIRY = 168 X_PLEX_CONTAINER_SIZE = 50 # max results to return in a single search page ACCEPT_LANGUAGE = 'en-US,en' +LANGUAGE_CODE = 'en' # Plex Header Configuation X_PLEX_PROVIDES = 'player,controller' # one or more of [player, controller, server] @@ -75,9 +81,13 @@ def resetBaseHeaders(): X_PLEX_PRODUCT = PROJECT # Plex application name, eg Laika, Plex Media Server, Media Link X_PLEX_VERSION = VERSION # Plex application version number USER_AGENT = '{0}/{1}'.format(PROJECT, VERSION) +TEMP_PATH = None USE_CERT_BUNDLE = False +SKIP_HOST_CHECK = {} +NO_HOST_CHECK = False + INTERFACE = None TIMER = None APP = None @@ -245,14 +255,16 @@ def joinArgs(args, includeQuestion=True): def getPlexHeaders(): return {"X-Plex-Platform": INTERFACE.getGlobal("platform"), - "X-Plex-Version": INTERFACE.getGlobal("appVersionStr"), + "X-Plex-Version": ADDON.getAddonInfo('version'), "X-Plex-Client-Identifier": INTERFACE.getGlobal("clientIdentifier"), "X-Plex-Platform-Version": INTERFACE.getGlobal("platformVersion", "unknown"), - "X-Plex-Product": INTERFACE.getGlobal("product"), + "X-Plex-Product": "PM4K", "X-Plex-Provides": not INTERFACE.getPreference("remotecontrol", False) and 'player' or '', "X-Plex-Device": INTERFACE.getGlobal("device"), + "X-Plex-Device-Vendor": INTERFACE.getGlobal("vendor"), "X-Plex-Model": INTERFACE.getGlobal("model"), "X-Plex-Device-Name": INTERFACE.getGlobal("friendlyName"), + "X-Plex-Language": LANGUAGE_CODE, 'Accept-Encoding': 'gzip,deflate', 'Accept-Language': ACCEPT_LANGUAGE, 'User-Agent': '{0}/{1}'.format("PM4K", ADDON.getAddonInfo('version')) @@ -285,11 +297,11 @@ def validInt(int_str): return 0 -def bitrateToString(bits): +def bitrateToString(bits, multiplier=1): if not bits: return '' - speed = bits / 1000000.0 + speed = bits / 1000000.0 * multiplier if speed < 1: speed = int(round(bits / 1000.0)) return '{0} Kbps'.format(speed) @@ -313,6 +325,55 @@ def parsePlexDirectHost(hostname): return v6 and base.replace("-", ":") or base.replace("-", ".") +# stolen from icmplib +def resolve(name, family=None, use_orig=False): + ''' + Resolve a hostname or FQDN to an IP address. Depending on the name + specified in parameters, several IP addresses may be returned. + + This function relies on the DNS name server configured on your + operating system. + + :type name: str + :param name: A hostname or a Fully Qualified Domain Name (FQDN). + + :type family: int, optional + :param family: The address family. Can be set to `4` for IPv4 or `6` + for IPv6 addresses. By default, this function searches for IPv4 + addresses first for compatibility reasons (A DNS lookup) before + searching for IPv6 addresses (AAAA DNS lookup). + + :rtype: list[str] + :returns: A list of IP addresses corresponding to the name passed as + a parameter. + + :raises NameLookupError: If the requested name does not exist or + cannot be resolved. + + ''' + try: + if family == 6: + _family = socket.AF_INET6 + else: + _family = socket.AF_INET + + func = socket.getaddrinfo if not use_orig else socket.getaddrinfo_orig + + lookup = func( + host=name, + port=None, + family=_family, + type=socket.SOCK_DGRAM) + + return [address[4][0] for address in lookup] + + except OSError: + if not family: + return resolve(name, 6) + + raise Exception(name) + + class CompatEvent(Event): def wait(self, timeout): Event.wait(self, timeout) @@ -398,4 +459,30 @@ def reset(self): self.ticks = 0 +AUDIO_CODECS_VERB = { + 'aac': 'AAC', + 'ac3': 'AC3', + 'alac': 'ALAC', + 'dca': 'DTS', + 'eac3': 'EAC3', + 'flac': 'FLAC', + 'mp2': 'MP2', + 'mp3': 'MP3', + 'opus': 'Opus', + 'pcm': 'PCM', + 'truehd': 'TrueHD', + 'vorbis': 'Vorbis', + 'wmapro': 'WMA Pro', + 'wmav2': 'Windows Media Audio 2', + 'wmavoice': 'WMA Voice' +} + +AUDIO_CODECS = list(AUDIO_CODECS_VERB.keys()) + +AUDIO_CODECS_TC = ['mp3', 'ac3', 'aac', 'opus', 'vorbis', 'eac3', 'flac', 'alac'] + +AUDIO_CODECS_TC_VERB = {codec: AUDIO_CODECS_VERB[codec] for codec in AUDIO_CODECS_TC} + + + TIMER = Timer diff --git a/script.plexmod/lib/_included_packages/plexnet/video.py b/script.plexmod/lib/_included_packages/plexnet/video.py index 81fd2be572..fb841281ff 100644 --- a/script.plexmod/lib/_included_packages/plexnet/video.py +++ b/script.plexmod/lib/_included_packages/plexnet/video.py @@ -1,4 +1,6 @@ from __future__ import absolute_import +import datetime + from functools import wraps from . import plexobjects @@ -13,7 +15,7 @@ from .mixins import AudioCodecMixin from lib.data_cache import dcm -from lib.util import T +from lib.util import T, shortDF, durationToShortText class PlexVideoItemList(plexobjects.PlexItemList): @@ -39,10 +41,25 @@ def forceMediaChoice(method): @wraps(method) def _impl(self, *method_args, **method_kwargs): # set mediaChoice if we don't have any yet, or the one we have is incomplete and the new one isn't - media = method_kwargs.get("media", self.media()[0]) + media = method_kwargs.get("media", None) partIndex = method_kwargs.get("partIndex", 0) - if not self.mediaChoice or (not self.mediaChoice.media.hasStreams() and media.hasStreams()): - self.setMediaChoice(media=media, partIndex=partIndex) + if not self.mediaChoice or not self.mediaChoice.media or not self.mediaChoice.media.hasStreams(): + if not media: + # if we don't have a chosen media yet, check whether the user has previously chosen one + pbs = util.INTERFACE.playbackManager(self) + if pbs.media_version: + for m in self.media(): + if m.id == pbs.media_version: + media = m + break + if not media: + try: + media = self.media()[0] + except (TypeError, IndexError): + pass + + if not self.mediaChoice or media.hasStreams(): + self.setMediaChoice(media=media, partIndex=partIndex) return method(self, *method_args, **method_kwargs) return _impl @@ -54,6 +71,7 @@ class Video(media.MediaItem, AudioCodecMixin): manually_selected_sub_stream = False current_subtitle_is_embedded = False _current_subtitle_idx = None + _prev_subtitle_idx = None _noSpoilers = False def __init__(self, *args, **kwargs): @@ -99,6 +117,10 @@ def videoStreams(self): def audioStreams(self): return [] + @property + def playbackSettings(self): + return util.INTERFACE.playbackManager(self) + def selectedVideoStream(self, fallback=False): if self.videoStreams: for stream in self.videoStreams: @@ -117,18 +139,31 @@ def selectedAudioStream(self, fallback=False): return self.audioStreams[0] return None - def selectedSubtitleStream(self, forced_subtitles_override=False, fallback=False): - if self._current_subtitle_idx: - try: - return self.subtitleStreams[self._current_subtitle_idx] - except IndexError: - pass + def selectedSubtitleStream(self, forced_subtitles_override=False, deselect_subtitles=None, + fallback=False, ref="_current_subtitle_idx", force_from_plex=False): + if ref: + sidx = getattr(self, ref) + if sidx: + try: + return self.subtitleStreams[sidx] + except IndexError: + pass + + selas = self.selectedAudioStream() if self.subtitleStreams: for stream in self.subtitleStreams: if stream.isSelected(): + if force_from_plex: + if stream != force_from_plex: + continue + util.DEBUG_LOG("Subtitle stream requested to be the Plex decision, returning: {}", stream) + return stream + + sel_stream = stream + stream_forced = sel_stream.forced.asBool() if forced_subtitles_override and \ - stream.forced.asBool() and self.manually_selected_sub_stream != stream.id: + stream_forced and self.manually_selected_sub_stream != sel_stream.id: # try finding a non-forced variant of this stream possible_alt = None for alt_stream in self.subtitleStreams: @@ -143,30 +178,43 @@ def selectedSubtitleStream(self, forced_subtitles_override=False, fallback=False util.DEBUG_LOG("Selecting stream {} instead of {}", possible_alt, stream) stream.setSelected(False) possible_alt.setSelected(True) - self.current_subtitle_is_embedded = possible_alt.embedded - if self._current_subtitle_idx != possible_alt.typeIndex: - self._current_subtitle_idx = possible_alt.typeIndex - return possible_alt - - if self._current_subtitle_idx != stream.typeIndex: - self._current_subtitle_idx = stream.typeIndex - self.current_subtitle_is_embedded = stream.embedded - return stream + stream_forced = False + + sel_stream = possible_alt + if (not self.manually_selected_sub_stream or self.manually_selected_sub_stream != sel_stream.id) and \ + deselect_subtitles and selas and str(selas.languageCode) in deselect_subtitles and \ + not stream_forced: + util.DEBUG_LOG("Not selecting {} subtitle stream because audio is {}", + sel_stream.languageCode, selas.languageCode) + self._current_subtitle_idx = None + return + + if self._current_subtitle_idx != sel_stream.typeIndex: + self._current_subtitle_idx = sel_stream.typeIndex + self.current_subtitle_is_embedded = sel_stream.embedded + return sel_stream if fallback: stream = self.subtitleStreams[0] + if deselect_subtitles and selas and str(selas.languageCode) in deselect_subtitles and not stream.forced.asBool(): + return if self._current_subtitle_idx != stream.typeIndex: self._current_subtitle_idx = stream.typeIndex return stream return None def setMediaChoice(self, media=None, partIndex=0): - media = media or self.media()[0] + try: + media = media or self.media()[0] + except (TypeError, IndexError): + return + self.mediaChoice = mediachoice.MediaChoice(media, partIndex=partIndex) @forceMediaChoice - def selectStream(self, stream, _async=True, from_session=False): - self.mediaChoice.part.setSelectedStream(stream.streamType.asInt(), stream.id, _async, from_session=from_session, - video=self) + def selectStream(self, stream, _async=True, from_session=False, session_id=None, sync_to_server=True): + if sync_to_server: + self.mediaChoice.part.setSelectedStream(stream.streamType.asInt(), stream.id, _async, from_session=from_session, + session_id=session_id, video=self) # Update any affected streams if stream.streamType.asInt() == plexstream.PlexStream.TYPE_AUDIO: for audioStream in self.audioStreams: @@ -186,7 +234,12 @@ def selectStream(self, stream, _async=True, from_session=False): subtitleStream.setSelected(False) @forceMediaChoice - def cycleSubtitles(self, forward=True): + def cycleSubtitles(self, forward=True, sync_to_server=False): + """ + Only used by SeekDialog Subtitle Quick Settings to toggle subs + @param sync_to_server: Don't persist changes to the PMS + @return: selected stream + """ amount = len(self.subtitleStreams) if not amount: return False @@ -208,12 +261,43 @@ def cycleSubtitles(self, forward=True): stream = self.subtitleStreams[-1] util.DEBUG_LOG("Selecting subtitle stream: {} (was: {})", stream, cur) - self.selectStream(stream) + self.selectStream(stream, sync_to_server=sync_to_server) return stream @forceMediaChoice - def disableSubtitles(self): - self.selectStream(plexstream.NONE_STREAM) + def disableSubtitles(self, sync_to_server=False): + """ + Only used by SeekDialog Subtitle Quick Settings to toggle subs + @param sync_to_server: Don't persist changes to the PMS + @return: + """ + # store previously selected subtitle on disable, to be able to re-enable it from seekdialog + self._prev_subtitle_idx = self._current_subtitle_idx + self.selectStream(plexstream.NONE_STREAM, sync_to_server=sync_to_server) + + @forceMediaChoice + def enableSubtitles(self, sync_to_server=False): + """ + Only used by SeekDialog Subtitle Quick Settings to toggle subs + @param sync_to_server: Don't persist changes to the PMS + @return: selected stream + """ + stream = self.selectedSubtitleStream(ref="_prev_subtitle_idx") + if not stream: + # use fallback + stream = self.selectedSubtitleStream(fallback=True) + self.selectStream(stream, sync_to_server=sync_to_server) + return stream + + def findSubtitles(self, language="en", hearing_impaired=0, forced=0): + data = self.server.query('%s/subtitles' % self.key, language=language, hearingImpaired=hearing_impaired, + forced=forced) + if data: + return [media.SubtitleStream(elem, initpath=self.initpath, server=self.server) for elem in data] + return [] + + def downloadSubtitles(self, key): + self.server.query('%s/subtitles' % self.key, key=key, codec="srt", method=self.server.session.put) @property def hasSubtitle(self): @@ -248,21 +332,32 @@ def analyze(self): """ self.server.query('/%s/analyze' % self.key) + def refresh(self): + self.server.query('/library/metadata/%s/refresh' % self.ratingKey, method="put") + self.clearCache() + def markWatched(self, **kwargs): path = '/:/scrobble?key=%s&identifier=com.plexapp.plugins.library' % self.ratingKey self.server.query(path) + self.clearCache() self.reload(**kwargs) def markUnwatched(self, **kwargs): path = '/:/unscrobble?key=%s&identifier=com.plexapp.plugins.library' % self.ratingKey self.server.query(path) + self.clearCache() self.reload(**kwargs) + def removeFromContinueWatching(self, **kwargs): + path = '/actions/removeFromContinueWatching?ratingKey={}'.format(self.ratingKey) + self.server.query(path, method=self.server.session.put) + self.reload(**kwargs) + + removeCW = removeFromContinueWatching + # def play(self, client): # client.playMedia(self) - def refresh(self): - self.server.query('%s/refresh' % self.key, method=self.server.session.put) def _getStreamURL(self, **params): if self.TYPE not in ('movie', 'episode', 'track'): @@ -280,7 +375,8 @@ def _getStreamURL(self, **params): 'mediaIndex': params.get('mediaIndex', 0), 'directStream': '1', 'directPlay': '0', - 'X-Plex-Platform': params.get('platform', ''), + 'X-Plex-Platform': params.get('platform', util.X_PLEX_PLATFORM), + 'X-Plex-Platform-Version': params.get('platformVersion', util.X_PLEX_PLATFORM_VERSION), # 'X-Plex-Platform': params.get('platform', util.INTERFACE.getGlobal('platform')), 'maxVideoBitrate': max(mvb, 64) if mvb else None, 'videoResolution': '{0}x{1}'.format(*vr) if vr else None @@ -300,6 +396,8 @@ def _getStreamURL(self, **params): @forceMediaChoice def resolutionString(self): + if not self.mediaChoice: + return '' res = self.mediaChoice.media.videoResolution if not res: return '' @@ -309,19 +407,74 @@ def resolutionString(self): else: return res.upper() + @property + def bestMedia(self): + return sorted(self.media(), key=lambda m: m.videoResolution)[0] + + def meta_resolutionString(self, *args, **kwargs): + try: + media = self.bestMedia + if media.videoResolution.isdigit(): + return '{0}p'.format(media.videoResolution) + else: + return media.videoResolution.upper() + except: + return '' + + def meta_addedAt(self, *args, **kwargs): + addedAt = self.get("addedAt") + if addedAt: + return datetime.datetime.fromtimestamp(addedAt.asFloat()).strftime(shortDF) + return '' + + def meta_originallyAvailableAt(self, *args, **kwargs): + val = self.get("originallyAvailableAt") + if val: + return datetime.datetime.strptime(val, "%Y-%m-%d").strftime(shortDF) + return '' + + def meta_lastViewedAt(self, *args, **kwargs): + val = self.get("lastViewedAt") + if val: + return datetime.datetime.fromtimestamp(val.asFloat()).strftime(shortDF) + return '' + + def meta_contentRating(self, *args, **kwargs): + return self.get("contentRating", '') + + def meta_duration(self, *args, **kwargs): + return durationToShortText(self.get("duration", '').asInt(), noSpaces=True) + + def meta_viewCount(self, *args, **kwargs): + return self.get("viewCount", '') + + def meta_mediaBitrate(self, *args, **kwargs): + try: + media = self.bestMedia + bits = int(media.get("bitrate", '')) + return util.bitrateToString(bits, multiplier=1000) + except: + return '' + @forceMediaChoice def audioCodecString(self): + if not self.mediaChoice: + return '' codec = (self.mediaChoice.media.audioCodec or '').lower() return self.translateAudioCodec(codec).upper() @forceMediaChoice def videoCodecString(self): + if not self.mediaChoice: + return '' return (self.mediaChoice.media.videoCodec or '').upper() @property @forceMediaChoice def videoCodecRendering(self): + if not self.mediaChoice: + return '' stream = self.mediaChoice.videoStream if not stream: @@ -331,6 +484,8 @@ def videoCodecRendering(self): @forceMediaChoice def audioChannelsString(self, translate_func=util.dummyTranslate): + if not self.mediaChoice: + return '' channels = self.mediaChoice.media.audioChannels.asInt() if channels == 1: @@ -368,6 +523,15 @@ def _remainingTimeString(self, view_offset=None): def available(self): return any(v.isAccessible() for v in self.media()) + @property + def combined_roles(self): + roles = [] + if self.directors(): + roles += self.directors()[:2] + if self.roles(): + roles += self.roles() + return roles + class SectionOnDeckMixin(object): _sectionOnDeckCount = None @@ -384,13 +548,39 @@ def sectionOnDeckCount(self): return self._sectionOnDeckCount -class PlayableVideo(Video, media.RelatedMixin): +class CachableItemsMixin(object): + @property + def cachable(self): + return 'items' in util.INTERFACE.getPreference('cache_requests') and not self._not_cachable + + def clearChildCaches(self, return_urls=False): + # clear caches of this season and its items + if not self.cachable: + return + cks = [] + urls = [] + for e in self.getImmediateChildren(): + cks_, urls_ = e.clearCache(return_urls=True) + cks += cks_ + urls += urls_ + + cks = list(set(cks)) + urls = list(set(urls)) + + if return_urls: + return cks, urls + + self._clearCache(cks, urls) + + +class PlayableVideo(CachableItemsMixin, Video, media.RelatedMixin): __slots__ = ("extras", "guids", "chapters") TYPE = None _videoStreams = None _audioStreams = None _subtitleStreams = None _current_subtitle_idx = None + isExtra = False def _setData(self, data): Video._setData(self, data) @@ -410,6 +600,7 @@ def setMediaChoice(self, *args, **kwargs): """ Reset cached streams after setting a mediaChoice """ + self._current_subtitle_idx = None super(PlayableVideo, self).setMediaChoice(*args, **kwargs) self.resetStreams() @@ -425,7 +616,8 @@ def reload(self, *args, **kwargs): if self.get('viewOffset'): del self.viewOffset - fromMediaChoice = kwargs.get("fromMediaChoice", False) + fromMediaChoice = kwargs.pop("fromMediaChoice", False) + forceSubtitlesFromPlex = kwargs.pop("forceSubtitlesFromPlex", False) kwargs["includeMarkers"] = 1 @@ -440,7 +632,12 @@ def reload(self, *args, **kwargs): partID = self.mediaChoice.part.id streamIDs = [] if self.mediaChoice.media.hasStreams(): - subtitleStream = self.selectedSubtitleStream(fallback=False) + if forceSubtitlesFromPlex: + subtitleStream = self.selectedSubtitleStream(ref=None, force_from_plex=forceSubtitlesFromPlex) + else: + subtitleStream = self.selectedSubtitleStream(fallback=False, + forced_subtitles_override=self.settings.getPreference("forced_subtitles_override", False) and util.ACCOUNT.subtitlesForced == 0, + deselect_subtitles=self.settings.getPreference("disable_subtitle_languages", [])) videoStream = self.selectedVideoStream(fallback=True) audioStream = self.selectedAudioStream(fallback=True) streamIDs = [] @@ -483,11 +680,27 @@ def postPlay(self, **params): hubs[hub.hubIdentifier] = hub return hubs + def fetchExternalExtras(self): + query = '{}/extras'.format(self.key) + data = self.server.query(query) + container = plexobjects.PlexContainer(data, initpath=query, server=self.server, address=query) + items = plexobjects.PlexItemList(data, Clip, "Video", server=self.server, container=container) + self.extras = list(items) + + @property + def in_progress(self): + return bool(self.get('viewOffset').asInt()) + + @property + def has_credit_markers(self): + if hasattr(self, 'markers'): + return bool(filter(lambda m: m.type == 'credits', self.markers)) + @plexobjects.registerLibType class Movie(PlayableVideo): __slots__ = ("collections", "countries", "directors", "genres", "media", "producers", "roles", "reviews", - "writers", "markers", "sessionKey", "user", "player", "session", "transcodeSession") + "writers", "studios", "markers", "sessionKey", "user", "player", "session", "transcodeSession") TYPE = 'movie' def _setData(self, data): @@ -503,6 +716,8 @@ def _setData(self, data): self.producers = plexobjects.PlexItemList(data, media.Producer, media.Producer.TYPE, server=self.server) self.roles = plexobjects.PlexItemList(data, media.Role, media.Role.TYPE, server=self.server, container=self.container) + self.studios = plexobjects.PlexItemList(data, media.Studio, media.Studio.TYPE, server=self.server, + container=self.container) self.reviews = plexobjects.PlexItemList(data, media.Review, media.Review.TYPE, server=self.server, container=self.container) self.writers = plexobjects.PlexItemList(data, media.Writer, media.Writer.TYPE, server=self.server) @@ -570,20 +785,23 @@ def getStreamURL(self, **params): @plexobjects.registerLibType -class Show(Video, media.RelatedMixin, SectionOnDeckMixin): - __slots__ = ("_genres", "guids", "onDeck") +class Show(CachableItemsMixin, Video, media.RelatedMixin, SectionOnDeckMixin): + __slots__ = ("_genres", "guids", "onDeck", "locations") TYPE = 'show' def _setData(self, data): Video._setData(self, data) if self.isFullObject(): self._genres = plexobjects.PlexItemList(data, media.Genre, media.Genre.TYPE, server=self.server) + self.directors = plexobjects.PlexItemList(data, media.Director, media.Director.TYPE, server=self.server, + container=self.container) self.roles = plexobjects.PlexItemList(data, media.Role, media.Role.TYPE, server=self.server, container=self.container) self.guids = plexobjects.PlexItemList(data, media.Guid, media.Guid.TYPE, server=self.server) #self.related = plexobjects.PlexItemList(data.find('Related'), plexlibrary.Hub, plexlibrary.Hub.TYPE, server=self.server, container=self) self.extras = PlexVideoItemList(data.find('Extras'), initpath=self.initpath, server=self.server, container=self) self.onDeck = PlexVideoItemList(data.find('OnDeck'), initpath=self.initpath, server=self.server, container=self) + self.locations = plexobjects.PlexItemList(data, media.Location, media.Location.TYPE, server=self.server) @property def unViewedLeafCount(self): @@ -597,13 +815,10 @@ def isWatched(self): def isFullyWatched(self): return self.isWatched - @property - def playbackSettings(self): - return util.INTERFACE.playbackManager(self) - def seasons(self): path = self.key - return plexobjects.listItems(self.server, path, Season.TYPE) + return plexobjects.listItems(self.server, path, Season.TYPE, cachable=self.cachable, cache_ref=self.cacheRef, + not_cachable=self._not_cachable) def season(self, title): path = self.key @@ -611,13 +826,18 @@ def season(self, title): def episodes(self, watched=None, offset=None, limit=None): leavesKey = '/library/metadata/%s/allLeaves' % self.ratingKey - return plexobjects.listItems(self.server, leavesKey, watched=watched, offset=offset, limit=limit) + return plexobjects.listItems(self.server, leavesKey, watched=watched, offset=offset, limit=limit, + cachable=self.cachable, cache_ref=self.cacheRef, not_cachable=self._not_cachable) def episode(self, title): path = '/library/metadata/%s/allLeaves' % self.ratingKey return plexobjects.findItem(self.server, path, title) - def all(self, *args, **kwargs): + def all(self, unwatched=False, *args, **kwargs): + if unwatched: + eps = self.episodes(watched=False) + if eps: + return eps return self.episodes() def watched(self): @@ -626,9 +846,6 @@ def watched(self): def unwatched(self): return self.episodes(watched=False) - def refresh(self): - self.server.query('/library/metadata/%s/refresh' % self.ratingKey) - def genres(self): genres = dcm.getCacheData("show_genres", self.ratingKey) if genres: @@ -640,9 +857,17 @@ def genres(self): dcm.setCacheData("show_genres", self.ratingKey, [g.tag for g in self._genres]) return self._genres + def getImmediateChildren(self): + return self.seasons() + + def clearCache(self, return_urls=False, **kwargs): + if return_urls: + return self.clearChildCaches(return_urls=True) + self.clearChildCaches() + @plexobjects.registerLibType -class Season(Video): +class Season(CachableItemsMixin, Video): TYPE = 'season' def _setData(self, data): @@ -668,7 +893,8 @@ def isFullyWatched(self): def episodes(self, watched=None, offset=None, limit=None): path = self.key - return plexobjects.listItems(self.server, path, watched=watched, offset=offset, limit=limit) + return plexobjects.listItems(self.server, path, watched=watched, offset=offset, limit=limit, + cachable=self.cachable, cache_ref=self.cacheRef, not_cachable=self._not_cachable) def episode(self, title): path = self.key @@ -678,7 +904,8 @@ def all(self, *args, **kwargs): return self.episodes() def show(self): - return plexobjects.listItems(self.server, self.parentKey)[0] + return plexobjects.listItems(self.server, self.parentKey, cachable=self.cachable, cache_ref=self.cacheRef, + not_cachable=self._not_cachable)[0] def watched(self): return self.episodes(watched=True) @@ -686,6 +913,14 @@ def watched(self): def unwatched(self): return self.episodes(watched=False) + def getImmediateChildren(self): + return self.episodes() + + def clearCache(self, return_urls=False, **kwargs): + if return_urls: + return self.clearChildCaches(return_urls=True) + self.clearChildCaches() + @plexobjects.registerLibType class Episode(PlayableVideo, SectionOnDeckMixin): @@ -700,6 +935,7 @@ def _setData(self, data): PlayableVideo._setData(self, data) if self.isFullObject(): self.directors = plexobjects.PlexItemList(data, media.Director, media.Director.TYPE, server=self.server) + self._roles = plexobjects.PlexItemList(data, media.Role, media.Role.TYPE, server=self.server) self.media = plexobjects.PlexMediaItemList(data, plexmedia.PlexMedia, media.Media.TYPE, initpath=self.initpath, server=self.server, media=self) self.writers = plexobjects.PlexItemList(data, media.Writer, media.Writer.TYPE, server=self.server) else: @@ -721,6 +957,8 @@ def defaultTitle(self): @property def defaultThumb(self): + if self.settings.getPreference("hub_season_thumbnails", True): + return self.parentThumb or self.grandparentThumb or self.thumb return self.grandparentThumb or self.parentThumb or self.thumb @property @@ -749,10 +987,6 @@ def isWatched(self): def isFullyWatched(self): return self.get('viewCount').asInt() > 0 and not self.get('viewOffset').asInt() - @property - def inProgress(self): - return bool(self.get('viewOffset').asInt()) - @property def playbackSettings(self): return self.show().playbackSettings @@ -764,7 +998,8 @@ def season(self): skipParent = self.get('skipParent').asBool() key = self.parentKey if not skipParent else self.grandparentKey if not self._season: - items = plexobjects.listItems(self.server, key) + items = plexobjects.listItems(self.server, key, cachable=self.cachable, cache_ref=self.cacheRef, + not_cachable=self._not_cachable) if items: self._season = items[0] @@ -772,7 +1007,8 @@ def season(self): def show(self): if not self._show: - self._show = plexobjects.listItems(self.server, self.grandparentKey)[0] + self._show = plexobjects.listItems(self.server, self.grandparentKey, cachable=self.cachable, + cache_ref=self.cacheRef, not_cachable=self._not_cachable)[0] return self._show @property @@ -781,7 +1017,7 @@ def genres(self): @property def roles(self): - return self.show().roles + return self._roles or self.show().roles def getRelated(self, offset=None, limit=None, _max=36): return self.show().getRelated(offset=offset, limit=limit, _max=_max) diff --git a/script.plexmod/lib/_included_packages/plexnet/videosession.py b/script.plexmod/lib/_included_packages/plexnet/videosession.py index e77f993333..dc11e33cc6 100644 --- a/script.plexmod/lib/_included_packages/plexnet/videosession.py +++ b/script.plexmod/lib/_included_packages/plexnet/videosession.py @@ -27,6 +27,7 @@ class MediaDetails: "subtitleStreamDecision": "subtitle_stream.decision", "subtitleLocation": "subtitle_stream.location", "subtitleBurn": "subtitle_stream.burn", + "subtitleAutoSync": "subtitle_stream.should_auto_sync", } def __init__(self, *args, **kwargs): @@ -272,10 +273,12 @@ def value(self, obj): class DPAttributeEqualsValue(DPAttribute): - def __init__(self, attr, compareTo, retVal, source="details.session"): + def __init__(self, attr, compareTo, retVal, retValFalse=None, source="details.session", fallback=None): DPAttribute.__init__(self, attr, source=source) self.compareTo = compareTo self.retVal = retVal + self.retValFalse = retValFalse + self.fallback = fallback def value(self, obj): """ @@ -288,6 +291,10 @@ def value(self, obj): result = DPAttribute.value(self, obj) if result == self.resolve(self.compareTo, obj): return self.resolve(self.retVal, obj) + elif result is None and self.fallback is not None: + return self.resolve(self.fallback, obj) + elif self.retValFalse is not None: + return self.resolve(self.retValFalse, obj) class DPAttributeMapped(DPAttribute): @@ -337,7 +344,13 @@ class ModePPI(ComputedPPIValue): name = "Mode" dataPoints = [ DPAttributeSession("partDecision"), - DPAttributeExists("local", source="session.player", returnValue="local"), + DPAttributeEqualsValue("local", "1", + DPAttribute("server_is_local", source="details"), + DPAttribute("server_is_local", source="details"), + source="session.player", + fallback=DPAttribute("server_is_local", source="details")), + #DPAttribute("location", source="session.session"), + #DPAttribute("server_is_local", source="details"), DPAttributeMapped() ] @@ -357,7 +370,8 @@ class VideoPPI(ComputedPPIValue): dataPoints = [ DPAttributesDiffer("videoCodec"), DPAttributesDiffer("videoResolution", valueFormatter=lambda i, v1, v2: [normRes(v1), normRes(v2)]), - DPAttributesDiffer("videoBitrate", formatTrue=u"%(val1)s->%(val2)skbit", formatFalse=u"%(val1)skbit"), + DPAttributesDiffer("videoBitrate", formatTrue=u"%(val1)s->%(val2)skbit", formatFalse=u"%(val1)skbit", + valueFormatter=lambda i, v1, v2: [v1, v2 if v2 != "2147483647" else "?"]), lambda i: [ (i.details.session.videoStreamDecision + " HW") if i.details.session.transcodeVideoDecision == "transcode" and i.details.session.transcodeHWEncoding @@ -386,7 +400,8 @@ class SubtitlesPPI(ComputedPPIValue): DPAttributesDiffer("subtitleCodec", valueFormatter=lambda i, v1, v2: [v1, "burn" if i.details.session.subtitleBurn else v2]), DPAttributeEqualsValue("subtitleStreamDecision", "burn", DPAttribute("subtitleStreamDecision")), - DPAttributeExists("subtitleLocation") + DPAttributeExists("subtitleLocation"), + DPAttributesDiffer("subtitleAutoSync", valueFormatter=lambda i, v1, v2: [v1 and "auto-sync" or None, None]), ] @@ -426,11 +441,11 @@ def __init__(self, ref, *args, **kwargs): class VideoSessionInfo: - def __init__(self, sessionMediaContainer, mediaContainer, incompleteSessionData=False): + def __init__(self, sessionMediaContainer, mediaContainer, server_is_local, incompleteSessionData=False): self.mediaItem = mediaContainer self.session = sessionMediaContainer self.details = MediaDetailsHolder(self.mediaItem, self.session, mediaContainer.mediaChoice, incompleteSessionData=incompleteSessionData) - + self.details.server_is_local = server_is_local and "lan (verified)" or "remote" self.attributes = SessionAttributes(self) diff --git a/script.plexmod/lib/advancedsettings.py b/script.plexmod/lib/advancedsettings.py index b4fdbd725c..1b10abc813 100644 --- a/script.plexmod/lib/advancedsettings.py +++ b/script.plexmod/lib/advancedsettings.py @@ -24,7 +24,7 @@ def load(self): self._data = f.read() f.close() except: - LOG('script.plex: No advancedsettings.xml found') + LOG('script.plexmod: No advancedsettings.xml found') def write(self, data=None): self._data = data = data or self._data diff --git a/script.plexmod/lib/backgroundthread.py b/script.plexmod/lib/backgroundthread.py index 958d77ba57..ba1a39efe5 100644 --- a/script.plexmod/lib/backgroundthread.py +++ b/script.plexmod/lib/backgroundthread.py @@ -254,5 +254,4 @@ def shutdown(self): def kill(self): self.threader.kill() - -BGThreader = ThreaderManager(worker_count=util.getSetting('worker_count', 5)) +BGThreader = ThreaderManager(worker_count=util.getSetting('worker_count', 3)) diff --git a/script.plexmod/lib/cache.py b/script.plexmod/lib/cache.py index d488c88934..96d1747e19 100644 --- a/script.plexmod/lib/cache.py +++ b/script.plexmod/lib/cache.py @@ -88,12 +88,12 @@ def load(self): try: self.memorySize = int(ADV_MSIZE_RE.search(cachexml).group(1)) // 1024 // 1024 except: - DEBUG_LOG("script.plex: invalid or not found memorysize in advancedsettings.xml") + DEBUG_LOG("script.plexmod: invalid or not found memorysize in advancedsettings.xml") try: self.readFactor = int(ADV_RFACT_RE.search(cachexml).group(1)) except: - DEBUG_LOG("script.plex: invalid or not found readfactor in advancedsettings.xml") + DEBUG_LOG("script.plexmod: invalid or not found readfactor in advancedsettings.xml") # self._cleanData = data.replace(cachexml, "") #else: diff --git a/script.plexmod/lib/compat.py b/script.plexmod/lib/compat.py index d7558a7cb0..3a763a0a99 100644 --- a/script.plexmod/lib/compat.py +++ b/script.plexmod/lib/compat.py @@ -4,6 +4,7 @@ try: datetime.datetime.strptime('0', '%H') + new_datetime = datetime.datetime except TypeError: # Fix for datetime issues with XBMC/Kodi class new_datetime(datetime.datetime): diff --git a/script.plexmod/lib/data_cache.py b/script.plexmod/lib/data_cache.py index 9766cbdea6..daa56a6a4e 100644 --- a/script.plexmod/lib/data_cache.py +++ b/script.plexmod/lib/data_cache.py @@ -29,6 +29,7 @@ class DataCacheManager(object): def __init__(self): self._currentServerUUID = None plexapp.util.APP.on('change:selectedServer', self.setServerUUID) + plexapp.util.APP.on('change:tempServer', self.setServerUUID) if self.USE_GZ: self.DC_PATH += "z" if xbmcvfs.exists(self.DC_PATH): @@ -59,6 +60,7 @@ def __init__(self): def deinit(self): plexapp.util.APP.off('change:selectedServer', self.setServerUUID) + plexapp.util.APP.off('change:tempServer', self.setServerUUID) def getCacheData(self, context, identifier): ret = self.DATA_CACHES["cache"].get(self._currentServerUUID, {}).get(context, {}).get(identifier, {}) diff --git a/script.plexmod/lib/i18n.py b/script.plexmod/lib/i18n.py index ee7288b6d4..62272ce365 100644 --- a/script.plexmod/lib/i18n.py +++ b/script.plexmod/lib/i18n.py @@ -1,9 +1,15 @@ # coding=utf-8 -from kodi_six import xbmcaddon +from .kodi_util import ADDON -ADDON = xbmcaddon.Addon() +def T(ID, eng=''): + s = ADDON.getLocalizedString(ID) + return s if s != "" else eng -def T(ID, eng=''): - return ADDON.getLocalizedString(ID) +TRANSLATED_ROLES = { + 'Director': T(32383, 'Director'), + 'Writer': T(32402, 'Writer'), + 'Producer': T(34031, 'Producer'), + '': T(32441, 'Unknown') +} diff --git a/script.plexmod/lib/kodi_util.py b/script.plexmod/lib/kodi_util.py index 1875f9fcb8..d2f4c73ab8 100644 --- a/script.plexmod/lib/kodi_util.py +++ b/script.plexmod/lib/kodi_util.py @@ -1,7 +1,9 @@ # coding=utf-8 -from kodi_six import xbmc, xbmcgui, xbmcvfs +# noinspection PyUnresolvedReferences +from kodi_six import xbmc, xbmcgui, xbmcvfs, xbmcaddon +ADDON = xbmcaddon.Addon() _build = None # buildversion looks like: XX.X[-TAG] (a+.b+.c+) (.+); there are kodi builds that don't set the build version @@ -16,7 +18,7 @@ KODI_VERSION_MAJOR, KODI_VERSION_MINOR = int(_splitver[0].split("-")[0].strip()), \ int(_splitver[1].split(" ")[0].split("-")[0].strip()) except: - xbmc.log('script.plex: Couldn\'t determine Kodi version, assuming 19.4. Got: {}'.format(sys_ver)) + xbmc.log('script.plexmod: Couldn\'t determine Kodi version, assuming 19.4. Got: {}'.format(sys_ver), xbmc.LOGINFO) # assume something "old" KODI_VERSION_MAJOR = 19 KODI_VERSION_MINOR = 4 @@ -30,11 +32,13 @@ except: pass if not parsedBuild: - xbmc.log('script.plex: Couldn\'t determine build version, falling back to Kodi version', xbmc.LOGINFO) + xbmc.log('script.plexmod: Couldn\'t determine build version, falling back to Kodi version', xbmc.LOGINFO) # calculate a comparable build number KODI_BUILD_NUMBER = int("{0}{1:02d}{2:03d}".format(_bmajor, int(_bminor), int(_bpatch))) +FROM_KODI_REPOSITORY = ADDON.getAddonInfo('name') == "PM4K for Plex" + if KODI_VERSION_MAJOR > 18: translatePath = xbmcvfs.translatePath @@ -42,16 +46,7 @@ translatePath = xbmc.translatePath -def setGlobalProperty(key, val, base='script.plex.{0}'): - xbmcgui.Window(10000).setProperty(base.format(key), val) - - -def setGlobalBoolProperty(key, boolean, base='script.plex.{0}'): - xbmcgui.Window(10000).setProperty(base.format(key), boolean and '1' or '') - - -def getGlobalProperty(key): - return xbmc.getInfoLabel('Window(10000).Property(script.plex.{0})'.format(key)) +ICON_PATH = translatePath(ADDON.getAddonInfo('icon')) def ensureHome(): diff --git a/script.plexmod/lib/logging.py b/script.plexmod/lib/logging.py index 829f1436e3..43cf961d82 100644 --- a/script.plexmod/lib/logging.py +++ b/script.plexmod/lib/logging.py @@ -1,7 +1,10 @@ # coding=utf-8 +from __future__ import absolute_import + import sys import traceback import types +import logging from kodi_six import xbmc @@ -13,22 +16,43 @@ def log(msg, *args, **kwargs): level = kwargs.pop("level", xbmc.LOGINFO) + prepend_msg = kwargs.pop('prepend_msg', None) + if prepend_msg: + msg = '{0}: {1}'.format(prepend_msg, msg) + if kwargs: # resolve dynamic kwargs msg = msg.format(**dict((k, v()) if isinstance(v, types.FunctionType) else v for k, v in kwargs.items())) - xbmc.log('script.plex: {0}'.format(msg), level) + xbmc.log('script.plexmod: {0}'.format(msg), level) def log_error(txt='', hide_tb=False): short = str(sys.exc_info()[1]) if hide_tb: - xbmc.log('script.plex: ERROR: {0} - {1}'.format(txt, short), xbmc.LOGERROR) + xbmc.log('script.plexmod: ERROR: {0} - {1}'.format(txt, short), xbmc.LOGERROR) return short tb = traceback.format_exc() xbmc.log("_________________________________________________________________________________", xbmc.LOGERROR) - xbmc.log('script.plex: ERROR: ' + txt, xbmc.LOGERROR) + xbmc.log('script.plexmod: ERROR: ' + txt, xbmc.LOGERROR) for l in tb.splitlines(): xbmc.log(' ' + l, xbmc.LOGERROR) xbmc.log("_________________________________________________________________________________", xbmc.LOGERROR) xbmc.log("`", xbmc.LOGERROR) + + + +def service_log(msg, level=xbmc.LOGINFO, realm="Updater"): + xbmc.log('script.plexmod/{}: {}'.format(realm, msg), level) + + +class KodiLogProxyHandler(logging.Handler): + def __init__(self, level=logging.NOTSET, log_func=log): + self.log_func = log_func + super(KodiLogProxyHandler, self).__init__(level) + + def emit(self, record): + try: + self.log_func(self.format(record)) + except: + self.handleError(record) diff --git a/script.plexmod/lib/main.py b/script.plexmod/lib/main.py index 9b0e10a503..776159056d 100644 --- a/script.plexmod/lib/main.py +++ b/script.plexmod/lib/main.py @@ -6,6 +6,13 @@ import threading import six import sys +import logging +import time + +try: + from _thread import interrupt_main +except: + from thread import interrupt_main from kodi_six import xbmc @@ -18,10 +25,11 @@ from plexnet import plexapp from .templating import render_templates -from .windows import background, userselect, home, windowutils, kodigui +from .windows import background, userselect, home, windowutils, kodigui, busy from . import player from . import backgroundthread from . import util +from .logging import KodiLogProxyHandler from .data_cache import dcm BACKGROUND = None @@ -35,27 +43,53 @@ _Timer = threading.Timer -def waitForThreads(): - util.DEBUG_LOG('Main: Checking for any remaining threads') - while len(threading.enumerate()) > 1: - for t in threading.enumerate(): - if t != threading.currentThread(): - if t.is_alive(): - util.DEBUG_LOG('Main: Waiting on: {0}...', t.name) - if isinstance(t, _Timer): - t.cancel() +if util.addonSettings.debugRequests: + logger = logging.getLogger("urllib3") + logger.addHandler(KodiLogProxyHandler(level=logging.DEBUG, + log_func=lambda *a, **kw: util.log(*a, prepend_msg="[urllib3]", **kw))) + logger.setLevel(logging.DEBUG) - try: - t.join(.25) - except: - util.ERROR() +def waitForThreads(): + util.DEBUG_LOG('Main: Checking for any remaining threads (current: {})'.format(threading.currentThread().name)) + started = time.time() + exit_timer_was_alive = exit_timer.is_alive() + + # if the exit timer was still alive at this point, try cancelling all threads for 5 seconds + if not exit_timer_started or exit_timer_was_alive: + while len(threading.enumerate()) > 1 and time.time() < started + util.addonSettings.maxShutdownWait: + alive_threads = [t for t in list(threading.enumerate()) if t.is_alive()] + alive_threads_out = ", ".join(t.name for t in alive_threads) + alive_threads_count = len(alive_threads) + + # With certain linux instances we might have two threads, while Dummy is the one we're on. + if alive_threads_count == 2 and "Dummy" in alive_threads_out and "MainThread" in alive_threads_out: + break + + for t in threading.enumerate(): + if t != threading.currentThread(): + if t.is_alive(): + util.DEBUG_LOG('Main: Waiting on: {0}... (alive: {1})', t.name, alive_threads_out) + if isinstance(t, _Timer): + t.cancel() + + try: + t.join(.25) + except: + util.ERROR() + util.MONITOR.waitForAbort(0.05) + else: + util.DEBUG_LOG("Main: Not waiting for remaining threads as exit already took to long; hard exit") + + if time.time() >= started + util.addonSettings.maxShutdownWait or (exit_timer_started and not exit_timer_was_alive): + util.LOG('Main: script.plexmod: threads took too long or timer hit, HARD EXITING') + sys.exit(0) @atexit.register def realExit(): - xbmc.log('Main: script.plex: REALLY FINISHED', xbmc.LOGINFO) + xbmc.log('Main: script.plexmod: REALLY FINISHED', xbmc.LOGINFO) if quitKodi: - xbmc.log('Main: script.plex: QUITTING KODI', xbmc.LOGINFO) + xbmc.log('Main: script.plexmod: QUITTING KODI', xbmc.LOGINFO) xbmc.executebuiltin('Quit') elif restart: @@ -67,6 +101,17 @@ def signout(): util.DEBUG_LOG('Main: Signing out...') plexapp.ACCOUNT.signOut() +exit_timer_started = False + +def hardExit(): + util.LOG('Main: script.plexmod: timer hit, triggering hard exit...') + xbmc.executebuiltin('StopScript(script.plexmod)') + interrupt_main() + + +exit_timer = threading.Timer(util.addonSettings.maxShutdownWait, hardExit) +exit_timer.name = 'HARDEXIT-TIMER' + def main(force_render=False): global BACKGROUND @@ -96,14 +141,14 @@ def main(force_render=False): def _main(): - global quitKodi, restart + global quitKodi, restart, exit_timer_started # uncomment to profile code #1 #pr = cProfile.Profile() #pr.enable() util.DEBUG_LOG('[ STARTED: {0} -------------------------------------------------------------------- ]', util.ADDON.getAddonInfo('version')) - if util.KODI_VERSION_MAJOR > 19 and util.DEBUG and util.getSetting('dump_config', False): + if util.KODI_VERSION_MAJOR > 19 and util.DEBUG and util.getSetting('dump_config'): lv = len(util.ADDON.getAddonInfo('version')) util.DEBUG_LOG('[ SETTINGS DUMP {0}-------------------------------------------------------------------- ' ']', (lv - 4)*'-') @@ -129,6 +174,7 @@ def _main(): (len(plexapp.ACCOUNT.homeUsers) > 1 or plexapp.ACCOUNT.isProtected) ): + oldAccID = plexapp.ACCOUNT.ID result = userselect.start(BACKGROUND._winID) if not result: return @@ -145,14 +191,23 @@ def _main(): if not fromSwitch: util.DEBUG_LOG('Main: User selected') + # store previous account ID for fast user switch + if oldAccID and oldAccID != plexapp.ACCOUNT.ID: + util.setSetting('previous_user', oldAccID) + + closeOption = "exit" try: selectedServer = plexapp.SERVERMANAGER.selectedServer if not selectedServer: background.setBusy() - util.DEBUG_LOG('Main: Waiting for selected server...') + base_timeout = max( + util.addonSettings.plextvTimeoutConnect * util.addonSettings.maxRetries1 + + util.addonSettings.plextvTimeoutRead * util.addonSettings.maxRetries1, + util.addonSettings.connCheckTimeout * util.addonSettings.maxRetries1) + util.DEBUG_LOG('Main: Waiting for selected server... (max timeout: {})', base_timeout) try: - for timeout, skip_preferred, skip_owned in ((10, False, False), (10, True, True)): + for timeout, skip_preferred, skip_owned in ((base_timeout, True, False), (base_timeout, True, True)): plex.CallbackEvent(plexapp.util.APP, 'change:selectedServer', timeout=timeout).wait() selectedServer = plexapp.SERVERMANAGER.checkSelectedServerSearch( @@ -174,7 +229,7 @@ def _main(): return util.CRON.cancelReceiver(windowutils.HOME) - if not windowutils.HOME.closeOption or windowutils.HOME.closeOption == "quit": + if not windowutils.HOME.closeOption or windowutils.HOME.closeOption in ("quit", "exit"): if windowutils.HOME.closeOption == "quit": quitKodi = True return @@ -187,8 +242,19 @@ def _main(): signout() break elif closeOption == 'switch': + # store last user ID + util.DEBUG_LOG('Main: Switching users...: {}', plexapp.ACCOUNT.ID) plexapp.ACCOUNT.isAuthenticated = False fromSwitch = True + elif isinstance(closeOption, dict): + if closeOption.get('fast_switch'): + uid = closeOption['fast_switch'] + util.DEBUG_LOG('Main: Fast-Switching users...: {}', uid) + util.setSetting('previous_user', plexapp.ACCOUNT.ID) + with busy.BusySignalContext(plexapp.util.APP, "account:response"): + if plexapp.ACCOUNT.switchHomeUser(uid) and plexapp.ACCOUNT.switchUser: + util.DEBUG_LOG('Waiting for user change...') + elif closeOption == 'recompile': render_templates(force=True) util.LOG("Restarting Home") @@ -198,41 +264,65 @@ def _main(): restart = True return finally: + try: + kodiExiting = closeOption == "kodi_exit" or windowutils.HOME.closeOption == "kodi_exit" + except: + kodiExiting = False + + if closeOption in ("quit", "exit", "restart") and not kodiExiting: + if not exit_timer.is_alive(): + util.DEBUG_LOG("Main: Starting hard exit timer of {} seconds...", util.addonSettings.maxShutdownWait) + exit_timer.start() + exit_timer_started = True windowutils.shutdownHome() BACKGROUND.activate() + background.setShutdown() gc.collect(2) + if kodiExiting: + return + else: break + except KeyboardInterrupt: + util.LOG("Main: Interrupted, hard exiting...") + sys.exit(0) + except SystemExit: + util.LOG("Main: SystemExit exception caught (inner)...") + return except: util.ERROR() finally: - util.DEBUG_LOG('Main: SHUTTING DOWN...') - dcm.storeDataCache() - dcm.deinit() - plexapp.util.INTERFACE.playbackManager.deinit() - background.setShutdown() - player.shutdown() - plexapp.util.APP.preShutdown() - util.CRON.stop() - backgroundthread.BGThreader.shutdown() - plexapp.util.APP.shutdown() - waitForThreads() - background.setBusy(False) - background.setSplash(False) - background.killMonitor() - - # uncomment to profile code #2 - #pr.disable() - #sortby = SortKey.CUMULATIVE - #s = io.StringIO() - #ps = pstats.Stats(pr, stream=s).sort_stats(sortby) - #ps.print_stats() - #util.DEBUG_LOG(s.getvalue()) - - util.DEBUG_LOG('FINISHED') - util.shutdown() - gc.collect(2) + try: + util.DEBUG_LOG('Main: SHUTTING DOWN...') + dcm.storeDataCache() + dcm.deinit() + plexapp.util.INTERFACE.shutdownCache() + plexapp.util.INTERFACE.playbackManager.deinit() + player.shutdown() + plexapp.util.APP.preShutdown() + util.CRON.stop() + backgroundthread.BGThreader.shutdown() + plexapp.util.APP.shutdown() + waitForThreads() + background.setBusy(False) + background.setSplash(False) + background.killMonitor() + + # uncomment to profile code #2 + #pr.disable() + #sortby = SortKey.CUMULATIVE + #s = io.StringIO() + #ps = pstats.Stats(pr, stream=s).sort_stats(sortby) + #ps.print_stats() + #util.DEBUG_LOG(s.getvalue()) + + util.DEBUG_LOG('FINISHED') + util.shutdown() + gc.collect(2) + except SystemExit: + util.LOG("Main: SystemExit exception caught (outer)...") + return if util.KODI_VERSION_MAJOR == 18: realExit() diff --git a/script.plexmod/lib/path_mapping.py b/script.plexmod/lib/path_mapping.py index e6fe30919a..5754cbd74c 100644 --- a/script.plexmod/lib/path_mapping.py +++ b/script.plexmod/lib/path_mapping.py @@ -52,7 +52,7 @@ def load(self): def mapping(self): return self.PATH_MAP and getSetting("path_mapping", True) - def getMappedPathFor(self, path, server): + def getMappedPathFor(self, path, server, return_rep=False): if self.mapping: match = ("", "") @@ -63,8 +63,17 @@ def getMappedPathFor(self, path, server): if all(match): map_path, pms_path = match - return map_path, pms_path - return None, None + + if return_rep: + sep = norm_sep(map_path) + + # replace match and normalize path separator to separator style of map_path + url = path.replace(pms_path, map_path, 1).replace(sep == "/" and "\\" or "/", sep) + + # fixme: this is dirty. + return url, pms_path, sep + return map_path, pms_path, None + return None, None, None def deletePathMapping(self, target, server=None, save=True): server = server or plexnet.util.SERVERMANAGER.selectedServer diff --git a/script.plexmod/lib/playback_utils.py b/script.plexmod/lib/playback_utils.py index 34dd29052d..714411c6fb 100644 --- a/script.plexmod/lib/playback_utils.py +++ b/script.plexmod/lib/playback_utils.py @@ -10,7 +10,7 @@ -ADDON = xbmcaddon.Addon() +ADDON = util.ADDON ATTR_MAP = { @@ -19,8 +19,12 @@ "c": "auto_skip_credits", "e": "show_intro_skip_early", "p": "skip_post_play_tv", + "v": "media_version", + "s": "auto_sync", } +VIRTUAL_ATTRS = ("media_version",) + # I know dicts are ordered in py3, but we want to be compatible with py2. TRANS_MAP = OrderedDict(( ("binge_mode", 33618), @@ -28,6 +32,7 @@ ("auto_skip_credits", 32526), ("show_intro_skip_early", 33505), ("skip_post_play_tv", 32973), + ("auto_sync", 33655), )) ATTR_MAP_REV = dict((v, k) for k, v in ATTR_MAP.items()) @@ -55,16 +60,26 @@ class PlaybackManager(object): def __init__(self): self.reset() # bind settings change signals - for v in ATTR_MAP.values(): + for k, v in ATTR_MAP.items(): + if k in VIRTUAL_ATTRS: + continue plexapp.util.APP.on('change:{}'.format(v), lambda **kwargs: self.setGlob(**kwargs)) plexapp.util.APP.on('change:selectedServer', lambda **kwargs: self.setServerUUID(**kwargs)) + plexapp.util.APP.on('change:tempServer', lambda **kwargs: self.setServerUUID(**kwargs)) plexapp.util.APP.on("loaded:cached_user", lambda **kwargs: self.setUserID(**kwargs)) plexapp.util.APP.on("change:user", lambda **kwargs: self.setUserID(**kwargs)) plexapp.util.APP.on('init', lambda **kwargs: self.setUserID(**kwargs)) def deinit(self): + # unbind settings change signals + for k, v in ATTR_MAP.items(): + if k in VIRTUAL_ATTRS: + continue + plexapp.util.APP.off('change:{}'.format(v), lambda **kwargs: self.setGlob(**kwargs)) + plexapp.util.APP.off('change:selectedServer', lambda **kwargs: self.setServerUUID(**kwargs)) + plexapp.util.APP.off('change:tempServer', lambda **kwargs: self.setServerUUID(**kwargs)) plexapp.util.APP.off("loaded:cached_user", lambda **kwargs: self.setUserID(**kwargs)) plexapp.util.APP.off("change:user", lambda **kwargs: self.setUserID(**kwargs)) plexapp.util.APP.off('init', lambda **kwargs: self.setUserID(**kwargs)) @@ -131,7 +146,7 @@ def setGlob(self, skey=None, value=None, **kwargs): if skey is not None and value is not None: self.glob = self.glob._replace(**{skey: value}) else: - self.glob = PlaybackSettings(**dict((k, util.getUserSetting(k, False)) for k in ATTR_MAP.values())) + self.glob = PlaybackSettings(**dict((k, util.getUserSetting(k)) for k in ATTR_MAP.values())) def setServerUUID(self, server=None): if not server and not plexapp.SERVERMANAGER.selectedServer: diff --git a/script.plexmod/lib/player.py b/script.plexmod/lib/player.py index a4011fcd89..b242b641d5 100644 --- a/script.plexmod/lib/player.py +++ b/script.plexmod/lib/player.py @@ -6,9 +6,11 @@ import re import os import random +import uuid from kodi_six import xbmc from kodi_six import xbmcgui +from iso639 import languages from . import backgroundthread from . import kodijsonrpc from . import colors @@ -33,8 +35,13 @@ def __init__(self, player, session_id=None): self.timelineType = None self.ignoreTimelines = False self.queuingNext = False + self.queuingSpecific = False self.playQueue = None self.sessionID = session_id + self.playbackID = None + self.isMapped = False + self.currentlyPlaying = None + self.reused = False def onAVChange(self): pass @@ -96,6 +103,9 @@ def getIntroOffset(self, offset=None, setSkipped=False): def setup(self, duration, meta, offset, bif_url, **kwargs): pass + def reset(self): + pass + @property def trueTime(self): return self.baseOffset + self.player.currentTime @@ -118,6 +128,14 @@ def currentDuration(self): return self._lastDuration + def onKodiExit(self, *args, **kwargs): + util.MONITOR.off("system.exit", self.onKodiExit) + util.DEBUG_LOG("{}: onKodiExit", self.__class__.__name__) + self.updateNowPlaying(state=self.player.STATE_STOPPED, overrideChecks=True) + self.ignoreTimelines = True + # kill previous timeline data + plexapp.util.APP.nowplayingmanager.reset() + def updateNowPlaying(self, refreshQueue=False, t=None, state=None, overrideChecks=False): if self.ignoreTimelines: util.DEBUG_LOG("UpdateNowPlaying: ignoring timeline as requested") @@ -130,6 +148,10 @@ def updateNowPlaying(self, refreshQueue=False, t=None, state=None, overrideCheck if not self.shouldSendTimeline(item): return + try: + player_time_str = self.player.getTime() if self.player.playState != self.player.STATE_STOPPED else "N/A" + except RuntimeError: + player_time_str = "N/A" util.DEBUG_LOG("UpdateNowPlaying: {0}, refreshQueue: {1} state: {2} (player: {5}) " "overrideChecks: {3} time: {4} (player: {6})" .format(item.ratingKey, @@ -138,44 +160,59 @@ def updateNowPlaying(self, refreshQueue=False, t=None, state=None, overrideCheck overrideChecks, t, self.player.playState, - self.player.getTime() if self.player.isPlayingVideo() else "N/A")) + player_time_str)) state = state or self.player.playState obj = item.choice - # Ignore sending timelines for multi part media with no duration + # Ignore sending timelines for multi-part media with no duration if obj and obj.part and obj.part.duration.asInt() == 0 and obj.media.parts and len(obj.media.parts) > 1: util.LOG("Timeline not supported: the current part doesn't have a valid duration") return # if we've been called explicitly with a time, honor force_time = t is not None - _time = t or int(self.trueTime * 1000) + _time = max(t or int(self.trueTime * 1000), 0) # self.trigger("progress", [m, item, time]) if refreshQueue and self.playQueue: self.playQueue.refreshOnTimeline = True + playbackTime = 0 + if getattr(self, "dialog", None) and self.dialog.playbackTime: + playbackTime = self.dialog.playbackTime + data = plexnetUtil.AttributeDict({ "key": str(item.key), "ratingKey": str(item.ratingKey), "guid": str(item.guid), "url": str(item.url), "duration": item.duration.asInt(), - "containerKey": str(item.container.address) + "playbackTime": str(playbackTime), + "additional_params": { + 'hasMDE': 1, + 'X-Plex-Client-Profile-Name': 'Generic', + 'X-Plex-Client-Identifier': item.settings.getGlobal('clientIdentifier'), + 'X-Plex-Session-Identifier': self.sessionID, + 'X-Plex-Session-Id': self.sessionID, + 'X-Plex-Playback-Id': self.playbackID + #"containerKey": str(item.container.address) + } }) new_time_stored = plexapp.util.APP.nowplayingmanager.updatePlaybackState( self.timelineType, data, state, _time, self.playQueue, duration=self.currentDuration(), - force=overrideChecks, force_time=force_time + force=overrideChecks, force_time=force_time, server=item.server, + continuing=self.queuingNext or self.queuingSpecific ) if new_time_stored: # only update our immediate progress if we should (e.g. if updatePlaybackState reported a new time based - # on _time - self._progressHld[str(item.ratingKey)] = _time + # on _time, and only if the item isn't fully watched, yet (True) + if not self._progressHld.get(str(item.ratingKey)) is True: + self._progressHld[str(item.ratingKey)] = _time def getVolume(self): return util.rpc.Application.GetProperties(properties=["volume"])["volume"] @@ -183,6 +220,18 @@ def getVolume(self): def sessionEnded(self): self.player.sessionID = None + def prev(self): + return + + def next(self): + return + + def playAt(self, pos): + return + + def hideOSD(self, **kwargs): + return + class SeekPlayerHandler(BasePlayerHandler): NO_SEEK = 0 @@ -208,11 +257,17 @@ def __init__(self, player, session_id=None): self.waitingForSOS = False self.chapters = None self.stoppedManually = False + self.endedManually = False self.inBingeMode = False self.skipPostPlay = False self.prePlayWitnessed = False self.queuingNext = False - self.useAlternateSeek = util.isCoreELEC + self.queuingSpecific = False + self._progressHld = {} + self.useAlternateSeek = util.getSetting('use_alternate_seek2') + self.useResumeFix = self.useAlternateSeek + self.isMapped = False + self.reused = False self.reset() def reset(self): @@ -223,31 +278,37 @@ def reset(self): self.seekOnStart = 0 self.waitingForSOS = False self._lastDuration = 0 - self._progressHld = {} + self._subtitleStreamOffset = None self.mode = self.MODE_RELATIVE self.ended = False + self.endedManually = False self.stoppedManually = False self.prePlayWitnessed = False self.queuingNext = False + self.queuingSpecific = False + self.isMapped = False + self.creditMarkerHit = None - def setup(self, duration, meta, offset, bif_url, title='', title2='', seeking=NO_SEEK, chapters=None): + def setup(self, duration, meta, offset, bif_url, title='', title2='', seeking=NO_SEEK, chapters=None, + is_mapped=False): + util.MONITOR.on("system.exit", self.onKodiExit) self.ended = False self.baseOffset = offset / 1000.0 self.seeking = seeking self.duration = duration self._lastDuration = duration - self._progressHld = {} self.bifURL = bif_url self.title = title self.title2 = title2 self.chapters = chapters or [] - self.playedThreshold = plexapp.util.INTERFACE.getPlayedThresholdValue() - self.ignoreTimelines = False - self.queuingNext = False + self.playedThreshold = plexapp.util.INTERFACE.getPlayedThresholdValue() # percentage self.stoppedManually = False self.inBingeMode = False self.skipPostPlay = False self.prePlayWitnessed = False + self._subtitleStreamOffset = None + self.isMapped = is_mapped + self.playbackID = str(uuid.uuid4()) self.getDialog(setup=True) self.dialog.setup(self.duration, meta, int(self.baseOffset * 1000), self.bifURL, self.title, self.title2, chapters=self.chapters, keepMarkerDef=seeking == self.SEEK_IN_PROGRESS) @@ -282,10 +343,13 @@ def shouldShowPostPlay(self): if util.getUserSetting('post_play_never', False): return False + if self.player.video and self.player.video.isExtra: + return False + if self.playlist and self.playlist.TYPE == 'playlist': return False - if not self.stoppedManually and self.skipPostPlay: + if not (self.stoppedManually or self.endedManually) and self.skipPostPlay: return False if (not util.addonSettings.postplayAlways and self._lastDuration <= FIVE_MINUTES_MILLIS)\ @@ -317,17 +381,24 @@ def next(self, on_end=False): hasNext = False if self.playlist: hasNext = bool(next(self.playlist)) - if hasNext: - self.seeking = self.SEEK_PLAYLIST + + self.triggerProgressEvent() if on_end: + # todo: this needs to be seriously cleaned up; showPostPlay/showShowPostPlay have too much impact on things + # they don't control/shouldn't control if self.showPostPlay(): + if hasNext: + self.seeking = self.SEEK_PLAYLIST return True + elif util.getUserSetting('post_play_never', False) and not self.skipPostPlay: + return False - if not self.playlist or self.stoppedManually or (self.playlist and not hasNext): + if not self.playlist or self.stoppedManually or self.endedManually or (self.playlist and not hasNext): return False - self.triggerProgressEvent() + if hasNext: + self.seeking = self.SEEK_PLAYLIST self.player.playVideoPlaylist(self.playlist, handler=self, resume=False) @@ -347,8 +418,10 @@ def playAt(self, pos): if not self.playlist or not self.playlist.setCurrent(pos): return False + self.triggerProgressEvent() self.seeking = self.SEEK_PLAYLIST - self.player.playVideoPlaylist(self.playlist, handler=self, resume=self.player.resume) + self.dialog.prepareNewPlayback(queuing_specific=True, ignore_tick=True, ignore_input=True) + self.player.playVideoPlaylist(self.playlist, handler=self, resume=False) return True @@ -370,7 +443,7 @@ def hideOSD(self, delete=False): if delete: d = self.dialog self.dialog = None - d.doClose() + d.doClose(delete=delete) del d util.garbageCollect() @@ -388,12 +461,10 @@ def seek(self, offset, settings_changed=False, seeking=SEEK_IN_PROGRESS): if self.isDirectPlay and not settings_changed: util.DEBUG_LOG('New absolute player offset: {0}', self.offset) - if self.player.playerObject.offsetIsValid(offset / 1000): + if self.player.playerObject.offsetIsValid(offset / 1000) and not self.player.isExternal: if self.seekAbsolute(offset): return - self.updateNowPlaying(state=self.player.STATE_PAUSED) # To for update after seek - self.seeking = self.SEEK_IN_PROGRESS if self.player.playState == self.player.STATE_PAUSED: @@ -413,6 +484,10 @@ def rewind(self): def seekAbsolute(self, seek=None): self.seekOnStart = seek or (self.seekOnStart if self.seekOnStart else None) + + if self.player.isExternal: + return True + if self.seekOnStart is not None: seekSeconds = self.seekOnStart / 1000.0 try: @@ -422,17 +497,25 @@ def seekAbsolute(self, seek=None): except RuntimeError: # Not playing a file util.DEBUG_LOG("SeekAbsolute: runtime error") return False - self.updateNowPlaying(state=self.player.STATE_PAUSED) # To for update after seek # Some devices seem to have an issue with the self.player.seekTime function where after the seek the video # will be playing, but the audio won't for a few seconds(I've seen up to 15 seconds). Using this alternate # way to seek avoids that issue. + + # we only apply the fix for a significant seek, otherwise the event might not fire, and we end up with + # an unconsumed self.seekOnStart, which leads to never sending timeline events if self.useAlternateSeek: currentTime = self.player.getTime() relativeSeekSeconds = seekSeconds - currentTime - util.DEBUG_LOG("SeekAbsolute: Seeking to offset: {0}, current time: {1}, relative seek: {2}".format( - seekSeconds, currentTime, relativeSeekSeconds)) - xbmc.executebuiltin('Seek({})'.format(relativeSeekSeconds)) + if abs(relativeSeekSeconds) > 1.0: + util.DEBUG_LOG("SeekAbsolute: Relative-seeking to offset: {0}, current time: {1}, relative seek: {2}".format( + seekSeconds, currentTime, relativeSeekSeconds)) + xbmc.executebuiltin('Seek({})'.format(relativeSeekSeconds)) + else: + util.DEBUG_LOG( + "SeekAbsolute: Not relative-seeking to offset: {0}, as offset diff is too small ({1}). Resetting seekOnStart".format( + seekSeconds, relativeSeekSeconds)) + self.seekOnStart = 0 else: util.DEBUG_LOG("SeekAbsolute: Seeking to {0}", self.seekOnStart) self.player.seekTime(seekSeconds) @@ -455,20 +538,31 @@ def onAVStarted(self): if self.dialog: self.dialog.onAVStarted() + self.ignoreTimelines = False + # check if embedded subtitle was set correctly if self.isDirectPlay and self.player.video and self.player.video.current_subtitle_is_embedded: - try: - playerID = kodijsonrpc.rpc.Player.GetActivePlayers()[0]["playerid"] - currIdx = kodijsonrpc.rpc.Player.GetProperties(playerid=playerID, properties=['currentsubtitle'])[ - 'currentsubtitle']['index'] - if currIdx != self.player.video._current_subtitle_idx: - util.LOG("Embedded Subtitle index was incorrect ({}), setting to: {}". - format(currIdx, self.player.video._current_subtitle_idx)) - self.dialog.setSubtitles() - else: - util.DEBUG_LOG("Embedded subtitle was correctly set in Kodi") - except: - util.ERROR("Exception when trying to check for embedded subtitles") + got_player = False + tries = 0 + while not got_player and tries < 50: + try: + playerID = kodijsonrpc.rpc.Player.GetActivePlayers()[0]["playerid"] + got_player = True + currIdx = kodijsonrpc.rpc.Player.GetProperties(playerid=playerID, properties=['currentsubtitle'])[ + 'currentsubtitle']['index'] + if currIdx != self.player.video._current_subtitle_idx + self.subtitleStreamOffset: + util.LOG("Embedded Subtitle index was incorrect ({}), setting to: {}". + format(currIdx, self.player.video._current_subtitle_idx + self.subtitleStreamOffset)) + self.dialog.setSubtitles() + else: + util.DEBUG_LOG("Embedded subtitle was correctly set in Kodi") + except IndexError: + util.DEBUG_LOG("Player not available yet, retrying ({}/{})".format(tries, 50)) + tries += 1 + util.MONITOR.waitForAbort(0.1) + except: + util.ERROR("Exception when trying to check for embedded subtitles") + break def onPrePlayStarted(self): util.DEBUG_LOG('SeekHandler: onPrePlayStarted, DP: {}', self.isDirectPlay) @@ -478,6 +572,7 @@ def onPrePlayStarted(self): def onPlayBackStarted(self): util.DEBUG_LOG('SeekHandler: onPlayBackStarted, DP: {}', self.isDirectPlay) + self.updateNowPlaying(refreshQueue=True) if self.dialog: @@ -486,22 +581,74 @@ def onPlayBackStarted(self): #if not self.prePlayWitnessed and self.isDirectPlay: if self.isDirectPlay: self.setSubtitles(do_sleep=False) + if self.reused: + util.DEBUG_LOG("SeekHandler: This handler was reused, make sure the right audio track is set.") + self.setAudioTrack() def onPlayBackResumed(self): - self.updateNowPlaying() + vpsc = False + if self.dialog and self.dialog.videoPausedForAudioStreamChange: + vpsc = True + + util.DEBUG_LOG('SeekHandler: onPlayBackResumed, DP: {}, ignoring (VPSC): {}', self.isDirectPlay, vpsc) + if not vpsc: + self.updateNowPlaying() if self.dialog: self.dialog.onPlayBackResumed() util.CRON.forceTick() # self.hideOSD() + def getVideoPlayedFac(self, ref=None): + return (ref if ref is not None else self.trueTime * 1000) / float(self.duration) + @property def videoPlayedFac(self): - return self.trueTime * 1000 / float(self.duration) + return self.getVideoPlayedFac() + + @property + def playedThresholdPerc(self): + if not self.player.video: + return 90 + + server_thres = self.player.video.server.prefs.get("LibraryVideoPlayedThreshold", None) + if server_thres is None: + return int(self.playedThreshold) + return int(server_thres) + + def getVideoWatched(self, ref=None): + """ + 0:at selected threshold percentage|1:at final credits marker position|2:at first credits marker position|3:earliest between threshold percent and first credits marker + :param ref: + :return: bool + """ + if not self.player.video: + return False + + playedAtBH = self.player.video.server.prefs.get("LibraryVideoPlayedAtBehaviour", None) + if playedAtBH is None: + playedAtBH = util.getSetting("played_threshold_behaviour") + playedAtBH = int(playedAtBH) + + watchedByPerc = self.getVideoPlayedFac(ref=ref) >= self.playedThresholdPerc / 100.0 or self.player.isExternal + + if playedAtBH == 0 or not self.player.video.has_credit_markers: + util.DEBUG_LOG("SeekPlayerHandler: Watched item due to percentage: {}", watchedByPerc) + return watchedByPerc + elif playedAtBH == 1 and self.creditMarkerHit == "final": + util.DEBUG_LOG("SeekPlayerHandler: Watched item due to final credits marker") + return True + elif playedAtBH == 2 and self.creditMarkerHit == "first": + util.DEBUG_LOG("SeekPlayerHandler: Watched item due to first credits marker") + return True + elif playedAtBH == 3 and (watchedByPerc or self.creditMarkerHit): + util.DEBUG_LOG("SeekPlayerHandler: Watched item due to percentage or credits marker") + return True + return False @property def videoWatched(self): - return self.videoPlayedFac >= self.playedThreshold or self.player.isExternal + return self.getVideoWatched() def triggerProgressEvent(self): if not self.player.video: @@ -512,8 +659,20 @@ def triggerProgressEvent(self): # progress already consumed return - self.player.trigger('video.progress', data=(rk, self._progressHld[rk] if not self.videoWatched else True)) - self._progressHld = {} + gprk = None + prk = None + if self.player.video.type == "episode": + prk = self.player.video.parentRatingKey + gprk = self.player.video.grandparentRatingKey + + vw = self.getVideoWatched( + ref=self._progressHld[rk] if self._progressHld[rk] > self.trueTime * 1000 else None) + if vw: + self._progressHld[rk] = True + self.player.trigger('video.progress', data=(gprk, prk, rk, vw or self._progressHld[rk])) + + def getProgressForItem(self, rk, default=0): + return self._progressHld.get(rk, default) def onPlayBackStopped(self): util.DEBUG_LOG('SeekHandler: onPlayBackStopped - ' @@ -524,26 +683,29 @@ def onPlayBackStopped(self): if self.dialog: self.dialog.onPlayBackStopped() - if self.queuingNext: - if self.isDirectPlay and self.playlist and self.playlist.hasNext(): + if self.queuingNext or self.queuingSpecific: + if self.isDirectPlay and self.playlist and (self.playlist.hasNext() or self.queuingSpecific): self.hideOSD(delete=True) # fixme: the on_end value is a hack here, we should rename or use a different parameter - if self.next(on_end=not self.skipPostPlay): - return + if self.queuingNext: + if self.next(on_end=not self.skipPostPlay): + return if self.seeking not in (self.SEEK_IN_PROGRESS, self.SEEK_REWIND): - self.updateNowPlaying() + if not self.queuingSpecific: + self.updateNowPlaying() self.triggerProgressEvent() - # show post play if possible, if an item has been watched (90% by Plex standards) - if self.seeking != self.SEEK_PLAYLIST and self.duration: - playedFac = self.videoPlayedFac - util.DEBUG_LOG("Player - played-threshold: {}/{}", playedFac, self.playedThreshold) - if playedFac >= self.playedThreshold and self.next(on_end=True): - return + if not self.queuingSpecific: + # show post play if possible, if an item has been watched (90% by Plex standards) + if self.seeking != self.SEEK_PLAYLIST and self.duration: + util.DEBUG_LOG("Player - played-threshold: {}%/{}%", + int(self.videoPlayedFac * 100), int(self.playedThresholdPerc)) + if self.videoWatched and self.next(on_end=True): + return if (self.seeking not in (self.SEEK_IN_PROGRESS, self.SEEK_PLAYLIST) or - (self.seeking == self.SEEK_PLAYLIST and self.stoppedManually)): + (self.seeking == self.SEEK_PLAYLIST and (self.stoppedManually or self.endedManually))): self.hideOSD(delete=True) self.sessionEnded() @@ -584,81 +746,258 @@ def onPlayBackEnded(self): self.sessionEnded() def onPlayBackPaused(self): - self.updateNowPlaying() + vpsc = False + if self.dialog and self.dialog.videoPausedForAudioStreamChange: + vpsc = True + + util.DEBUG_LOG('SeekHandler: onPlayBackPaused, DP: {}, ignoring (VPSC): {}', self.isDirectPlay, vpsc) + if not vpsc: + self.updateNowPlaying() if self.dialog: self.dialog.onPlayBackPaused() def onPlayBackSeek(self, stime, offset): if self.waitingForSOS: + util.DEBUG_LOG("SeekHandler: onPlayBackSeek: currently waiting for seekOnStart, not reacting: {}", self.seekOnStart) return util.DEBUG_LOG('SeekHandler: onPlayBackSeek - {0}, {1}, {2}', stime, offset, self.seekOnStart) + + # store original seekOnStart as it can change during seek attempts + origSOS = self.seekOnStart + if self.dialog: self.dialog.onPlayBackSeek(stime, offset) - if self.dialog and self.isDirectPlay and self.seekOnStart: - withinSOS = self.seekOnStart - 5000 + if self.dialog and self.isDirectPlay and origSOS: + seekWindow = util.addonSettings.altseekValidSeekWindow + withinSOSLow = origSOS - seekWindow + # allow the upper bounds to move because we might be playing (and moving forward) + withinSOSHigh = origSOS + seekWindow + min(seekWindow, 2000) tries = 0 - while not self.player.isPlayingVideo() and tries < 50: - xbmc.sleep(100) + while not self.player.isPlayingVideo() and tries < 50 and not util.MONITOR.abortRequested(): + util.MONITOR.waitForAbort(0.1) tries += 1 - if self.player.getTime() * 1000 < withinSOS: - self.waitingForSOS = True - # checking infoLabel Player.Seeking would be the better solution here, but we're dealing with stuff like - # CoreELEC, which doesn't necessarily properly honor this - xbmc.sleep(250) - self.seek(self.seekOnStart) - xbmc.sleep(util.addonSettings.coreelecResumeSeekWait) - - util.DEBUG_LOG("OnPlayBackSeek: SeekOnStart: " - "Expecting to be within 5 seconds of {}, currently at: {}", self.seekOnStart, - self.player.getTime()) + try: + p_time = self.player.getTime() + except RuntimeError: + # kodi isn't playing anything + util.LOG("SeekHandler: onPlayBackSeek: Called without playing player, exiting.") + return - tries = 0 - max_tries = int(5000 / util.addonSettings.coreelecResumeSeekWait) - while self.player.isPlayingVideo() and self.player.getTime() * 1000 < withinSOS and tries < max_tries: - util.DEBUG_LOG("OnPlayBackSeek: SeekOnStart: Not there, yet, " - "seeking again ({}, {})", self.seekOnStart, self.player.getTime()) - self.seek(self.seekOnStart) - tries += 1 - xbmc.sleep(util.addonSettings.coreelecResumeSeekWait) - if tries >= max_tries: - util.DEBUG_LOG("OnPlayBackSeek: SeekOnStart: Couldn't properly seek on start within ~5 seconds.") + if tries: + # move SOS a little + withinSOSHigh += 100 * tries + + util.DEBUG_LOG("SeekHandler: onPlayBackSeek: Playing: {}, Time: {}", self.player.isPlayingVideo(), p_time) + + SOSSuccess = True + + # this block should only be entered with alternate seek enabled + if self.useResumeFix and (p_time * 1000 < withinSOSLow or p_time * 1000 > withinSOSHigh): + # on certain problematic devices such as CoreELEC and LG, we advise to use the alternate seek fix, which + # uses a relative Kodi seek instead of the native absolute one. This can lead to onSeek being triggered + # without the player having actually seeked. In this case we need to monitor the player for a while and + # re-seek if necessary. + if self.useResumeFix and origSOS > 500: + util.DEBUG_LOG("SeekHandler: onPlayBackSeek: resumeFix: enabling waiting for seekOnStart (low: {}, high: {})", withinSOSLow, withinSOSHigh) + self.waitingForSOS = True + # checking infoLabel Player.Seeking would be the better solution here, but we're dealing with stuff like + # CoreELEC, which doesn't necessarily properly honor this + withinSOSHigh += 250 + util.MONITOR.waitForAbort(0.25) + + needsReSeek = False + if (self.useResumeFix and origSOS > 500) or not self.useResumeFix: + # seekOnStart might've changed to 0 + if self.player.getTime() * 1000 < withinSOSLow or self.player.getTime() * 1000 > withinSOSHigh: + util.DEBUG_LOG("SeekHandler: onPlayBackSeek: resumeFix: not there, yet, re-seeking: ({}, {}, {})", self.player.getTime(), withinSOSLow, withinSOSHigh) + needsReSeek = True + self.seek(origSOS) + else: + util.DEBUG_LOG("SeekHandler: onPlayBackSeek: resumeFix: we've reached {}", origSOS) else: - util.DEBUG_LOG("OnPlayBackSeek: Seeked on start to: {0}", self.seekOnStart) - self.waitingForSOS = False - self.dialog.offset = self.seekOnStart + util.DEBUG_LOG("SeekHandler: onPlayBackSeek: SOS is less than 500ms, not triggering seek") + + if self.useResumeFix and origSOS > 500 and needsReSeek: + # clamp to lower 500ms at least + seekWait = max(util.addonSettings.coreelecResumeSeekWait, 500) + withinSOSHigh += seekWait + util.MONITOR.waitForAbort(seekWait / 1000.0) + + util.DEBUG_LOG("OnPlayBackSeek: SeekOnStart: " + "Expecting to be within {} seconds of {}, currently at: {}, CoreELEC resume seek wait: {}ms", + (withinSOSHigh - withinSOSLow) / 1000, origSOS, self.player.getTime(), seekWait) + + tries = 0 + max_tries = int(5000 / seekWait) + while (self.player.isPlayingVideo() and self.player.getTime() * 1000 < withinSOSLow or self.player.getTime() * 1000 > withinSOSHigh) and tries < max_tries: + util.DEBUG_LOG("OnPlayBackSeek: SeekOnStart: Not there, yet, " + "seeking again ({}, range: {}, {})", origSOS, withinSOSHigh - withinSOSLow, self.player.getTime()) + if util.MONITOR.abortRequested(): + util.DEBUG_LOG("OnPlayBackSeek: SeekOnStart: Abort requested while waiting for seek") + SOSSuccess = False + break + elif not self.player.isPlayingVideo(): + util.DEBUG_LOG("OnPlayBackSeek: SeekOnStart: Player not playing video while waiting for seek") + return + + withinSOSHigh += 250 + util.MONITOR.waitForAbort(0.25) + self.seek(origSOS) + + tries += 1 + withinSOSHigh += seekWait + util.MONITOR.waitForAbort(seekWait / 1000.0) + if tries >= max_tries: + util.DEBUG_LOG("OnPlayBackSeek: SeekOnStart: Couldn't properly seek on start within ~5 seconds.") + SOSSuccess = False + else: + if not SOSSuccess: + util.DEBUG_LOG("OnPlayBackSeek: Seek on start failed") + else: + util.DEBUG_LOG("OnPlayBackSeek: Seeked on start to: {0}", origSOS) + + # should not be necessary due to other recent changes to dialog persistence, but it doesn't hurt, either + appliedOffset = None + if self.dialog: + if SOSSuccess and ((self.useResumeFix and origSOS > 500) or not self.useResumeFix): + appliedOffset = int(self.player.getTime() * 1000) if self.useResumeFix else origSOS + util.DEBUG_LOG("SeekHandler: onPlayBackSeek: Setting dialog offset to {}", appliedOffset) + # set to current time if we succeeded, as seekOnStart could've been set to 0 in the meantime by the relative seek + self.dialog.offset = appliedOffset + + if SOSSuccess: + util.DEBUG_LOG("SeekHandler: onPlayBackSeek: SeekOnStart applied: {}", appliedOffset) + else: + util.DEBUG_LOG("SeekHandler: onPlayBackSeek: SeekOnStart not successful: {}", origSOS) + self.waitingForSOS = False self.seekOnStart = 0 + if self.useResumeFix and self.dialog: + self.dialog.offset = appliedOffset + self.dialog.selectedOffset = appliedOffset + self.dialog.update() self.updateOffset() # self.showOSD(from_seek=True) - def setSubtitles(self, do_sleep=True, honor_forced_subtitles_override=True): + @property + def subtitleStreamOffset(self): + if self.player.playerObject and self.isDirectPlay and self.player.playerObject.metadata.isMapped: + if self._subtitleStreamOffset is not None: + return self._subtitleStreamOffset + + # when mapped, Kodi finds external subs on its own and places them at the top of the list, before + # embedded subs. + kodisubs = None + tries = 0 + while not kodisubs and tries < 50: + try: + playerID = kodijsonrpc.rpc.Player.GetActivePlayers()[0]["playerid"] + kodisubs = kodijsonrpc.rpc.Player.GetProperties(playerid=playerID, properties=['subtitles'])["subtitles"] + break + except IndexError: + pass + tries += 1 + util.MONITOR.waitForAbort(0.1) + if not kodisubs: + # this can happen occasionally, if the player isn't ready, yet, but we account for that + util.DEBUG_LOG("SeekHandler: subtitleStreamOffset: Returning zero as player not available or no " + "subtitles found") + return 0 + if kodisubs: + # find embedded subtitle stream in Plex + ess = None + ext_subs_amount = 0 + for ss in self.player.video.subtitleStreams: + if not ss.languageCode: + util.DEBUG_LOG("Skipping subtitle: {}, no language code found".format(ss)) + continue + if not ess and ss.embedded: + ess = ss + # ss.score: only downloaded external subtitles have a score; skip them, as they're not visible to + # Kodi + elif not ss.embedded and not ss.score: + ext_subs_amount += 1 + + if not ess: + self._subtitleStreamOffset = 0 + util.DEBUG_LOG("SeekHandler: subtitleStreamOffset: Returning zero as we didn't find an embedded subtitle") + return 0 + + util.DEBUG_LOG("SeekHandler: subtitleStreamOffset: Found embedded subtitle at: {}", ext_subs_amount) + + # find embedded subtitle stream in Kodi stream list + # we know Kodi puts external subtitles first, start there (Kodi might see more external subs or the PMS + # hasn't detected them, so we still need to find the embedded sub we're searching for) + + # use iso639 to determine the streams' languages (Kodi uses the bibliographic language code, Plex uses + # the terminological one (e.g: ger vs. deu, fre vs. fra) + ess_lang = languages.get(part2t=ess.languageCode) + for sub in kodisubs[ext_subs_amount:]: + if (sub['isdefault'] == ess.default.asBool() and sub['isforced'] == ess.forced.asBool() and + sub['name'] == six.ensure_str(ess.title) and languages.get( + part2b=sub['language']) == ess_lang): + self._subtitleStreamOffset = sub['index'] - ess.typeIndex + util.DEBUG_LOG("SeekHandler: subtitleStreamOffset: Returning offset: {} ({})", + self._subtitleStreamOffset, sub) + return self._subtitleStreamOffset + + util.LOG("SeekHandler: Couldn't find embedded subtitle in Kodi subtitle list: {}, assuming no difference", ess) + self._subtitleStreamOffset = 0 + + # old implementation + # embeddedStreams = list(filter(lambda x: x.embedded, self.player.video.subtitleStreams)) + # difflen = len(kodisubs) - len(embeddedStreams) + ## does our sub-count differ from the one Kodi sees? if so, adjust the offset + # if difflen > 0: + # self._subtitleStreamOffset = difflen + # return self._subtitleStreamOffset + ## if not, do we have external subtitles? if so, we need to adjust the offset + # self._subtitleStreamOffset = len(list(filter(lambda x: not x.embedded, self.player.video.subtitleStreams))) + # return self._subtitleStreamOffset + return 0 + + def setSubtitles(self, do_sleep=True, honor_forced_subtitles_override=True, honor_deselect_subtitles=True, + ref="_current_subtitle_idx"): + util.DEBUG_LOG("SeekHandler: setSubtitles") if not self.player.video: util.LOG("Warning: SetSubtitles: no player.video object available") return subs = self.player.video.selectedSubtitleStream( forced_subtitles_override=honor_forced_subtitles_override and util.getSetting("forced_subtitles_override", - False)) + ) and plexnetUtil.ACCOUNT.subtitlesForced == 0, + deselect_subtitles=honor_deselect_subtitles and util.getSetting("disable_subtitle_languages") or [], + ref=ref + ) + + # we want to get the subtitle stream offset regardless of whether we have subtitles selected or not, + # as the subtitle amount might change during playback (e.g. kodi subtitle download adds one to the list) + # but our concern are existing external subtitles in path mapped mode, which Kodi adds to the top of the list + sso = self.subtitleStreamOffset if subs: if do_sleep: xbmc.sleep(100) - path = subs.getSubtitleServerPath() + # the subtitle stream might not have had the correct amount of data set to properly determine auto sync + # reinit the auto sync state with our current video + subs.init_auto_sync(video=self.player.video) + path = subs.getSubtitleServerPath(auto_sync=subs.should_auto_sync) if self.isDirectPlay: self.player.showSubtitles(False) if path: - util.DEBUG_LOG('Setting subtitle path: {0} ({1})', path, subs) + util.DEBUG_LOG('Setting subtitle path: {0} ({1})', plexnetUtil.cleanToken(path), subs) self.player.setSubtitles(path) self.player.showSubtitles(True) else: # u_til.TEST(subs.__dict__) # u_til.TEST(self.player.video.mediaChoice.__dict__) - util.DEBUG_LOG('Enabling embedded subtitles at: {0} ({1})', subs.typeIndex, subs) - self.player.setSubtitleStream(subs.typeIndex) + + util.DEBUG_LOG('Enabling embedded subtitles at: {0} ({1})', subs.typeIndex + sso, subs) + self.player.setSubtitleStream(subs.typeIndex + sso) self.player.showSubtitles(True) else: @@ -666,25 +1005,31 @@ def setSubtitles(self, do_sleep=True, honor_forced_subtitles_override=True): def setAudioTrack(self): self.player.lastPlayWasBGM = False - if self.isDirectPlay: + if self.isDirectPlay and self.player.video: track = self.player.video.selectedAudioStream() if track: - # only try finding the current audio stream when the BG music isn't playing and wasn't the last - # thing played, because currentaudiostream doesn't populate for audio-only items; in that case, - # always select the proper audio stream - if not self.player.lastPlayWasBGM: + currIdx = None + tries = 0 + while currIdx != track.typeIndex and tries < 40: try: playerID = kodijsonrpc.rpc.Player.GetActivePlayers()[0]["playerid"] - currIdx = kodijsonrpc.rpc.Player.GetProperties(playerid=playerID, properties=['currentaudiostream'])['currentaudiostream']['index'] - if currIdx == track.typeIndex: - util.DEBUG_LOG('Audio track is correct index: {0}', track.typeIndex) - return + currIdx = \ + kodijsonrpc.rpc.Player.GetProperties(playerid=playerID, properties=['currentaudiostream'])[ + 'currentaudiostream']['index'] except: - util.ERROR() + pass + if currIdx == track.typeIndex: + util.DEBUG_LOG('Audio track is correct index: {0}', track.typeIndex) + return + + if currIdx is not None: + util.DEBUG_LOG('Switching audio track - index: {0} to {1} (try: {1})', currIdx, track.typeIndex, tries + 1) + util.MONITOR.waitForAbort(0.1) + self.player.setAudioStream(track.typeIndex) + else: + util.MONITOR.waitForAbort(0.1) + tries += 1 - util.MONITOR.waitForAbort(0.1) - util.DEBUG_LOG('Switching audio track - index: {0}', track.typeIndex) - self.player.setAudioStream(track.typeIndex) def updateOffset(self): try: @@ -731,6 +1076,7 @@ def onVideoWindowOpened(self): def onVideoWindowClosed(self): self.hideOSD() util.DEBUG_LOG('SeekHandler: onVideoWindowClosed - Seeking={0}', self.seeking) + self.player.trigger('videowindow.closed', session_id=self.sessionID, video=self.player.video) if not self.seeking: # send events as we might not have seen onPlayBackEnded and/or onPlayBackStopped in certain cases, # especially when postplay isn't wanted and we're at the end of a show @@ -739,19 +1085,28 @@ def onVideoWindowClosed(self): # self.triggerProgressEvent() if self.player.isPlaying(): self.player.stopAndWait() + if not self.playlist or not self.playlist.hasNext(): if not self.shouldShowPostPlay(): + util.DEBUG_LOG("SeekHandler: Not showing post-play (VideoWindowClosed)") self.sessionEnded() def onVideoOSD(self): # xbmc.executebuiltin('Dialog.Close(seekbar,true)') # Doesn't work :) + util.DEBUG_LOG('SeekHandler: onVideoOSD - Seeking={0}', self.seeking) + if self.queuingSpecific or self.queuingNext: + return self.showOSD() def tick(self): if (self.seeking != self.SEEK_IN_PROGRESS and not self.ended and self.player.started and not self.seekOnStart - and not self.queuingNext and not self.stoppedManually and self.player.isPlayingVideo() and - self.player.playState != self.player.STATE_STOPPED): - self.updateNowPlaying() + and not self.queuingNext and not self.queuingSpecific and not self.stoppedManually and + self.player.isPlayingVideo() and self.player.playState != self.player.STATE_STOPPED): + self.updateNowPlaying(t=self.dialog.timeKeeperTime if self.player.isExternal else None) + else: + util.DEBUG_LOG("Not ticking UpdateNowPlaying: {}, {}, {}, {}, {}, {}, {}, {}", self.seeking, + self.ended, self.player.started, self.seekOnStart, self.queuingNext, self.stoppedManually, + self.player.isPlayingVideo(), self.player.playState) if self.dialog and getattr(self.dialog, "_ignoreTick", None) is not True: self.dialog.tick() @@ -772,12 +1127,15 @@ def sessionEnded(self): class AudioPlayerHandler(BasePlayerHandler): - def __init__(self, player): - BasePlayerHandler.__init__(self, player) + def __init__(self, player, session_id=None): + BasePlayerHandler.__init__(self, player, session_id=session_id) self.timelineType = 'music' util.setGlobalProperty('track.ID', '') self.extractTrackInfo() + def setup(self, *args, **kwargs): + util.MONITOR.on("system.exit", self.onKodiExit) + def extractTrackInfo(self): if not self.player.isPlayingAudio(): return @@ -807,7 +1165,7 @@ def extractTrackInfo(self): track = plexobjects.PlexObject.deSerialize(base64.urlsafe_b64decode(data.encode('utf-8'))) track.softReload() self.media = track - pobj = plexplayer.PlexAudioPlayer(track) + pobj = plexplayer.PlexAudioPlayer(track, session_id=self.sessionID) self.player.playerObject = pobj self.updatePlayQueueTrack(track) util.setGlobalProperty('track.ID', track.ratingKey) # This is used in the skins to match a listitem @@ -835,7 +1193,7 @@ def playQueueCallback(self, **kwargs): citem = kodijsonrpc.rpc.Player.GetItem(playerid=0, properties=['comment'])['item'] plexID = citem['comment'].split(':', 1)[0] except: - util.ERROR() + #util.ERROR() return current = plist.getposition() @@ -861,12 +1219,17 @@ def playQueueCallback(self, **kwargs): plist.add(url, li) if swap is not None: - plist[0].setInfo('music', { - 'playcount': swap + 1, - }) + if util.KODI_VERSION_MAJOR >= 20: + vi = plist[0].getMusicInfoTag() + vi.setPlayCount(swap + 1) + + else: + plist[0].setInfo('music', { + 'playcount': swap + 1, + }) # Now swap the track to the correct position. This seems to be the only way to update the kodi playlist position to the current track's new position - if swap is not None and swap != current: + if swap is not None: kodijsonrpc.rpc.Playlist.Swap(playlistid=xbmc.PLAYLIST_MUSIC, position1=0, position2=swap + 1) try: kodijsonrpc.rpc.Playlist.Remove(playlistid=xbmc.PLAYLIST_MUSIC, position=0) @@ -897,10 +1260,12 @@ def updatePlayQueueTrack(self, track): @property def trueTime(self): - try: - return self.player.getTime() - except: - return self.player.currentTime + if self.player.playState != self.player.STATE_STOPPED: + try: + return self.player.getTime() + except: + return self.player.currentTime + return self.player.currentTime def stampCurrentTime(self): try: @@ -914,6 +1279,7 @@ def onMonitorInit(self): self.updateNowPlaying(state='playing') def onPlayBackStarted(self): + util.DEBUG_LOG('AudioPlayerHandler: onPlayBackStarted') self.player.lastPlayWasBGM = False self.updatePlayQueue(delay=True) self.extractTrackInfo() @@ -921,9 +1287,11 @@ def onPlayBackStarted(self): self.updateNowPlaying(state='playing') def onAVStarted(self): + util.DEBUG_LOG('AudioPlayerHandler: onAVStarted') self.player.trigger('started.audio') def onAVChange(self): + util.DEBUG_LOG('AudioPlayerHandler: onAVChange') self.player.trigger('changed.audio') def onPlayBackResumed(self): @@ -961,10 +1329,11 @@ def tick(self): class BGMPlayerHandler(BasePlayerHandler): - def __init__(self, player, rating_key): + def __init__(self, player, init_data): BasePlayerHandler.__init__(self, player) self.timelineType = 'music' - self.currentlyPlaying = rating_key + self.initData = init_data + self.currentlyPlaying = init_data[2] util.setGlobalProperty('track.ID', '') self.oldVolume = util.rpc.Application.GetProperties(properties=["volume"])["volume"] @@ -1012,6 +1381,9 @@ def onPlayBackStopped(self): def onPlayBackEnded(self): self.onPlayBackStopped() + if util.getSetting('theme_music_loop') and not self.player.dontRequeueBGM: + self.player.playBackgroundMusic(*self.initData) + def onPlayBackFailed(self): self.onPlayBackStopped() @@ -1060,9 +1432,12 @@ def __init__(self, *args, **kwargs): self.handler = AudioPlayerHandler(self) self.isExternal = False + self.on('action', self.playerAction) + def init(self): self._closed = False self._nextItem = None + self._ignorePlaybackFailure = False self.started = False self.bgmPlaying = False self.bgmStarting = False @@ -1079,6 +1454,7 @@ def init(self): self.thread = None self.ignoreStopEvents = False self.isExternal = False + self.dontRequeueBGM = False if xbmc.getCondVisibility('Player.HasMedia') and self.isPlayingAudio() and not self.bgmPlaying: self.started = True self.resume = False @@ -1092,6 +1468,8 @@ def open(self): def close(self, shutdown=False): self._closed = True + if shutdown: + self.off('action', self.playerAction) def reset(self): self.video = None @@ -1100,6 +1478,8 @@ def reset(self): self.playerObject = None self.pauseAfterPlaybackStarted = False self.ignoreStopEvents = False + self._ignorePlaybackFailure = False + self.dontRequeueBGM = False #self.handler = AudioPlayerHandler(self) self.currentTime = 0 @@ -1116,6 +1496,30 @@ def control(self, cmd): util.DEBUG_LOG('Player - Control: Pausing') xbmc.executebuiltin('PlayerControl(Play)') + def playerAction(self, action, **kwargs): + """ + Signal receiver for specific player actions (called by SeekDialog for example) + @param action: "next", "prev", "playAt" + @param kwargs: "pos"=playlist index; in case of action == "playAt" + @return: + """ + if not self.handler: + util.DEBUG_LOG("Player: Can't handle action without handler") + return + + util.DEBUG_LOG('Player - Action: {} ({})', action, str(kwargs)) + + self.handler.hideOSD() + + if action == "next": + self.handler.next() + + elif action == "prev": + self.handler.prev() + + elif action == "playAt": + self.handler.playAt(kwargs['pos']) + @property def playState(self): if xbmc.getCondVisibility('Player.Playing'): @@ -1163,7 +1567,7 @@ def playBackgroundMusic(self, source, volume, rating_key, *args, **kwargs): else: # don't re-queue the currently playing theme - if self.handler.currentlyPlaying == rating_key: + if isinstance(self.handler, BGMPlayerHandler) and self.handler.currentlyPlaying == rating_key: return # cancel any currently playing theme before starting the new one @@ -1180,7 +1584,8 @@ def playBackgroundMusic(self, source, volume, rating_key, *args, **kwargs): self.started = False self.bgmStarting = True - self.handler = BGMPlayerHandler(self, rating_key) + self.dontRequeueBGM = False + self.handler = BGMPlayerHandler(self, [source, volume, rating_key]) # store current volume if it's different from the BGM volume if volume < curVol: @@ -1197,8 +1602,11 @@ def playVideo(self, video, resume=False, force_update=False, session_id=None, ha if self.bgmPlaying: self.stopAndWait() - self.handler = handler if handler and isinstance(handler, SeekPlayerHandler) \ - else SeekPlayerHandler(self, session_id or self.sessionID) + if handler and isinstance(handler, SeekPlayerHandler): + self.handler = handler + self.handler.reused = True + else: + self.handler = SeekPlayerHandler(self, session_id or self.sessionID) self.video = video self.resume = resume @@ -1220,6 +1628,7 @@ def getOSSPathHint(self, meta): return cleaned_path def _playVideo(self, offset=0, seeking=0, force_update=False, playerObject=None, session_id=None): + self.sessionID = session_id or self.sessionID self.trigger('new.video', video=self.video) self.trigger( 'change.background', @@ -1227,12 +1636,12 @@ def _playVideo(self, offset=0, seeking=0, force_update=False, playerObject=None, ) try: if not playerObject: - self.playerObject = plexplayer.PlexPlayer(self.video, offset, forceUpdate=force_update) + self.playerObject = plexplayer.PlexPlayer(self.video, offset, forceUpdate=force_update, session_id=self.sessionID) self.playerObject.build() self.playerObject = self.playerObject.getServerDecision() except plexplayer.DecisionFailure as e: util.showNotification(e.reason, header=util.T(32448, 'Playback Failed!')) - return + raise except: util.ERROR(notify=True) return @@ -1251,12 +1660,13 @@ def _playVideo(self, offset=0, seeking=0, force_update=False, playerObject=None, util.MONITOR.waitForAbort(util.addonSettings.consecutiveVideoPbWait) self.ignoreStopEvents = False - self.sessionID = session_id or self.sessionID # fixme: this handler might be accessing a new playerObject, not the one it's expecting to access, # especially when .next() is used + self.handler.reset() self.handler.setup(self.video.duration.asInt(), meta, offset, bifURL, title=self.video.grandparentTitle, - title2=self.video.title, seeking=seeking, chapters=self.video.chapters) + title2=self.video.title, seeking=seeking, chapters=self.video.chapters, + is_mapped=meta.isMapped) # try to get an early intro offset so we can skip it if necessary introOffset = None @@ -1298,7 +1708,9 @@ def _playVideo(self, offset=0, seeking=0, force_update=False, playerObject=None, url = util.addURLParams(url, { 'X-Plex-Client-Profile-Name': 'Generic', - 'X-Plex-Client-Identifier': plexapp.util.INTERFACE.getGlobal('clientIdentifier') + 'X-Plex-Client-Identifier': self.video.settings.getGlobal('clientIdentifier'), + 'X-Plex-Session-Identifier': self.sessionID, + 'X-Plex-Session-Id': self.sessionID }) li = xbmcgui.ListItem(self.video.title, path=url) vtype = self.video.type if self.video.type in ('movie', 'episode', 'musicvideo') else 'video' @@ -1379,14 +1791,34 @@ def _playVideo(self, offset=0, seeking=0, force_update=False, playerObject=None, util.DEBUG_LOG("Setting VideoInfo: {}".format( plexnetUtil.cleanObjTokens(info, flistkeys=[]) )) - li.setInfo('video', info) + li.setArt({ 'poster': self.video.defaultThumb.asTranscodedImageURL(347, 518), 'fanart': self.video.defaultArt.asTranscodedImageURL(1920, 1080), 'thumb': self.video.defaultThumb.asTranscodedImageURL(256, 256), }) + if util.KODI_VERSION_MAJOR >= 20: + li.setInfo('video', {'size': info['size']}) + + vi = li.getVideoInfoTag() + vi.setMediaType(info['mediatype']) + vi.setTitle(info['title']) + vi.setOriginalTitle(info['originaltitle']) + vi.setTvShowTitle(info['tvshowtitle']) + vi.setYear(info['year']) + vi.setPlot(info['plot']) + vi.setPath(info['path']) + vi.setIMDBNumber(info['imdbnumber']) + if vtype == "episode": + vi.setEpisode(info['episode']) + vi.setSeason(info['season']) + else: + li.setInfo('video', info) + self.trigger('starting.video') + self.handler.queuingNext = False + self.handler.queuingSpecific = False self.play(url, li) def playVideoPlaylist(self, playlist, resume=False, handler=None, session_id=None): @@ -1394,7 +1826,17 @@ def playVideoPlaylist(self, playlist, resume=False, handler=None, session_id=Non self.stopAndWait() if handler and isinstance(handler, SeekPlayerHandler): + util.DEBUG_LOG("PlayVideoPlaylist: Reusing old handler: {}", handler) self.handler = handler + self.handler.reused = True + #self.handler.queuingNext = True + #self.handler.seekOnStart = 0 + #self.handler.baseOffset = 0 + #if self.handler.dialog: + # self.handler.dialog.doClose(delete=True) + #self.handler.dialog = None + self.playerObject = None + self.currentTime = 0 else: self.handler = SeekPlayerHandler(self, session_id or self.sessionID) @@ -1434,14 +1876,15 @@ def playAudio(self, track, fanart=None, **kwargs): self.stopAndWait() self.ignoreStopEvents = True - self.handler = AudioPlayerHandler(self) - self.playerObject = plexplayer.PlexAudioPlayer(track) + self.sessionID = "AUD%s" % track.ratingKey + self.handler = AudioPlayerHandler(self, session_id=self.sessionID) + self.handler.setup() + self.playerObject = plexplayer.PlexAudioPlayer(track, session_id=self.sessionID) url, li = self.createTrackListItem(track, fanart) self.stopAndWait() self.ignoreStopEvents = False # maybe fixme: once started, self.sessionID will never be None for Audio - self.sessionID = "AUD%s" % track.ratingKey self.trigger('starting.audio') self.play(url, li, **kwargs) @@ -1450,8 +1893,10 @@ def playAlbum(self, album, startpos=-1, fanart=None, **kwargs): self.stopAndWait() self.ignoreStopEvents = True - self.handler = AudioPlayerHandler(self) - self.playerObject = plexplayer.PlexAudioPlayer() + self.sessionID = "ALB%s" % album.ratingKey + self.handler = AudioPlayerHandler(self, session_id=self.sessionID) + self.handler.setup() + self.playerObject = plexplayer.PlexAudioPlayer(session_id=self.sessionID) plist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC) plist.clear() index = 1 @@ -1462,7 +1907,6 @@ def playAlbum(self, album, startpos=-1, fanart=None, **kwargs): xbmc.executebuiltin('PlayerControl(RandomOff)') self.stopAndWait() self.ignoreStopEvents = False - self.sessionID = "ALB%s" % album.ratingKey self.trigger('starting.audio') self.play(plist, startpos=startpos, **kwargs) @@ -1471,8 +1915,10 @@ def playAudioPlaylist(self, playlist, startpos=-1, fanart=None, **kwargs): self.stopAndWait() self.ignoreStopEvents = True - self.handler = AudioPlayerHandler(self) - self.playerObject = plexplayer.PlexAudioPlayer() + self.sessionID = "PLS%s" % getattr(playlist, "ratingKey", getattr(playlist, "id", random.randint(0, 1000))) + self.handler = AudioPlayerHandler(self, session_id=self.sessionID) + self.handler.setup() + self.playerObject = plexplayer.PlexAudioPlayer(session_id=self.sessionID) plist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC) plist.clear() index = 1 @@ -1491,7 +1937,6 @@ def playAudioPlaylist(self, playlist, startpos=-1, fanart=None, **kwargs): xbmc.executebuiltin('PlayerControl(RandomOff)') self.stopAndWait() self.ignoreStopEvents = False - self.sessionID = "PLS%s" % getattr(playlist, "ratingKey", getattr(playlist, "id", random.randint(0, 1000))) self.trigger('starting.audio') self.play(plist, startpos=startpos, **kwargs) @@ -1501,7 +1946,7 @@ def createTrackListItem(self, track, fanart=None, index=0): track = track.reload() url = self.playerObject.build(track)['url'] li = xbmcgui.ListItem(track.title, path=url) - li.setInfo('music', { + info = { 'artist': six.text_type(track.originalTitle or track.grandparentTitle), 'title': six.text_type(track.title), 'album': six.text_type(track.parentTitle), @@ -1512,7 +1957,8 @@ def createTrackListItem(self, track, fanart=None, index=0): # fixme: this is not really necessary, as we don't go the plugin:// route anymore. # changing the track identification style would mean a bigger rewrite, though, so let's keep it. 'comment': 'PLEX-{0}:{1}'.format(track.ratingKey, data) - }) + } + art = fanart or track.defaultArt li.setArt({ 'fanart': art.asTranscodedImageURL(1920, 1080), @@ -1521,6 +1967,20 @@ def createTrackListItem(self, track, fanart=None, index=0): }) if fanart: li.setArt({'fanart': fanart}) + + if util.KODI_VERSION_MAJOR >= 20: + ai = li.getMusicInfoTag() + ai.setArtist(info['artist']) + ai.setTitle(info['title']) + ai.setAlbum(info['album']) + ai.setDisc(info['discnumber']) + ai.setTrack(info['tracknumber']) + ai.setDuration(info['duration']) + ai.setPlayCount(info['playcount']) + ai.setComment(info['comment']) + else: + li.setInfo('music', info) + return (url, li) def onPrePlayStarted(self): @@ -1600,6 +2060,10 @@ def onPlayBackStopped(self): def onPlayBackEnded(self): if not self.sessionID: return + + if self.isExternal: + self.trigger('videowindow.closed', session_id=self.sessionID, video=self.video) + util.DEBUG_LOG('Player - ENDED' + (not self.started and ': FAILED' or '')) if self.ignoreStopEvents: return @@ -1626,7 +2090,7 @@ def onPlayBackError(self): if not self.handler: return - if self.handler.onPlayBackFailed(): + if self.handler.onPlayBackFailed() and not self._ignorePlaybackFailure: self.ignoreStopEvents = True util.showNotification('Playback Error!') self.stopAndWait() @@ -1639,7 +2103,7 @@ def onPlayBackFailed(self): if not self.handler: return - if self.handler.onPlayBackFailed(): + if self.handler.onPlayBackFailed() and not self._ignorePlaybackFailure: util.showNotification(util.T(32448, 'Playback Failed!')) self.stopAndWait() self.close() @@ -1685,11 +2149,13 @@ def onSeekOSD(self): def stopAndWait(self): if self.isPlaying(): util.DEBUG_LOG('Player: Stopping and waiting...') + self.dontRequeueBGM = True self.stop() if not util.MONITOR.abortRequested(): - while not util.MONITOR.waitForAbort(0.05) and self.isPlaying(): + while not util.MONITOR.waitForAbort(0.1) and self.isPlaying(): if util.MONITOR.abortRequested(): break + util.MONITOR.waitForAbort(0.2) util.DEBUG_LOG('Player: Stopping and waiting...Done') def monitor(self): @@ -1744,11 +2210,28 @@ def _videoMonitor(self): hasFullScreened = False ct = 0 + util.DEBUG_LOG("VideoMonitor: Initializing...") while self.isPlayingVideo() and not util.MONITOR.abortRequested() and not self._closed: - try: - self.currentTime = self.getTime() - except RuntimeError: - break + if self.handler and (self.handler.queuingNext or self.handler.queuingSpecific): + # when waiting for the next item to be fully initialized, don't set self.currentTime, otherwise + # onPlaybackStarted could push an invalid trueTime + util.DEBUG_LOG("VideoMonitor: Waiting for next item to queue...") + while (self.handler and (self.handler.queuingNext or self.handler.queuingSpecific) + and not util.MONITOR.abortRequested() and not self._closed): + util.MONITOR.waitForAbort(0.1) + + util.DEBUG_LOG("VideoMonitor: Started") + + if not self.isExternal: + p_time = None + t_tries = 0 + while not p_time and not util.MONITOR.abortRequested() and t_tries < 50 and not self.isExternal: + try: + self.currentTime = p_time = self.getTime() + except RuntimeError: + util.DEBUG_LOG("VideoMonitor: Waiting for player readiness...") + t_tries += 1 + util.MONITOR.waitForAbort(0.1) util.MONITOR.waitForAbort(0.1) if xbmc.getCondVisibility('Window.IsActive(videoosd)'): diff --git a/script.plexmod/lib/plex.py b/script.plexmod/lib/plex.py index 7b2e1549f1..c80282ee60 100644 --- a/script.plexmod/lib/plex.py +++ b/script.plexmod/lib/plex.py @@ -16,6 +16,7 @@ from .playback_utils import PlaybackManager from . windows.settings import PlayedThresholdSetting from . import util +from lib.plex_hosts import pdm from six.moves import range if six.PY2: @@ -24,6 +25,9 @@ _Event = threading.Event +UNDEF = "__UNDEF__" + + class PlexTimer(plexapp.util.Timer): def shouldAbort(self): return util.MONITOR.abortRequested() @@ -88,14 +92,15 @@ class PlexInterface(plexapp.AppInterface): None: {}, } _globals = { - 'platform': 'Kodi', + 'platform': util.platform or 'Kodi', 'appVersionStr': util.ADDON.getAddonInfo('version'), 'clientIdentifier': CLIENT_ID, - 'platformVersion': xbmc.getInfoLabel('System.BuildVersion'), + 'platformVersion': util.platform_version or plexnet_util.X_PLEX_PLATFORM_VERSION, 'product': 'PM4K', 'provides': 'player', - 'device': util.getPlatform() or plexapp.PLATFORM, - 'model': 'Unknown', + 'device': util.device or util.getPlatform() or plexapp.PLATFORM, + 'vendor': util.vendor or '', + 'model': util.model or 'Unknown', 'friendlyName': getFriendlyName(), 'supports1080p60': True, 'vp9Support': True, @@ -113,21 +118,22 @@ class PlexInterface(plexapp.AppInterface): plexapp.Res((1024, 768)), plexapp.Res((1280, 720)), plexapp.Res((1280, 720)), - maxVideoRes, maxVideoRes, maxVideoRes, maxVideoRes, maxVideoRes + maxVideoRes, maxVideoRes, maxVideoRes, maxVideoRes, maxVideoRes, maxVideoRes, maxVideoRes, maxVideoRes ], 'transcodeVideoBitrates': [ - "64", "96", "208", "320", "720", "1500", "2000", "3000", "4000", "8000", "10000", "12000", "20000", "0" + "64", "96", "208", "320", "720", "1500", "2000", "3000", "4000", "6000", "8000", "10000", "12000", "16000", + "20000", "26000", "0" ], 'deviceInfo': plexapp.DeviceInfo() } bingeModeManager = None - def getPreference(self, pref, default=None): + def getPreference(self, pref, default=UNDEF, user=False): if pref == 'manual_connections': return self.getManualConnections() else: - return util.getSetting(pref, default) + return util.getSetting(pref, default=default) if not user else util.getUserSetting(pref, default=default) def getPlaybackFeatures(self): return self.getPreference("playback_features", @@ -151,14 +157,67 @@ def getManualConnections(self): def setPreference(self, pref, value): util.setSetting(pref, value) + def getRCBaseKey(self): + return "_".join((plexapp.SERVERMANAGER.selectedServer.uuid[-8:], plexapp.ACCOUNT.ID)) + + def clearRequestsCache(self): + try: + util.DEBUG_LOG('Main: Clearing requests cache...') + asyncadapter.Session().cache.clear() + plexnet_util.CACHED_PLEX_URLS = {} + except: + pass + + def prepareCache(self): + if not util.getSetting('persist_requests_cache'): + return + self.loadCache() + + def loadCache(self): + s = asyncadapter.Session() + urls = {} + hub_item_states = {} + try: + urls = s.cache.other["stored_urls"] + hub_item_states = s.cache.other["item_states"] + success = s.cache.other["last_shutdown_successful"] == True + except (KeyError, ValueError, UnicodeDecodeError): + success = False + + if not success: + util.LOG('PlexInterface: Last cache state invalid, clearing cache.') + self.clearRequestsCache() + else: + util.LOG('PlexInterface: Loaded cached URLs.') + try: + del s.cache.other["last_shutdown_successful"] + except KeyError: + # this should never happen; might've been old interference with the service and the old style of + # initializing the cache load in global space, not via plex.init() + pass + plexnet_util.CACHED_PLEX_URLS = urls + util.HUB_ITEM_STATES = hub_item_states + + def shutdownCache(self): + if util.getSetting('persist_requests_cache'): + s = asyncadapter.Session() + s.cache.other["stored_urls"] = plexnet_util.CACHED_PLEX_URLS + s.cache.other["item_states"] = util.HUB_ITEM_STATES + s.cache.other["last_shutdown_successful"] = True + s.remove_expired_responses() + util.LOG('PlexInterface: Stored cached urls.') + else: + self.clearRequestsCache() + util.LOG('PlexInterface: Cleared requests cache.') + def getRegistry(self, reg, default=None, sec=None): if sec == 'myplex' and reg == 'MyPlexAccount': - ret = util.getSetting('{0}.{1}'.format(sec, reg), default) + ret = util.getSetting('{0}.{1}'.format(sec, reg), default=default) if ret: return ret return json.dumps({'authToken': util.getSetting('auth.token')}) else: - return util.getSetting('{0}.{1}'.format(sec, reg), default) + return util.getSetting('{0}.{1}'.format(sec, reg), default=default) def setRegistry(self, reg, value, sec=None): util.setSetting('{0}.{1}'.format(sec, reg), value) @@ -238,18 +297,18 @@ def supportsSurroundSound(self): def getQualityIndex(self, qualityType): if qualityType == self.QUALITY_LOCAL: - return self.getPreference("local_quality", 13) + return self.getPreference("local_quality2", 16) elif qualityType == self.QUALITY_ONLINE: - return self.getPreference("online_quality", 13) + return self.getPreference("online_quality2", 16) else: - return self.getPreference("remote_quality", 13) + return self.getPreference("remote_quality2", 16) def getMaxResolution(self, quality_type, allow4k=False): qualityIndex = self.getQualityIndex(quality_type) if qualityIndex >= 9: if "allow_4k" in self.getPlaybackFeatures(): - return allow4k and 2160 or 1088 + return allow4k and self.maxVerticalDPRes or 1088 else: return 1088 elif qualityIndex >= 6: @@ -259,6 +318,10 @@ def getMaxResolution(self, quality_type, allow4k=False): else: return 360 + @property + def maxVerticalDPRes(self): + return util.addonSettings.unlockRes and 99999 or 2160 + def getThemeMusicValue(self): index = 10 - self.getPreference("theme_music", 5) if index > 0: @@ -267,7 +330,7 @@ def getThemeMusicValue(self): def getPlayedThresholdValue(self): values = list(reversed(PlayedThresholdSetting.options)) - return int(values[self.getPreference("played_threshold", 1)].replace(" %", "")) / 100.0 + return int(values[self.getPreference("played_threshold", 1)].replace(" %", "")) def onSmartDiscoverLocalChange(value=None, **kwargs): @@ -288,7 +351,8 @@ def onManualIPChange(**kwargs): plexapp.refreshResources(True) -plexapp.util.setInterface(PlexInterface()) +PLEX_INTERFACE = PlexInterface() +plexapp.util.setInterface(PLEX_INTERFACE) plexapp.util.INTERFACE.playbackManager = PlaybackManager() plexapp.util.APP.on('change:smart_discover_local', onSmartDiscoverLocalChange) plexapp.util.APP.on('change:prefer_local', onPreferLANChange) @@ -298,8 +362,8 @@ def onManualIPChange(**kwargs): plexapp.util.APP.on('change:manual_port_0', onManualIPChange) plexapp.util.APP.on('change:manual_port_1', onManualIPChange) -plexapp.util.CHECK_LOCAL = util.getSetting('smart_discover_local', True) -plexapp.util.LOCAL_OVER_SECURE = util.getSetting('prefer_local', False) +plexapp.util.CHECK_LOCAL = util.getSetting('smart_discover_local') +plexapp.util.LOCAL_OVER_SECURE = util.getSetting('prefer_local') # set requests timeout TIMEOUT_READ = float(util.addonSettings.requestsTimeoutRead) @@ -319,13 +383,20 @@ def onManualIPChange(**kwargs): asyncadapter.DEFAULT_TIMEOUT = pnhttp.DEFAULT_TIMEOUT asyncadapter.DEFAULT_TIMEOUT = pnhttp.DEFAULT_TIMEOUT plexapp.util.ACCEPT_LANGUAGE = util.ACCEPT_LANGUAGE_CODE +plexapp.util.LANGUAGE_CODE = util.LANGUAGE_CODE plexapp.setUserAgent(defaultUserAgent()) plexnet_util.BASE_HEADERS = plexnet_util.getPlexHeaders() asyncadapter.MAX_RETRIES = int(util.addonSettings.maxRetries1) +asyncadapter.DEBUG_REQUESTS = plexnet_util.DEBUG_REQUESTS = util.addonSettings.debugRequests +asyncadapter.REQUESTS_CACHE_EXPIRY = util.addonSettings.requestsCacheExpiry if util.addonSettings.useCertBundle != "system": util.LOG("Using certificate bundle: {}".format(util.addonSettings.useCertBundle)) plexnet_util.USE_CERT_BUNDLE = util.addonSettings.useCertBundle plexnet_util.translatePath = util.translatePath +plexnet_util.DEFAULT_SETTINGS = util.DEFAULT_SETTINGS +plexnet_util.TEMP_PATH = asyncadapter.TEMP_PATH = util.translatePath("special://temp/") +plexnet_util.SKIP_HOST_CHECK = pdm.getOrigHosts() +plexnet_util.NO_HOST_CHECK = util.getSetting('handle_plexdirect') == "never" class CallbackEvent(plexapp.util.CompatEvent): @@ -375,6 +446,8 @@ def close(self): def init(): util.DEBUG_LOG('Initializing...') + PLEX_INTERFACE.prepareCache() + timed_out = False retries = 0 while retries == 0 or (retries < asyncadapter.MAX_RETRIES and timed_out): diff --git a/script.plexmod/lib/plex_hosts.py b/script.plexmod/lib/plex_hosts.py index b4793e1620..23958ff83f 100644 --- a/script.plexmod/lib/plex_hosts.py +++ b/script.plexmod/lib/plex_hosts.py @@ -45,21 +45,30 @@ def getHosts(self): def hadHosts(self): return bool(self._orig_hosts) - def newHosts(self, hosts, source="stored"): + def getOrigHosts(self): + return self._orig_hosts or {} + + def newHosts(self, hosts, source="stored", force_mapping=None): """ hosts should be a list of plex.direct connection uri's """ + force_mapping = force_mapping or [] + util.DEBUG_LOG("PlexHostsManager: Force Mapping: {}", force_mapping) for address in hosts: parsed = urlparse(address) ip = parsePlexDirectHost(parsed.hostname) # ignore docker V4 hosts - if util.addonSettings.ignoreDockerV4 and ":" not in ip and IPv4Address(text_type(ip)) in DOCKER_NETWORK: + if (util.addonSettings.ignoreDockerV4 and ":" not in ip and IPv4Address(text_type(ip)) in DOCKER_NETWORK + and (not force_mapping or address not in force_mapping)): util.DEBUG_LOG("Ignoring plex.direct local {} Docker IPv4 address: {}", source, parsed.hostname) continue if parsed.hostname not in self._hosts: self._hosts[parsed.hostname] = plexnet.http.RESOLVED_PD_HOSTS.get(parsed.hostname, ip) - util.LOG("Found new unmapped {} plex.direct host: {}", source, parsed.hostname) + util.LOG("Found new unmapped {} plex.direct host: {}, IP: {}", source, parsed.hostname, ip) + + def resetHosts(self): + self._hosts = self._orig_hosts.copy() @property def differs(self): diff --git a/script.plexmod/lib/templating/core.py b/script.plexmod/lib/templating/core.py index 30a533922d..59c98cb19c 100644 --- a/script.plexmod/lib/templating/core.py +++ b/script.plexmod/lib/templating/core.py @@ -1,12 +1,14 @@ # coding=utf-8 import os import glob +import shutil from pprint import pformat from kodi_six import xbmcvfs, xbmc from ibis.context import ContextDict from lib.logging import log as LOG, log_error as ERROR from .util import deep_update +from ..util import PROFILE from lib.os_utils import fast_iglob from .filters import * @@ -60,10 +62,29 @@ class TemplateEngine(object): def init(self, target_dir, template_dir, custom_template_dir): self.target_dir = target_dir + + + # Alternative to checking env var: automatically set if dir isn't + # writable with `if not os.access(path, os.W_OK):` + if os.getenv("INSTALLATION_DIR_AVOID_WRITE"): + # Use the user addon data directory in installations where the extension installation directory is not writable, for example when the addon is installed through the system package manager + # Redirect template write target_dir to writable addon_data + writable_base = os.path.join(PROFILE, "resources/skins/Main/1080i") + os.makedirs(writable_base, exist_ok=True) + # Link media dir into addon dir, so templates can access it via relative path + link_path = os.path.join(PROFILE, "resources/skins/Main/media") + media_src = os.path.join(os.path.dirname(target_dir), "media") + if not os.path.exists(link_path): + try: + os.symlink(media_src, link_path, True) + except (OSError, NotImplementedError): + # If symlink fails (eg on windows without admin access), copy instead + shutil.copytree(media_src, link_path) + self.target_dir = writable_base self.template_dir = template_dir self.custom_template_dir = custom_template_dir self.get_available_templates() - paths = [custom_template_dir, template_dir] + paths = [custom_template_dir, self.template_dir] LOG("Looking for templates in: {}", paths) self.prepare_loader(paths) diff --git a/script.plexmod/lib/templating/render.py b/script.plexmod/lib/templating/render.py index c5e4440678..e94514e814 100644 --- a/script.plexmod/lib/templating/render.py +++ b/script.plexmod/lib/templating/render.py @@ -85,7 +85,7 @@ def update_progress(at, length, message): lastRes = getSetting('last_resolution', "1920x1080").split("x") lastSeenRes = [int(lastRes[0]), int(lastRes[1])] - if curThemeVer < THEME_VERSION or (force or + if curThemeVer != THEME_VERSION or (force or lastSeenRes != DISPLAY_RESOLUTION or addonSettings.alwaysCompileTemplates or len(fast_glob(os.path.join(engine.template_dir, "script-plex-*.xml.tpl"))) != diff --git a/script.plexmod/lib/util.py b/script.plexmod/lib/util.py index c2e2e176f1..de2f63b7d6 100644 --- a/script.plexmod/lib/util.py +++ b/script.plexmod/lib/util.py @@ -5,7 +5,6 @@ import os import sys import re -import binascii import json import threading import math @@ -24,41 +23,35 @@ import plexnet.util from .kodijsonrpc import rpc -# noinspection PyUnresolvedReferences -from kodi_six import xbmc -# noinspection PyUnresolvedReferences -from kodi_six import xbmcgui -# noinspection PyUnresolvedReferences -from kodi_six import xbmcaddon -# noinspection PyUnresolvedReferences -from kodi_six import xbmcvfs from . import colors # noinspection PyUnresolvedReferences from .exceptions import NoDataException from .logging import log, log_error # noinspection PyUnresolvedReferences -from .i18n import T +from .i18n import T, TRANSLATED_ROLES from . import aspectratio -from .kodi_util import * -from plexnet import signalsmixin +# noinspection PyUnresolvedReferences +from .kodi_util import (ADDON, xbmc, xbmcvfs, xbmcaddon, xbmcgui, translatePath, KODI_VERSION_MAJOR, KODI_VERSION_MINOR, + KODI_BUILD_NUMBER, FROM_KODI_REPOSITORY) +from .properties import setGlobalProperty, setGlobalBoolProperty, waitForGPEmpty, waitForConsumption, getGlobalProperty +# noinspection PyUnresolvedReferences +from .addonsettings import addonSettings, AddonSettings +from .settings_util import getSetting, getUserSetting, setSetting, USER_SETTINGS, JSON_SETTINGS, DEFAULT_SETTINGS +from .monitor import MONITOR + DEBUG = True _SHUTDOWN = False -ADDON = xbmcaddon.Addon() - -SETTINGS_LOCK = threading.Lock() - -SKIN_PLEXTUARY = xbmc.getSkinDir() == "skin.plextuary" -FROM_KODI_REPOSITORY = ADDON.getAddonInfo('name') == "PM4K for Plex" +SKIN_PLEXTUARY = "skin.plextuary" in xbmc.getSkinDir() PROFILE = translatePath(ADDON.getAddonInfo('profile')) DEF_THEME = "modern-colored" -THEME_VERSION = 27 +THEME_VERSION = 68 -xbmc.log('script.plex: Kodi {0}.{1} (build {2})'.format(KODI_VERSION_MAJOR, KODI_VERSION_MINOR, KODI_BUILD_NUMBER), +xbmc.log('script.plexmod: Kodi {0}.{1} (build {2})'.format(KODI_VERSION_MAJOR, KODI_VERSION_MINOR, KODI_BUILD_NUMBER), xbmc.LOGINFO) @@ -81,16 +74,16 @@ def getLanguageCode(add_def=None): base, variant = data.split("_") lang += "{}-{},{}".format(base, variant.upper(), base) else: - lang = data + lang = base = data if add_def and lang not in add_def: lang += ",{}".format(add_def) - return lang + return lang, base try: - ACCEPT_LANGUAGE_CODE = getLanguageCode(add_def='en-US,en') + ACCEPT_LANGUAGE_CODE, LANGUAGE_CODE = getLanguageCode(add_def='en-US,en') except: - ACCEPT_LANGUAGE_CODE = 'en-US,en' + ACCEPT_LANGUAGE_CODE, LANGUAGE_CODE = ('en-US,en', 'en') try: @@ -105,50 +98,10 @@ def getLanguageCode(add_def=None): # we currently only support vertical scaling for smaller ARs; change to != once we know how to scale horizontally NEEDS_SCALING = round(CURRENT_AR, 2) < round(1920 / 1080, 2) - -def getSetting(key, default=None): - with SETTINGS_LOCK: - setting = ADDON.getSetting(key) - is_json = key in JSON_SETTINGS - return _processSetting(setting, default, is_json=is_json) - - -def getUserSetting(key, default=None): - if not plexnet.util.ACCOUNT: - return default - - is_json = key in JSON_SETTINGS - - key = '{}.{}'.format(key, plexnet.util.ACCOUNT.ID) - with SETTINGS_LOCK: - setting = ADDON.getSetting(key) - return _processSetting(setting, default, is_json=is_json) - - -JSON_SETTINGS = [] -USER_SETTINGS = [] - - -def _processSetting(setting, default, is_json=False): - if not setting: - return default - if isinstance(default, bool): - return setting.lower() == 'true' - elif isinstance(default, float): - return float(setting) - elif isinstance(default, int): - return int(float(setting or 0)) - elif isinstance(default, list) and not is_json: - if setting: - return json.loads(binascii.unhexlify(setting)) - else: - return default - - return setting - - HOME_BUTTON_MAPPED = None +HUB_ITEM_STATES = {} + def homeButtonMapped(*args, **kwargs): global HOME_BUTTON_MAPPED @@ -159,80 +112,6 @@ def homeButtonMapped(*args, **kwargs): homeButtonMapped() -class AddonSettings(object): - """ - @DynamicAttrs - """ - - _proxiedSettings = ( - ("debug", False), - ("kodi_skip_stepping", False), - ("auto_seek", True), - ("auto_seek_delay", 1), - ("dynamic_timeline_seek", False), - ("fast_back", True), - ("dynamic_backgrounds", True), - ("background_art_blur_amount2", 0), - ("background_art_opacity_amount2", 20), - ("screensaver_quiz", False), - ("postplay_always", False), - ("postplay_timeout", 16), - ("skip_intro_button_timeout", 10), - ("skip_credits_button_timeout", 10), - ("playlist_visit_media", False), - ("intro_skip_early", False), - ("show_media_ends_info", True), - ("show_media_ends_label", True), - ("background_colour", None), - ("skip_intro_button_show_early_threshold1", 70), - ("requests_timeout_connect", 5.0), - ("requests_timeout_read", 10.0), - ("plextv_timeout_connect", 1.0), - ("plextv_timeout_read", 2.0), - ("local_reach_timeout", 10), - ("auto_skip_offset", 2.5), - ("conn_check_timeout", 2.5), - ("postplayCancel", True), - ("skip_marker_timer_cancel", True), - ("skip_marker_timer_immediate", False), - ("low_drift_timer", True), - ("player_show_buffer", True), - ("buffer_wait_max", 120), - ("buffer_insufficient_wait", 10), - ("continue_use_thumb", True), - ("use_bg_fallback", False), - ("dbg_crossfade", True), - ("subtitle_use_extended_title", True), - ("poster_resolution_scale_perc", 100), - ("consecutive_video_pb_wait", 0.0), - ("retrieve_all_media_up_front", False), - ("library_chunk_size", 240), - ("verify_mapped_files", True), - ("episode_no_spoiler_blur", 16), - ("ignore_docker_v4", True), - ("cache_home_users", True), - ("intro_marker_max_offset", 600), - ("hubs_rr_max", 250), - ("max_retries1", 3), - ("use_cert_bundle", "acme"), - ("cache_templates", True), - ("always_compile_templates", False), - ("tickrate", 1.0), - ("honor_plextv_dnsrebind", True), - ("honor_plextv_pam", True), - ("coreelec_resume_seek_wait", 350), - ) - - def __init__(self): - # register every known setting camelCased as an attribute to this instance - for setting, default in self._proxiedSettings: - name_split = setting.split("_") - setattr(self, name_split[0] + ''.join(x.capitalize() or '_' for x in name_split[1:]), - getSetting(setting, default)) - - -addonSettings = AddonSettings() - DEBUG = addonSettings.debug @@ -261,105 +140,6 @@ def TEST(msg): xbmc.log('---TEST: {0}'.format(msg), xbmc.LOGINFO) -class UtilityMonitor(xbmc.Monitor, signalsmixin.SignalsMixin): - def __init__(self, *args, **kwargs): - xbmc.Monitor.__init__(self, *args, **kwargs) - signalsmixin.SignalsMixin.__init__(self) - - def watchStatusChanged(self): - self.trigger('changed.watchstatus') - - def actionStop(self): - self.stopPlayback() - - def actionQuit(self): - LOG('OnSleep: Exit Kodi') - xbmc.executebuiltin('Quit') - - def actionReboot(self): - LOG('OnSleep: Reboot') - xbmc.restart() - - def actionShutdown(self): - LOG('OnSleep: Shutdown') - xbmc.shutdown() - - def actionHibernate(self): - LOG('OnSleep: Hibernate') - xbmc.executebuiltin('Hibernate') - - def actionSuspend(self): - LOG('OnSleep: Suspend') - xbmc.executebuiltin('Suspend') - - def actionCecstandby(self): - LOG('OnSleep: CEC Standby') - xbmc.executebuiltin('CECStandby') - - def actionLogoff(self): - LOG('OnSleep: Sign Out') - xbmc.executebuiltin('System.LogOff') - - def onNotification(self, sender, method, data): - LOG("Notification: {} {} {}".format(sender, method, data)) - if sender == 'script.plexmod' and method.endswith('RESTORE'): - from .windows import kodigui, windowutils - - def exit_mainloop(): - LOG("Addon never properly started, can't reactivate") - windowutils.HOME.doClose() - - if not kodigui.BaseFunctions.lastWinID: - exit_mainloop() - return - if kodigui.BaseFunctions.lastWinID > 13000: - reInitAddon() - setGlobalProperty('is_active', '1') - xbmc.executebuiltin('ActivateWindow({0})'.format(kodigui.BaseFunctions.lastWinID)) - else: - exit_mainloop() - return - - elif sender == "xbmc" and method == "System.OnSleep": - if getSetting('action_on_sleep', "none") != "none": - getattr(self, "action{}".format(getSetting('action_on_sleep', "none").capitalize()))() - self.trigger('system.sleep') - - elif sender == "xbmc" and method == "System.OnWake": - self.trigger('system.wakeup') - - def stopPlayback(self): - LOG('Monitor: Stopping media playback') - xbmc.Player().stop() - - def onScreensaverActivated(self): - DEBUG_LOG("Monitor: OnScreensaverActivated") - self.trigger('screensaver.activated') - if getSetting('player_stop_on_screensaver', False) and xbmc.Player().isPlayingVideo(): - self.stopPlayback() - - def onScreensaverDeactivated(self): - DEBUG_LOG("Monitor: OnScreensaverDeactivated") - self.trigger('screensaver.deactivated') - - def onDPMSActivated(self): - DEBUG_LOG("Monitor: OnDPMSActivated") - self.trigger('dpms.activated') - #self.stopPlayback() - - def onDPMSDeactivated(self): - DEBUG_LOG("Monitor: OnDPMSDeactivated") - self.trigger('dpms.deactivated') - #self.stopPlayback() - - def onSettingsChanged(self): - """ unused stub, but works if needed """ - pass - - -MONITOR = UtilityMonitor() - - hasCustomBGColour = False if KODI_VERSION_MAJOR > 18: hasCustomBGColour = not addonSettings.dynamicBackgrounds and addonSettings.backgroundColour and \ @@ -380,20 +160,6 @@ def reInitAddon(): populateTimeFormat() -def setSetting(key, value): - with SETTINGS_LOCK: - value = _processSettingForWrite(value) - ADDON.setSetting(key, value) - - -def _processSettingForWrite(value): - if isinstance(value, list): - value = binascii.hexlify(json.dumps(value)) - elif isinstance(value, bool): - value = value and 'true' or 'false' - return str(value) - - def showNotification(message, time_ms=3000, icon_path=None, header=ADDON.getAddonInfo('name')): try: icon_path = icon_path or translatePath(ADDON.getAddonInfo('icon')) @@ -447,33 +213,33 @@ def durationToText(seconds): return '0 seconds' -def durationToShortText(ms, shortHourMins=False): +def durationToShortText(ms, shortHourMins=False, shortSeconds=False, noSpaces=False): """ Converts seconds to a short user friendly string Example: 143 -> 2m 23s """ days = int(ms / 86400000) if days: - return '{0} d'.format(days) + return '{0}{1}d'.format(days, "" if noSpaces else " ") left = ms % 86400000 hours = int(left / 3600000) if hours: - hours_s = '{0} h '.format(hours) + hours_s = '{0}{1}h '.format(hours, "" if noSpaces else " ") else: hours_s = '' left = left % 3600000 mins = int(left / 60000) if mins: if shortHourMins and hours: - return '{0}:{1} h'.format(hours, mins) - return hours_s + '{0} m'.format(mins) + return '{0}:{1}{2}h'.format(hours, mins, "" if noSpaces else " ") + return hours_s + '{0}{1}m'.format(mins, "" if noSpaces else " ") elif hours_s: return hours_s.rstrip() secs = int(left % 60000) if secs: secs /= 1000 - return '{0} s'.format(secs) - return '0 s' + return '{0}{1}s'.format(round(secs) if shortSeconds and round(secs) == int(secs) else secs, "" if noSpaces else " ") + return noSpaces and '0s' or '0 s' def cleanLeadingZeros(text): @@ -495,11 +261,12 @@ def simpleSize(size): Example: 12345 -> 12.06 KB """ s = 0 + i = 0 if size > 0: i = int(math.floor(math.log(size, 1024))) p = math.pow(1024, i) s = round(size / p, 2) - if (s > 0): + if s > 0: return '%s %s' % (s, SIZE_NAMES[i]) else: return '0B' @@ -844,8 +611,11 @@ def getTimeFormat(): def getShortDateFormat(): try: - return (rpc.Settings.GetSettingValue(setting="locale.shortdateformat")["value"] - .replace("DD", "%d").replace("MM", "%m").replace("YYYY", "%Y")) + fromAPI = rpc.Settings.GetSettingValue(setting="locale.shortdateformat")["value"] + if fromAPI == "regional": + return xbmc.getRegion('dateshort').replace('%-d', '%d') + else: + return fromAPI.replace("DD", "%d").replace("MM", "%m").replace("YYYY", "%Y") except: DEBUG_LOG("Couldn't get locale.shortdateformat setting, falling back to MM/DD/YYYY") return "%d/%m/%Y" @@ -895,21 +665,63 @@ def getPlatform(): platform = getPlatform() +platform_version = None +device = None +vendor = None +model = None def getCoreELEC(): + global platform, device, platform_version, vendor, model try: stdout = subprocess.check_output('lsb_release', shell=True).decode() match = re.search(r'CoreELEC', stdout) + if match: + platform = "Linux" + try: + model = subprocess.check_output(['cat', '/proc/device-tree/model']).decode().strip("\0 \n\r") + vendor, device = model.split() + except: + pass + try: + platform_version = stdout.split(":")[1].strip() + except: + pass + + if model: + #device = ("{} ({})".format(model, stdout.strip("\0 \n\r")).replace("\0", "") + # .replace("\n", "").replace("\r", "")) + device = "{} (CoreELEC)".format(model).replace("\0", "").replace("\n", "").replace("\r", "") + return True + + except: + pass + return False + +def getWebOS(): + try: + stdout = subprocess.check_output('uname -a', shell=True).decode() + match = re.search(r'webos', stdout, re.IGNORECASE) if match: return True - return False except: - return False + pass + return False + + +def getPlatformFlavor(): + flavor = 'default' + if platform in ['Linux', 'RaspberryPi']: + flavor = "CoreELEC" if getCoreELEC() else "LG WebOS" if getWebOS() else 'default' + + if flavor != 'default': + LOG("{} detected".format(flavor)) + return flavor -isCoreELEC = getCoreELEC() if platform in ['Linux', 'RaspberryPi'] else False +platformFlavor = getPlatformFlavor() +altSeekRecommended = platformFlavor != 'default' def getRunningAddons(): @@ -953,6 +765,10 @@ def getProgressImage(obj, perc=None, view_offset=None): view_offset = obj.get('viewOffset') and obj.viewOffset.asInt() if not view_offset or not obj.get('duration'): return '' + try: + view_offset = int(view_offset) + except ValueError: + return '' pct = int((view_offset / obj.duration.asFloat()) * 100) else: pct = perc @@ -964,8 +780,10 @@ def getProgressImage(obj, perc=None, view_offset=None): def backgroundFromArt(art, width=1920, height=1080, background=colors.noAlpha.Background): if not art: return + + w, h = scaleResolution(width, height, by=addonSettings.backgroundResolutionScalePerc) return art.asTranscodedImageURL( - width, height, + w, h, blur=addonSettings.backgroundArtBlurAmount2, opacity=addonSettings.backgroundArtOpacityAmount2, background=background @@ -1033,7 +851,7 @@ def dumpSettings(): all_settings = SETTING_RE.findall(data) f.close() except: - LOG('script.plex: No settings.xml found') + LOG('script.plexmod: No settings.xml found') return final = OrderedDict({"settings": OrderedDict((k, []) for k in sections), "addon_settings": [], "unspecified": []}) diff --git a/script.plexmod/lib/windows/busy.py b/script.plexmod/lib/windows/busy.py index c3da82aa58..37a87b0101 100644 --- a/script.plexmod/lib/windows/busy.py +++ b/script.plexmod/lib/windows/busy.py @@ -69,6 +69,31 @@ def inner(*args, **kwargs): return methodWrap +def busy_property(delay=True, delay_time=0.5): + def methodWrap(func): + def inner(win, *args, **kwargs): + def setProp(w): + w.setProperty('busy', '1') + + timer = None + if delay: + timer = threading.Timer(delay_time, lambda: setProp(win)) + timer.start() + else: + setProp(win) + try: + return func(win, *args, **kwargs) + finally: + if timer and timer.is_alive(): + timer.cancel() + timer.join() + del timer + win.setProperty('busy', '') + return inner + return methodWrap + + + def widthDialog(method, msg, *args, **kwargs): condition = kwargs.pop("condition", None) delay = kwargs.pop("delay", False) diff --git a/script.plexmod/lib/windows/currentplaylist.py b/script.plexmod/lib/windows/currentplaylist.py index 45621bd894..53629b7491 100644 --- a/script.plexmod/lib/windows/currentplaylist.py +++ b/script.plexmod/lib/windows/currentplaylist.py @@ -14,6 +14,14 @@ from . import windowutils +def require_duration(f): + def wrapper(self, *args, **kwargs): + if not self.duration: + self.setDuration() + return f(self, *args, **kwargs) + return wrapper + + class CurrentPlaylistWindow(kodigui.ControlledWindow, windowutils.UtilMixin): xmlFile = 'script-plex-music_current_playlist.xml' path = util.ADDON.getAddonInfo('path') @@ -55,6 +63,8 @@ class CurrentPlaylistWindow(kodigui.ControlledWindow, windowutils.UtilMixin): def __init__(self, *args, **kwargs): kodigui.ControlledWindow.__init__(self, *args, **kwargs) self.selectedOffset = 0 + self.duration = None + self.track = None self.setDuration() self.exitCommand = None self.musicPlayerWinID = kwargs.get('winID') @@ -110,14 +120,17 @@ def onClick(self, controlID): self.seekButtonClicked() elif controlID == self.SHUFFLE_BUTTON_ID: self.fillPlaylist() + self.selectPlayingItem() elif controlID == self.SHUFFLE_REMOTE_BUTTON_ID: player.PLAYER.handler.playQueue.setShuffle() elif controlID == self.REPEAT_BUTTON_ID: self.repeatButtonClicked() elif controlID == self.SKIP_PREV_BUTTON_ID: self.skipPrevButtonClicked() + self.selectPlayingItem() elif controlID == self.SKIP_NEXT_BUTTON_ID: self.skipNextButtonClicked() + self.selectPlayingItem() elif controlID == self.OPTIONS_BUTTON_ID: self.optionsButtonClicked() elif controlID == self.STOP_BUTTON_ID: @@ -145,6 +158,8 @@ def onAudioStarting(self, *args, **kwargs): def onAudioStarted(self, *args, **kwargs): util.setGlobalProperty('ignore_spinner', '') self.ignoreStopCommands = False + self.selectedOffset = 0 + self.duration = None self.setDuration() def onAudioChanged(self, *args, **kwargs): @@ -193,7 +208,7 @@ def optionsButtonClicked(self, pos=(670, 1060)): options.append({'key': 'to_artist', 'display': T(32301, 'Go to Artist')}) options.append({'key': 'to_section', 'display': T(32302, u'Go to {0}').format(track.getLibrarySectionTitle())}) - choice = dropdown.showDropdown(options, pos, close_direction='down', pos_is_bottom=True, close_on_playback_ended=True) + choice = dropdown.showDropdown(options, pos, pos_is_bottom=True, close_on_playback_ended=True) if not choice: return @@ -226,12 +241,14 @@ def playQueueCallback(self, **kwargs): self.fillPlaylist() - for ni in self.playlistListControl: + # due to Kodi playlist limitations and necessary swappery, we might've got the current item twice in the list; + # select the latest one + for ni in reversed(self.playlistListControl): if ni.dataSource['comment'].split(':', 1)[0] == plexID: self.playlistListControl.selectItem(ni.pos()) break - xbmc.sleep(100) + util.MONITOR.waitForAbort(0.25) newViewPos = self.playlistListControl.getViewPosition() if viewPos != newViewPos: @@ -309,11 +326,18 @@ def checkSeekActions(self, action, controlID): def setDuration(self): try: - duration = player.PLAYER.getTotalTime() * 1000 + #duration = None + #if self.track: + # duration = self.track.duration.asInt() + #if not duration: + # duration = player.PLAYER.getTotalTime() * 1000 + #if not duration: + duration = player.PLAYER.getMusicInfoTag().getDuration() * 1000 self.duration = duration if duration > 0 else self.duration - except RuntimeError: # Not playing + except (RuntimeError, AttributeError): # Not playing pass + @require_duration def seekForward(self, offset): self.selectedOffset += offset if self.selectedOffset > self.duration: @@ -321,6 +345,7 @@ def seekForward(self, offset): self.updateSelectedProgress() + @require_duration def seekBack(self, offset): self.selectedOffset -= offset if self.selectedOffset < 0: @@ -328,6 +353,7 @@ def seekBack(self, offset): self.updateSelectedProgress() + @require_duration def seekMouse(self, action): x = self.mouseXTrans(action.getAmount1()) y = self.mouseYTrans(action.getAmount2()) @@ -340,6 +366,7 @@ def seekMouse(self, action): self.selectedOffset = int((x - self.BAR_X) / float(self.SEEK_IMAGE_WIDTH) * self.duration) self.updateSelectedProgress() + @require_duration def updateSelectedProgress(self): if not self.duration: return diff --git a/script.plexmod/lib/windows/dropdown.py b/script.plexmod/lib/windows/dropdown.py index 233a00459b..3da455352e 100644 --- a/script.plexmod/lib/windows/dropdown.py +++ b/script.plexmod/lib/windows/dropdown.py @@ -1,6 +1,6 @@ from __future__ import absolute_import -from kodi_six import xbmcgui +from kodi_six import xbmc, xbmcgui from lib import util from . import kodigui @@ -16,11 +16,13 @@ class DropdownDialog(kodigui.BaseDialog): width = 1920 height = 1080 optionHeight = util.vscalei(66) + separatorHeight = util.vscalei(2) dropWidth = 360 borderOff = -20 GROUP_ID = 100 OPTIONS_LIST_ID = 250 + SCROLLBAR_ID = 1152 def __init__(self, *args, **kwargs): kodigui.BaseDialog.__init__(self, *args, **kwargs) @@ -40,6 +42,9 @@ def __init__(self, *args, **kwargs): self.optionsCallback = kwargs.get('options_callback', None) self.header = kwargs.get('header') self.selectIndex = kwargs.get('select_index') + self.selectItem = kwargs.get('select_item') + self.isSubList = kwargs.get('is_sub_list') + self.openSubLists = kwargs.get('open_sublists') self.onCloseCallback = kwargs.get('onclose_callback') self.choice = None @@ -57,10 +62,13 @@ def y(self): def onFirstInit(self): self.setProperty('dropdown', self.setDropdownProp and '1' or '') self.setProperty('header', self.header) + optLen = len(list(filter(lambda x: x != SEPARATOR, self.options))) + separators = len(list(filter(lambda x: x == SEPARATOR, self.options))) + self.setBoolProperty('scroll', optLen > 14) self.optionsList = kodigui.ManagedControlList(self, self.OPTIONS_LIST_ID, 14) - self.showOptions() - height = min(self.optionHeight * 14, len(self.options) * self.optionHeight) + 80 - ol_height = height - 80 + openSubList = self.showOptions() + height = min(self.optionHeight * 14, optLen * self.optionHeight + separators * self.separatorHeight) + util.vscalei(86) + ol_height = height - util.vscalei(86) y = self.y if isinstance(y, int) and y + height > self.height: @@ -68,10 +76,6 @@ def onFirstInit(self): y -= self.optionHeight y = max(0, y) - ol_height = height - 80 - if self.header: - ol_height -= util.vscalei(86) - shadowControl = self.getControl(110) if self.header: shadowControl.setHeight(height + util.vscalei(86)) @@ -83,7 +87,9 @@ def onFirstInit(self): if y == "middle": y = util.vperci(util.vscale(ol_height)) - self.getControl(100).setPosition(self.x, y) + self.getControl(100).setPosition(self.x, int(y)) + if self.header: + shadowControl.setPosition(-60, util.vscalei(-106)) self.setProperty('show', '1') self.setProperty('close.direction', self.closeDirection) @@ -91,14 +97,20 @@ def onFirstInit(self): from lib import player player.PLAYER.on('session.ended', self.playbackSessionEnded) + if openSubList and self.openSubLists: + # once the item is selected, open its sublist if wanted + self.setChoice() + def onAction(self, action): try: pass except: util.ERROR() + controlID = self.getFocusId() + if self.roundRobin and action in (xbmcgui.ACTION_MOVE_UP, xbmcgui.ACTION_MOVE_DOWN) and \ - self.getFocusId() == self.OPTIONS_LIST_ID: + controlID == self.OPTIONS_LIST_ID: to_pos = None last_index = self.optionsList.size() - 1 @@ -117,6 +129,13 @@ def onAction(self, action): return self.lastSelectedItem = self.optionsList.control.getSelectedPosition() + elif self.suboptionCallback and action == xbmcgui.ACTION_MOVE_RIGHT: + if self.optionsList.getSelectedItem().dataSource.get("is_sub_list"): + self.setChoice() + + elif controlID == self.SCROLLBAR_ID and action == xbmcgui.ACTION_SELECT_ITEM: + self.setChoice() + return kodigui.BaseDialog.onAction(self, action) @@ -129,7 +148,7 @@ def onClick(self, controlID): def playbackSessionEnded(self, **kwargs): self.doClose() - def doClose(self): + def doClose(self, **kw): if self.closeOnPlaybackEnded: from lib import player player.PLAYER.off('session.ended', self.playbackSessionEnded) @@ -150,7 +169,7 @@ def setChoice(self): if not mli: return - choice = self.options[self.optionsList.getSelectedPosition()] + choice = self.options[self.optionsList.getSelectedPos()] if choice.get('ignore'): return @@ -158,7 +177,16 @@ def setChoice(self): if self.suboptionCallback: options = self.suboptionCallback(choice) if options: - sub = showDropdown(options, (self.x + 290, self.y + 10), close_direction='left', with_indicator=True) + sub_select = None + if self.selectItem and self.selectItem.get("sub"): + sub_select = self.selectItem["sub"] + + # disable scrollbar temporarily + oldprop = self.getBoolProperty('scroll') + self.setBoolProperty('scroll', False) + sub = showDropdown(options, (self.x + 290, self.y + 10), close_direction='left', + with_indicator=True, select_item=sub_select, is_sub_list=True) + self.setBoolProperty('scroll', oldprop) if not sub: return @@ -176,10 +204,22 @@ def setChoice(self): def showOptions(self): items = [] options = [] + sids = None + hadSub = False + if self.selectItem: + sids = self.selectItem.copy() + sids["indicator"] = '' + if "sub" in sids: + sids.pop("sub") + hadSub = True + for oo in self.options: if oo: o = oo.copy() - item = kodigui.ManagedListItem(o['display'], thumbnailImage=o.get('indicator', ''), data_source=o) + ds = o.copy() + # clear indicator for the dataSource so we can find it later + ds["indicator"] = '' + item = kodigui.ManagedListItem(o['display'], thumbnailImage=o.get('indicator', ''), data_source=ds) item.setProperty('with.indicator', self.withIndicator and '1' or '') item.setProperty('align', self.alignItems) items.append(item) @@ -204,6 +244,28 @@ def showOptions(self): if self.selectIndex is not None: self.optionsList.setSelectedItemByPos(self.selectIndex) self.lastSelectedItem = self.selectIndex + elif sids is not None: + # select the wanted item and wait for it to actually be selected + mli = self.optionsList.getListItemByDataSource(sids) + if not mli: + util.DEBUG_LOG("Dropdown: item not found: {}", sids) + return False + + pos = self.optionsList.getManagedItemPosition(mli) + self.optionsList.setSelectedItemByPos(pos) + while self.optionsList and self.optionsList.getSelectedPos() != pos: + util.MONITOR.waitForAbort(0.01) + + if not self.optionsList: + util.DEBUG_LOG("Dropdown: something went wrong") + return False + + self.lastSelectedItem = pos + + # we expect further sub-dropdowns + if hadSub and self.suboptionCallback: + return True + return False class DropdownHeaderDialog(DropdownDialog): @@ -214,7 +276,7 @@ class DropdownHeaderDialog(DropdownDialog): def showDropdown( options, pos=None, pos_is_bottom=False, - close_direction='top', + close_direction='left', set_dropdown_prop=True, with_indicator=False, suboption_callback=None, @@ -224,6 +286,9 @@ def showDropdown( options_callback=None, header=None, select_index=None, + select_item=None, + open_sublists=False, + is_sub_list=False, onclose_callback=None, dialog_props=None ): @@ -243,6 +308,9 @@ def showDropdown( options_callback=options_callback, header=header, select_index=select_index, + select_item=select_item, + open_sublists=open_sublists, + is_sub_list=is_sub_list, onclose_callback=onclose_callback, dialog_props=dialog_props, ) @@ -261,6 +329,9 @@ def showDropdown( options_callback=options_callback, header=header, select_index=select_index, + select_item=select_item, + open_sublists=open_sublists, + is_sub_list=is_sub_list, onclose_callback=onclose_callback, dialog_props=dialog_props, ) diff --git a/script.plexmod/lib/windows/episodes.py b/script.plexmod/lib/windows/episodes.py index db99a1d761..d44a5668c4 100644 --- a/script.plexmod/lib/windows/episodes.py +++ b/script.plexmod/lib/windows/episodes.py @@ -1,9 +1,12 @@ from __future__ import absolute_import import requests.exceptions +import copy from kodi_six import xbmc from kodi_six import xbmcgui -from plexnet import plexapp, playlist, plexplayer +from collections import OrderedDict + +from plexnet import plexapp, playlist, plexplayer, plexlibrary, util as pnUtil from lib import backgroundthread from lib import metadata @@ -22,16 +25,24 @@ from . import search from . import videoplayer from . import windowutils -from .mixins import SeasonsMixin, RatingsMixin, SpoilersMixin, PlaybackBtnMixin +from .mixins.seasons import SeasonsMixin +from .mixins.spoilers import SpoilersMixin +from .mixins.playbackbtn import PlaybackBtnMixin +from .mixins.thememusic import ThemeMusicMixin +from .mixins.watchlist import WatchlistUtilsMixin, removeFromWatchlistBlind +from .mixins.ratings import RatingsMixin +from .mixins.roles import RolesMixin +from .mixins.common import CommonMixin VIDEO_RELOAD_KW = dict(includeExtras=1, includeExtrasCount=10, includeChapters=1) class EpisodeReloadTask(backgroundthread.Task): - def setup(self, episode, callback, with_progress=False): + def setup(self, episode, callback, with_progress=False, set_item_info=False): self.episode = episode self.callback = callback self.withProgress = with_progress + self.setItemInfo = set_item_info return self def run(self): @@ -46,7 +57,7 @@ def run(self): self.episode.reload(checkFiles=1, includeChapters=1, fromMediaChoice=self.episode.mediaChoice is not None) if self.isCanceled(): return - self.callback(self, self.episode, with_progress=self.withProgress) + self.callback(self, self.episode, with_progress=self.withProgress, set_item_info=self.setItemInfo) except requests.exceptions.RequestException: raise util.NoDataException except: @@ -73,6 +84,7 @@ def prepareListItem(self, data, mli): mli.setBoolProperty('watched', mli.dataSource.isFullyWatched) if not mli.dataSource.isWatched: mli.setProperty('unwatched.count', str(mli.dataSource.unViewedLeafCount)) + mli.setBoolProperty('unwatched.count.large', mli.dataSource.unViewedLeafCount.asInt() > 999) mli.setProperty('unwatched', '1') mli.setProperty('progress', util.getProgressImage(mli.dataSource)) @@ -182,11 +194,11 @@ def __init__(self, episode, season=None, select_episode=True): self.select_episode = select_episode -VIDEO_PROGRESS = {} - +VIDEO_PROGRESS = OrderedDict() class EpisodesWindow(kodigui.ControlledWindow, windowutils.UtilMixin, SeasonsMixin, RatingsMixin, SpoilersMixin, - PlaybackBtnMixin, playbacksettings.PlaybackSettingsMixin): + RolesMixin, PlaybackBtnMixin, ThemeMusicMixin, WatchlistUtilsMixin, CommonMixin, + playbacksettings.PlaybackSettingsMixin): xmlFile = 'script-plex-episodes.xml' path = util.ADDON.getAddonInfo('path') theme = 'Main' @@ -194,9 +206,11 @@ class EpisodesWindow(kodigui.ControlledWindow, windowutils.UtilMixin, SeasonsMix width = 1920 height = 1080 + supportsAutoPlay = True + THUMB_AR16X9_DIM = util.scaleResolution(657, 393) POSTER_DIM = util.scaleResolution(420, 630) - RELATED_DIM = util.scaleResolution(268, 397) + RELATED_DIM = util.scaleResolution(268, 402) EXTRA_DIM = util.scaleResolution(329, 185) ROLES_DIM = util.scaleResolution(334, 334) @@ -216,6 +230,7 @@ class EpisodesWindow(kodigui.ControlledWindow, windowutils.UtilMixin, SeasonsMix PROGRESS_IMAGE_ID = 250 + MAIN_BUTTON_GROUP_ID = 300 PLAY_BUTTON_ID = 301 PLAY_BUTTON_DISABLED_ID = 306 SHUFFLE_BUTTON_ID = 302 @@ -231,10 +246,13 @@ def __init__(self, *args, **kwargs): windowutils.UtilMixin.__init__(self) SpoilersMixin.__init__(self, *args, **kwargs) PlaybackBtnMixin.__init__(self, *args, **kwargs) + WatchlistUtilsMixin.__init__(self) self.episode = None self.reset(kwargs.get('episode'), kwargs.get('season'), kwargs.get('show')) self.parentList = kwargs.get('parentList') self.cameFrom = kwargs.get('came_from') + self.fromWatchlist = kwargs.get('from_watchlist') + self.startOver = kwargs.get('start_over') self.tasks = backgroundthread.Tasks() def reset(self, episode, season=None, show=None): @@ -255,6 +273,7 @@ def reset(self, episode, season=None, show=None): self.seasons = None self.manuallySelected = False self.manuallySelectedSeason = False + self.hadUserInteraction = False self.currentItemLoaded = False self.lastItem = None self.lastFocusID = None @@ -263,7 +282,7 @@ def reset(self, episode, season=None, show=None): self.useBGM = False PlaybackBtnMixin.reset(self) - def doClose(self): + def doClose(self, **kw): self.closing = True self.episodesPaginator = None self.relatedPaginator = None @@ -277,6 +296,18 @@ def doClose(self): except KeyError: pass + def onBlindClose(self): + if self.openedWithAutoPlay and not self.started: + vp = None + if self.show_.ratingKey in VIDEO_PROGRESS: + # access progress data for current show only + vp = copy.deepcopy(VIDEO_PROGRESS[self.show_.ratingKey]).get(self.season.ratingKey, {}) + + if vp: + self.show_.reload(checkFiles=1, **VIDEO_RELOAD_KW) + if self.show_.isFullyWatched: + removeFromWatchlistBlind(self.show_.guid) + @busy.dialog() def _onFirstInit(self): self.episodeListControl = kodigui.ManagedControlList(self, self.EPISODE_LIST_ID, 5) @@ -295,39 +326,41 @@ def _onFirstInit(self): self._setup() self.postSetup() - def doAutoPlay(self): + def doAutoPlay(self, blind=False): # First reload the video to get all the other info self.initialEpisode.reload(checkFiles=1, **VIDEO_RELOAD_KW) # We're not hitting onFirstInit when autoplaying from home, setup hooks here, so we can grab video progress self._setup_hooks() self.openedWithAutoPlay = True - return self.playButtonClicked(force_episode=self.initialEpisode, from_auto_play=True) + return self.playButtonClicked(force_episode=self.initialEpisode, from_auto_play=True, start_over=self.startOver) def onFirstInit(self): self._onFirstInit() - if self.show_ and self.show_.theme and not util.getSetting("slow_connection", False) and \ + if self.show_ and not util.getSetting("slow_connection") and \ (not self.cameFrom or self.cameFrom not in (self.show_.ratingKey, "postplay")) and \ not self.openedWithAutoPlay: - volume = self.show_.settings.getThemeMusicValue() - if volume > 0: - player.PLAYER.playBackgroundMusic(self.show_.theme.asURL(True), volume, - self.show_.ratingKey) - self.useBGM = True + self.themeMusicInit(self.show_) self.openedWithAutoPlay = False @busy.dialog() def onReInit(self): self.playBtnClicked = False - self.useBGM = False + self.themeMusicReinit(self.show_) if not self.tasks: self.tasks = backgroundthread.Tasks() - vp = VIDEO_PROGRESS.copy() + vp = None + if self.show_.ratingKey in VIDEO_PROGRESS: + # access progress data for current show only + vp = copy.deepcopy(VIDEO_PROGRESS[self.show_.ratingKey]).get(self.season.ratingKey, {}) - if self.manuallySelected and not VIDEO_PROGRESS: + if (self.manuallySelected and not VIDEO_PROGRESS) or self.cameFrom in ("info", "show", "library"): + if self.cameFrom in ("info", "show", "library"): + self.cameFrom = None + return util.DEBUG_LOG("Episodes: ReInit: Not doing anything, as we've previously manually selected " "this item and don't have progress") return @@ -343,12 +376,16 @@ def onReInit(self): self.episodeListControl.reset() self.relatedListControl.reset() self.reset(episode=redirect.episode if redirect.select_episode else None, season=redirect.season) + self.hadUserInteraction = True self._setup() self.postSetup() return except AttributeError: raise util.NoDataException + if self.cameFrom == "info": + self.cameFrom = None + # keep progress data if we've been opened from another view, as parent views might need the updates as well if not self.cameFrom: VIDEO_PROGRESS.clear() @@ -357,44 +394,56 @@ def onReInit(self): if not mli or not self.episodesPaginator: return + if vp: + self.show_.reload(checkFiles=1, **VIDEO_RELOAD_KW) + self.wl_auto_remove(self.show_) + reload_items = [mli] skip_progress_for = None if vp: skip_progress_for = [] + break_next = False for m in self.episodeListControl: # pagination boundary if not m.dataSource: continue - if m.dataSource.ratingKey in vp: + if m.dataSource.ratingKey in vp or break_next: reload_items.append(m) - skip_progress_for.append(m.dataSource.ratingKey) - del vp[m.dataSource.ratingKey] + if not break_next: + skip_progress_for.append(m.dataSource.ratingKey) + del vp[m.dataSource.ratingKey] + else: + break if not vp: - break + # for multi-episode videos reload the next one after this progress event as well + break_next = True reload_items = list(set(reload_items)) - select_episode = reload_items and reload_items[-1] or mli + #select_episode = reload_items and reload_items[-1] or mli - self.episodesPaginator.setEpisode(select_episode.dataSource) + #self.episodesPaginator.setEpisode(select_episode.dataSource) if not reload_items: self.selectPlayButton() - self.reloadItems(items=reload_items, with_progress=True, skip_progress_for=skip_progress_for) + self.reloadItems(items=reload_items, with_progress=True, skip_progress_for=skip_progress_for, + set_item_info=True) self.fillSeasons(self.show_, seasonsFilter=lambda x: len(x) > 1, selectSeason=self.season, update=True, do_focus=not self.manuallySelectedSeason) self.fillRelated() def postSetup(self): self.checkForHeaderFocus(xbmcgui.ACTION_MOVE_DOWN, initial=True) - self.selectPlayButton() + if not self.hadUserInteraction: + self.selectPlayButton() self.initialized = True def selectPlayButton(self): - selected = self.episodeListControl.getSelectedItem() - if selected: - set_focus = self.getPlayButtonID(selected, base=not self.currentItemLoaded - and self.PLAY_BUTTON_DISABLED_ID or None) - self.setCondFocusId(set_focus) + if not self.fromWatchlist: + selected = self.episodeListControl.getSelectedItem() + if selected: + set_focus = self.getPlayButtonID(selected, base=not self.currentItemLoaded + and self.PLAY_BUTTON_DISABLED_ID or None) + self.setCondFocusId(set_focus) @busy.dialog() def setup(self): @@ -416,6 +465,7 @@ def _setup(self): self.relatedPaginator = RelatedPaginator(self.relatedListControl, leaf_count=int(self.show_.relatedCount), parent_window=self) + self.watchlist_setup(self.show_) self.updateProperties() self.setBoolProperty("initialized", True) self.fillEpisodes() @@ -429,99 +479,129 @@ def _setup(self): self.fillRoles(hasPrev) def selectEpisode(self, from_reinit=False): - util.DEBUG_LOG("SelectEpisode called: {}, {}, {}, {}", from_reinit, self.episode, VIDEO_PROGRESS, self.cameFrom) + util.DEBUG_LOG("SelectEpisode called: {}, {}, {}, {}, {}, {}", from_reinit, self.episode, self.season, + self.show_, VIDEO_PROGRESS, self.cameFrom) if not self.episodesPaginator: return - had_progress_data = bool(VIDEO_PROGRESS) + had_progress_data = False progress_data_left = None - if had_progress_data: - progress_data_left = VIDEO_PROGRESS.copy() + progress_data = None + if self.show_.ratingKey in VIDEO_PROGRESS: + # access progress data for current show only + progress_data = copy.deepcopy(VIDEO_PROGRESS[self.show_.ratingKey]) set_main_progress_to = None selected_new = False last_mli_seen = None - was_last_mli = False + progress_for_last_mli = False - for mli in self.episodeListControl: - # pagination boundary - if not mli.dataSource: - continue + mli = self.episodeListControl[0] + + if progress_data or not self.season.isFullyWatched: + if progress_data: + # check for progress data in current season + progress_data_left = progress_data.pop(self.season.ratingKey, None) + had_progress_data = bool(progress_data_left) - is_last_mli = was_last_mli = self.episodeListControl.isLastItem(mli) - - just_fully_watched = False - - if progress_data_left and mli.dataSource: - progress = progress_data_left.pop(mli.dataSource.ratingKey, False) - # progress can be False (no entry), a number (progress), or True (fully watched just now) - # select it if it's not watched or in progress - if progress is True: - # ep was just watched - just_fully_watched = True - mli.setProperty('unwatched', '') - mli.setProperty('watched', '1') - mli.setProperty('progress', '') - mli.setProperty('unwatched.count', '') - mli.dataSource.set('viewCount', mli.dataSource.get('viewCount', 0).asInt() + 1) - mli.dataSource.set('viewOffset', 0) - self.setUserItemInfo(mli, fully_watched=True) - - elif progress and progress > 60000: - # ep has progress - mli.setProperty('watched', '') - mli.setProperty('progress', util.getProgressImage(mli.dataSource, view_offset=progress)) - mli.dataSource.set('viewOffset', progress) - self.setUserItemInfo(mli, watched=True) - set_main_progress_to = progress - - elif progress and progress <= 60000: - # reset progress as we might've had progress before - mli.setProperty('progress', '') - mli.dataSource.set('viewOffset', '') - self.setUserItemInfo(mli) - - # after immediately updating the watched state, if we still have data left, continue - if progress is True and progress_data_left: + for mli in self.episodeListControl: + # pagination boundary + if not mli.dataSource: continue - last_mli_seen = mli - - # first condition: we select self.episode if we've got no progress data, or we haven't watched it just now. - # second condition: we've just come from playback with progress upon reinit. select the next available - # episode that's either unwatched or in progress. if we're at the last item in the list, select it as well. - # third condition: select the next unwatched episode if we don't have self.episode and didn't have any - # player progress, which happens when being called without an episode (season view, show view). - if (mli.dataSource == self.episode and not just_fully_watched and not progress_data_left) or \ - (had_progress_data and not progress_data_left and ((not just_fully_watched - and not mli.dataSource.isFullyWatched) or (just_fully_watched and is_last_mli))) or \ - ((not had_progress_data or not from_reinit) and not self.episode and not mli.dataSource.isFullyWatched): - if self.episodeListControl.getSelectedPosition() < mli.pos(): + is_last_mli = self.episodeListControl.isLastItem(mli) + + just_fully_watched = False + + if progress_data_left and mli.dataSource: + progress = progress_data_left.pop(mli.dataSource.ratingKey, False) + progress_for_last_mli = progress and is_last_mli + + # progress can be False (no entry), a number (progress), or True (fully watched just now) + # select it if it's not watched or in progress + if progress: + if progress is True: + # ep was just watched + just_fully_watched = True + mli.setProperty('unwatched', '') + mli.setProperty('watched', '1') + mli.setProperty('progress', '') + mli.setProperty('unwatched.count', '') + mli.setProperty('unwatched.count.large', '') + mli.dataSource.set('viewCount', mli.dataSource.get('viewCount', 0).asInt() + 1) + mli.dataSource.set('viewOffset', 0) + mli.dataSource.markWatched() + self.setUserItemInfo(mli, fully_watched=True) + + elif progress > 60000: + # ep has progress + mli.setProperty('watched', '') + mli.setProperty('progress', util.getProgressImage(mli.dataSource, view_offset=progress)) + mli.dataSource.set('viewOffset', progress) + self.setUserItemInfo(mli, watched=True) + set_main_progress_to = progress + + elif progress <= 60000: + # reset progress as we might've had progress before + mli.setProperty('progress', '') + mli.dataSource.set('viewOffset', '') + self.setUserItemInfo(mli) + set_main_progress_to = 0 + + mli.dataSource.clearCache() + + if self.noRatings: + self.populateRatings(mli.dataSource, mli, hide_ratings=self.hideSpoilers(mli.dataSource)) + + # after immediately updating the watched state, if we still have data left, continue + if progress is True and progress_data_left: + continue + + last_mli_seen = mli + + # first condition: we select self.episode if we've got no progress data, or we haven't watched it just now. + # second condition: we've just come from playback with progress upon reinit. select the next available + # episode that's either unwatched or in progress. if we're at the last item in the list, select it as well. + # third condition: select the next unwatched episode if we don't have self.episode and didn't have any + # player progress, which happens when being called without an episode (season view, show view). + if (mli.dataSource == self.episode and not just_fully_watched and not progress_data_left) or \ + (had_progress_data and not progress_data_left and ((not just_fully_watched + and not mli.dataSource.isFullyWatched) or (just_fully_watched and is_last_mli))) or \ + ((not had_progress_data or not from_reinit) and not self.episode and not mli.dataSource.isFullyWatched): + #if self.episodeListControl.getSelectedPosition() < mli.pos(): self.episodeListControl.selectItem(mli.pos()) self.episodesPaginator.setEpisode(self.episode or mli.dataSource) self.lastItem = mli - selected_new = True - if just_fully_watched: - set_main_progress_to = 0 + selected_new = mli + if just_fully_watched: + set_main_progress_to = 0 - # this is a little counter-intuitive - None is actually valid here, and if set to None, setProgress will - # use the actual item progress, not ours - self.setProgress(mli, view_offset=set_main_progress_to) - break - else: - # no matching episode found - mli = self.episodeListControl.getSelectedItem() - self.setProgress(mli, view_offset=0) + # this is a little counter-intuitive - None is actually valid here, and if set to None, setProgress will + # use the actual item progress, not ours + self.setProgress(mli, view_offset=set_main_progress_to) + break + else: + # no matching episode found + mli = self.episodeListControl.getSelectedItem() + self.setProgress(mli, view_offset=0) + elif self.season.isFullyWatched and not self.episode: + self.episodeListControl.selectItem(mli.pos()) + self.episodesPaginator.setEpisode(mli) + self.lastItem = mli - if from_reinit and not self.cameFrom: - if progress_data_left: + if from_reinit and had_progress_data: + # we had progress data for our current season and still have progress data for the current TV show + if progress_data: # we've probably watched something in the next season - key = '/library/metadata/{0}'.format(list(progress_data_left.keys())[-1]) + ns = progress_data[list(progress_data.keys())[-1]] + key = '/library/metadata/{0}'.format(list(ns.keys())[-1]) ep = plexapp.SERVERMANAGER.selectedServer.getObject(key) if ep.parentIndex != self.season.index and ep.grandparentRatingKey == self.show_.ratingKey: + util.LOG("Progress data left for TV show, going to season of " + "remaining episode with progress data: {}", ep) raise RedirectToEpisode(ep) - elif was_last_mli and last_mli_seen.dataSource.isFullyWatched and self.getSeasons(): + elif progress_for_last_mli and last_mli_seen.dataSource.isFullyWatched and self.getSeasons(): # check if we need to go to the next season remaining_seasons = self.seasons[self.seasons.index(self.season)+1:] if remaining_seasons: @@ -537,6 +617,12 @@ def selectEpisode(self, from_reinit=False): if not from_reinit: self.currentItemLoaded = False + # wait for ep list to update + waited = 0 + while self.episodeListControl.getSelectedItem() != selected_new and waited < 20: + util.MONITOR.waitForAbort(0.1) + waited += 1 + self.episode = None def onAction(self, action): @@ -555,8 +641,11 @@ def onAction(self, action): elif action == xbmcgui.ACTION_PREV_ITEM: self.prev() + if action in (xbmcgui.ACTION_MOVE_DOWN, xbmcgui.ACTION_MOVE_LEFT, xbmcgui.ACTION_MOVE_RIGHT): + self.hadUserInteraction = True + if action == xbmcgui.ACTION_MOVE_UP and controlID in (self.EPISODE_LIST_ID, self.SEASONS_LIST_ID): - self.updateBackgroundFrom((self.show_ or self.season.show())) + self.updateBackgroundFrom((self.season or self.show_ or self.season.show())) if controlID == self.SEASONS_LIST_ID and action in (xbmcgui.ACTION_MOVE_LEFT, xbmcgui.ACTION_MOVE_RIGHT): self.manuallySelectedSeason = True @@ -564,20 +653,40 @@ def onAction(self, action): elif controlID == self.EPISODE_LIST_ID: if self.checkForHeaderFocus(action): return + elif self.isWatchedAction(action): + mli = self.episodeListControl.getSelectedItem() + if not mli or mli.getProperty("is.boundary"): + return + self.toggleWatched(mli) + self.selectEpisode() + return elif action == xbmcgui.ACTION_CONTEXT_MENU: self.optionsButtonClicked(from_item=True) return elif controlID == self.RELATED_LIST_ID: - if self.relatedPaginator.boundaryHit: + if self.relatedPaginator and self.relatedPaginator.boundaryHit: self.relatedPaginator.paginate() return elif action in (xbmcgui.ACTION_MOVE_LEFT, xbmcgui.ACTION_MOVE_RIGHT): self.updateBackgroundFrom(self.relatedListControl.getSelectedItem().dataSource) + elif self.isWatchedAction(action) and xbmc.getCondVisibility('ControlGroup({}).HasFocus(0)'.format(self.MAIN_BUTTON_GROUP_ID)): + mli = self.episodeListControl.getSelectedItem() + if not mli or mli.getProperty("is.boundary"): + return + + self.toggleWatched(mli) + self.selectEpisode() + return + if controlID == self.LIST_OPTIONS_BUTTON_ID and self.checkOptionsAction(action): return elif action == xbmcgui.ACTION_CONTEXT_MENU: + if controlID in (self.PLAY_BUTTON_ID, self.PLAY_BUTTON_ID + 1000) and util.getSetting('assume_resume'): + self.playButtonClicked(force_resume_menu=True) + return + if not xbmc.getCondVisibility('ControlGroup({0}).HasFocus(0)'.format(self.OPTIONS_GROUP_ID)): self.lastNonOptionsFocusID = self.lastFocusID self.setFocusId(self.OPTIONS_GROUP_ID) @@ -620,10 +729,18 @@ def onVideoProgress(self, data=None, **kwargs): return util.DEBUG_LOG("Storing video progress data: {}", data) - VIDEO_PROGRESS[data[0]] = data[1] + gprk, prk, rk, state = data + if gprk not in VIDEO_PROGRESS: + VIDEO_PROGRESS[gprk] = OrderedDict() + + if prk not in VIDEO_PROGRESS[gprk]: + VIDEO_PROGRESS[gprk][prk] = OrderedDict() + + VIDEO_PROGRESS[gprk][prk][rk] = state def onBGMStarted(self, **kwargs): - self.playBtnClicked = True + #self.playBtnClicked = True + pass def checkOptionsAction(self, action): if action == xbmcgui.ACTION_MOVE_UP: @@ -669,6 +786,8 @@ def onClick(self, controlID): elif controlID == self.SEARCH_BUTTON_ID: self.searchButtonClicked() elif controlID == self.SEASONS_LIST_ID: + if self.fromWatchlist: + return mli = self.seasonsListControl.getSelectedItem() if not mli: return @@ -678,7 +797,10 @@ def onClick(self, controlID): else: self.setCondFocusId(self.EPISODE_LIST_ID) elif controlID == self.ROLES_LIST_ID: - self.roleClicked() + if self.fromWatchlist: + return + if not self.roleClicked(): + return elif controlID == self.EXTRA_LIST_ID: self.openItem(self.extraListControl) elif controlID == self.RELATED_LIST_ID: @@ -703,8 +825,22 @@ def onFocus(self, controlID): elif xbmc.getCondVisibility('ControlGroup(50).HasFocus(0) + !ControlGroup(300).HasFocus(0) + !ControlGroup(1300).HasFocus(0)'): self.setProperty('on.extras', '1') - if player.PLAYER.bgmPlaying and player.PLAYER.handler.currentlyPlaying != self.show_.ratingKey: - player.PLAYER.stopAndWait() + def toggleWatched(self, mli=None, item=None, state=None, **kw): + if not mli and not item: + return + + item = item or mli.dataSource + watched = super(EpisodesWindow, self).toggleWatched(item, state=state, **VIDEO_RELOAD_KW) + if watched is None: + return + + self.show_ = (self.episode or self.season).show().reload(includeExtras=1, includeExtrasCount=10, + includeOnDeck=1) + if watched: + self.wl_auto_remove(self.show_) + self.checkIsWatchlisted(self.show_) + self.updateItems(mli) + util.MONITOR.watchStatusChanged() def openItem(self, control=None, item=None, came_from=None): if not item: @@ -716,33 +852,13 @@ def openItem(self, control=None, item=None, came_from=None): self.processCommand(opener.open(item, came_from=came_from)) def roleClicked(self): - mli = self.rolesListControl.getSelectedItem() - if not mli: - return - - sectionRoles = busy.widthDialog(mli.dataSource.sectionRoles, '') - - if not sectionRoles: - util.DEBUG_LOG('No sections found for actor') + if self.fromWatchlist: return - if len(sectionRoles) > 1: - x, y = self.getRoleItemDDPosition() - - options = [{'role': r, 'display': r.reasonTitle} for r in sectionRoles] - choice = dropdown.showDropdown(options, (x, y), pos_is_bottom=True, close_direction='bottom') - - if not choice: - return - - role = choice['role'] - else: - role = sectionRoles[0] - - self.processCommand(opener.open(role)) + return super(EpisodesWindow, self).roleClicked() - def getRoleItemDDPosition(self): - y = 980 + def getRoleItemDDPosition(self, *args, **kwargs): + y = 900 if xbmc.getCondVisibility('Control.IsVisible(500)'): y += 380 if xbmc.getCondVisibility('Control.IsVisible(501)'): @@ -754,17 +870,7 @@ def getRoleItemDDPosition(self): if xbmc.getCondVisibility('Integer.IsGreater(Window.Property(hub.focus),1) + Control.IsVisible(501)'): y -= 500 - tries = 0 - focus = xbmc.getInfoLabel('Container(402).Position') - while tries < 2 and focus == '': - focus = xbmc.getInfoLabel('Container(402).Position') - xbmc.sleep(250) - tries += 1 - - focus = int(focus) - - x = ((focus + 1) * 304) - 100 - return x, y + return super(EpisodesWindow, self).getRoleItemDDPosition(y=y, container_id="402") def getSeasons(self): if not self.seasons: @@ -847,7 +953,8 @@ def searchButtonClicked(self): section_id = self.show_.getLibrarySectionId() self.processCommand(search.dialog(self, section_id=section_id or None)) - def playButtonClicked(self, shuffle=False, force_episode=None, from_auto_play=False): + def playButtonClicked(self, shuffle=False, force_episode=None, from_auto_play=False, force_resume_menu=False, + start_over=False): if shuffle: seasonOrShow = self.season or self.show_ items = seasonOrShow.all() @@ -858,7 +965,8 @@ def playButtonClicked(self, shuffle=False, force_episode=None, from_auto_play=Fa return True else: - return self.episodeListClicked(force_episode=force_episode, from_auto_play=from_auto_play) + return self.episodeListClicked(force_episode=force_episode, from_auto_play=from_auto_play, + force_resume_menu=force_resume_menu, start_over=start_over) def shuffleButtonClicked(self): self.playButtonClicked(shuffle=True) @@ -898,19 +1006,32 @@ def infoButtonClicked(self): thumb=episode.thumb, thumb_opts=self.getThumbnailOpts(episode, hide_spoilers=hide_spoilers), thumb_fallback='script.plex/thumb_fallbacks/show.png', - info=hide_spoilers and T(33008, '') or episode.summary, + info=(hide_spoilers and self.noSummaries and T(33008, '')) or episode.summary, background=self.getProperty('background'), is_16x9=True, video=episode ) + self.cameFrom = "info" - def episodeListClicked(self, force_episode=None, from_auto_play=False): - if (not self.currentItemLoaded or self.playBtnClicked) and not from_auto_play: + def episodeListClicked(self, force_episode=None, from_auto_play=False, force_resume_menu=False, + start_over=False): + if self.playBtnClicked and not from_auto_play: util.DEBUG_LOG("Not honoring play action: currentItemLoaded: {0}, " "playBtnClicked: {1}, from_auto_play: {2}", self.currentItemLoaded, self.playBtnClicked, from_auto_play) return + # wait for current item to be loaded + if not from_auto_play: + amount = 0 + while not self.currentItemLoaded and amount < 50: + util.MONITOR.waitForAbort(0.1) + amount += 1 + + if not self.currentItemLoaded: + util.DEBUG_LOG("Not honoring play action: currentItemLoaded: False") + return + if not force_episode: mli = self.episodeListControl.getSelectedItem() if not mli or mli.getProperty("is.boundary"): @@ -925,23 +1046,26 @@ def episodeListClicked(self, force_episode=None, from_auto_play=False): return resume = False - if episode.viewOffset.asInt(): - choice = dropdown.showDropdown( - options=[ - {'key': 'resume', 'display': T(32429, 'Resume from {0}').format(util.timeDisplay(episode.viewOffset.asInt()).lstrip('0').lstrip(':'))}, - {'key': 'play', 'display': T(32317, 'Play from beginning')} - ], - pos=(660, "middle"), - close_direction='none', - set_dropdown_prop=False, - header=T(32314, 'In Progress'), - dialog_props=from_auto_play and self.dialogProps or None - ) + if episode.viewOffset.asInt() and not start_over: + if not util.getSetting('assume_resume') or force_resume_menu: + choice = dropdown.showDropdown( + options=[ + {'key': 'resume', 'display': T(32429, 'Resume from {0}').format(util.timeDisplay(episode.viewOffset.asInt()).lstrip('0').lstrip(':'))}, + {'key': 'play', 'display': T(32317, 'Play from beginning')} + ], + pos=(660, "middle"), + close_direction='none', + set_dropdown_prop=False, + header=T(32314, 'In Progress'), + dialog_props=from_auto_play and self.dialogProps or None + ) - if not choice: - return + if not choice: + return - if choice['key'] == 'resume': + if choice['key'] == 'resume': + resume = True + else: resume = True if not from_auto_play: @@ -957,12 +1081,14 @@ def episodeListClicked(self, force_episode=None, from_auto_play=False): pl.setCurrent(episode) self.processCommand(videoplayer.play(play_queue=pl, resume=resume, bgm=self.useBGM)) + self.playBtnClicked = False return True self.processCommand(videoplayer.play(video=episode, resume=resume, bgm=self.useBGM)) + self.playBtnClicked = False return True except util.NoDataException: - util.ERROR("No data - disconnected?", notify=True, time_ms=5000) + util.ERROR("No data - deleted or server disconnected?", notify=True, time_ms=5000) self.doClose() def optionsButtonClicked(self, from_item=False): @@ -972,6 +1098,10 @@ def optionsButtonClicked(self, from_item=False): if mli and not mli.getProperty("is.boundary"): inProgress = mli.dataSource.viewOffset.asInt() + if inProgress and util.getSetting('assume_resume'): + options.append({'key': 'play_startover', 'display': T(32317, 'Play from beginning')}) + options.append(dropdown.SEPARATOR) + if not mli.dataSource.isWatched or inProgress: options.append({'key': 'mark_watched', 'display': T(32319, 'Mark Played')}) if mli.dataSource.isWatched or inProgress: @@ -996,8 +1126,11 @@ def optionsButtonClicked(self, from_item=False): options.append({'key': 'playback_settings', 'display': T(32925, 'Playback Settings')}) options.append(dropdown.SEPARATOR) - if mli.dataSource.server.allowsMediaDeletion: - options.append({'key': 'delete', 'display': T(32322, 'Delete')}) + if plexapp.ACCOUNT.isAdmin: + options.append({'key': 'refresh', 'display': T(33719, 'Refresh metadata')}) + + if mli.dataSource.server.allowsMediaDeletion: + options.append({'key': 'delete', 'display': T(32322, 'Delete')}) # if xbmc.getCondVisibility('Player.HasAudio') and self.section.TYPE == 'artist': # options.append({'key': 'add_to_queue', 'display': 'Add To Queue'}) @@ -1009,6 +1142,9 @@ def optionsButtonClicked(self, from_item=False): options.append({'key': 'to_section', 'display': T(32324, u'Go to {0}').format( self.show_.getLibrarySectionTitle())}) + if 'items' in util.getSetting('cache_requests'): + options.append({'key': 'cache_reset', 'display': T(33728, "Clear cache for item")}) + pos = (500, util.vscalei(620)) bottom = False if from_item: @@ -1027,32 +1163,42 @@ def optionsButtonClicked(self, from_item=False): if choice['key'] == 'play_next': xbmc.executebuiltin('PlayerControl(Next)') elif choice['key'] == 'mark_watched': - mli.dataSource.markWatched(**VIDEO_RELOAD_KW) - self.updateItems(mli) - util.MONITOR.watchStatusChanged() + self.toggleWatched(mli, state=True) elif choice['key'] == 'mark_unwatched': - mli.dataSource.markUnwatched(**VIDEO_RELOAD_KW) - self.updateItems(mli) - util.MONITOR.watchStatusChanged() + self.toggleWatched(mli, state=False) elif choice['key'] == 'mark_season_watched': - self.season.markWatched(**VIDEO_RELOAD_KW) - self.updateItems() - util.MONITOR.watchStatusChanged() + self.toggleWatched(item=self.season, state=True) elif choice['key'] == 'mark_season_unwatched': - self.season.markUnwatched(**VIDEO_RELOAD_KW) - self.updateItems() - util.MONITOR.watchStatusChanged() + self.toggleWatched(item=self.season, state=False) elif choice['key'] == 'to_show': + self.cameFrom = "show" self.processCommand(opener.open( self.season.parentRatingKey, came_from=self.season.parentRatingKey) ) elif choice['key'] == 'to_section': - self.goHome(self.show_.getLibrarySectionId()) + self.cameFrom = "library" + section = plexlibrary.LibrarySection.fromFilter(self.show_) + self.processCommand(opener.sectionClicked(section, + came_from=self.show_.ratingKey) + ) elif choice['key'] == 'delete': self.delete(mli.dataSource) elif choice['key'] == 'playback_settings': self.playbackSettings(self.show_, pos, bottom) + elif choice['key'] == 'refresh': + mli.dataSource.refresh() + self.updateItems(mli) + elif choice['key'] == 'play_startover': + self.episodeListClicked(start_over=True) + elif choice["key"] == "cache_reset": + try: + util.DEBUG_LOG('Clearing requests cache for {}...', mli.dataSource) + mli.dataSource.clearCache() + mli.dataSource.reload() + self.updateItems(mli) + except Exception as e: + util.DEBUG_LOG("Couldn't clear cache: {}", e) def mediaButtonClicked(self): options = [] @@ -1072,6 +1218,7 @@ def mediaButtonClicked(self): ds.setMediaChoice(choice['key']) choice['key'].set('selected', 1) + pnUtil.INTERFACE.playbackManager(mli.dataSource, key="media_version", value=choice['key'].id) self.setPostReloadItemInfo(ds, mli) def delete(self, item): @@ -1126,7 +1273,7 @@ def checkForHeaderFocus(self, action, initial=False): if action in (xbmcgui.ACTION_MOVE_RIGHT, xbmcgui.ACTION_MOVE_LEFT) and lastItem: items = self.episodesPaginator.wrap(mli, lastItem, action) - xbmc.sleep(100) + #xbmc.sleep(100) mli = self.episodeListControl.getSelectedItem() if items: self.reloadItems(items) @@ -1135,6 +1282,7 @@ def checkForHeaderFocus(self, action, initial=False): if mli != self.lastItem and not mli.getProperty("is.boundary"): self.lastItem = mli self.setProgress(mli) + self.fillRoles(self.relatedPaginator and self.relatedPaginator.leafCount) if action in (xbmcgui.ACTION_MOVE_UP, xbmcgui.ACTION_PAGE_UP): if mli.getProperty('is.header'): @@ -1148,9 +1296,9 @@ def checkForHeaderFocus(self, action, initial=False): def updateProperties(self): showTitle = self.show_ and self.show_.title or '' - + self.setBoolProperty('disable_playback', self.fromWatchlist) self.setBoolProperty('current_item.loaded', False) - self.updateBackgroundFrom(self.show_ or self.season.show()) + self.updateBackgroundFrom(self.season or self.show_) self.setProperty('season.thumb', (self.season or self.show_).thumb.asTranscodedImageURL(*self.POSTER_DIM)) self.setProperty('show.title', showTitle) self.setProperty('season.title', (self.season or self.show_).title) @@ -1178,6 +1326,8 @@ def updateItems(self, item=None): item.setProperty('progress', util.getProgressImage(item.dataSource)) (self.season or self.show_).reload() + if self.noRatings: + self.populateRatings(item.dataSource, item, hide_ratings=self.hideSpoilers(item.dataSource)) self.setUserItemInfo(item) else: self.fillEpisodes(update=True) @@ -1214,7 +1364,8 @@ def setUserItemInfo(self, mli, video=None, types=("title", "thumbnail", "summary methods.append(("setLabel", tit)) if "summary" in types: - properties["summary"] = hide_spoilers and T(33008, '') or video.summary.strip().replace('\t', ' ') + properties["summary"] = ((hide_spoilers and self.noSummaries and T(33008, '')) or + video.summary.strip().replace('\t', ' ')) if "thumbnail" in types: methods.append(("setThumbnailImage", @@ -1252,20 +1403,20 @@ def setItemInfo(self, video, mli): mli.setProperty('year', video.year) mli.setProperty('content.rating', video.contentRating.split('/', 1)[-1]) mli.setProperty('genre', self.genre) - - self.populateRatings(video, mli) + self.populateRatings(video, mli, hide_ratings=self.hideSpoilers(video) and self.noRatings) def setPostReloadItemInfo(self, video, mli): - self.setItemAudioAndSubtitleInfo(video, mli) - mli.setProperty('unwatched', not video.isWatched and '1' or '') - mli.setProperty('watched', video.isFullyWatched and '1' or '') - mli.setProperty('video.res', video.resolutionString()) - mli.setProperty('audio.codec', video.audioCodecString()) - mli.setProperty('video.codec', video.videoCodecString()) - mli.setProperty('audio.channels', video.audioChannelsString(metadata.apiTranslate)) - mli.setProperty('video.rendering', video.videoCodecRendering) - mli.setBoolProperty('unavailable', not video.available()) - mli.setBoolProperty('media.multiple', len(list(filter(lambda x: x.isAccessible(), video.media()))) > 1) + if not self.fromWatchlist: + self.setItemAudioAndSubtitleInfo(video, mli) + mli.setProperty('unwatched', not video.isWatched and '1' or '') + mli.setProperty('watched', video.isFullyWatched and '1' or '') + mli.setProperty('video.res', video.resolutionString()) + mli.setProperty('audio.codec', video.audioCodecString()) + mli.setProperty('video.codec', video.videoCodecString()) + mli.setProperty('audio.channels', video.audioChannelsString(metadata.apiTranslate)) + mli.setProperty('video.rendering', video.videoCodecRendering) + mli.setBoolProperty('unavailable', not video.available()) + mli.setBoolProperty('media.multiple', len(list(filter(lambda x: x.isAccessible(), video.media()))) > 1) directors = u' / '.join([d.tag for d in video.directors()][:2]) directorsLabel = len(video.directors) > 1 and T(32401, u'DIRECTORS').upper() or T(32383, @@ -1290,7 +1441,8 @@ def setItemAudioAndSubtitleInfo(self, video, mli): mli.setProperty('audio', sas and sas.getTitle(metadata.apiTranslate) or T(32309, 'None')) sss = video.selectedSubtitleStream(forced_subtitles_override= - util.getSetting("forced_subtitles_override", False)) + util.getSetting("forced_subtitles_override") and pnUtil.ACCOUNT.subtitlesForced == 0, + deselect_subtitles=util.getSetting("disable_subtitle_languages")) if sss: if len(video.subtitleStreams) > 1: mli.setProperty( @@ -1345,7 +1497,7 @@ def fillEpisodes(self, update=False): self.selectEpisode() self.reloadItems(items, with_progress=True) - def reloadItems(self, items, with_progress=False, skip_progress_for=None): + def reloadItems(self, items, with_progress=False, skip_progress_for=None, set_item_info=False): tasks = [] for mli in items: if not mli.dataSource: @@ -1355,16 +1507,17 @@ def reloadItems(self, items, with_progress=False, skip_progress_for=None): if skip_progress_for: item_progress = False if mli.dataSource.ratingKey in skip_progress_for else with_progress - task = EpisodeReloadTask().setup(mli.dataSource, self.reloadItemCallback, with_progress=item_progress) + task = EpisodeReloadTask().setup(mli.dataSource, self.reloadItemCallback, with_progress=item_progress, + set_item_info=set_item_info) self.tasks.add(task) tasks.append(task) - backgroundthread.BGThreader.addTasks(tasks) + backgroundthread.BGThreader.addTasksToFront(tasks) def getPlayButtonID(self, mli, base=None): return (base and base or self.PLAY_BUTTON_ID) + (mli.getProperty('media.multiple') and 1000 or 0) - def reloadItemCallback(self, task, episode, with_progress=False): + def reloadItemCallback(self, task, episode, with_progress=False, set_item_info=False): self.tasks.remove(task) del task @@ -1380,8 +1533,10 @@ def reloadItemCallback(self, task, episode, with_progress=False): try: self.setPostReloadItemInfo(episode, mli) + if set_item_info: + self.setUserItemInfo(mli) except: - util.ERROR("No data - disconnected?", notify=True, time_ms=5000) + util.ERROR("No data - deleted or server disconnected?", notify=True, time_ms=5000) self.doClose() if with_progress: @@ -1401,7 +1556,7 @@ def reloadItemCallback(self, task, episode, with_progress=False): tries = 0 PBID = self.getPlayButtonID(mli) while not xbmc.getCondVisibility('Control.IsVisible({})'.format(PBID)) \ - and not util.MONITOR.abortRequested() and tries < 5: + and not util.MONITOR.abortRequested() and tries < 15: util.MONITOR.waitForAbort(0.1) tries += 1 if xbmc.getCondVisibility('Control.IsVisible({})'.format(PBID)) and self.getFocusId() != PBID: @@ -1440,8 +1595,6 @@ def fillExtras(self, has_prev=False): self.extraListControl.reset() self.extraListControl.addItems(items) - - self.setProperty('divider.{0}'.format(self.EXTRA_LIST_ID), has_prev and '1' or '') return True def fillRelated(self, has_prev=False): @@ -1453,20 +1606,23 @@ def fillRelated(self, has_prev=False): if not items: return False - self.setProperty('divider.{0}'.format(self.RELATED_LIST_ID), has_prev and '1' or '') - return True def fillRoles(self, has_prev=False): items = [] idx = 0 - if not self.show_.roles: + ds = self.episodeListControl.getSelectedItem().dataSource + + if not ds.roles: self.rolesListControl.reset() return False - for role in self.show_.roles(): - mli = kodigui.ManagedListItem(role.tag, role.role, thumbnailImage=role.thumb.asTranscodedImageURL(*self.ROLES_DIM), data_source=role) + for role in ds.combined_roles: + mli = kodigui.ManagedListItem(role.tag, role.role or + util.TRANSLATED_ROLES[role.translated_role], + thumbnailImage=role.thumb.asTranscodedImageURL(*self.ROLES_DIM), + data_source=role) mli.setProperty('index', str(idx)) items.append(mli) idx += 1 @@ -1474,8 +1630,6 @@ def fillRoles(self, has_prev=False): if not items: return False - self.setProperty('divider.{0}'.format(self.ROLES_LIST_ID), has_prev and '1' or '') - self.rolesListControl.reset() self.rolesListControl.addItems(items) return True diff --git a/script.plexmod/lib/windows/home.py b/script.plexmod/lib/windows/home.py index 64db1538d3..ddedcfebd5 100644 --- a/script.plexmod/lib/windows/home.py +++ b/script.plexmod/lib/windows/home.py @@ -24,8 +24,11 @@ from . import optionsdialog from . import playlists from . import search -from . import windowutils -from .mixins import SpoilersMixin +from . import background +from .mixins.spoilers import SpoilersMixin +from .mixins.watchlist import removeFromWatchlistBlind +from .mixins.common import CommonMixin + HUBS_REFRESH_INTERVAL = 300 # 5 Minutes HUB_PAGE_SIZE = 10 @@ -67,12 +70,12 @@ def run(self): if self.isCanceled(): return - if not plexapp.SERVERMANAGER.selectedServer: + if not plexapp.SERVERMANAGER.selectedServer or not self.section.server: # Could happen during sign-out for instance return try: - hubs = HubsList(plexapp.SERVERMANAGER.selectedServer.hubs(self.section.key, count=HUB_PAGE_SIZE, + hubs = HubsList(self.section.server.hubs(self.section.key, count=HUB_PAGE_SIZE, section_ids=self.section_keys, ignore_hubs=self.ignore_hubs)).init() if self.isCanceled(): @@ -84,7 +87,7 @@ def run(self): hubs.invalid = True self.callback(self.section, hubs) except: - util.ERROR("No data - disconnected?", notify=True, time_ms=5000) + util.ERROR("No data - deleted or server disconnected?", notify=True, time_ms=5000) util.DEBUG_LOG('Generic exception when fetching section: {0}', repr(self.section.title)) hubs = HubsList().init() hubs.invalid = True @@ -114,7 +117,7 @@ def run(self): except plexnet.exceptions.BadRequest: util.DEBUG_LOG('404 on hub: {0}', repr(self.hub.hubIdentifier)) except util.NoDataException: - util.ERROR("No data - disconnected?", notify=True, time_ms=5000) + util.ERROR("No data - deleted or server disconnected?", notify=True, time_ms=5000) except: util.DEBUG_LOG('Something went wrong when updating hub: {0}', repr(self.hub.hubIdentifier)) @@ -157,12 +160,22 @@ def run(self): if self.canceledCallback: self.canceledCallback(self.hub) except util.NoDataException: - util.ERROR("No data - disconnected?", notify=True, time_ms=5000) + util.ERROR("No data - deleted or server disconnected?", notify=True, time_ms=5000) except: util.DEBUG_LOG('Something went wrong when extending hub: {0}', repr(self.hub.hubIdentifier)) + util.ERROR() + + +class VirtualSection(object): + locations = [] + isMapped = False + + @property + def server(self): + return plexapp.SERVERMANAGER.selectedServer -class HomeSection(object): +class HomeSection(VirtualSection): key = None type = 'home' title = T(32332, 'Home') @@ -173,8 +186,10 @@ class HomeSection(object): home_section = HomeSection() +watchlist_section = None + -class PlaylistsSection(object): +class PlaylistsSection(VirtualSection): key = 'playlists' type = 'playlists' title = T(32333, 'Playlists') @@ -272,7 +287,7 @@ def onDestroy(self): self.unHookSignals() -class HomeWindow(kodigui.BaseWindow, util.CronReceiver, SpoilersMixin): +class HomeWindow(kodigui.BaseWindow, util.CronReceiver, CommonMixin, SpoilersMixin): xmlFile = 'script-plex-home.xml' path = util.ADDON.getAddonInfo('path') theme = 'Main' @@ -292,6 +307,9 @@ class HomeWindow(kodigui.BaseWindow, util.CronReceiver, SpoilersMixin): SERVER_LIST_ID = 260 REFRESH_SL_ID = 262 + USER_MENU_BG_ID = 801 + USER_MENU_GROUP_ID = 901 + PLAYER_STATUS_BUTTON_ID = 204 HUB_AR16X9_00 = 400 @@ -383,6 +401,15 @@ class HomeWindow(kodigui.BaseWindow, util.CronReceiver, SpoilersMixin): # PLAYLISTS 'playlists.audio': {'index': 5, 'text2lines': True, 'title': T(32048, 'Audio')}, 'playlists.video': {'index': 6, 'text2lines': True, 'ar16x9': True, 'title': T(32053, 'Video')}, + # WATCHLIST + 'watchlist.continueWatching': {'index': 1, 'with_progress': False, 'do_updates': True, 'text2lines': True}, + 'watchlist.coming-soon': {'index': 2, 'with_progress': False, 'do_updates': True, 'text2lines': True}, + 'watchlist.recently-added': {'index': 3, 'with_progress': False, 'do_updates': True, 'text2lines': True}, + 'home.top_watchlisted': {'index': 4, 'with_progress': False, 'do_updates': True, 'text2lines': True}, + 'home.coming-soon': {'index': 7, 'with_progress': False, 'do_updates': True, 'text2lines': True}, + 'home.trending-friends': {'index': 8, 'with_progress': False, 'do_updates': True, 'text2lines': True}, + 'home.trending-for-you': {'index': 13, 'with_progress': False, 'do_updates': True, 'text2lines': True}, + 'home.new-for-you': {'index': 14, 'with_progress': False, 'do_updates': True, 'text2lines': True}, } THUMB_POSTER_DIM = util.scaleResolution(244, 361) @@ -405,8 +432,11 @@ def __init__(self, *args, **kwargs): self.updateHubs = {} self.changingServer = False self._shuttingDown = False + self._checkingForExit = False self._skipNextAction = False self._reloadOnReinit = False + self._recheckPD = False + self._checkingPD = False self._applyTheme = False self._ignoreTick = False self._ignoreInput = False @@ -414,13 +444,18 @@ def __init__(self, *args, **kwargs): self._restarting = False self._anyItemAction = False self._odHubsDirty = False + self._updateSourceChanged = False self.librarySettings = None self.hubSettings = None self.anyLibraryHidden = False self.wantedSections = None self.movingSection = False self._initialMovingSectionPos = None + self.block_section_change = False self.go_root = False + self.kodi_exiting = False + + from . import windowutils windowutils.HOME = self self.lock = threading.Lock() @@ -430,7 +465,7 @@ def __init__(self, *args, **kwargs): def onFirstInit(self): # set last BG image if possible if util.addonSettings.dynamicBackgrounds: - bgUrl = util.getSetting("last_bg_url") + bgUrl = util.getSetting("last_bg_url.{}".format(plexapp.ACCOUNT.ID)) if bgUrl: self.windowSetBackground(bgUrl) @@ -444,7 +479,7 @@ def onFirstInit(self): self.sectionList = kodigui.ManagedControlList(self, self.SECTION_LIST_ID, 7) self.serverList = kodigui.ManagedControlList(self, self.SERVER_LIST_ID, 10) - self.userList = kodigui.ManagedControlList(self, self.USER_LIST_ID, 3) + self.userList = kodigui.ManagedControlList(self, self.USER_LIST_ID, 5) self.hubControls = ( kodigui.ManagedControlList(self, self.HUB_AR16X9_00, 5), @@ -482,7 +517,7 @@ def onFirstInit(self): self.hookSignals() util.CRON.registerReceiver(self) self.updateProperties() - self.checkPlexDirectHosts(plexapp.SERVERMANAGER.serversByUuid.values(), source="stored") + self.checkPlexDirectHosts(list(plexapp.SERVERMANAGER.serversByUuid.values()), source="stored") def closeWRecompileTpls(self): self._applyTheme = False @@ -491,9 +526,13 @@ def closeWRecompileTpls(self): self.doClose() def onReInit(self): + util.DEBUG_LOG("Home: On ReInit") if self._ignoreReInit: return + if player.PLAYER.bgmPlaying: + player.PLAYER.stopAndWait() + self._anyItemAction = False if self._applyTheme: self.closeWRecompileTpls() @@ -509,8 +548,11 @@ def onReInit(self): return if self._reloadOnReinit: + if self._recheckPD: + self.checkPlexDirectHosts(list(plexapp.SERVERMANAGER.serversByUuid.values())) self.serverRefresh() self._reloadOnReinit = False + self._recheckPD = False if self.lastFocusID: # try focusing the last focused ID. if that's a hub, and it's empty (=not focusable), try focusing the @@ -534,68 +576,103 @@ def onReInit(self): self._updateOnDeckHubs() def checkPlexDirectHosts(self, servers, source="stored", *args, **kwargs): - handlePD = util.getSetting('handle_plexdirect', 'ask') - if handlePD == "never": - return - - hosts = [] - for server in servers: - # only check stored or myplex servers - if server.sourceType not in (None, plexresource.ResourceConnection.SOURCE_MYPLEX): - continue - # if we're set to honor dnsRebindingProtection=1 and the server has this flag at 0 or - # if we're set to honor publicAddressMatches=1 and the server has this flag at 0, and we haven't seen the - # server locally, skip plex.direct handling - if ((util.addonSettings.honorPlextvDnsrebind and not server.dnsRebindingProtection) or - (util.addonSettings.honorPlextvPam and not server.sameNetwork and not server.anyLANConnection)): - util.DEBUG_LOG("Ignoring DNS handling for plex.direct connections of: {}", server) - continue - hosts += [c.address for c in server.connections] - - knownHosts = pdm.getHosts() - pdHosts = [host for host in hosts if ".plex.direct:" in host] - - util.DEBUG_LOG("Checking host mapping for {} {} connections: {}", len(pdHosts), source, ", ".join(pdHosts)) - - newHosts = set(pdHosts) - set(knownHosts) - if newHosts: - pdm.newHosts(newHosts, source=source) - diffLen = len(pdm.diff) - - # there are situations where the myPlexManager's resources are ready earlier than - # any other. In that case, force the check. - force = plexapp.MANAGER.gotResources + while self._checkingPD: + util.MONITOR.waitForAbort(0.1) + try: + self._checkingPD = True + util.DEBUG_LOG("Home: checkPlexDirectHosts: {} ({})", servers, source) + handlePD = util.getSetting('handle_plexdirect') + if handlePD == "never": + return - if ((source == "stored" and plexapp.ACCOUNT.isOffline) or source == "myplex" or force) and pdm.differs: - if handlePD == 'ask': - button = optionsdialog.show( - T(32993, '').format(diffLen), - T(32994, '').format(diffLen), - T(32328, 'Yes'), - T(32035, 'Always'), - T(32033, 'Never'), - ) - if button not in (0, 1, 2): - return + forcePD = util.getSetting('force_pd_mapping') + + hosts = [] + s = [] + for server in servers: + force_check = False + # we might have an active connection that's marked as local but a combination of settings doesn't allow us + # to connect insecurely; force plex.direct handling in this case + if (server.activeConnection and ".plex.direct:" in server.activeConnection.address and + not server.activeConnection.pdHostnameResolved) or forcePD: + util.DEBUG_LOG("Forcing check for plex.direct connections of: {} (force: {})", server, forcePD) + force_check = True + + if not force_check: + # only check stored or myplex servers + if server.sourceType not in (None, plexresource.ResourceConnection.SOURCE_MYPLEX): + continue + # if we're set to honor dnsRebindingProtection=1 and the server has this flag at 0 or + # if we're set to honor publicAddressMatches=1 and the server has this flag at 0, and we haven't seen the + # server locally, skip plex.direct handling + if (((util.addonSettings.honorPlextvDnsrebind and not server.dnsRebindingProtection) or + (util.addonSettings.honorPlextvPam and not server.sameNetwork and not server.anyLANConnection)) + and not server.anyPDHostNotResolvable): + util.DEBUG_LOG("Ignoring DNS handling for plex.direct connections of: {}", server) + continue + hosts += [c.address for c in server.connections] + s.append(server.name) + + knownHosts = pdm.getHosts() + pdHosts = [host for host in hosts if ".plex.direct:" in host] + + util.DEBUG_LOG("Checking host mapping for {} {} connections: {}, servers: {}", + len(pdHosts), source, ", ".join(pdHosts), ", ".join(s)) + + newHosts = set(pdHosts) - set(knownHosts) + if newHosts: + force_mapping = [] + # even for docker hosts we might want to force the mapping if it's the active connection and it didn't + # resolve + for server in servers: + if not server.anyPDHostNotResolvable and not forcePD: + continue + addrs = [c.address for c in server.connections if ".plex.direct:" in c.address and (not c.pdHostnameResolved or forcePD)] + force_mapping += addrs + util.DEBUG_LOG("Forcing mapping for connections via: {}", addrs) + pdm.newHosts(newHosts, source=source, force_mapping=force_mapping) + diffLen = len(pdm.diff) + + # there are situations where the myPlexManager's resources are ready earlier than + # any other. In that case, force the check. + force = plexapp.MANAGER.gotResources + + util.DEBUG_LOG("Plex.direct hosts that we'll add to advancedsettings.xml: {}", pdm.diff) + + if ((source == "stored" and plexapp.ACCOUNT.isOffline) or source == "myplex" or force or forcePD) and pdm.differs: + if handlePD == 'ask': + button = optionsdialog.show( + T(32993, '').format(diffLen), + T(32994, '').format(diffLen), + T(32328, 'Yes'), + T(32035, 'Always'), + T(32033, 'Never'), + ) + if button not in (0, 1, 2): + pdm.resetHosts() + return - if button == 1: - util.setSetting('handle_plexdirect', 'always') - elif button == 2: - util.setSetting('handle_plexdirect', 'never') - return + if button == 1: + util.setSetting('handle_plexdirect', 'always') + elif button == 2: + util.setSetting('handle_plexdirect', 'never') + pdm.resetHosts() + return - hadHosts = pdm.hadHosts - pdm.write() + hadHosts = pdm.hadHosts + pdm.write() - if not hadHosts and handlePD == "ask": - optionsdialog.show( - T(32995, ''), - T(32996, ''), - T(32997, 'OK'), - ) - else: - # be less intrusive - util.showNotification(T(32996, ''), header=T(32995, '')) + if not hadHosts and handlePD == "ask": + optionsdialog.show( + T(32995, ''), + T(32996, ''), + T(32997, 'OK'), + ) + else: + # be less intrusive + util.showNotification(T(32996, ''), header=T(32995, '')) + finally: + self._checkingPD = False def loadLibrarySettings(self): setting_key = 'home.settings.{}.{}'.format(plexapp.SERVERMANAGER.selectedServer.uuid[-8:], plexapp.ACCOUNT.ID) @@ -633,12 +710,14 @@ def saveHubSettings(self): @property def currentHub(self): - hub_focus = int(self.getProperty('hub.focus')) + try: + hub_focus = int(self.getProperty('hub.focus')) + except ValueError: + return None + if len(self.hubControls) > hub_focus and self.hubControls[hub_focus]: hub_control = self.hubControls[hub_focus] hub = hub_control.dataSource - if not hub or hub.hubIdentifier == "home.continue": - return return hub @property @@ -646,7 +725,7 @@ def ignoredHubs(self): return [combo for combo, data in self.hubSettings.items() if not data.get("show", True)] def updateProperties(self, *args, **kwargs): - self.setBoolProperty('bifurcation_lines', util.getSetting('hubs_bifurcation_lines', False)) + self.setBoolProperty('bifurcation_lines', util.getSetting('hubs_bifurcation_lines')) def focusFirstValidHub(self, startIndex=None): indices = self.hubFocusIndexes @@ -686,11 +765,16 @@ def hookSignals(self): plexapp.util.APP.on('account:response', self.displayServerAndUser) plexapp.util.APP.on('sli:reachability:received', self.displayServerAndUser) plexapp.util.APP.on('change:hubs_bifurcation_lines', self.updateProperties) - plexapp.util.APP.on('change:no_episode_spoilers3', self.setDirty) + plexapp.util.APP.on('change:no_episode_spoilers4', self.setDirty) plexapp.util.APP.on('change:spoilers_allowed_genres2', self.setDirty) plexapp.util.APP.on('change:hubs_use_new_continue_watching', self.setDirty) plexapp.util.APP.on('change:path_mapping_indicators', self.setDirty) + plexapp.util.APP.on('change:hub_season_thumbnails', self.setDirty) + plexapp.util.APP.on('change:use_watchlist', self.setDirty) + plexapp.util.APP.on('change:force_pd_mapping', self.setHostsDirty) plexapp.util.APP.on('change:debug', self.setDebugFlag) + plexapp.util.APP.on('change:update_source', self.updateSourceChanged) + plexapp.util.APP.on('watchlist:modified', self.watchlistDirty) plexapp.util.APP.on('theme_relevant_setting', self.setThemeDirty) player.PLAYER.on('session.ended', self.updateOnDeckHubs) @@ -713,11 +797,16 @@ def unhookSignals(self): plexapp.util.APP.off('account:response', self.displayServerAndUser) plexapp.util.APP.off('sli:reachability:received', self.displayServerAndUser) plexapp.util.APP.off('change:hubs_bifurcation_lines', self.updateProperties) - plexapp.util.APP.off('change:no_episode_spoilers3', self.setDirty) + plexapp.util.APP.off('change:no_episode_spoilers4', self.setDirty) plexapp.util.APP.off('change:spoilers_allowed_genres2', self.setDirty) plexapp.util.APP.off('change:hubs_use_new_continue_watching', self.setDirty) plexapp.util.APP.off('change:path_mapping_indicators', self.setDirty) + plexapp.util.APP.off('change:hub_season_thumbnails', self.setDirty) + plexapp.util.APP.off('change:use_watchlist', self.setDirty) + plexapp.util.APP.off('change:force_pd_mapping', self.setHostsDirty) plexapp.util.APP.off('change:debug', self.setDebugFlag) + plexapp.util.APP.off('change:update_source', self.updateSourceChanged) + plexapp.util.APP.off('watchlist:modified', self.watchlistDirty) plexapp.util.APP.off('theme_relevant_setting', self.setThemeDirty) player.PLAYER.off('session.ended', self.updateOnDeckHubs) @@ -728,7 +817,69 @@ def unhookSignals(self): util.MONITOR.off('system.sleep', self.disableUpdates) util.MONITOR.off('system.wakeup', self.onWake) + + def updateSourceChanged(self, value, **kwargs): + self._updateSourceChanged = value + + + def doUpdate(self): + self._shuttingDown = True + self._ignoreTick = True + self.stopRetryingRequests() + + # fixme: add "update" to the list of closeOptions for which we should force quit if necessary? + # self.closeOption = "update" + self.unhookSignals() + self.doClose() + return True + + + def service_responder(self): + if util.getGlobalProperty('notify_update'): + is_downgrade = bool(util.getGlobalProperty('update_is_downgrade', consume=True)) + self.showBusy(False) + button = optionsdialog.show( + T(33670, 'Update available'), + T(33671, 'Current: {current_version}\nNew: {new_version}\n\nChangelog:\n{changelog}').format( + current_version=util.ADDON.getAddonInfo('version'), + new_version=util.getGlobalProperty('update_available'), + changelog=util.getGlobalProperty('update_changelog'), + ), + T(33683, 'Exit, download and install'), + T(33684, 'Later') if not is_downgrade else T(32329, 'No'), + delay_buttons=1.8, big=True, close_timeout=3600 + ) + if button == 0: + resp = "commence" + else: + resp = "cancel" + util.setGlobalProperty('update_response', resp, wait=True) + util.setGlobalProperty('notify_update', '', wait=True) + + if resp == "commence": + # wait for it to be consumed + try: + util.waitForConsumption('update_response', timeout=200) + finally: + return self.doUpdate() + def tick(self): + if self._shuttingDown: + util.DEBUG_LOG("Home: Not ticking, shutdown flag set") + return + + if self.movingSection: + util.DEBUG_LOG("Home: Not ticking, currently moving a section") + return + + if self.is_active and self.service_responder(): + util.DEBUG_LOG("Home: Not ticking, service responder signalled positive exit") + return + + if self.is_active and self._updateSourceChanged: + util.setGlobalProperty('update_source_changed', self._updateSourceChanged, wait=True) + self._updateSourceChanged = False + if not self.lastSection or self._ignoreTick: return @@ -736,27 +887,44 @@ def tick(self): if hubs is None: return - if (self.is_active and time.time() - hubs.lastUpdated > HUBS_REFRESH_INTERVAL and + if (self.is_active and not self._checkingForExit and time.time() - hubs.lastUpdated > HUBS_REFRESH_INTERVAL and not xbmc.Player().isPlayingVideo()): + util.DEBUG_LOG("Home: Ticking, section stale, calling showHubs(update=True)") self.showHubs(self.lastSection, update=True) - def doClose(self): + def doClose(self, force=True): + util.DEBUG_LOG("Home: doClose called, triggering close.windows") plexapp.util.APP.trigger('close.windows') - super(HomeWindow, self).doClose() + #if self.sectionChangeThread and self.sectionChangeThread.isAlive(): + # self.sectionChangeThread.join(timeout=2.0) + + super(HomeWindow, self).doClose(force=force) + + def stopRetryingRequests(self): + util.DEBUG_LOG("Stopping request retries") + plexnet.asyncadapter.STOP_RETRYING_REQUESTS = True def shutdown(self): + util.DEBUG_LOG("Home: shutdown called") self._shuttingDown = True + self._ignoreTick = True + self.stopRetryingRequests() try: self.serverList.reset() except AttributeError: pass + util.DEBUG_LOG("Home: unhooking signals") self.unhookSignals() - self.storeLastBG() + if (self.closeOption != "switch" and + (not isinstance(self.closeOption, dict) or (isinstance(self.closeOption, dict) and not self.closeOption.get('fast_switch')))): + self.storeLastBG() + util.DEBUG_LOG("Home: exiting shutdown method") + def storeLastBG(self): if util.addonSettings.dynamicBackgrounds: - oldbg = util.getSetting("last_bg_url", "") + oldbg = util.getSetting("last_bg_url.{}".format(plexapp.ACCOUNT.ID), '') # store BG url of first hub, first item, as this is most likely to be the one we're focusing on the # next start try: @@ -776,21 +944,21 @@ def storeLastBG(self): bg = util.backgroundFromArt(ds.art, width=self.width, height=self.height) if bg: - util.DEBUG_LOG('Storing BG for {0}, "{1}"'.format(self.hubControls[index].dataSource, - ds.defaultTitle)) - util.setSetting("last_bg_url", bg) + util.DEBUG_LOG('Storing BG for {0}, "{1}", acc {2}'.format(self.hubControls[index].dataSource, + ds.defaultTitle, plexapp.ACCOUNT.ID)) + util.setSetting("last_bg_url.{}".format(plexapp.ACCOUNT.ID), bg) return except: util.LOG("Couldn't store last background") def onAction(self, action): controlID = self.getFocusId() - - if self._ignoreInput: + if self._ignoreInput or self._shuttingDown: return try: if self._skipNextAction: + util.DEBUG_LOG("Home: Skipping next action") self._skipNextAction = False return @@ -804,7 +972,11 @@ def onAction(self, action): return if action == xbmcgui.ACTION_CONTEXT_MENU: - show_section = self.sectionMenu() + try: + self.block_section_change = True + show_section = self.sectionMenu() + finally: + self.block_section_change = False if not show_section: return else: @@ -816,6 +988,12 @@ def onAction(self, action): if action == xbmcgui.ACTION_SELECT_ITEM: self.showServers() return + elif action == xbmcgui.ACTION_CONTEXT_MENU and util.getUserSetting('previous_server', None): + uuid = util.getUserSetting('previous_server', None) + if uuid != plexapp.SERVERMANAGER.selectedServer.uuid: + self.selectServer(uuid) + return + elif action == xbmcgui.ACTION_MOUSE_LEFT_CLICK: self.showServers(mouse=True) self.setBoolProperty('show.servers', True) @@ -824,6 +1002,20 @@ def onAction(self, action): if action == xbmcgui.ACTION_SELECT_ITEM: self.showUserMenu() return + elif action == xbmcgui.ACTION_CONTEXT_MENU and util.getSetting('previous_user'): + # check whether we can fast swap (account is not protected) + # get user + uid = util.getSetting('previous_user') + if uid == plexapp.ACCOUNT.ID: + return + + user = plexapp.ACCOUNT.getHomeUser(uid) + if not user or user.isProtected: + self.doUserOption(force_option="switch") + return + + self.doUserOption(force_option={"fast_switch": user.id}) + return elif action == xbmcgui.ACTION_MOUSE_LEFT_CLICK: self.showUserMenu(mouse=True) self.setBoolProperty('show.options', True) @@ -849,11 +1041,14 @@ def onAction(self, action): _continue = self.checkHubItem(controlID, action=action) if not _continue: return + elif self.isWatchedAction(action): + self.toggleWatched(controlID) + return elif action == xbmcgui.ACTION_PLAYER_PLAY: self.hubItemClicked(controlID, auto_play=True) return elif action == xbmcgui.ACTION_CONTEXT_MENU: - show_section = self.hubMenu() + show_section = self.hubMenu(controlID) if not show_section: return else: @@ -876,11 +1071,7 @@ def onAction(self, action): return if controlID == self.SECTION_LIST_ID and self.sectionList.control.getSelectedPosition() > 0: - self.sectionList.setSelectedItemByPos(0) - # set lastSection here already, otherwise tick() might interfere - # fixme: Might still happen in a race condition, check later - self.lastSection = home_section - self.showHubs(home_section) + self.goHome() return if util.addonSettings.fastBack and not optionsFocused and offSections \ @@ -902,20 +1093,37 @@ def onAction(self, action): self.lastNonOptionsFocusID = None return - if action in (xbmcgui.ACTION_NAV_BACK, xbmcgui.ACTION_PREVIOUS_MENU): - ex = self.confirmExit() - # 0 = exit; 1 = minimize; 2 = cancel - if ex.button in (2, None): - return - elif ex.button == 1: - self.storeLastBG() - util.setGlobalProperty('is_active', '') - xbmc.executebuiltin('ActivateWindow(10000)') - return - elif ex.button == 0: - self._shuttingDown = True - if ex.modifier == "quit": - self.closeOption = "quit" + if action in (xbmcgui.ACTION_NAV_BACK, xbmcgui.ACTION_PREVIOUS_MENU) and not self._checkingForExit: + try: + self._checkingForExit = True + if self._shuttingDown: + # rare case confirmed in Kodi 18 when requests are still running and we're exiting quickly + return + + util.DEBUG_LOG("Home: Showing exit confirmation dialog") + + ex = self.confirmExit() + # 0 = exit; 1 = minimize; 2 = cancel + if ex.button in (2, None): + return + elif ex.button == 1: + self.storeLastBG() + util.setGlobalProperty('is_active', '') + xbmc.executebuiltin('ActivateWindow(10000)') + return + elif ex.button == 0: + self._shuttingDown = True + util.DEBUG_LOG("Home: Initiating shutdown, setting background") + background.setShutdown() + if ex.modifier == "quit": + self.closeOption = "quit" + self.unhookSignals() + else: + self.closeOption = "exit" + self.doClose() + return + finally: + self._checkingForExit = False # 0 passes the action to the BaseWindow and exits HOME except: @@ -957,7 +1165,11 @@ def onFocus(self, controlID): if 399 < controlID < 500: self.setProperty('hub.focus', str(self.hubFocusIndexes[controlID - 400])) - if controlID == self.SECTION_LIST_ID and not self.changingServer: + if self.movingSection: + return + + if (controlID == self.SECTION_LIST_ID and not self.changingServer and not self._checkingForExit and not + self._shuttingDown): self.checkSectionItem() if xbmc.getCondVisibility('ControlGroup(50).HasFocus(0) + ControlGroup(100).HasFocus(0)'): @@ -965,13 +1177,20 @@ def onFocus(self, controlID): elif controlID != 250 and xbmc.getCondVisibility('ControlGroup(50).HasFocus(0) + !ControlGroup(100).HasFocus(0)'): util.setGlobalBoolProperty('off.sections', '1') - if player.PLAYER.bgmPlaying: - player.PLAYER.stopAndWait() + def goHome(self, **kwargs): + self.setProperty('hub.focus', '') + self.setFocusId(self.SECTION_LIST_ID) + self.sectionList.setSelectedItemByPos(0) + # set lastSection here already, otherwise tick() might interfere + # fixme: Might still happen in a race condition, check later + self.lastSection = home_section + self.showHubs(home_section) + return def confirmExit(self): lBtnExit = T(32336, 'Exit') lBtnQuit = T(32704, 'Quit Kodi') - modifier = util.getSetting('exit_default_is_quit', False) and "quit" or "exit" + modifier = util.getSetting('exit_default_is_quit') and "quit" or "exit" ret = plexnet.util.AttributeDict(button=None, modifier=modifier) @@ -997,6 +1216,29 @@ def actionCallback(dialog, actionID, controlID): return ret + def toggleWatched(self, controlID=None, item=None, state=None): + if not controlID and not item: + return + + if controlID: + control = self.hubControls[controlID - 400] + mli = control.getSelectedItem() + if not mli: + return + + if mli.dataSource is None: + return + item = mli.dataSource + + if super(HomeWindow, self).toggleWatched(item, state=state) is None: + return + + if item.isFullyWatched: + guid = item.show().guid if item.TYPE in ('episode', 'season') else item.guid + removeFromWatchlistBlind(guid) + self._updateOnDeckHubs() + + def searchButtonClicked(self): self.processCommand(search.dialog(self)) @@ -1006,20 +1248,20 @@ def updateOnDeckHubs(self, **kwargs): def _updateOnDeckHubs(self, **kwargs): util.DEBUG_LOG('UpdateOnDeckHubs called') self._odHubsDirty = False - if util.getSetting("speedy_home_hubs2", False): - util.DEBUG_LOG("Using alternative home hub refresh") - sections = set() - for mli in self.sectionList: - if mli.dataSource is not None and mli.dataSource != self.lastSection: - sections.add(mli.dataSource) - tasks = [SectionHubsTask().setup(s, self.sectionHubsCallback, self.wantedSections, self.ignoredHubs) - for s in [self.lastSection] + list(sections)] - else: - # fetch hubs we need to update - rp = self.getCurrentHubsPositions(self.lastSection) - tasks = [UpdateHubTask().setup(hub, self.updateHubCallback, - reselect_pos=rp.get(hub.getCleanHubIdentifier(self.lastSection.key is None))) - for hub in self.updateHubs.values()] + #if util.getSetting("speedy_home_hubs2"): + # util.DEBUG_LOG("Using alternative home hub refresh") + # sections = set() + # for mli in self.sectionList: + # if mli.dataSource is not None and mli.dataSource != self.lastSection: + # sections.add(mli.dataSource) + # tasks = [SectionHubsTask().setup(s, self.sectionHubsCallback, self.wantedSections, self.ignoredHubs) + # for s in [self.lastSection] + list(sections) if not s.server.DEFER_HUBS and s != self.lastSection] + #else: + # fetch hubs we need to update + rp = self.getCurrentHubsPositions(self.lastSection) + tasks = [UpdateHubTask().setup(hub, self.updateHubCallback, + reselect_pos=rp.get(hub.getCleanHubIdentifier(self.lastSection.key is None))) + for hub in self.updateHubs.values()] self.tasks += tasks backgroundthread.BGThreader.addTasks(tasks) @@ -1030,11 +1272,23 @@ def setDirty(self, *args, **kwargs): self._reloadOnReinit = True self.cacheSpoilerSettings() + def setHostsDirty(self, *args, **kwargs): + self._recheckPD = True + self.setDirty() + + def watchlistDirty(self, *args, **kwargs): + # mark watchlist hub dirty + if watchlist_section: + hubs = self.sectionHubs.get(watchlist_section.key) + if hubs: + util.DEBUG_LOG("Home: Setting watchlist hubs dirty") + hubs.lastUpdated = time.time() - HUBS_REFRESH_INTERVAL - 1 + def setThemeDirty(self, *args, **kwargs): - self._applyTheme = util.getSetting("theme", "modern-colored") + self._applyTheme = util.getSetting("theme") def setDebugFlag(self, *args, **kwargs): - util.DEBUG = util.getSetting("debug", False) + util.DEBUG = util.getSetting("debug") util.addonSettings.debug = util.DEBUG def fullyRefreshHome(self, *args, **kwargs): @@ -1053,12 +1307,12 @@ def enableUpdates(self, *args, **kwargs): def refreshLastSection(self, *args, **kwargs): self.enableUpdates() - if not xbmc.Player().isPlayingVideo(): + if not xbmc.Player().isPlayingVideo() and not self._shuttingDown and self.is_active: util.LOG("Refreshing last section after wake events") - self.showHubs(self.lastSection, force=True) + self.showHubs(self.lastSection, force=True, update=True) def onWake(self, *args, **kwargs): - wakeAction = util.getSetting('action_on_wake', util.isCoreELEC and 'wait_5' or 'wait_1') + wakeAction = util.getSetting('action_on_wake', util.platformFlavor == 'CoreELEC' and 'wait_5' or 'wait_1') if wakeAction == "restart": self._ignoreReInit = True self._restarting = True @@ -1099,8 +1353,9 @@ def serverRefresh(self, section=None): with self.lock: self.setProperty('hub.focus', '') self.displayServerAndUser() - self.loadLibrarySettings() - self.loadHubSettings() + if plexapp.SERVERMANAGER.selectedServer: + self.loadLibrarySettings() + self.loadHubSettings() if not plexapp.SERVERMANAGER.selectedServer: self.setFocusId(self.USER_BUTTON_ID) return False @@ -1122,22 +1377,42 @@ def hubItemClicked(self, hubControlID, auto_play=False): if mli.dataSource is None: return + # auto resume for in-progress items + if util.getSetting('home_inprogress_resume'): + if mli.dataSource.TYPE in ('episode', 'movie') and mli.dataSource.in_progress: + auto_play = True + carryProps = None if auto_play: carryProps = self.carriedProps + use_ds = mli.dataSource + ds_changed = False + + extra_kwargs = {} + if mli.dataSource.is_watchlist: + extra_kwargs['from_watchlist'] = True + extra_kwargs['external_item'] = True + + if mli.dataSource.TYPE in ("season", "episode"): + # we need to change the datasource if someone clicks an episode in a discover hub (watchlist), to go + # to the corresponding show + use_ds = mli.dataSource.show() + ds_changed = True + try: - command = opener.open(mli.dataSource, auto_play=auto_play, dialog_props=carryProps) + command = opener.open(use_ds, auto_play=auto_play, dialog_props=carryProps, **extra_kwargs) if command == "NODATA": raise util.NoDataException except util.NoDataException: - util.ERROR("No data - disconnected?", notify=True, time_ms=5000) + util.ERROR("No data - deleted or server disconnected?", notify=True, time_ms=5000) return if self._restarting: return - self.updateListItem(mli) + if not ds_changed: + self.updateListItem(mli) if not mli: return @@ -1198,8 +1473,16 @@ def sectionMenu(self): sections = [playlists_section] + plexapp.SERVERMANAGER.selectedServer.library.sections() options = [] + use_sep = False if "order" in self.librarySettings and self.librarySettings["order"]: options.append({'key': 'reset_order', 'display': T(33040, "Reset library order")}) + use_sep = True + + if util.getSetting('cache_requests'): + options.append({'key': 'cache_reset', 'display': T(33720, "Clear all caches")}) + use_sep = True + + if use_sep: options.append(dropdown.SEPARATOR) had_section = False @@ -1212,6 +1495,14 @@ def sectionMenu(self): } ) had_section = True + + # hack for an inexistant watchlist due to it being hidden + if util.getUserSetting("use_watchlist", True) and not self.librarySettings.get("/library/sections/watchlist", {}).get("show", True): + options.append({'key': 'show', + 'section_id': "/library/sections/watchlist", + 'display': T(33029, "Show library: {}").format(T(34000, 'Watchlist')) + }) + if self.hubSettings: had_hidden_hub = False hidden_hubs_opts = [] @@ -1248,13 +1539,14 @@ def sectionMenu(self): else: options = [] - if plexapp.ACCOUNT.isAdmin and section != playlists_section: + + if plexapp.ACCOUNT.isAdmin and section not in (watchlist_section, playlists_section): options = [{'key': 'refresh', 'display': T(33082, "Scan Library Files")}, {'key': 'emptyTrash', 'display': T(33083, "Empty Trash")}, {'key': 'analyze', 'display': T(33084, "Analyze")}, dropdown.SEPARATOR] - if section.locations: + if section.locations and util.getSetting('path_mapping'): for loc in section.locations: source, target = section.getMappedPath(loc) loc_is_mapped = source and target @@ -1271,6 +1563,10 @@ def sectionMenu(self): options.append({'key': 'move', 'display': T(33039, "Move")}) options.append(dropdown.SEPARATOR) + if 'libraries' in util.getSetting('cache_requests') and section != watchlist_section: + options.append({'key': 'section_cache_reset', 'display': T(33721, "Clear library cache (not items)")}) + options.append(dropdown.SEPARATOR) + if self.hubSettings: for section_hub_key in self.ignoredHubs: if not section_hub_key.startswith("{}:".format(section.key)): @@ -1360,11 +1656,34 @@ def sectionMenu(self): section.analyze() return - def hubMenu(self): + elif choice["key"] == "cache_reset": + try: + plexapp.util.INTERFACE.clearRequestsCache() + except Exception as e: + util.DEBUG_LOG("Couldn't clear requests cache: {}", e) + + elif choice["key"] == "section_cache_reset": + try: + util.DEBUG_LOG('Clearing requests cache for section {}...', section.title) + section.clearCache() + except Exception as e: + util.DEBUG_LOG("Couldn't clear library cache: {}", e) + + def hubMenu(self, hubControlID): hub = self.currentHub if not hub: return + control = self.hubControls[hubControlID - 400] + mli = control.getSelectedItem() + if not mli: + return + + if mli.dataSource is None or mli.dataSource == kodigui.DUMMY_DATA_SOURCE: + return + + ds = mli.dataSource + section_hub_key = "{}:{}".format(self.lastSection.key, hub.hubIdentifier) hub_title = section_hub_key @@ -1372,7 +1691,57 @@ def hubMenu(self): hub_title = plexapp.SERVERMANAGER.selectedServer.currentHubs.get(section_hub_key, section_hub_key) - options = [{'key': 'hide', 'display': "Hide Hub: {}".format(hub_title)}] + select_base = 0 + + options = [] + has_prev = False + if hub.hubIdentifier not in ("home.continue", "continueWatching"): + options.append({'key': 'hide', 'display': T(33659, "Hide Hub: {}").format(hub_title)}) + has_prev = True + + if ds.TYPE in ('episode', 'season', 'movie', 'show'): + if has_prev: + options.append(dropdown.SEPARATOR) + + has_mp = False + if not mli.getProperty('watched'): + options.append({'key': 'mark_watched', 'display': T(32319, "Mark Played")}) + select_base = has_prev and 1 or 0 + has_mp = True + + if ds.isFullyWatched or ds.isWatched or ds.viewedLeafCount.asInt() > 0: + options.append({'key': 'mark_unwatched', 'display': T(32318, "Mark Unplayed")}) + select_base = has_prev and 1 or has_mp and 0 + has_mp = True + + if ds.TYPE in ('episode', 'movie'): + #hub.hubIdentifier == "continueWatching"): + if hub.hubIdentifier in ("home.continue", "continueWatching", "home.ondeck"): + # allow removing items from CW + options.append(dropdown.SEPARATOR) + options.append({'key': 'remove_cw', 'display': T(33662, "Remove from Continue Watching")}) + if not has_mp: + select_base = 1 + if util.getSetting('home_inprogress_resume') and ds.in_progress: + # this is an in progress item that would be auto resumed; add specific entry to visit media instead + options.insert(0, dropdown.SEPARATOR) + options.insert(1, {'key': 'start_over', 'display': T(32317, 'Play from beginning')}) + options.insert(2, {'key': 'to_item', 'display': T(33019, "Visit media item")}) + select_base = 1 + elif ds.in_progress: + options.insert(0, dropdown.SEPARATOR) + options.insert(1, {'key': 'start_over', 'display': T(32317, 'Play from beginning')}) + options.insert(2, {'key': 'resume', 'display': T(32429, "Resume from {}").format(util.timeDisplay(ds.viewOffset.asInt()).lstrip('0').lstrip(':'))}) + + + if ds.TYPE in ('episode', 'season'): + options.append(dropdown.SEPARATOR) + options.append({'key': 'to_show', 'display': T(32323, "Go To Show")}) + if ds.TYPE == 'episode': + options.append({'key': 'to_season', 'display': T(32400, "Go To Season")}) + + if 'items' in util.getSetting('cache_requests'): + options.append({'key': 'cache_reset', 'display': T(33728, "Clear cache for item")}) choice = dropdown.showDropdown( options, @@ -1380,7 +1749,7 @@ def hubMenu(self): close_direction='none', set_dropdown_prop=False, header=T(33030, 'Choose action for: {}').format(hub.title), - select_index=0, + select_index=select_base, align_items="left", dialog_props=self.carriedProps ) @@ -1395,6 +1764,88 @@ def hubMenu(self): self.saveHubSettings() return self.lastSection + elif choice["key"] in ("mark_watched", "mark_unwatched"): + if util.getSetting('home_confirm_actions'): + button = optionsdialog.show( + T(32319, "Mark Played") if choice["key"] == "mark_watched" else T(32318, "Mark Unplayed"), + u"{} {}".format(mli.label, mli.label2), + T(32328, 'Yes'), + T(32329, 'No'), + dialog_props=self.carriedProps + ) + + if button != 0: + return + + if choice["key"] == "mark_watched": + self.toggleWatched(item=ds, state=True) + + elif choice["key"] == "mark_unwatched": + mli.dataSource.markUnwatched() + self._updateOnDeckHubs() + + elif choice["key"] == "remove_cw": + if util.getSetting('home_confirm_actions'): + button = optionsdialog.show( + T(33662, "Remove from Continue Watching"), + u"{} {}".format(mli.label, mli.label2), + T(32328, 'Yes'), + T(32329, 'No'), + dialog_props=self.carriedProps + ) + + if button != 0: + return + + ds.removeFromContinueWatching() + self._updateOnDeckHubs() + + elif choice["key"] in ("to_season", "to_show"): + target = ds.show() if choice["key"] == "to_show" else ds.season() + try: + command = opener.open(target, dialog_props=self.carriedProps) + if command == "NODATA": + raise util.NoDataException + except util.NoDataException: + util.ERROR("No data - deleted or server disconnected?", notify=True, time_ms=5000) + return + + elif choice["key"] == "to_item": + try: + command = opener.open(ds, dialog_props=self.carriedProps) + if command == "NODATA": + raise util.NoDataException + except util.NoDataException: + util.ERROR("No data - deleted or server disconnected?", notify=True, time_ms=5000) + return + + elif choice["key"] == "start_over": + try: + command = opener.open(ds, auto_play=True, start_over=True, dialog_props=self.carriedProps) + if command == "NODATA": + raise util.NoDataException + except util.NoDataException: + util.ERROR("No data - deleted or server disconnected?", notify=True, time_ms=5000) + return + return + + elif choice["key"] == "resume": + try: + command = opener.open(ds, auto_play=True, dialog_props=self.carriedProps) + if command == "NODATA": + raise util.NoDataException + except util.NoDataException: + util.ERROR("No data - deleted or server disconnected?", notify=True, time_ms=5000) + return + return + + elif choice["key"] == "cache_reset": + try: + util.DEBUG_LOG('Clearing requests cache for {}...', ds) + ds.clearCache() + except Exception as e: + util.DEBUG_LOG("Couldn't clear cache: {}", e) + def sectionMover(self, item, action): def stop_moving(reset=False): # set everything to non-moving and re-insert home item @@ -1440,6 +1891,7 @@ def stop_moving(reset=False): self.sectionList.selectItem(0) self.sectionList.moveItem(item, next_index) + self.sectionList.selectItem(next_index) elif action == xbmcgui.ACTION_SELECT_ITEM: stop_moving() @@ -1488,7 +1940,7 @@ def checkHubItem(self, controlID, action=None): if not mli or not mli.getProperty('is.end') or mli.getProperty('is.updating') == '1': # round robining - if mli and util.getSetting("hubs_round_robin", False): + if mli and util.getSetting("hubs_round_robin"): mlipos = control.getManagedItemPosition(mli) # in order to not round-robin when the next chunk is loading, implement our own cheap round-robining @@ -1501,10 +1953,17 @@ def checkHubItem(self, controlID, action=None): self.updateBackgroundFrom(control[0].dataSource) return elif (action == xbmcgui.ACTION_MOVE_LEFT and mlipos == 0 - and (controlID, mlipos) == self._lastSelectedItem): + and ((controlID, mlipos) == self._lastSelectedItem)): if not control.dataSource.more.asInt(): last_item_index = len(control) - 1 control.selectItem(last_item_index) + while control.getSelectedPos() != last_item_index: + util.MONITOR.waitForAbort(0.1) + + if not control[last_item_index].dataSource: + last_item_index -= 1 + control.selectItem(last_item_index) + self._lastSelectedItem = (controlID, last_item_index) self.updateBackgroundFrom(control[last_item_index].dataSource) else: @@ -1550,13 +2009,17 @@ def cleanTasks(self): self.tasks = [t for t in self.tasks if t] def sectionChanged(self, force=False): + if self._shuttingDown: + return + self.sectionChangeTimeout = time.time() + 0.5 # wait 2s at max if we're currently awaiting any hubs to reload # fixme: this can be done in a better way, probably waited = 0 while any(self.tasks) and waited < 20: - self.showBusy(True) + if waited > 5: + self.showBusy(True) util.MONITOR.waitForAbort(0.1) waited += 1 self.showBusy(False) @@ -1571,10 +2034,16 @@ def sectionChanged(self, force=False): self.sectionChangeThread.start() def _sectionChanged(self, immediate=False): + if self._shuttingDown: + return + if not immediate: if not self.sectionChangeTimeout: return while not util.MONITOR.waitForAbort(0.1): + # timing issue + if not self.sectionChangeTimeout: + return if time.time() >= self.sectionChangeTimeout: break @@ -1586,6 +2055,9 @@ def _sectionChanged(self, immediate=False): def _sectionReallyChanged(self, section): with self.lock: + while self.block_section_change: + util.MONITOR.waitForAbort(0.1) + self.setProperty('hub.focus', '') if util.addonSettings.dynamicBackgrounds: self.backgroundSet = False @@ -1606,7 +2078,12 @@ def _sectionReallyChanged(self, section): def sectionHubsCallback(self, section, hubs, reselect_pos_dict=None): with self.lock: update = bool(self.sectionHubs.get(section.key)) + # sort hubs by hubmap index + hubs.sort(key=lambda hub: self.HUBMAP.get(hub.getCleanHubIdentifier(is_home=section.key is None), + {"index": 999})["index"]) + self.sectionHubs[section.key] = hubs + self.setBoolProperty('loading.content', False) if self.lastSection == section: self.showHubs(section, update=update, reselect_pos_dict=reselect_pos_dict) @@ -1637,6 +2114,7 @@ def extendHubCallback(self, hub, items, reselect_pos=None): self.updateHubCallback(hub, items, reselect_pos=reselect_pos) def showSections(self, focus_section=None): + global watchlist_section self.sectionHubs = {} items = [] @@ -1647,6 +2125,16 @@ def showSections(self, focus_section=None): sections = [] + # https://discover.provider.plex.tv/library/sections/watchlist/all?includeAdvanced=1&includeMeta=1 + if not plexapp.ACCOUNT.isOffline and util.getUserSetting("use_watchlist", True) and ("/library/sections/watchlist" not in self.librarySettings + or ("/library/sections/watchlist" in self.librarySettings and self.librarySettings["/library/sections/watchlist"].get("show", True))): + # get watchlist + from plexnet import plexlibrary + wl = watchlist_section = plexlibrary.WatchlistSection(None, server=plexapp.SERVERMANAGER.getDiscoverServer()) + if wl.has_data(): + wl.title = T(34000, 'Watchlist') + sections.append(wl) + if "playlists" not in self.librarySettings \ or ("playlists" in self.librarySettings and self.librarySettings["playlists"].get("show", True)): pl = plexapp.SERVERMANAGER.selectedServer.playlists() @@ -1679,10 +2167,10 @@ def showSections(self, focus_section=None): if plexapp.SERVERMANAGER.selectedServer.hasHubs(): self.tasks = [SectionHubsTask().setup(s, self.sectionHubsCallback, self.wantedSections, self.ignoredHubs) - for s in [home_section] + sections] + for s in [home_section] + sections if not s.server.DEFER_HUBS] backgroundthread.BGThreader.addTasks(self.tasks) - show_pm_indicator = util.getSetting('path_mapping_indicators', True) + show_pm_indicator = util.getSetting('path_mapping_indicators') for section in sections: mli = kodigui.ManagedListItem(section.title, thumbnailImage='script.plex/home/type/{0}.png'.format(section.type), @@ -1691,6 +2179,8 @@ def showSections(self, focus_section=None): if section == playlists_section: mli.setProperty('is.playlists', '1') mli.setThumbnailImage('script.plex/home/type/playlists.png') + elif section == watchlist_section: + mli.setThumbnailImage('script.plex/home/type/watchlist.png') if pmm.mapping and show_pm_indicator: mli.setBoolProperty('is.mapped', section.isMapped) items.append(mli) @@ -1744,22 +2234,26 @@ def getCurrentHubsPositions(self, section): rp[identifier] = (str(mli.dataSource.ratingKey), pos) return rp + @busy.busy_property() def _showHubs(self, section=None, update=False, force=False, reselect_pos_dict=None): if not update: self.clearHubs() - if not plexapp.SERVERMANAGER.selectedServer.hasHubs(): + if not section.server.DEFER_HUBS and not plexapp.SERVERMANAGER.selectedServer.hasHubs(): return if section.key is False: - self.showBusy(False) return - self.showBusy(True) - hubs = self.sectionHubs.get(section.key) section_stale = False + if hubs is None and section.server.DEFER_HUBS: + util.DEBUG_LOG('Showing deferred hubs - Section: {0} - Update: {1}', section.key, update) + force = True + hubs = HubsList() + self.setBoolProperty('loading.content', True) + if not force: if hubs is not None: section_stale = time.time() - hubs.lastUpdated > HUBS_REFRESH_INTERVAL @@ -1767,7 +2261,6 @@ def _showHubs(self, section=None, update=False, force=False, reselect_pos_dict=N # hubs.invalid is True when the last hub update errored. if the hub is stale, refresh it, though if hubs is not None and hubs.invalid and not section_stale: util.DEBUG_LOG("Section fetch has failed: {}", section.key) - self.showBusy(False) self.setBoolProperty('no.content', True) return @@ -1778,7 +2271,6 @@ def _showHubs(self, section=None, update=False, force=False, reselect_pos_dict=N break if section.type != "home": - self.showBusy(False) self.setBoolProperty('no.content', True) return @@ -1801,49 +2293,46 @@ def _showHubs(self, section=None, update=False, force=False, reselect_pos_dict=N return util.DEBUG_LOG('Showing hubs - Section: {0} - Update: {1}', section.key, update) - try: - hasContent = False - skip = {} + hasContent = False + skip = {} - for hub in hubs: - identifier = hub.getCleanHubIdentifier(is_home=not section.key) + for hub in hubs: + identifier = hub.getCleanHubIdentifier(is_home=not section.key) - if identifier not in self.HUBMAP: - util.DEBUG_LOG('UNHANDLED - Hub: {0} [{1}]({2})'.format(hub.hubIdentifier, identifier, - len(hub.items))) - continue + if identifier not in self.HUBMAP: + util.DEBUG_LOG('UNHANDLED - Hub: {0} [{1}]({2})'.format(hub.hubIdentifier, identifier, + len(hub.items))) + continue - skip[self.HUBMAP[identifier]['index']] = 1 + skip[self.HUBMAP[identifier]['index']] = 1 - if self.showHub(hub, is_home=not section.key, - reselect_pos=reselect_pos_dict.get(identifier) if reselect_pos_dict else None): - if hub.items: - hasContent = True - if self.HUBMAP[identifier].get('do_updates'): - self.updateHubs[identifier] = hub + if self.showHub(hub, is_home=not section.key, + reselect_pos=reselect_pos_dict.get(identifier) if reselect_pos_dict else None): + if hub.items: + hasContent = True + if self.HUBMAP[identifier].get('do_updates'): + self.updateHubs[identifier] = hub - if not hasContent: - self.setBoolProperty('no.content', True) + if not hasContent: + self.setBoolProperty('no.content', True) - lastSkip = 0 - if skip: - lastSkip = min(skip.keys()) + lastSkip = 0 + if skip: + lastSkip = min(skip.keys()) - focus = None - if update: - for i, control in enumerate(self.hubControls): - if i in skip: - lastSkip = i - continue - if self.getFocusId() == control.getId(): - focus = lastSkip - control.reset() + focus = None + if update: + for i, control in enumerate(self.hubControls): + if i in skip: + lastSkip = i + continue + if self.getFocusId() == control.getId(): + focus = lastSkip + control.reset() - if focus is not None: - self.setFocusId(focus) - self.storeLastBG() - finally: - self.showBusy(False) + if focus is not None: + self.setFocusId(focus) + self.storeLastBG() def showHub(self, hub, items=None, is_home=False, reselect_pos=None): identifier = hub.getCleanHubIdentifier(is_home=is_home) @@ -1876,7 +2365,9 @@ def createParentedListItem(self, obj, thumb_w, thumb_h, with_parent_title=False) title = u'{0} - {1}'.format(obj.parentTitle, obj.title) else: title = obj.parentTitle or obj.title or '' + mli = kodigui.ManagedListItem(title, thumbnailImage=obj.defaultThumb.asTranscodedImageURL(thumb_w, thumb_h), data_source=obj) + return mli def createSimpleListItem(self, obj, thumb_w, thumb_h): @@ -1905,8 +2396,11 @@ def createSeasonListItem(self, obj, wide=False): mli = self.createParentedListItem(obj, *self.THUMB_POSTER_DIM) # mli.setLabel2('Season {0}'.format(obj.index)) mli.setProperty('thumb.fallback', 'script.plex/thumb_fallbacks/show.png') + mli.setLabel2(obj.title) + if not obj.isWatched: mli.setProperty('unwatched.count', str(obj.unViewedLeafCount)) + mli.setBoolProperty('unwatched.count.large', obj.unViewedLeafCount > 999) mli.setBoolProperty('watched', obj.isFullyWatched) return mli @@ -1923,6 +2417,7 @@ def createShowListItem(self, obj, wide=False): mli.setProperty('thumb.fallback', 'script.plex/thumb_fallbacks/show.png') if not obj.isWatched: mli.setProperty('unwatched.count', str(obj.unViewedLeafCount)) + mli.setBoolProperty('unwatched.count.large', obj.unViewedLeafCount > 999) mli.setBoolProperty('watched', obj.isFullyWatched) return mli @@ -2004,7 +2499,7 @@ def _showHub(self, hub, hubitems=None, reselect_pos=None, identifier=None, index if not hub.items and not hubitems: control.reset() - if self.lastFocusID == index + 400: + if self.lastFocusID == index + 400 and not self._anyItemAction: util.DEBUG_LOG("Hub {} was focused but is gone.", identifier) hubControlIndex = self.lastFocusID - 400 self.focusFirstValidHub(hubControlIndex) @@ -2019,7 +2514,7 @@ def _showHub(self, hub, hubitems=None, reselect_pos=None, identifier=None, index use_reselect_pos = False if reselect_pos is not None: rk, pos = reselect_pos - use_reselect_pos = True if rk is not None else (reselect_pos > 0 or reselect_pos == -1) + use_reselect_pos = True if rk is not None else (pos > 0 or pos == -1) if pos == 0 and not use_reselect_pos: # we might want to force the first position, check the hubs position @@ -2029,6 +2524,19 @@ def _showHub(self, hub, hubitems=None, reselect_pos=None, identifier=None, index items = [] check_spoilers = False + + # fetch previously seen item states + # date, view count, last viewed at + hub_item_state_key = "_".join([plexapp.util.INTERFACE.getRCBaseKey(), identifier]) + hub_item_states = (util.HUB_ITEM_STATES.get(hub_item_state_key, {}) or {"movie": 0, + "episode": 0, + "season": 0, + "show": 0}) + cks = [] + urls = [] + + hub_is_watchlist = hub.is_watchlist + for obj in hubitems or hub.items: if not self.backgroundSet and not use_reselect_pos: if self.updateBackgroundFrom(obj): @@ -2044,10 +2552,31 @@ def _showHub(self, hub, hubitems=None, reselect_pos=None, identifier=None, index # with_art sets the wide parameter which includes the episode title wide = no_spoilers in ("funwatched", "unwatched") and not self.noTitles + # determine whether we need to clear caches based on item parameters + if obj.cachable and obj.type in hub_item_states: + seen = hub_item_states[obj.type] + last_update = max(int(obj.get('addedAt', 0)), int(obj.get('updatedAt', 0))) + if seen < last_update: + _cks, _urls = obj.clearCache(return_urls=True) + cks += _cks + urls += _urls + hub_item_states[obj.type] = last_update + + if hub_is_watchlist: + obj.is_watchlist = True + mli = self.createListItem(obj, wide=wide) if mli: items.append(mli) + if util.getSetting('cache_requests'): + cks = list(set(cks)) + urls = list(set(urls)) + if cks: + obj._clearCache(cks, urls) + + util.HUB_ITEM_STATES[hub_item_state_key] = hub_item_states + if with_progress: for mli in items: mli.setProperty('progress', util.getProgressImage(mli.dataSource)) @@ -2058,7 +2587,7 @@ def _showHub(self, hub, hubitems=None, reselect_pos=None, identifier=None, index # use episode thumbnail for in progress episodes if mli.dataSource.type == 'episode' and util.addonSettings.continueUseThumb and check_spoilers: # blur them if we don't want any spoilers and the episode hasn't been fully watched - if mli.dataSource._noSpoilers: + if self.noResumeImages and mli.dataSource._noSpoilers: extra_opts = {"blur": util.addonSettings.episodeNoSpoilerBlur} thumb = mli.dataSource.thumb @@ -2111,9 +2640,12 @@ def _showHub(self, hub, hubitems=None, reselect_pos=None, identifier=None, index for idx, mli in enumerate(control): if mli.dataSource and mli.dataSource.ratingKey and str(mli.dataSource.ratingKey) == rk: if cur_pos != idx: - util.DEBUG_LOG("Hub {}: Reselect: Found {} in list, reselecting", identifier, rk) + util.DEBUG_LOG("Hub {}: Reselect: Found {} in list ({} vs. {}), reselecting", + identifier, rk, idx, pos) control.selectItem(idx) rk_found = True + pos = idx + break else: return if rk_found: @@ -2164,6 +2696,7 @@ def updateListItem(self, mli): mli.setProperty('unwatched.count', '') else: mli.setProperty('unwatched.count', str(obj.unViewedLeafCount)) + mli.setBoolProperty('unwatched.count.large', obj.unViewedLeafCount > 999) def sectionClicked(self): item = self.sectionList.getSelectedItem() @@ -2171,9 +2704,11 @@ def sectionClicked(self): return section = item.dataSource + self.lastSection = section - if section.type in ('show', 'movie', 'artist', 'photo'): + if section.type in ('show', 'movie', 'artist', 'photo', 'mixed'): self.processCommand(opener.sectionClicked(section)) + self.sectionChangeTimeout = None elif section.type in ('playlists',): self.processCommand(opener.handleOpen(playlists.PlaylistsWindow)) @@ -2217,11 +2752,13 @@ def showServers(self, from_refresh=False, mouse=False): item = ServerListItem(s.name, not s.owned and s.owner or '', data_source=s) item.uuid = s.uuid item.onUpdate() - item.setProperty('current', plexapp.SERVERMANAGER.selectedServer.uuid == s.uuid and '1' or '') + if plexapp.SERVERMANAGER.selectedServer: + item.setProperty('current', plexapp.SERVERMANAGER.selectedServer.uuid == s.uuid and '1' or '') items.append(item) if len(items) > 1: items[0].setProperty('first', '1') + items[-1].setProperty('last', '1') elif items: items[0].setProperty('only', '1') @@ -2245,42 +2782,54 @@ def showServers(self, from_refresh=False, mouse=False): if not from_refresh: plexapp.refreshResources() - def selectServer(self): + def selectServer(self, uuid=None): if self._shuttingDown: return - mli = self.serverList.getSelectedItem() - if not mli: - return + if not uuid: + mli = self.serverList.getSelectedItem() + if not mli: + return + server = mli.dataSource + else: + server = plexapp.SERVERMANAGER.getServer(uuid) + if not server: + return + + # store last used server + prevUUID = plexapp.SERVERMANAGER.selectedServer.uuid self.changingServer = True - # this is broken - with busy.BusySignalContext(plexapp.util.APP, "change:selectedServer") as bc: - self.setFocusId(self.SECTION_LIST_ID) + self.setFocusId(self.SECTION_LIST_ID) - server = mli.dataSource + # fixme: this might still trigger a dialog, re-triggering the previously opened windows + if not self._shuttingDown and not server.isReachable(): + if server.pendingReachabilityRequests > 0: + util.messageDialog(T(32339, 'Server is not accessible'), T(32340, 'Connection tests are in ' + 'progress. Please wait.')) + else: + util.messageDialog( + T(32339, 'Server is not accessible'), T(32341, 'Server is not accessible. Please sign into ' + 'your server and check your connection.') + ) + self.changingServer = False + return - # fixme: this might still trigger a dialog, re-triggering the previously opened windows - if not self._shuttingDown and not server.isReachable(): - if server.pendingReachabilityRequests > 0: - util.messageDialog(T(32339, 'Server is not accessible'), T(32340, 'Connection tests are in ' - 'progress. Please wait.')) - else: - util.messageDialog( - T(32339, 'Server is not accessible'), T(32341, 'Server is not accessible. Please sign into ' - 'your server and check your connection.') - ) - bc.ignoreSignal = True - return + + with busy.BusySignalContext(plexapp.util.APP, "change:selectedServer") as bc: changed = plexapp.SERVERMANAGER.setSelectedServer(server, force=True) if not changed: bc.ignoreSignal = True self.changingServer = False + else: + util.setSetting('previous_server.{}'.format(plexapp.ACCOUNT.ID), prevUUID) def showUserMenu(self, mouse=False): items = [] + if util.getGlobalProperty("update_available"): + items.append(kodigui.ManagedListItem(T(33670, 'Update available'), data_source='update')) if plexapp.ACCOUNT.isSignedIn: if not len(plexapp.ACCOUNT.homeUsers) and not util.addonSettings.cacheHomeUsers: plexapp.ACCOUNT.updateHomeUsers(refreshSubscription=True) @@ -2296,41 +2845,87 @@ def showUserMenu(self, mouse=False): items.append(kodigui.ManagedListItem(T(32459, 'Offline Mode'), data_source='go_online')) else: items.append(kodigui.ManagedListItem(T(32460, 'Sign In'), data_source='signin')) + items.append(kodigui.ManagedListItem(T(32924, 'Minimize'), data_source='minimize')) + items.append(kodigui.ManagedListItem(T(32336, 'Exit'), data_source='exit')) if len(items) > 1: items[0].setProperty('first', '1') items[-1].setProperty('last', '1') else: items[0].setProperty('only', '1') + # somehow dynamically setting the list height here doesn't work. We need a height that's bigger than our + # possible available items in the template self.userList.reset() self.userList.addItems(items) itemHeight = util.vscale(66, r=0) - self.getControl(801).setHeight((len(items) * itemHeight) + 80) + self.userList.setHeight((len(items) * itemHeight)) + self.getControl(self.USER_MENU_GROUP_ID).setHeight((len(items) * itemHeight)) + self.getControl(self.USER_MENU_BG_ID).setHeight((len(items) * itemHeight) + 80) if not mouse: self.setFocusId(self.USER_LIST_ID) - def doUserOption(self): - mli = self.userList.getSelectedItem() - if not mli: - return + def doUserOption(self, force_option=None): + if not force_option: + mli = self.userList.getSelectedItem() + if not mli: + return - option = mli.dataSource + option = mli.dataSource + else: + option = force_option + + def kill_background(): + util.DEBUG_LOG("Killing last background image") + kodigui.LAST_BG_URL = None + self.windowSetBackground(None) self.setFocusId(self.USER_BUTTON_ID) if option == 'settings': from . import settings settings.openWindow() + elif option == 'update': + self.setBoolProperty('show.options', False) + self.showBusy() + self.setFocusId(self.SECTION_LIST_ID) + util.setGlobalProperty('update_requested', '1', wait=True) elif option == 'go_online': plexapp.ACCOUNT.refreshAccount() elif option == 'refresh_users': plexapp.ACCOUNT.updateHomeUsers(refreshSubscription=True) return True + elif option == 'signout': + button = optionsdialog.show( + T(32344, 'Sign Out'), + T(33669, 'Really sign out?'), + T(32329, 'No'), + T(32328, 'Yes'), + dialog_props=self.carriedProps + ) + + if button != 1: + return + self.closeOption = option + kill_background() + self.doClose() + elif option == 'exit': + self._shuttingDown = True + util.DEBUG_LOG("Home: Initiating shutdown, setting background") + background.setShutdown() + self.closeOption = "exit" + self.doClose() + return + elif option == 'minimize': + self.storeLastBG() + util.setGlobalProperty('is_active', '') + xbmc.executebuiltin('ActivateWindow(10000)') + return else: self.closeOption = option + kill_background() self.doClose() def showAudioPlayer(self): diff --git a/script.plexmod/lib/windows/info.py b/script.plexmod/lib/windows/info.py index e34c851c07..ae19cb62be 100644 --- a/script.plexmod/lib/windows/info.py +++ b/script.plexmod/lib/windows/info.py @@ -55,6 +55,9 @@ def getVideoInfo(self): summary = [self.info] medias = self.video.media() + if not medias: + return self.info + mediaCount = len(medias) onlyOneMedia = mediaCount == 1 partCount = sum(len(m.parts) for m in medias) diff --git a/script.plexmod/lib/windows/kodigui.py b/script.plexmod/lib/windows/kodigui.py index be7ef15418..d9b98e924d 100644 --- a/script.plexmod/lib/windows/kodigui.py +++ b/script.plexmod/lib/windows/kodigui.py @@ -4,6 +4,7 @@ import threading import time import traceback +import os from kodi_six import xbmc from kodi_six import xbmcgui @@ -41,13 +42,22 @@ def onClosed(self): @classmethod def open(cls, **kwargs): - window = cls(cls.xmlFile, cls.path, cls.theme, cls.res, **kwargs) - window.modal() + path = cls.path + aggressive = kwargs.pop('aggressive', False) + if os.getenv("INSTALLATION_DIR_AVOID_WRITE"): + path = util.PROFILE + window = cls(cls.xmlFile, path, cls.theme, cls.res, **kwargs) + window.modal(aggressive=aggressive) return window @classmethod def create(cls, show=True, **kwargs): - window = cls(cls.xmlFile, cls.path, cls.theme, cls.res, **kwargs) + # Use the user addon data directory in installations where the extension installation directory is not writable + path = cls.path + if os.getenv("INSTALLATION_DIR_AVOID_WRITE"): + path = util.PROFILE + window = cls(cls.xmlFile, path, cls.theme, cls.res, **kwargs) + if show: window.show() if xbmcgui.getCurrentWindowId() < 13000: @@ -57,10 +67,10 @@ def create(cls, show=True, **kwargs): window.isOpen = xbmcgui.getCurrentWindowId() >= 13000 return window - def modal(self): + def modal(self, aggressive=False): self.isOpen = True try: - self.doModal() + self.doModal(aggressive=aggressive) except SystemExit: pass self.onClosed() @@ -99,57 +109,86 @@ def propertyContext(self, prop, val='1'): def setBoolProperty(self, key, boolean): self.setProperty(key, boolean and '1' or '') + def getBoolProperty(self, key): + return self.getProperty(key) == '1' + + def waitForVisibility(self, control): + return waitForVisibility(control) + + def waitAndSetFocus(self, control): + self.waitForVisibility(control) + self.setFocusId(control) + LAST_BG_URL = None BG_NA = "script.plex/home/background-fallback_black.png" class XMLBase(object): + defer_init = False + defer_init_time = 0.25 + def onInit(self, count=0): - try: - self.getControl(666) - except RuntimeError as e: - if e.args and "Non-Existent Control" in e.args[0]: - if count == 0: - # retry once - xbmc.sleep(250) - return self.onInit(count=1) - - util.ERROR("Possibly broken XML file: {}, triggering recompilation.".format(self.xmlFile)) - util.showNotification("Recompiling templates", time_ms=1000, - header="Possibly broken XML file(s)") - try: - xbmc.Player().stop() - except: - pass - xbmc.sleep(1000) + if not self.started: + if self.defer_init: + util.DEBUG_LOG("Kodigui: Deferring init of {} for {}s", self, self.defer_init_time) + util.MONITOR.waitForAbort(self.defer_init_time) + try: + self.getControl(666) + except RuntimeError as e: + if e.args and "Non-Existent Control" in e.args[0]: + if count < 8: + # retry + xbmc.sleep(250) + return self.onInit(count=count+1) + + util.ERROR("Possibly broken XML file: {}, triggering recompilation.".format(self.xmlFile)) + util.showNotification("Recompiling templates", time_ms=1000, + header="Possibly broken XML file(s)") - if self.__class__.__name__ == "HomeWindow": try: - self._errored = True - self.closeWRecompileTpls() - finally: - return - elif self.__class__.__name__ == "BackgroundWindow": + if xbmc.Player().isPlaying(): + try: + xbmc.Player().stop() + except: + pass + + tries = 0 + while xbmc.Player().isPlaying() and tries < 50: + util.MONITOR.waitForAbort(0.1) + tries += 1 + except: + pass + + xbmc.sleep(1000) + + if self.__class__.__name__ == "HomeWindow": + try: + self._errored = True + self.closeWRecompileTpls() + finally: + return + elif self.__class__.__name__ == "BackgroundWindow": + try: + self._errored = True + self.doClose() + finally: + return + try: self._errored = True self.doClose() finally: + from . import windowutils + windowutils.HOME.closeWRecompileTpls() return - - try: - self._errored = True - self.doClose() - finally: - from . import windowutils - windowutils.HOME.closeWRecompileTpls() - return - raise + raise self._onInit() def goHomeAction(self, action): if (util.HOME_BUTTON_MAPPED is not None and action.getButtonCode() == int(util.HOME_BUTTON_MAPPED) and hasattr(self, "goHome")): + util.DEBUG_LOG("Kodigui: Going home action") self.goHome(with_root=True) return True return @@ -158,6 +197,7 @@ def goHomeAction(self, action): class BaseWindow(XMLBase, xbmcgui.WindowXML, BaseFunctions): __slots__ = ("_closing", "_winID", "started", "finishedInit", "dialogProps", "isOpen", "_errored", "_closeSignalled") + supportsAutoPlay = False def __init__(self, *args, **kwargs): BaseFunctions.__init__(self) @@ -176,7 +216,7 @@ def __init__(self, *args, **kwargs): def onCloseSignal(self, *args, **kwargs): self._closeSignalled = True - self.doClose() + self.doClose(force=True) def _onInit(self): global LAST_BG_URL @@ -214,6 +254,7 @@ def _onInit(self): if hasattr(self, "onFirstInit"): self.onFirstInit() self.finishedInit = True + util.setGlobalProperty('active_window', self.__class__.__name__) except util.NoDataException: self.exitCommand = "NODATA" @@ -227,15 +268,27 @@ def onAction(self, action): def onReInit(self): pass + def doAutoPlay(self, blind=False): + pass + + def onBlindClose(self): + pass + def waitForOpen(self, base_win_id=None): tries = 0 while ((not base_win_id and not self.isOpen) or - (base_win_id and xbmcgui.getCurrentWindowId() <= base_win_id)) \ - and not util.MONITOR.waitForAbort(1) and tries < 120: + (base_win_id and xbmcgui.getCurrentWindowId() <= base_win_id)) and tries < 120: if tries == 0: - util.LOG("Couldn't open window {}, other dialog open? Retrying for 120s.", self) + util.LOG("Couldn't open window {}, other dialog open? Retrying for 120s. ({}, {}, {})", (self, base_win_id, xbmcgui.getCurrentWindowId(), self.isOpen)) + if util.MONITOR.abortRequested(): + util.LOG("Couldn't open window {}, abort requested ({}, {}, {})", (self, base_win_id, xbmcgui.getCurrentWindowId(), self.isOpen)) + break self.show() - tries += 1 + if not self.isOpen: + tries += 1 + util.MONITOR.waitForAbort(1.0) + else: + break util.DEBUG_LOG("Window {} opened: {}", self, self.isOpen) @@ -252,16 +305,15 @@ def setProperty(self, key, value): xbmcgui.Window(self._winID).setProperty(key, value) xbmcgui.WindowXML.setProperty(self, key, value) except RuntimeError: - xbmc.log('kodigui.BaseWindow.setProperty: Missing window', xbmc.LOGDEBUG) + util.DEBUG_LOG('kodigui.BaseWindow.setProperty: Missing window ({}) ({})', self._winID, key) def setCondFocusId(self, focus): if self.getFocusId() != focus: self.setFocusId(focus) def updateBackgroundFrom(self, ds): - if util.addonSettings.dynamicBackgrounds: - return self.windowSetBackground(util.backgroundFromArt(ds.art, width=self.width, - height=self.height)) + if util.addonSettings.dynamicBackgrounds and ds: + return self.windowSetBackground(util.backgroundFromArt(ds.get('art', ds.get('parentArt', ds.get('grandparentArt', None))), width=self.width, height=self.height)) def windowSetBackground(self, value): if not util.addonSettings.dbgCrossfade: @@ -291,18 +343,50 @@ def windowSetBackground(self, value): LAST_BG_URL = value return value - def doClose(self): + def doClose(self, **kw): + force = kw.get('force', True) plexapp.util.APP.off('close.windows', self.onCloseSignal) - if not self.isOpen: + util.DEBUG_LOG("{}: doClose called, force: {}", self.__class__.__name__, force) + if not self.isOpen and not force: return self._closing = True self.isOpen = False self.close() - def show(self): + def show(self, aggressive=False): self._closing = False + # can we activate? + ct = 0 + while xbmcgui.getCurrentWindowDialogId() > 9999 and ct < 20: + util.MONITOR.waitForAbort(0.1) + ct += 1 + + lastWinID = BaseFunctions.lastWinID + #self.isOpen = True xbmcgui.WindowXML.show(self) + + if aggressive: + cid = xbmcgui.getCurrentWindowId() + util.DEBUG_LOG("{}: checking window state (ID: {}, last: {}, current: {})", self, self._winID, lastWinID, cid) + if(self._winID and cid != self._winID) or not self._winID or xbmcgui.getCurrentWindowId() == lastWinID: + # our current window ID _has_ to be different to the last one, if it isn't, handle. + # kodi doesn't throw an exception in case of a still active modal dialog, but instead just logs: + # Activate of window 'xxxxxx' refused because there are active modal dialogs + if xbmcgui.getCurrentWindowId() == lastWinID: + util.DEBUG_LOG('{}: not yet active, retrying', self.__class__.__name__) + util.MONITOR.waitForAbort(0.1) + + ct = 0 + while xbmcgui.getCurrentWindowId() == lastWinID and ct < 4 and not util.MONITOR.abortRequested(): + ct += 1 + # we might have run into an active dialog, which happens sometimes, so we didn't really activate the window + # retry + xbmcgui.WindowXML.show(self) + util.MONITOR.waitForAbort(0.5) + + util.DEBUG_LOG("{}: activation state (ID: {}, last: {}, current: {})", self, self._winID, lastWinID, xbmcgui.getCurrentWindowId()) + self.isOpen = xbmcgui.getCurrentWindowId() >= 13000 @property @@ -371,7 +455,7 @@ def setProperty(self, key, value): except RuntimeError: xbmc.log('kodigui.BaseDialog.setProperty: Missing window', xbmc.LOGDEBUG) - def doClose(self): + def doClose(self, **kw): plexapp.util.APP.off('close.dialogs', self.onCloseSignal) self._closing = True self.close() @@ -387,8 +471,8 @@ def onClosed(self): class ControlledBase: - def doModal(self): - self.show() + def doModal(self, aggressive=False): + self.show(aggressive=aggressive) self.wait() def wait(self): @@ -777,7 +861,11 @@ def setSelectedItem(self, item): self.control.selectItem(pos) def setSelectedItemByDataSource(self, data_source): - self.setSelectedItem(self.getListItemByDataSource(data_source)) + mli = self.getListItemByDataSource(data_source) + if mli: + self.setSelectedItem(mli) + return True + return False def removeItem(self, index): old = self.items.pop(index) @@ -949,9 +1037,6 @@ def onInit(self): class MultiWindow(object): - __slots__ = ("_windows", "_next", "_properties", "_current", "_allClosed", "exitCommand", "_currentOnAction", - "_closeSignalled") - def __init__(self, windows=None, default_window=None, **kwargs): self._windows = windows self._next = default_window or self._windows[0] @@ -962,7 +1047,8 @@ def __init__(self, windows=None, default_window=None, **kwargs): self.exitCommand = None def __getattr__(self, name): - return getattr(self._current, name) + if self._current: + return getattr(self._current, name) def onCloseSignal(self, *args, **kwargs): self._closeSignalled = True @@ -1042,11 +1128,19 @@ def _onFirstInit(self): plexapp.util.APP.on('close.windows', self.onCloseSignal) self.onFirstInit() - def doClose(self): + def doClose(self, **kw): plexapp.util.APP.off('close.windows', self.onCloseSignal) self._allClosed = True self._current.doClose() + def goHomeAction(self, action): + if (util.HOME_BUTTON_MAPPED is not None + and action.getButtonCode() == int(util.HOME_BUTTON_MAPPED) and hasattr(self, "goHome")): + util.DEBUG_LOG("MultiWindow: Going home action") + self.goHome(with_root=True) + return True + return + def onFirstInit(self): pass @@ -1056,6 +1150,8 @@ def onReInit(self): def onAction(self, action): if action == xbmcgui.ACTION_PREVIOUS_MENU or action == xbmcgui.ACTION_NAV_BACK: self.doClose() + elif self.goHomeAction(action): + return self._currentOnAction(action) def onClick(self, controlID): @@ -1300,3 +1396,10 @@ def __enter__(self): def __exit__(self, exc_type, exc_value, traceback): xbmcgui.Window(10000).setProperty('script.plex.{}'.format(self.prop), self.end or self.old) + + +def waitForVisibility(control): + tries = 0 + while not xbmc.getCondVisibility('Control.IsVisible({0})'.format(control)) and tries < 50: + util.MONITOR.waitForAbort(0.1) + tries += 1 diff --git a/script.plexmod/lib/windows/library.py b/script.plexmod/lib/windows/library.py index bf807a367f..8bf7b75025 100644 --- a/script.plexmod/lib/windows/library.py +++ b/script.plexmod/lib/windows/library.py @@ -13,6 +13,8 @@ from kodi_six import xbmc from kodi_six import xbmcgui from plexnet import playqueue +from plexnet import plexobjects +from plexnet import util as pnUtil from six.moves import range from lib import backgroundthread @@ -23,12 +25,14 @@ from . import dropdown from . import kodigui from . import opener +from . import optionsdialog from . import preplay from . import search from . import subitems from . import windowutils -from . import mixins -from .mixins import PlaybackBtnMixin +from .mixins.playbackbtn import PlaybackBtnMixin +from .mixins.watchlist import removeFromWatchlistBlind +from .mixins.common import CommonMixin KEYS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' @@ -48,7 +52,7 @@ ) ) -THUMB_POSTER_DIM = util.scaleResolution(268, 397) +THUMB_POSTER_DIM = util.scaleResolution(268, 402) THUMB_AR16X9_DIM = util.scaleResolution(619, 348) THUMB_SQUARE_DIM = util.scaleResolution(355, 355) ART_AR16X9_DIM = util.scaleResolution(630, 355) @@ -109,6 +113,8 @@ 'collection': T(32490, 'Collections'), 'folder': T(32491, 'Folders'), 'track': T(33644, 'Tracks'), + # watchlist + 'movies_shows': T(34002, "Movies & Shows"), } SORT_KEYS = { @@ -116,54 +122,77 @@ 'titleSort': {'title': T(32357, 'By Title'), 'display': T(32358, 'Title'), 'defSortDesc': False}, 'addedAt': {'title': T(32351, 'By Date Added'), 'display': T(32352, 'Date Added'), 'defSortDesc': True}, 'originallyAvailableAt': {'title': T(32353, 'By Release Date'), 'display': T(32354, 'Release Date'), - 'defSortDesc': True}, - 'lastViewedAt': {'title': T(32355, 'By Date Viewed'), 'display': T(32356, 'Date Viewed'), 'defSortDesc': True}, + 'defSortDesc': True, 'subDisplay': 'originallyAvailableAt', 'subDisplayExclusive': True}, + 'lastViewedAt': {'title': T(32355, 'By Date Viewed'), 'display': T(32356, 'Date Viewed'), 'defSortDesc': True, 'subDisplay': 'lastViewedAt'}, 'rating': {'title': T(33107, 'By Critic Rating'), 'display': T(33108, ' Critic Rating'), 'defSortDesc': True}, 'audienceRating': {'title': T(33101, 'By Audience Rating'), 'display': T(33102, 'Audience Rating'), 'defSortDesc': True}, # called "Rating" in PlexWeb, using more obvious "This is this user's rating" here 'userRating': {'title': T(33103, 'By my Rating'), 'display': T(33104, 'My Rating'), 'defSortDesc': True}, 'contentRating': {'title': T(33105, 'By Content Rating'), 'display': T(33106, 'Content Rating'), - 'defSortDesc': True}, - 'resolution': {'title': T(32361, 'By Resolution'), 'display': T(32362, 'Resolution'), 'defSortDesc': True}, - 'duration': {'title': T(32363, 'By Duration'), 'display': T(32364, 'Duration'), 'defSortDesc': True}, + 'defSortDesc': False, 'subDisplay': 'contentRating'}, + 'resolution': {'title': T(32361, 'By Resolution'), 'display': T(32362, 'Resolution'), 'defSortDesc': True, 'subDisplay': 'resolutionString'}, + 'duration': {'title': T(32363, 'By Duration'), 'display': T(32364, 'Duration'), 'defSortDesc': True, 'subDisplay': 'duration'}, 'unwatched': {'title': T(32367, 'By Unplayed'), 'display': T(32368, 'Unplayed'), 'defSortDesc': False}, - 'viewCount': {'title': T(32371, 'By Play Count'), 'display': T(32372, 'Play Count'), 'defSortDesc': True} + 'year': {'title': T(32377, 'Year'), 'display': T(32377, 'Year'), 'defSortDesc': True}, + 'viewOffset': {'title': T(34040, 'By Progress'), 'display': T(34041, 'Progress'), 'defSortDesc': True}, + 'viewCount': {'title': T(32371, 'By Play Count'), 'display': T(32372, 'Play Count'), 'defSortDesc': True, 'subDisplay': 'viewCount'}, + 'mediaBitrate': {'title': T(33731, 'By Bitrate'), 'display': T(33732, 'Bitrate'), 'defSortDesc': True, 'subDisplay': 'mediaBitrate'}, + 'random': {'title': T(33730, 'Randomly'), 'display': T(33730, 'Randomly'), 'defSortDesc': True}, }, 'show': { 'titleSort': {'title': T(32357, 'By Title'), 'display': T(32358, 'Title'), 'defSortDesc': False}, 'year': {'title': T(32377, "Year"), 'display': T(32377, "Year"), 'defSortDesc': True}, 'show.titleSort': {'title': T(32457, 'By Show'), 'display': T(32456, 'Show'), 'defSortDesc': False}, - 'episode.addedAt': {'title': T(33042, 'Episode Date Added'), 'display': T(33042, 'Episode Date Added'), 'defSortDesc': True}, - 'originallyAvailableAt': {'title': T(32365, 'By First Aired'), 'display': T(32366, 'First Aired'), - 'defSortDesc': False}, - 'unviewedLeafCount': {'title': T(32367, 'By Unplayed'), 'display': T(32368, 'Unplayed'), 'defSortDesc': True}, + 'originallyAvailableAt': {'title': T(32353, 'By Release Date'), 'display': T(32354, 'Release Date'), + 'defSortDesc': True, 'subDisplay': 'originallyAvailableAt', 'subDisplayExclusive': True}, 'rating': {'title': T(33107, 'By Critic Rating'), 'display': T(33108, ' Critic Rating'), 'defSortDesc': True}, 'audienceRating': {'title': T(33101, 'By Audience Rating'), 'display': T(33102, 'Audience Rating'), 'defSortDesc': True}, # called "Rating" in PlexWeb, using more obvious "This is this user's rating" here 'userRating': {'title': T(33103, 'By my Rating'), 'display': T(33104, 'My Rating'), 'defSortDesc': True}, 'contentRating': {'title': T(33105, 'By Content Rating'), 'display': T(33106, 'Content Rating'), - 'defSortDesc': True}, + 'defSortDesc': True, 'subDisplay': 'contentRating'}, + 'unviewedLeafCount': {'title': T(32367, 'By Unplayed'), 'display': T(32368, 'Unplayed'), 'defSortDesc': True}, + 'episode.addedAt': {'title': T(33042, 'Episode Date Added'), 'display': T(33042, 'Episode Date Added'), 'defSortDesc': True}, + 'addedAt': {'title': T(32351, 'By Date Added'), 'display': T(32352, 'Date Added'), 'defSortDesc': True, 'subDisplay': 'addedAt'}, + 'lastViewedAt': {'title': T(32355, 'By Date Added'), 'display': T(32356, 'Date Added'), 'defSortDesc': True, 'subDisplay': 'lastViewedAt'}, + 'random': {'title': T(33730, 'Randomly'), 'display': T(33730, 'Randomly'), 'defSortDesc': True}, }, 'artist': { 'titleSort': {'title': T(32357, 'By Title'), 'display': T(32358, 'Title'), 'defSortDesc': False}, - 'artist.titleSort': {'title': T(32463, 'By Artist'), 'display': T(32462, 'Artist'), 'defSortDesc': False}, + 'userRating': {'title': T(33103, 'By my Rating'), 'display': T(33104, 'My Rating'), 'defSortDesc': True}, + 'addedAt': {'title': T(32351, 'By Date Added'), 'display': T(32352, 'Date Added'), 'defSortDesc': True, 'subDisplay': 'addedAt'}, 'lastViewedAt': {'title': T(32369, 'By Date Played'), 'display': T(32370, 'Date Played'), 'defSortDesc': False}, + 'viewCount': {'title': T(32371, 'By Play Count'), 'display': T(32372, 'Play Count'), 'defSortDesc': True, 'subDisplay': 'viewCount'}, + 'random': {'title': T(33730, 'Randomly'), 'display': T(33730, 'Randomly'), 'defSortDesc': True}, }, 'track': { 'titleSort': {'title': T(32357, 'By Title'), 'display': T(32358, 'Title'), 'defSortDesc': False}, + 'userRating': {'title': T(33103, 'By my Rating'), 'display': T(33104, 'My Rating'), 'defSortDesc': True}, 'artist.titleSort': {'title': T(32463, 'By Artist'), 'display': T(32462, 'Artist'), 'defSortDesc': False}, - 'lastViewedAt': {'title': T(32369, 'By Date Played'), 'display': T(32370, 'Date Played'), 'defSortDesc': False}, + 'lastViewedAt': {'title': T(32369, 'By Date Played'), 'display': T(32370, 'Date Played'), 'defSortDesc': True}, 'viewCount': {'title': T(32371, 'By Play Count'), 'display': T(32372, 'Play Count'), 'defSortDesc': True} }, 'photo': { - 'titleSort': {'title': T(32357, 'By Title'), 'display': T(32358, 'Title'), 'defSortDesc': False}, + 'addedAt': {'title': T(32351, 'By Date Added'), 'display': T(32352, 'Date Added'), 'defSortDesc': True, 'subDisplay': 'addedAt'}, 'originallyAvailableAt': {'title': T(32373, 'By Date Taken'), 'display': T(32374, 'Date Taken'), - 'defSortDesc': True} + 'defSortDesc': True, 'subDisplay': 'originallyAvailableAt', 'subDisplayExclusive': True}, + 'photos.titleSort': {'title': T(32357, 'By Title'), 'display': T(32358, 'Title'), 'defSortDesc': False}, + 'mediaCount': {'title': T(34042, 'By Album'), 'display': T(34043, 'Album'), 'defSortDesc': False}, }, 'photodirectory': {}, - 'collection': {} + 'collection': {}, + # watchlist + 'movies_shows': { + 'watchlistedAt': {'title': T(32351, 'By Date Added'), 'display': T(32352, 'Date Added'), 'defSortDesc': True}, + 'titleSort': {'title': T(32357, 'By Title'), 'display': T(32358, 'Title'), 'defSortDesc': False}, + 'firstAvailableAt': {'title': T(32353, 'By Release Date'), 'display': T(32354, 'Release Date'), + 'defSortDesc': True}, + 'rating': {'title': T(33107, 'By Critic Rating'), 'display': T(33108, ' Critic Rating'), 'defSortDesc': True}, + 'audienceRating': {'title': T(33101, 'By Audience Rating'), 'display': T(33102, 'Audience Rating'), + 'defSortDesc': True}, + } } ITEM_TYPE = None @@ -175,9 +204,59 @@ def setItemType(type_=None): ITEM_TYPE = type_ util.setGlobalProperty('item.type', str(ITEM_TYPE)) +def getQueryItemType(section, fallback_to_section_type=False, force_include_collections=False): + base_type = ITEM_TYPE + + if fallback_to_section_type and not base_type: + base_type = section.TYPE + + if not base_type: + return + + type_ = plexobjects.SEARCHTYPES.get(base_type) + + # combine collections into types, otherwise jumpList/firstCharacter returns different results with + # includeCollections=1 + if force_include_collections and type_ is not None and type_ != 18: + type_ = "{},{}".format(type_, 18) + return type_ + +class CreateDefaultItemsTask(backgroundthread.Task): + def setup(self, startPos, count, totalSize, fallback, callback, key=None): + self.startPos = startPos + self.count = count + self.totalSize = totalSize + self.endPos = self.startPos + self.count + if self.endPos > self.totalSize: + self.endPos = self.totalSize + self.fallback = fallback + self.callback = callback + self.key = key + return self + + def contains(self, pos): + return self.startPos <= pos < self.endPos + + def run(self): + if self.isCanceled(): + return + + items = [] + firstMli = None + for x in range(self.startPos, self.endPos): + mli = kodigui.ManagedListItem('') + mli.setProperty('thumb.fallback', self.fallback) + mli.setProperty('index', str(x)) + if self.key: + mli.setProperty('key', self.key) + if x == self.startPos: # i.e. first item + firstMli = mli + items.append(mli) + self.callback(items, self.key, firstMli) class ChunkRequestTask(backgroundthread.Task): - def setup(self, section, start, size, callback, filter_=None, sort=None, unwatched=False, subDir=False): + def setup(self, section, start, size, callback, filter_=None, sort=None, unwatched=False, subDir=False, hdr=False, + dovi=False): self.section = section self.start = start self.size = size @@ -185,6 +264,8 @@ def setup(self, section, start, size, callback, filter_=None, sort=None, unwatch self.filter = filter_ self.sort = sort self.unwatched = unwatched + self.hdr = hdr + self.dovi = dovi self.subDir = subDir return self @@ -196,20 +277,16 @@ def run(self): return try: - type_ = None - if ITEM_TYPE == 'episode': - type_ = 4 - elif ITEM_TYPE == 'album': - type_ = 9 - elif ITEM_TYPE == 'collection': - type_ = 18 - elif ITEM_TYPE == 'track': - type_ = 10 + type_ = getQueryItemType(self.section) if ITEM_TYPE == 'folder': items = self.section.folder(self.start, self.size, self.subDir) else: - items = self.section.all(self.start, self.size, self.filter, self.sort, self.unwatched, type_=type_) + # supplying this type kills all results (bug: 2025/10/21) + if type_ == plexobjects.SEARCHTYPES["photo"]: + type_ = None + items = self.section.all(self.start, self.size, self.filter, self.sort, self.unwatched, type_=type_, + hdr=self.hdr, dovi=self.dovi) if self.isCanceled(): return @@ -310,7 +387,7 @@ def setSetting(self, setting, value): self._saveSettings() -class LibraryWindow(mixins.PlaybackBtnMixin, kodigui.MultiWindow, windowutils.UtilMixin): +class LibraryWindow(PlaybackBtnMixin, kodigui.MultiWindow, windowutils.UtilMixin, CommonMixin): bgXML = 'script-plex-blank.xml' path = util.ADDON.getAddonInfo('path') theme = 'Main' @@ -320,6 +397,8 @@ class LibraryWindow(mixins.PlaybackBtnMixin, kodigui.MultiWindow, windowutils.Ut # so that we fill an entire row CHUNK_SIZE = 240 CHUNK_OVERCOMMIT = 6 + DEFAULT_ITEMS_CHUNK_SIZE = 250 + DEFAULT_ITEMS_CHUNK_SIZE_BIG = 500 def __init__(self, *args, **kwargs): PlaybackBtnMixin.__init__(self) @@ -338,6 +417,7 @@ def __init__(self, *args, **kwargs): self.lastFocusID = None self.lastNonOptionsFocusID = None self.refill = False + self.subOptionCache = {} self.dcpjPos = 0 self.dcpjThread = None @@ -356,13 +436,19 @@ def reset(self): PlaybackBtnMixin.reset(self) util.setGlobalProperty('sort', '') self.filterUnwatched = self.librarySettings.getSetting('filter.unwatched', False) - self.sort = self.librarySettings.getSetting('sort', 'titleSort') - self.sortDesc = self.librarySettings.getSetting('sort.desc', False) + self.filterHDR = self.librarySettings.getSetting('filter.hdr', False) + self.filterDOVI = self.librarySettings.getSetting('filter.dovi', False) + self.filter = self.filter or self.librarySettings.getSetting('filter', None) + self.sort = self.librarySettings.getSetting('sort', self.section.DEFAULT_SORT) + self.sortDesc = self.librarySettings.getSetting('sort.desc', self.section.DEFAULT_SORT_DESC) self.alreadyFetchedChunkList = set() self.finalChunkPosition = 0 - self.CHUNK_SIZE = util.addonSettings.libraryChunkSize + if self.section.TYPE == 'movies_shows': + self.CHUNK_SIZE = min(100, util.addonSettings.libraryChunkSize) + else: + self.CHUNK_SIZE = util.addonSettings.libraryChunkSize key = self.section.key if not key.isdigit(): @@ -376,12 +462,19 @@ def reset(self): self.setWindows(VIEWS_POSTER.get('all')) self.setDefault(VIEWS_POSTER.get(viewtype)) + def setWatchlistDirty(self, *args, **kwargs): + if self.section.TYPE == 'movies_shows': + util.DEBUG_LOG("Library: Watchlist item state changed, setting dirty") + self.refill = True + @busy.dialog() - def doClose(self): + def doClose(self, **kw): + pnUtil.APP.off("watchlist:modified", self.setWatchlistDirty) self.tasks.kill() kodigui.MultiWindow.doClose(self) def onFirstInit(self): + pnUtil.APP.on("watchlist:modified", self.setWatchlistDirty) if self.showPanelControl and not self.refill: self.showPanelControl.newControl(self) self.keyListControl.newControl(self) @@ -389,30 +482,46 @@ def onFirstInit(self): self.setFocusId(self.VIEWTYPE_BUTTON_ID) self.setBoolProperty("initialized", True) else: - self.showPanelControl = kodigui.ManagedControlList(self, self.POSTERS_PANEL_ID, 5) + self.doRefill() - hideFilterOptions = self.section.TYPE == 'photodirectory' or self.section.TYPE == 'collection' + def doRefill(self): + self.showPanelControl = kodigui.ManagedControlList(self, self.POSTERS_PANEL_ID, 5) - self.keyListControl = kodigui.ManagedControlList(self, self.KEY_LIST_ID, 27) - self.setProperty('subDir', self.subDir and '1' or '') - self.setProperty('no.options', self.section.TYPE != 'photodirectory' and '1' or '') - self.setProperty('unwatched.hascount', self.section.TYPE == 'show' and '1' or '') - util.setGlobalProperty('sort', self.sort) - self.setProperty('filter1.display', self.filterUnwatched and T(32368, 'UNPLAYED') or T(32345, 'All')) - self.setProperty('sort.display', SORT_KEYS[self.section.TYPE].get(self.sort, SORT_KEYS['movie'].get(self.sort))['title']) - self.setProperty('media.itemType', ITEM_TYPE or self.section.TYPE) - self.setProperty('media.type', TYPE_PLURAL.get(ITEM_TYPE or self.section.TYPE, self.section.TYPE)) - self.setProperty('media', self.section.TYPE) - self.setProperty('hide.filteroptions', hideFilterOptions and '1' or '') + hideFilterOptions = self.section.TYPE == 'photodirectory' or self.section.TYPE == 'collection' - self.setTitle() - self.setBoolProperty("initialized", True) - self.fill() - self.refill = False - if self.getProperty('no.content') or self.getProperty('no.content.filtered'): - self.setFocusId(self.HOME_BUTTON_ID) - else: - self.setFocusId(self.POSTERS_PANEL_ID) + self.keyListControl = kodigui.ManagedControlList(self, self.KEY_LIST_ID, 27) + self.setProperty('disable_playback', self.section.TYPE == 'movies_shows' and '1' or '') + self.setProperty('subDir', self.subDir and '1' or '') + self.setProperty('no.options', self.section.TYPE != 'photodirectory' and '1' or '') + self.setProperty('unwatched.hascount', self.section.TYPE == 'show' and '1' or '') + util.setGlobalProperty('sort', self.sort) + self.setProperty('filter1.display', self.filterUnwatched and T(32368, 'UNPLAYED') or T(32345, 'All')) + try: + self.setProperty('sort.display', + SORT_KEYS[self.section.TYPE].get(self.sort, SORT_KEYS['movie'].get(self.sort))['title']) + except TypeError: + self.resetSort() + self.setProperty('sort.display', + SORT_KEYS[self.section.TYPE].get(self.sort, SORT_KEYS['movie'].get(self.sort))['title']) + self.setProperty('media.itemType', ITEM_TYPE or self.section.TYPE) + self.setProperty('media.type', TYPE_PLURAL.get(ITEM_TYPE or self.section.TYPE, self.section.TYPE)) + self.setProperty('media', self.section.TYPE) + self.setProperty('hide.filteroptions', hideFilterOptions and '1' or '') + + self.setTitle() + self.setBoolProperty("initialized", True) + self.fill() + self.refill = False + if self.getProperty('no.content') or self.getProperty('no.content.filtered'): + self.setFocusId(self.HOME_BUTTON_ID) + else: + self.setFocusId(self.POSTERS_PANEL_ID) + + def onReInit(self): + if self.refill: + self.doRefill() + if player.PLAYER.bgmPlaying: + player.PLAYER.stopAndWait() def onAction(self, action): try: @@ -436,15 +545,26 @@ def onAction(self, action): elif action == xbmcgui.ACTION_MOUSE_DRAG: self.onMouseDrag(action) elif action == xbmcgui.ACTION_CONTEXT_MENU: - if not xbmc.getCondVisibility('ControlGroup({0}).HasFocus(0)'.format(self.OPTIONS_GROUP_ID)): - self.lastNonOptionsFocusID = self.lastFocusID - self.setFocusId(self.OPTIONS_GROUP_ID) - return - else: - if self.lastNonOptionsFocusID: - self.setFocusId(self.lastNonOptionsFocusID) - self.lastNonOptionsFocusID = None + # item action possible? + had_action = self.itemOptions() + if not had_action: + if not xbmc.getCondVisibility('ControlGroup({0}).HasFocus(0)'.format(self.OPTIONS_GROUP_ID)): + self.lastNonOptionsFocusID = self.lastFocusID + self.setFocusId(self.OPTIONS_GROUP_ID) return + else: + if self.lastNonOptionsFocusID: + self.setFocusId(self.lastNonOptionsFocusID) + self.lastNonOptionsFocusID = None + return + else: + return + elif self.isWatchedAction(action): + mli = self.showPanelControl.getSelectedItem() + if not mli or not mli.dataSource: + return + self.toggleWatched(mli) + return elif action in (xbmcgui.ACTION_NAV_BACK, xbmcgui.ACTION_CONTEXT_MENU): if not xbmc.getCondVisibility('ControlGroup({0}).HasFocus(0)'.format(self.OPTIONS_GROUP_ID)) and \ @@ -492,9 +612,6 @@ def onFocus(self, controlID): if controlID == self.KEY_LIST_ID: self.selectKey() - if player.PLAYER.bgmPlaying: - player.PLAYER.stopAndWait() - def onItemChanged(self, mli): if not mli: return @@ -504,6 +621,86 @@ def onItemChanged(self, mli): self.showPhotoItemProperties(mli.dataSource) + def toggleWatched(self, mli, state=None, **kw): + item = mli.dataSource + guid = item.show().guid if item.TYPE in ('episode', 'season') else item.guid + watched = super(LibraryWindow, self).toggleWatched(item) + if watched is None: + return + + if watched: + removeFromWatchlistBlind(guid) + self.updateUnwatchedAndProgress(mli) + + def itemOptions(self): + mli = self.showPanelControl.getSelectedItem() + if not mli: + return True + + if mli.dataSource is None: + return True + + if mli.dataSource.TYPE in ('episode', 'season', 'movie', 'show'): + options = [] + ds = mli.dataSource + guid = mli.dataSource.show().guid if ds.TYPE in ('episode', 'season') else ds.guid + + if self.section.TYPE != "movies_shows": + # we don't want mark watched for watchlist items + if not mli.getProperty('watched'): + options.append({'key': 'mark_watched', 'display': T(32319, "Mark Played")}) + + if (ds.isFullyWatched or ds.isWatched or + (ds.TYPE in ("show", "season") and 0 < ds.unViewedLeafCount < ds.leafCount)): + options.append({'key': 'mark_unwatched', 'display': T(32318, "Mark Unplayed")}) + else: + options.append({'key': 'remove_from_watchlist', 'display': T(34011, "Remove from watchlist")}) + + title = mli.label + secondary = mli.label2 + if ds.TYPE in ("movie", "show"): + secondary = mli.getProperty('year') + elif ds.TYPE == "episode": + title = ds.defaultTitle + secondary = mli.getProperty('subtitle') + + label = u"{} ({})".format(six.ensure_str(title), six.ensure_str(secondary)) + + choice = dropdown.showDropdown( + options, + pos=(660, 441), + close_direction='none', + set_dropdown_prop=False, + header=T(33030, 'Choose action for: {}').format(label), + align_items="left", + ) + + if choice and choice["key"] in ("mark_watched", "mark_unwatched", "remove_from_watchlist"): + if util.getSetting('home_confirm_actions'): + button = optionsdialog.show( + T(32319, "Mark Played") if choice["key"] == "mark_watched" + else T(34011,"Remove from watchlist") + if choice["key"] == "remove_from_watchlist" else T(32318, "Mark Unplayed"), + label, + T(32328, 'Yes'), + T(32329, 'No'), + ) + + if button != 0: + return True + + if choice["key"] == "mark_watched": + self.toggleWatched(mli, state=True) + + elif choice["key"] == "mark_unwatched": + self.toggleWatched(mli, state=False) + + elif choice["key"] == "remove_from_watchlist": + removeFromWatchlistBlind(guid) + self.doRefill() + return True + + def updateKey(self, mli=None): mli = mli or self.showPanelControl.getSelectedItem() if not mli: @@ -562,6 +759,8 @@ def playButtonClicked(self, shuffle=False): if self.playBtnClicked: return + self.subOptionCache = {} + self.playBtnClicked = True filter_ = self.getFilterOpts() sort = self.getSortOpts() @@ -584,7 +783,7 @@ def playButtonClicked(self, shuffle=False): args['unwatched'] = '1' pq = playqueue.createPlayQueueForItem(self.section, options={'shuffle': shuffle}, args=args) - opener.open(pq, auto_play=True) + opener.open(pq, auto_play=True, auto_play_open=True) def shuffleButtonClicked(self): self.playButtonClicked(shuffle=True) @@ -620,10 +819,21 @@ def itemTypeButtonClicked(self): elif self.section.TYPE == 'artist': for t in ('artist', 'album', 'collection', 'track'): options.append({'type': t, 'display': TYPE_PLURAL.get(t, t)}) + elif self.section.TYPE == 'movies_shows': + for t in ('movies_shows', 'movie', 'show'): + options.append({'type': t, 'display': TYPE_PLURAL.get(t, t)}) else: return - result = dropdown.showDropdown(options, (1280, 106), with_indicator=True) + selectItem = None + curType = self.librarySettings.getItemType() + try: + selectItem = list(filter(lambda o: o["type"] == curType, options))[0] + except: + pass + + result = dropdown.showDropdown(options, (1280, 106), with_indicator=True, + select_item=not self.getBoolProperty('no.content.filtered') and selectItem or None) if not result: return @@ -657,7 +867,8 @@ def itemTypeButtonClicked(self): self.reset() self.clearFilters() - self.resetSort() + if self.section.TYPE != 'movies_shows': + self.resetSort() if not self.nextWindow(False): self.setProperty('media.type', TYPE_PLURAL.get(ITEM_TYPE or self.section.TYPE, self.section.TYPE)) @@ -673,8 +884,9 @@ def sortButtonClicked(self): defSortByOption = {} if self.section.TYPE == 'movie': - searchTypes = ['titleSort', 'addedAt', 'originallyAvailableAt', 'lastViewedAt', 'rating', 'audienceRating', - 'userRating', 'contentRating', 'resolution', 'duration'] + searchTypes = ['titleSort', 'year', 'originallyAvailableAt', 'rating', 'audienceRating', 'userRating', + 'contentRating', 'duration', 'viewOffset', 'viewCount', 'addedAt', 'lastViewedAt', + 'resolution', 'mediaBitrate', 'random'] if ITEM_TYPE == 'collection': searchTypes = ['titleSort', 'addedAt', 'contentRating'] @@ -687,10 +899,10 @@ def sortButtonClicked(self): elif self.section.TYPE == 'show': searchTypes = ['titleSort', 'year', 'originallyAvailableAt', 'rating', 'audienceRating', 'userRating', 'contentRating', 'unviewedLeafCount', 'episode.addedAt', - 'addedAt', 'lastViewedAt'] + 'addedAt', 'lastViewedAt', 'random'] if ITEM_TYPE == 'episode': searchTypes = ['titleSort', 'show.titleSort', 'addedAt', 'originallyAvailableAt', 'lastViewedAt', - 'rating', 'audienceRating', 'userRating'] + 'rating', 'audienceRating', 'userRating', 'mediaBitrate', 'random'] elif ITEM_TYPE == 'collection': searchTypes = ['titleSort', 'addedAt'] @@ -703,9 +915,10 @@ def sortButtonClicked(self): defSortByOption[stype] = option.get('defSortDesc') options.append(option) elif self.section.TYPE == 'artist': - searchTypes = ['titleSort', 'addedAt', 'lastViewedAt', 'viewCount'] + searchTypes = ['titleSort', 'userRating', 'addedAt', 'lastViewedAt', 'viewCount', 'random'] if ITEM_TYPE == 'album': - searchTypes = ['titleSort', 'artist.titleSort', 'addedAt', 'lastViewedAt', 'viewCount', 'originallyAvailableAt', 'rating'] + searchTypes = ['titleSort', 'artist.titleSort', 'addedAt', 'lastViewedAt', 'viewCount', + 'originallyAvailableAt', 'rating', 'random'] elif ITEM_TYPE == 'collection': searchTypes = ['titleSort', 'addedAt'] elif ITEM_TYPE == 'track': @@ -718,17 +931,32 @@ def sortButtonClicked(self): defSortByOption[stype] = option.get('defSortDesc') options.append(option) elif self.section.TYPE == 'photo': - searchTypes = ['titleSort', 'addedAt', 'originallyAvailableAt', 'rating'] + searchTypes = ['addedAt', 'originallyAvailableAt', 'photos.titleSort', 'mediaCount'] for stype in searchTypes: option = SORT_KEYS['photo'].get(stype, SORT_KEYS['movie'].get(stype)).copy() option['type'] = stype option['indicator'] = self.sort == stype and ind or '' defSortByOption[stype] = option.get('defSortDesc') options.append(option) + elif self.section.TYPE == 'movies_shows': + searchTypes = self.section.ALLOWED_SORT + for stype in searchTypes: + option = SORT_KEYS['movies_shows'].get(stype, SORT_KEYS['movie'].get(stype)).copy() + option['type'] = stype + option['indicator'] = self.sort == stype and ind or '' + defSortByOption[stype] = option.get('defSortDesc') + options.append(option) else: return - result = dropdown.showDropdown(options, (1280, 106), with_indicator=True) + selectItem = None + try: + selectItem = list(filter(lambda o: o["type"] == self.sort, options))[0] + except: + pass + + result = dropdown.showDropdown(options, (1280, 106), with_indicator=True, + select_item=not self.getBoolProperty('no.content.filtered') and selectItem or None) if not result: return @@ -739,6 +967,8 @@ def sortButtonClicked(self): else: self.sortDesc = defSortByOption.get(choice, False) + if choice == "random": + self.section.clearCache() self.sort = choice self.librarySettings.setSetting('sort', self.sort) @@ -769,6 +999,8 @@ def sortShowPanel(self, choice, force_refresh=False): self.fillShows() return + # inline sorting is disabled; this code will never be reached + if choice == 'addedAt': self.showPanelControl.sort(lambda i: i.dataSource.addedAt, reverse=self.sortDesc) elif choice == 'originallyAvailableAt': @@ -821,10 +1053,19 @@ def subOptionCallback(self, option): subKey = self.filter['sub']['val'] if option['type'] in ( - 'year', 'decade', 'genre', 'contentRating', 'collection', 'director', 'actor', 'country', 'studio', 'resolution', 'labels', - 'make', 'model', 'aperture', 'exposure', 'iso', 'lens' + 'year', 'decade', 'genre', 'contentRating', 'collection', 'director', 'actor', 'country', 'studio', 'network', 'resolution', 'label', + 'make', 'model', 'aperture', 'exposure', 'iso', 'lens', 'writer', 'producer', 'editionTitle', 'location', 'audioLanguage', 'subtitleLanguage' ): - options = [{'val': o.key, 'display': o.title, 'indicator': o.key == subKey and check or ''} for o in self.section.listChoices(option['type'])] + # cache suboptions + ck = (self.librarySettings.getItemType() or self.section.TYPE, option['type']) + if ck in self.subOptionCache: + options = self.subOptionCache[ck] + else: + options = [{'val': o.key, 'display': o.title, 'indicator': o.key == subKey and check or ''} for o in + self.section.listChoices(option['type'], + libtype=self.librarySettings.getItemType() or self.section.TYPE)] + self.subOptionCache[ck] = options + if not options: options = [{'val': None, 'display': T(32375, 'No filters available'), 'ignore': True}] @@ -841,11 +1082,14 @@ def filter1ButtonClicked(self): options = [] + if self.filter or self.filterHDR or self.filterDOVI: + options.append({'type': 'clear_filter', 'display': T(32376, 'CLEAR FILTER').upper(), 'indicator': 'script.plex/indicators/remove.png'}) + if self.section.TYPE in ('movie', 'show') and not ITEM_TYPE == 'collection': options.append({'type': 'unwatched', 'display': T(32368, 'UNPLAYED').upper(), 'indicator': self.filterUnwatched and check or ''}) - - if self.filter: - options.append({'type': 'clear_filter', 'display': T(32376, 'CLEAR FILTER').upper(), 'indicator': 'script.plex/indicators/remove.png'}) + if self.section.TYPE == 'movie': + options.append({'type': 'hdr', 'display': T(34037, 'HDR'), 'indicator': self.filterHDR and check or ''}) + options.append({'type': 'dovi', 'display': T(34036, 'DOVI'), 'indicator': self.filterDOVI and check or ''}) if options: options.append(None) # Separator @@ -855,14 +1099,21 @@ def filter1ButtonClicked(self): 'decade': {'type': 'decade', 'display': T(32378, 'Decade'), 'indicator': self.hasFilter('decade') and check or ''}, 'genre': {'type': 'genre', 'display': T(32379, 'Genre'), 'indicator': self.hasFilter('genre') and check or ''}, 'contentRating': {'type': 'contentRating', 'display': T(32380, 'Content Rating'), 'indicator': self.hasFilter('contentRating') and check or ''}, - 'network': {'type': 'studio', 'display': T(32381, 'Network'), 'indicator': self.hasFilter('studio') and check or ''}, + 'network': {'type': 'network', 'display': T(32381, 'Network'), 'indicator': self.hasFilter('network') and check or ''}, 'collection': {'type': 'collection', 'display': T(32382, 'Collection'), 'indicator': self.hasFilter('collection') and check or ''}, 'director': {'type': 'director', 'display': T(32383, 'Director'), 'indicator': self.hasFilter('director') and check or ''}, 'actor': {'type': 'actor', 'display': T(32384, 'Actor'), 'indicator': self.hasFilter('actor') and check or ''}, + 'writer': {'type': 'writer', 'display': T(32402, 'Writer'), 'indicator': self.hasFilter('writer') and check or ''}, + 'producer': {'type': 'producer', 'display': T(34031, 'Producer'), 'indicator': self.hasFilter('producer') and check or ''}, 'country': {'type': 'country', 'display': T(32385, 'Country'), 'indicator': self.hasFilter('country') and check or ''}, 'studio': {'type': 'studio', 'display': T(32386, 'Studio'), 'indicator': self.hasFilter('studio') and check or ''}, 'resolution': {'type': 'resolution', 'display': T(32362, 'Resolution'), 'indicator': self.hasFilter('resolution') and check or ''}, - 'labels': {'type': 'labels', 'display': T(32387, 'Labels'), 'indicator': self.hasFilter('labels') and check or ''}, + 'audioLanguage': {'type': 'audioLanguage', 'display': T(34032, 'Audio Language'), 'indicator': self.hasFilter('audioLanguage') and check or ''}, + 'subtitleLanguage': {'type': 'subtitleLanguage', 'display': T(34033, 'Subtitle Language'), 'indicator': self.hasFilter('subtitleLanguage') and check or ''}, + 'editionTitle': {'type': 'editionTitle', 'display': T(34035, 'Editions'), 'indicator': self.hasFilter('editionTitle') and check or ''}, + 'label': {'type': 'label', 'display': T(32387, 'Labels'), 'indicator': self.hasFilter('label') and check or ''}, + 'released': {'type': 'released', 'display': T(34001, 'Released'), + 'indicator': self.hasFilter('released') and check or ''}, 'make': {'type': 'make', 'display': T(32388, 'Camera Make'), 'indicator': self.hasFilter('make') and check or ''}, 'model': {'type': 'model', 'display': T(32389, 'Camera Model'), 'indicator': self.hasFilter('model') and check or ''}, @@ -872,53 +1123,82 @@ def filter1ButtonClicked(self): 'lens': {'type': 'lens', 'display': T(32392, 'Lens'), 'indicator': self.hasFilter('lens') and check or ''} } + for k, option in optionsMap.items(): + option["is_sub_list"] = True + + if pnUtil.ACCOUNT.isAdmin: + optionsMap['location'] = {'type': 'location', 'display': T(34034, 'Folder Location'), 'indicator': self.hasFilter('location') and check or ''} + if self.section.TYPE == 'movie': if ITEM_TYPE == 'collection': options.append(optionsMap['contentRating']) else: - for k in ('year', 'decade', 'genre', 'contentRating', 'collection', 'director', 'actor', 'country', 'studio', 'resolution', 'labels'): - options.append(optionsMap[k]) + for k in ('year', 'decade', 'genre', 'contentRating', 'collection', 'director', 'actor', + 'writer', 'producer', 'country', 'studio', 'resolution', 'audioLanguage', 'subtitleLanguage', + 'editionTitle', 'label', 'location'): + if k in optionsMap: + options.append(optionsMap[k]) elif self.section.TYPE == 'show': if ITEM_TYPE == 'episode': for k in ('year', 'collection', 'resolution'): options.append(optionsMap[k]) elif ITEM_TYPE == 'album': - for k in ('genre', 'year', 'decade', 'collection', 'labels'): + for k in ('genre', 'year', 'decade', 'collection', 'label'): options.append(optionsMap[k]) else: - for k in ('year', 'genre', 'contentRating', 'network', 'collection', 'actor', 'labels'): + for k in ('year', 'genre', 'contentRating', 'studio', 'network', 'collection', 'director', 'actor', 'writer', 'producer', 'label'): options.append(optionsMap[k]) elif self.section.TYPE == 'artist': for k in ('genre', 'country', 'collection'): options.append(optionsMap[k]) elif self.section.TYPE == 'photo': - for k in ('year', 'make', 'model', 'aperture', 'exposure', 'iso', 'lens', 'labels'): + for k in ('year', 'make', 'model', 'aperture', 'exposure', 'iso', 'lens', 'label'): + options.append(optionsMap[k]) + elif self.section.TYPE == 'movies_shows': + for k in self.section.ALLOWED_FILTERS: options.append(optionsMap[k]) - result = dropdown.showDropdown(options, (980, 106), with_indicator=True, suboption_callback=self.subOptionCallback) + result = dropdown.showDropdown(options, (980, 106), with_indicator=True, + suboption_callback=self.subOptionCallback, + select_item=not self.getBoolProperty('no.content.filtered') and self.filter or None, + open_sublists=not self.getBoolProperty('no.content.filtered')) if not result: return choice = result['type'] if choice == 'clear_filter': - self.filter = None + self.clearFilters(skip_display=True) + elif choice == 'unwatched': self.filterUnwatched = not self.filterUnwatched self.librarySettings.setSetting('filter.unwatched', self.filterUnwatched) + elif choice == 'hdr': + self.filterHDR = not self.filterHDR + self.librarySettings.setSetting('filter.hdr', self.filterHDR) + elif choice == 'dovi': + self.filterDOVI = not self.filterDOVI + self.librarySettings.setSetting('filter.dovi', self.filterDOVI) else: self.filter = result + self.librarySettings.setSetting('filter', self.filter) self.updateFilterDisplay() - if self.filter or choice in ('clear_filter', 'unwatched'): + if self.filter or choice in ('clear_filter', 'unwatched', 'hdr', 'dovi'): self.fill() - def clearFilters(self): + def clearFilters(self, skip_display=False): self.filter = None self.filterUnwatched = False + self.filterHDR = False + self.filterDOVI = False self.librarySettings.setSetting('filter.unwatched', self.filterUnwatched) - self.updateFilterDisplay() + self.librarySettings.setSetting('filter.hdr', self.filterHDR) + self.librarySettings.setSetting('filter.dovi', self.filterDOVI) + self.librarySettings.setSetting('filter', None) + if not skip_display: + self.updateFilterDisplay() def resetSort(self): self.sort = 'titleSort' @@ -936,10 +1216,27 @@ def updateFilterDisplay(self): if self.filter.get('sub'): disp = u'{0}: {1}'.format(disp, self.filter['sub']['display']) self.setProperty('filter1.display', disp) - self.setProperty('filter2.display', self.filterUnwatched and T(32368, 'Unplayed') or '') + boolFilters = [] + if self.filterUnwatched: + boolFilters.append(T(32368, 'Unplayed')) + if self.filterHDR: + boolFilters.append(T(34037, 'HDR')) + if self.filterDOVI: + boolFilters.append(T(34036, 'DOVI')) + self.setProperty('filter2.display', ", ".join(boolFilters)) else: self.setProperty('filter2.display', '') - self.setProperty('filter1.display', self.filterUnwatched and T(32368, 'Unplayed') or T(32345, 'All')) + boolFilters = [] + if self.filterUnwatched: + boolFilters.append(T(32368, 'Unplayed')) + else: + if not self.filterHDR and not self.filterDOVI: + boolFilters.append(T(32345, 'All')) + if self.filterHDR: + boolFilters.append(T(34037, 'HDR')) + if self.filterDOVI: + boolFilters.append(T(34036, 'DOVI')) + self.setProperty('filter1.display', ", ".join(boolFilters)) def showPanelClicked(self): mli = self.showPanelControl.getSelectedItem() @@ -950,15 +1247,25 @@ def showPanelClicked(self): updateUnwatchedAndProgress = False + self.subOptionCache = {} + + extra_kwargs = {} + + # watchlist + if sectionType == 'movies_shows': + extra_kwargs['from_watchlist'] = True + extra_kwargs['directly_from_watchlist'] = True + extra_kwargs['external_item'] = True + if mli.dataSource.TYPE == 'collection': - prevItemType = self.librarySettings.getItemType() + prevItemType = self.librarySettings.getItemType() or ITEM_TYPE self.processCommand(opener.open(mli.dataSource)) self.librarySettings.setItemType(prevItemType) elif self.section.TYPE == 'show' or mli.dataSource.TYPE == 'show' or mli.dataSource.TYPE == 'season' or mli.dataSource.TYPE == 'episode': if ITEM_TYPE == 'episode' or mli.dataSource.TYPE == 'episode' or mli.dataSource.TYPE == 'season': self.openItem(mli.dataSource) else: - self.processCommand(opener.handleOpen(subitems.ShowWindow, media_item=mli.dataSource, parent_list=self.showPanelControl)) + self.processCommand(opener.handleOpen(subitems.ShowWindow, media_item=mli.dataSource, parent_list=self.showPanelControl, **extra_kwargs)) if mli.dataSource.TYPE != 'season': # NOTE: A collection with Seasons doesn't have the leafCount/viewedLeafCount until you actually go into the season so we can't update the unwatched count here updateUnwatchedAndProgress = True elif self.section.TYPE == 'movie' or mli.dataSource.TYPE == 'movie': @@ -975,9 +1282,9 @@ def showPanelClicked(self): section.title = datasource.title self.processCommand(opener.handleOpen(LibraryWindow, windows=self._windows, default_window=self._next, section=section, filter_=self.filter, subDir=True)) - self.librarySettings.setItemType(self.librarySettings.getItemType()) + self.librarySettings.setItemType(self.librarySettings.getItemType() or ITEM_TYPE) else: - self.processCommand(opener.handleOpen(preplay.PrePlayWindow, video=datasource, parent_list=self.showPanelControl)) + self.processCommand(opener.handleOpen(preplay.PrePlayWindow if not sectionType == 'movies_shows' else preplay.PrePlayWindowWL, video=datasource, parent_list=self.showPanelControl, **extra_kwargs)) updateUnwatchedAndProgress = True elif self.section.TYPE == 'artist' or mli.dataSource.TYPE == 'artist' or mli.dataSource.TYPE == 'album' or mli.dataSource.TYPE == 'track': if ITEM_TYPE == 'album' or mli.dataSource.TYPE == 'album' or mli.dataSource.TYPE == 'track': @@ -1001,6 +1308,7 @@ def showPanelClicked(self): self.updateUnwatchedAndProgress(mli) def showPhoto(self, photo): + self.subOptionCache = {} if isinstance(photo, plexnet.photo.Photo) or photo.TYPE == 'clip': self.processCommand(opener.open(photo)) else: @@ -1010,24 +1318,18 @@ def updateUnwatchedAndProgress(self, mli): mli.dataSource.reload() if mli.dataSource.isWatched: mli.setProperty('unwatched', '') - mli.setBoolProperty('watched', mli.dataSource.isFullyWatched) mli.setProperty('unwatched.count', '') else: if self.section.TYPE == 'show' or mli.dataSource.TYPE == 'show' or mli.dataSource.TYPE == 'season': mli.setProperty('unwatched.count', str(mli.dataSource.unViewedLeafCount)) + mli.setBoolProperty('unwatched.count.large', mli.dataSource.unViewedLeafCount > 999) else: mli.setProperty('unwatched', '1') + mli.setBoolProperty('watched', mli.dataSource.isFullyWatched) mli.setProperty('progress', util.getProgressImage(mli.dataSource)) def setTitle(self): - if self.section.TYPE == 'artist': - self.setProperty('screen.title', T(32394, 'MUSIC').upper()) - elif self.section.TYPE in ('photo', 'photodirectory'): - self.setProperty('screen.title', T(32349, 'photos').upper()) - elif self.section.TYPE == 'collection': - self.setProperty('screen.title', T(32382, 'COLLECTION').upper()) - else: - self.setProperty('screen.title', self.section.TYPE == 'show' and T(32393, 'TV SHOWS').upper() or T(32348, 'movies').upper()) + self.setProperty('screen.title', self.section.title.upper()) self.updateFilterDisplay() @@ -1070,10 +1372,12 @@ def getFilterOpts(self): return None if not self.filter.get('sub'): - util.DEBUG_LOG('Filter missing sub-filter data') - return None + #util.DEBUG_LOG('Filter missing sub-filter data') + return self.filter['type'], "1" - return (self.filter['type'], six.moves.urllib.parse.unquote_plus(self.filter['sub']['val'])) + if isinstance(self.filter['sub']['val'], six.string_types) and self.filter['sub']['val'].startswith("/"): + return self.filter['type'], self.filter['sub']['val'] + return self.filter['type'], six.moves.urllib.parse.unquote_plus(self.filter['sub']['val']) def getSortOpts(self): if not self.sort: @@ -1081,10 +1385,19 @@ def getSortOpts(self): return (self.sort, self.sortDesc and 'desc' or 'asc') + + def getDefChunkSize(self, size): + return self.DEFAULT_ITEMS_CHUNK_SIZE if size < 1000 else self.DEFAULT_ITEMS_CHUNK_SIZE_BIG + + @property + def thumb_fallback(self): + return 'script.plex/thumb_fallbacks/{0}.png'.format(TYPE_KEYS.get(self.section.type, TYPE_KEYS['movie'])['fallback']) + @busy.dialog() def fillShows(self): self.setBoolProperty('no.content', False) self.setBoolProperty('no.content.filtered', False) + self.setBoolProperty('content.filling', True) items = [] jitems = [] self.keyItems = {} @@ -1093,24 +1406,24 @@ def fillShows(self): self.alreadyFetchedChunkList = set() self.finalChunkPosition = 0 - type_ = None - if ITEM_TYPE == 'episode': - type_ = 4 - elif ITEM_TYPE == 'album': - type_ = 9 - elif ITEM_TYPE == 'collection': - type_ = 18 - elif ITEM_TYPE == 'track': - type_ = 10 + type_ = getQueryItemType(self.section) + # supplying this type kills all results (bug: 2025/10/21) + if type_ == plexobjects.SEARCHTYPES["photo"]: + type_ = None - idx = 0 - fallback = 'script.plex/thumb_fallbacks/{0}.png'.format(TYPE_KEYS.get(self.section.type, TYPE_KEYS['movie'])['fallback']) + tasks = [] - if self.sort != 'titleSort' or ITEM_TYPE == 'folder' or self.subDir or self.section.TYPE == "collection": + kw = {} + if self.section.TYPE == 'movie': + kw.update({"hdr": self.filterHDR, "dovi": self.filterDOVI}) + + if self.sort != 'titleSort' or ITEM_TYPE in ('folder', 'episode') or self.subDir \ + or self.section.TYPE in ("collection", "movies_shows"): if ITEM_TYPE == 'folder': sectionAll = self.section.folder(0, 0, self.subDir) else: - sectionAll = self.section.all(0, 0, filter_=self.getFilterOpts(), sort=self.getSortOpts(), unwatched=self.filterUnwatched, type_=type_) + sectionAll = self.section.all(0, 0, filter_=self.getFilterOpts(), sort=self.getSortOpts(), + unwatched=self.filterUnwatched, type_=type_, **kw) totalSize = sectionAll.totalSize.asInt() @@ -1118,24 +1431,35 @@ def fillShows(self): self.showPanelControl.reset() self.keyListControl.reset() - if self.filter or self.filterUnwatched: + if (self.filter or self.filterUnwatched or self.filterHDR or self.filterDOVI + or self.librarySettings.getItemType()): self.setBoolProperty('no.content.filtered', True) else: self.setBoolProperty('no.content', True) + + return else: - for x in range(totalSize): - mli = kodigui.ManagedListItem('') - mli.setProperty('thumb.fallback', fallback) - mli.setProperty('index', str(x)) - items.append(mli) + for startPosition in range(0, totalSize, self.getDefChunkSize(totalSize)): + tasks.append(CreateDefaultItemsTask().setup(startPosition, self.getDefChunkSize(totalSize), totalSize, self.thumb_fallback, self._defaultItemsCallback)) else: - jumpList = self.section.jumpList(filter_=self.getFilterOpts(), sort=self.getSortOpts(), unwatched=self.filterUnwatched, type_=type_) + # find library collection mode setting, as we need to force-feed the collection type to the jumpList, + # if collection_mode is 2, otherwise the returned item count differs from /all with the same parameters + collection_mode = self.section.settings.get("collectionMode", + {"value": plexobjects.PlexValue(2)})["value"].asInt() + + jl_type = type_ + if collection_mode == 2 and not (self.filter or self.filterUnwatched): + jl_type = getQueryItemType(self.section, fallback_to_section_type=True, force_include_collections=True) + + jumpList = self.section.jumpList(filter_=self.getFilterOpts(), sort=self.getSortOpts(), + unwatched=self.filterUnwatched, type_=jl_type, **kw) if not jumpList: self.showPanelControl.reset() self.keyListControl.reset() - if self.filter or self.filterUnwatched: + if (self.filter or self.filterUnwatched or self.filterHDR or self.filterDOVI + or self.librarySettings.getItemType()): self.setBoolProperty('no.content.filtered', True) else: self.setBoolProperty('no.content', True) @@ -1145,23 +1469,21 @@ def fillShows(self): return + idx = 0 for kidx, ji in enumerate(jumpList): + ji_size = ji.size.asInt() mli = kodigui.ManagedListItem(ji.title, data_source=ji.key) mli.setProperty('key', ji.key) + mli.setProperty('index', str(kidx)) mli.setProperty('original', '{0:02d}'.format(kidx)) self.keyItems[ji.key] = mli jitems.append(mli) - totalSize += ji.size.asInt() - - for x in range(ji.size.asInt()): - mli = kodigui.ManagedListItem('') - mli.setProperty('key', ji.key) - mli.setProperty('thumb.fallback', fallback) - mli.setProperty('index', str(idx)) - items.append(mli) - if not x: # i.e. first item - self.firstOfKeyItems[ji.key] = mli - idx += 1 + totalSize += ji_size + + tasks.append(CreateDefaultItemsTask().setup(idx, ji.size.asInt(), totalSize, self.thumb_fallback, self._defaultItemsCallback, key=ji.key)) + idx += ji_size + + util.DEBUG_LOG('JumpList item size: {}', totalSize) util.setGlobalProperty('key', jumpList[0].key) @@ -1170,8 +1492,16 @@ def fillShows(self): self.showPanelControl.reset() self.keyListControl.reset() - self.showPanelControl.addItems(items) - self.keyListControl.addItems(jitems) + # Start the background tasks to create the default items + self.tasks.add(tasks) + backgroundthread.BGThreader.addTasksToFront(tasks) + + # Wait for the default items to be created + while backgroundthread.BGThreader.working() and not util.MONITOR.abortRequested(): + util.MONITOR.waitForAbort(0.1) + + if jitems: + self.keyListControl.addItems(jitems) self.showPanelControl.selectItem(0) self.setFocusId(self.POSTERS_PANEL_ID) @@ -1180,7 +1510,8 @@ def fillShows(self): for startChunkPosition in range(0, totalSize, self.CHUNK_SIZE): tasks.append( ChunkRequestTask().setup( - self.section, startChunkPosition, self.CHUNK_SIZE, self._chunkCallback, filter_=self.getFilterOpts(), sort=self.getSortOpts(), unwatched=self.filterUnwatched, subDir=self.subDir + self.section, startChunkPosition, self.CHUNK_SIZE, self._chunkCallback, filter_=self.getFilterOpts(), + sort=self.getSortOpts(), unwatched=self.filterUnwatched, subDir=self.subDir, **kw ) ) @@ -1314,6 +1645,28 @@ def fillPhotos(self): if keys: util.setGlobalProperty('key', keys[0]) + def _defaultItemsCallback(self, items, key, firstMli): + if not items: + return + + while True: + self.lock.acquire() + # When creating the default items for the title sort we need to add them to the list + # in order. So we look at the first index of the incoming items to see if it's the + # next batch of items to add. If not then it releases the lock and adds a small delay + # so that other threads can grab the lock. + if key and firstMli: + if int(firstMli.getProperty('index')) != self.showPanelControl.size(): + self.lock.release() + xbmc.sleep(1) + continue + + self.firstOfKeyItems[key] = firstMli + + self.showPanelControl.addItems(items) + self.lock.release() + break + def _chunkCallback(self, items, start): if not self.showPanelControl or not items: return @@ -1325,11 +1678,11 @@ def _chunkCallback(self, items, start): thumbDim = TYPE_KEYS.get(self.section.type, TYPE_KEYS['movie'])['thumb_dim'] artDim = TYPE_KEYS.get(self.section.type, TYPE_KEYS['movie']).get('art_dim', (256, 256)) + if not self.showPanelControl: + return + if ITEM_TYPE == 'episode': for offset, obj in enumerate(items): - if not self.showPanelControl: - return - mli = self.showPanelControl[pos] if obj: mli.dataSource = obj @@ -1341,13 +1694,14 @@ def _chunkCallback(self, items, start): subtitle = "\n" + subtitle else: subtitle = ' - ' + obj.originallyAvailableAt.asDatetime('%m/%d/%y') - mli.setLabel((obj.defaultTitle or '') + subtitle) + mli.setLabel((obj.defaultTitle or ''))# + subtitle) mli.setThumbnailImage(obj.defaultThumb.asTranscodedImageURL(*thumbDim)) mli.setProperty('summary', obj.summary) - mli.setLabel2(util.durationToText(obj.fixedDuration())) + #mli.setLabel2(util.durationToText(obj.fixedDuration())) + mli.setLabel2(subtitle) mli.setProperty('art', obj.defaultArt.asTranscodedImageURL(*artDim)) if not obj.isWatched: mli.setProperty('unwatched', '1') @@ -1364,9 +1718,6 @@ def _chunkCallback(self, items, start): elif ITEM_TYPE == 'album': for offset, obj in enumerate(items): - if not self.showPanelControl: - return - mli = self.showPanelControl[pos] if obj: mli.dataSource = obj @@ -1388,12 +1739,17 @@ def _chunkCallback(self, items, start): pos += 1 else: for offset, obj in enumerate(items): - if not self.showPanelControl: - return - mli = self.showPanelControl[pos] + try: + mli = self.showPanelControl[pos] + except RuntimeError: + util.LOG("Library/ChunkCallback: {} not found", pos) + pos += 1 + continue + if obj: mli.setProperty('index', str(pos)) + if obj.TYPE == 'track': mli.setLabel("{} - {}: {}".format(obj.grandparentTitle, obj.parentTitle, obj.title)) else: @@ -1402,7 +1758,9 @@ def _chunkCallback(self, items, start): if obj.TYPE == 'collection': colArtDim = TYPE_KEYS.get('collection').get('art_dim', (256, 256)) mli.setProperty('art', obj.artCompositeURL(*colArtDim)) - mli.setThumbnailImage(obj.artCompositeURL(*thumbDim)) + mli.setThumbnailImage(obj.server.getImageTranscodeURL( + obj.artCompositeURL(*tuple(2*dim for dim in thumbDim)), *thumbDim) + ) else: if obj.TYPE == 'photodirectory' and obj.composite: mli.setThumbnailImage(obj.composite.asTranscodedImageURL(*thumbDim)) @@ -1410,7 +1768,20 @@ def _chunkCallback(self, items, start): mli.setThumbnailImage(obj.defaultThumb.asTranscodedImageURL(*thumbDim)) mli.dataSource = obj mli.setProperty('summary', obj.get('summary')) - mli.setProperty('year', obj.get('year')) + + # get secondary sort based info + sk_data = SORT_KEYS[self.section.TYPE].get(self.sort, {'subDisplay': None}) + sub_display = sk_data.get('subDisplay', None) + sub_title = obj.get('year') + if sub_display: + if hasattr(obj, "meta_{}".format(sub_display)): + res = getattr(obj, "meta_{}".format(sub_display))('') + if res: + exclusive = sk_data.get('subDisplayExclusive', False) + sub_title = res + if not exclusive: + sub_title = "{} ({})".format(res, obj.get('year')) + mli.setProperty('year', sub_title) if obj.TYPE != 'collection': if not obj.isDirectory() and obj.get('duration').asInt(): @@ -1419,6 +1790,7 @@ def _chunkCallback(self, items, start): if not obj.isWatched and obj.TYPE != "Directory": if self.section.TYPE == 'show' or obj.TYPE == 'show' or obj.TYPE == 'season': mli.setProperty('unwatched.count', str(obj.unViewedLeafCount)) + mli.setBoolProperty('unwatched.count.large', obj.unViewedLeafCount > 999) else: mli.setProperty('unwatched', '1') elif obj.isFullyWatched and obj.TYPE != "Directory": @@ -1435,6 +1807,8 @@ def _chunkCallback(self, items, start): pos += 1 + self.setBoolProperty('content.filling', False) + def requestChunk(self, start): if util.addonSettings.retrieveAllMediaUpFront: return @@ -1452,13 +1826,14 @@ def requestChunk(self, start): self.alreadyFetchedChunkList.add(startChunkPosition) task = ChunkRequestTask().setup(self.section, startChunkPosition, self.CHUNK_SIZE, self._chunkCallback, filter_=self.getFilterOpts(), sort=self.getSortOpts(), - unwatched=self.filterUnwatched, subDir=self.subDir) + unwatched=self.filterUnwatched, subDir=self.subDir, hdr=self.filterHDR, + dovi=self.filterDOVI) self.tasks.add(task) backgroundthread.BGThreader.addTasksToFront([task]) -class PostersWindow(kodigui.ControlledWindow): +class PostersWindow(kodigui.ControlledWindow, windowutils.UtilMixin): xmlFile = 'script-plex-posters.xml' path = util.ADDON.getAddonInfo('path') theme = 'Main' @@ -1489,6 +1864,7 @@ class PostersWindow(kodigui.ControlledWindow): VIEWTYPE = 'panel' MULTI_WINDOW_ID = 0 + ROW_SIZE = 6 CHUNK_OVERCOMMIT = 6 @@ -1496,6 +1872,7 @@ class PostersSmallWindow(PostersWindow): xmlFile = 'script-plex-posters-small.xml' VIEWTYPE = 'panel2' MULTI_WINDOW_ID = 1 + ROW_SIZE = 10 CHUNK_OVERCOMMIT = 30 @@ -1503,6 +1880,7 @@ class ListView16x9Window(PostersWindow): xmlFile = 'script-plex-listview-16x9.xml' VIEWTYPE = 'list' MULTI_WINDOW_ID = 2 + ROW_SIZE = 0 CHUNK_OVERCOMMIT = 12 @@ -1515,6 +1893,7 @@ class SquaresWindow(PostersWindow): class ListViewSquareWindow(PostersWindow): xmlFile = 'script-plex-listview-square.xml' VIEWTYPE = 'list' + ROW_SIZE = 0 MULTI_WINDOW_ID = 1 diff --git a/script.plexmod/lib/windows/musicplayer.py b/script.plexmod/lib/windows/musicplayer.py index d9d63a484c..a760c49085 100644 --- a/script.plexmod/lib/windows/musicplayer.py +++ b/script.plexmod/lib/windows/musicplayer.py @@ -31,7 +31,7 @@ class MusicPlayerWindow(currentplaylist.CurrentPlaylistWindow): width = 1920 height = 1080 - SEEK_BUTTON_ID = 100 + SEEK_BUTTON_ID = 500 SEEK_IMAGE_ID = 200 SHUFFLE_REMOTE_BUTTON_ID = 422 REPEAT_BUTTON_ID = 401 @@ -50,6 +50,7 @@ def __init__(self, *args, **kwargs): self.album = kwargs.get('album') self.selectedOffset = 0 self.exitCommand = None + self.duration = None self.ignoreStopCommands = False if self.track: @@ -150,7 +151,7 @@ def skipNextButtonClicked(self): xbmc.executebuiltin('PlayerControl(Next)') def showPlaylist(self): - self.processCommand(opener.handleOpen(currentplaylist.CurrentPlaylistWindow, winID=xbmcgui.getCurrentWindowId())) + self.processCommand(opener.handleOpen(currentplaylist.CurrentPlaylistWindow, winID=self._winID)) def stopButtonClicked(self): player.PLAYER.stopAndWait() diff --git a/script.plexmod/lib/windows/opener.py b/script.plexmod/lib/windows/opener.py index 37ba37f328..773ccdd3ba 100644 --- a/script.plexmod/lib/windows/opener.py +++ b/script.plexmod/lib/windows/opener.py @@ -24,7 +24,9 @@ def open(obj, **kwargs): key = obj if not obj.startswith('/'): key = '/library/metadata/{0}'.format(obj) - return open(plexapp.SERVERMANAGER.selectedServer.getObject(key), **kwargs) + + server = kwargs.pop("server", None) or plexapp.SERVERMANAGER.selectedServer + return open(server.getObject(key), **kwargs) elif obj.TYPE == 'episode': return episodeClicked(obj, **kwargs) elif obj.TYPE == 'movie': @@ -64,11 +66,20 @@ def open(obj, **kwargs): def handleOpen(winclass, **kwargs): w = None try: + # we might just want the play preparation functionality of a window class to directly play an item or playlist + # if so, we won't actually open the window, just instantiate it, as to not add it to the kodi window history autoPlay = kwargs.pop("auto_play", False) - if autoPlay and hasattr(winclass, "doAutoPlay"): + autoPlayOpen = kwargs.pop("auto_play_open", False) + if autoPlay and winclass.supportsAutoPlay: + # create but don't open window w = winclass.create(show=False, **kwargs) - if w.doAutoPlay(): + if autoPlayOpen and w.doAutoPlay(blind=not autoPlayOpen): + # open window after autoPlay to be able to return to it after playback w.modal() + else: + # just autoPlay and don't open the window + w.doAutoPlay() + w.onBlindClose() else: w = winclass.open(**kwargs) return w.exitCommand or '' @@ -76,6 +87,8 @@ def handleOpen(winclass, **kwargs): pass except util.NoDataException: raise + except: + util.ERROR() finally: del w util.garbageCollect() @@ -85,7 +98,11 @@ def handleOpen(winclass, **kwargs): def playableClicked(playable, **kwargs): from . import preplay - return handleOpen(preplay.PrePlayWindow, video=playable, **kwargs) + if kwargs.get('from_watchlist', False): + win = preplay.PrePlayWindowWL + else: + win = preplay.PrePlayWindow + return handleOpen(win, video=playable, **kwargs) def episodeClicked(episode, **kwargs): diff --git a/script.plexmod/lib/windows/optionsdialog.py b/script.plexmod/lib/windows/optionsdialog.py index 21bd82bf43..83ae65675d 100644 --- a/script.plexmod/lib/windows/optionsdialog.py +++ b/script.plexmod/lib/windows/optionsdialog.py @@ -1,5 +1,7 @@ from __future__ import absolute_import +from threading import Timer + from lib import util from . import kodigui @@ -15,6 +17,8 @@ class OptionsDialog(kodigui.BaseDialog): GROUP_ID = 100 BUTTON_IDS = (1001, 1002, 1003) + DEFAULT_ID = 1001 + def __init__(self, *args, **kwargs): kodigui.BaseDialog.__init__(self, *args, **kwargs) self.header = kwargs.get('header') @@ -24,10 +28,15 @@ def __init__(self, *args, **kwargs): self.button2 = kwargs.get('button2') self.actionCallback = kwargs.get('action_callback') self.buttonChoice = None + self.select = kwargs.get('select', 0) + self.delayButtons = kwargs.get('delay_buttons', False) + self.closeTimeout = kwargs.get('close_timeout', None) + self._close_timer = None def onFirstInit(self): self.setProperty('header', self.header) self.setProperty('info', self.info) + self.setProperty('delay_buttons', bool(self.delayButtons) and '1' or '') if self.button2: self.setProperty('button.2', self.button2) @@ -39,8 +48,23 @@ def onFirstInit(self): self.setProperty('button.0', self.button0) self.setBoolProperty('initialized', True) - util.MONITOR.waitForAbort(0.1) - self.setFocusId(self.BUTTON_IDS[0]) + + if not self.delayButtons: + self.setup_buttons() + + if self.delayButtons: + Timer(self.delayButtons, self.setup_buttons).start() + + if self.closeTimeout: + self._close_timer = Timer(self.closeTimeout, self.doClose) + self._close_timer.start() + + + def setup_buttons(self): + self.setProperty('enable_buttons', '1') + if self.delayButtons: + util.MONITOR.waitForAbort(0.1) + self.setFocusId(self.BUTTON_IDS[self.select]) def onAction(self, action): controlID = self.getFocusId() @@ -53,15 +77,28 @@ def onAction(self, action): kodigui.BaseDialog.onAction(self, action) + def doClose(self, **kw): + if self._close_timer and self._close_timer.is_alive(): + self._close_timer.cancel() + + super(OptionsDialog, self).doClose() + def onClick(self, controlID): if controlID in self.BUTTON_IDS: self.buttonChoice = self.BUTTON_IDS.index(controlID) self.doClose() -def show(header, info, button0=None, button1=None, button2=None, action_callback=None): - w = OptionsDialog.open(header=header, info=info, button0=button0, button1=button1, button2=button2, - action_callback=action_callback) +class BigOptionsDialog(OptionsDialog): + xmlFile = 'script-plex-options_dialog_big.xml' + + +def show(header, info, button0=None, button1=None, button2=None, action_callback=None, dialog_props=None, select=0, + delay_buttons=None, big=False, close_timeout=None): + cls = big and BigOptionsDialog or OptionsDialog + w = cls.open(header=header, info=info, button0=button0, button1=button1, button2=button2, select=select, + action_callback=action_callback, dialog_props=dialog_props, delay_buttons=delay_buttons, + close_timeout=close_timeout) choice = w.buttonChoice del w util.garbageCollect() diff --git a/script.plexmod/lib/windows/pagination.py b/script.plexmod/lib/windows/pagination.py index 4d6a2df552..7e37c49f65 100644 --- a/script.plexmod/lib/windows/pagination.py +++ b/script.plexmod/lib/windows/pagination.py @@ -29,7 +29,7 @@ class MCLPaginator(object): def __init__(self, control, parent_window, page_size=None, orphans=None, leaf_count=None): self.control = control self.pageSize = page_size if page_size is not None else self.pageSize - self.orphans = orphans if orphans is not None else self.orphans + self.orphans = orphans if orphans is not None else self.pageSize self.leafCount = leaf_count self.parentWindow = parent_window @@ -227,7 +227,7 @@ def wrap(self, mli, last_mli, action): onlyTwo = self._currentAmount == 2 items = None - if action == xbmcgui.ACTION_MOVE_LEFT and index == 0: + if action == xbmcgui.ACTION_MOVE_LEFT and index == 0 and last_mli_index == 0: if onlyTwo and last_mli_index == self._currentAmount - 1: return @@ -271,6 +271,7 @@ def prepareListItem(self, data, mli): if data.type in ('season', 'show'): if not mli.dataSource.isWatched: mli.setProperty('unwatched.count', str(mli.dataSource.unViewedLeafCount) or '') + mli.setBoolProperty('unwatched.count.large', mli.dataSource.unViewedLeafCount > 999) else: mli.setBoolProperty('watched', mli.dataSource.isWatched) else: diff --git a/script.plexmod/lib/windows/photos.py b/script.plexmod/lib/windows/photos.py index 0bc159742b..b025602334 100644 --- a/script.plexmod/lib/windows/photos.py +++ b/script.plexmod/lib/windows/photos.py @@ -25,6 +25,8 @@ class PhotoWindow(kodigui.BaseWindow): width = 1920 height = 1080 + supportsAutoPlay = True + OVERLAY_BUTTON_ID = 250 OSD_GROUP_ID = 200 @@ -91,7 +93,7 @@ def onFirstInit(self): if self.autoPlay: self.play() - def doAutoPlay(self): + def doAutoPlay(self, blind=False): self.autoPlay = True return True @@ -143,6 +145,9 @@ def onAction(self, action): kodigui.BaseWindow.onAction(self, action) + def goHome(self, *args, **kwargs): + self.doClose() + def checkPqueueListChanged(self): item = self.pqueueList.getSelectedItem() if item == self.lastItem: @@ -489,7 +494,7 @@ def pause(self): def stop(self): self.doClose() - def doClose(self): + def doClose(self, **kw): self.pause() shutil.rmtree(self.tempFolder, ignore_errors=True) diff --git a/script.plexmod/lib/windows/playbacksettings.py b/script.plexmod/lib/windows/playbacksettings.py index b870608515..cbf731384c 100644 --- a/script.plexmod/lib/windows/playbacksettings.py +++ b/script.plexmod/lib/windows/playbacksettings.py @@ -33,12 +33,12 @@ def callback(opts, mli, force_off=False): # invalidate any other setting if bingemode enabled if choice["key"] == "binge_mode" and ic: for m in opts.items: - if m.dataSource["key"] != "binge_mode": + if m.dataSource["key"] not in ("binge_mode", "auto_sync"): callback(opts, m, force_off=True) del m # disable bingeMode if any other setting is enabled - elif choice["key"] != "binge_mode" and newSettings["binge_mode"]: + elif choice["key"] not in ("binge_mode", "auto_sync") and newSettings["binge_mode"]: m = opts.getListItem(0) callback(opts, m, force_off=True) del m diff --git a/script.plexmod/lib/windows/playersettings.py b/script.plexmod/lib/windows/playersettings.py index 1d6f6a7b74..e316518295 100644 --- a/script.plexmod/lib/windows/playersettings.py +++ b/script.plexmod/lib/windows/playersettings.py @@ -8,9 +8,11 @@ from lib import util from lib.util import T from . import kodigui +from .dialog import showOptionsDialog +from .mixins.subtitledl import PlexSubtitleDownloadMixin -class VideoSettingsDialog(kodigui.BaseDialog, util.CronReceiver): +class VideoSettingsDialog(kodigui.BaseDialog, util.CronReceiver, PlexSubtitleDownloadMixin): xmlFile = 'script-plex-video_settings_dialog.xml' path = util.ADDON.getAddonInfo('path') theme = 'Main' @@ -22,10 +24,14 @@ class VideoSettingsDialog(kodigui.BaseDialog, util.CronReceiver): def __init__(self, *args, **kwargs): kodigui.BaseDialog.__init__(self, *args, **kwargs) + PlexSubtitleDownloadMixin.__init__(self, *args, **kwargs) self.video = kwargs.get('video') self.viaOSD = kwargs.get('via_osd') self.nonPlayback = kwargs.get('non_playback') self.parent = kwargs.get('parent') + self.sessionID = None + if self.parent and self.parent.player: + self.sessionID = self.parent.player.handler.sessionID self.roundRobin = kwargs.get('round_robin', True) self.lastSelectedItem = 0 @@ -84,16 +90,22 @@ def tick(self): self.doClose() return + @property + def qualityOverride(self): + quality_type = self.video.getQualityType() + return self.video.settings.getPrefOverride(quality_type, self.video.settings.getQualityIndex(quality_type)) + def showSettings(self, init=False): video = self.video - override = video.settings.getPrefOverride('local_quality') - if override is not None and override < 13: - current = T((32001, 32002, 32003, 32004, 32005, 32006, 32007, 32008, 32009, 32010, 32011, 32012, 32013, 32014)[13 - override]) + override = self.qualityOverride + if override is not None and override < 16: + current = T((32001, 32017, 32002, 32016, 32003, 32004, 32005, 32015, 32006, 32007, 32008, 32009, 32010, + 32011)[16 - override]) else: current = u'{0} {1} ({2})'.format( plexnet.util.bitrateToString(video.mediaChoice.media.bitrate.asInt() * 1000), video.mediaChoice.media.getVideoResolutionString(), - video.mediaChoice.media.title or 'Original' + video.mediaChoice.media.title or T(32001, 'Original') ) audio, subtitle = self.getAudioAndSubtitleInfo() @@ -101,7 +113,8 @@ def showSettings(self, init=False): options = [ ('audio', T(32395, 'Audio'), audio), ('subs', T(32396, 'Subtitles'), subtitle), - ('quality', T(32397, 'Quality'), u'{0}'.format(current)) + ('quality', T(32397, 'Quality'), u'{0}'.format(current)), + ('download_subs', T(33703, "Download subtitles"), ''), ] if not self.nonPlayback: @@ -117,7 +130,7 @@ def showSettings(self, init=False): options.append(('kodi_colours', T(32967, 'Kodi Colour Settings'), '')) if self.viaOSD: - if self.parent.getProperty("show.PPI"): + if self.parent.getProperty("show.PPI") or self.parent._playerDebugActive or self.parent._playerNativePPIActive: options += [ ('stream_info', T(32483, 'Hide Stream Info'), ''), ] @@ -155,7 +168,10 @@ def getAudioAndSubtitleInfo(self): audio = T(32309, 'None') sss = self.video.selectedSubtitleStream( - forced_subtitles_override=util.getSetting("forced_subtitles_override", False)) + forced_subtitles_override=util.getSetting("forced_subtitles_override") and plexnet.util.ACCOUNT.subtitlesForced == 0, + deselect_subtitles=util.getSetting("disable_subtitle_languages") + ) + if sss: if len(self.video.subtitleStreams) > 1: subtitle = u'{0} \u2022 {1} {2}'.format(sss.getTitle(metadata.apiTranslate), len(self.video.subtitleStreams) - 1, T(32307, 'More')) @@ -177,14 +193,19 @@ def editSetting(self): result = mli.dataSource if result == 'audio': - showAudioDialog(self.video, non_playback=self.nonPlayback) + showAudioDialog(self.video, non_playback=self.nonPlayback, session_id=self.sessionID) elif result == 'subs': - showSubtitlesDialog(self.video, non_playback=self.nonPlayback) + showSubtitlesDialog(self.video, non_playback=self.nonPlayback, session_id=self.sessionID) + elif result == 'download_subs': + downloaded = self.downloadPlexSubtitles(self.video, non_playback=self.nonPlayback) + if downloaded: + self.video.selectStream(downloaded, from_session=not self.nonPlayback, sync_to_server=False) + self.video.manually_selected_sub_stream = downloaded.id elif result == 'quality': idx = None - override = self.video.settings.getPrefOverride('local_quality') - if override is not None and override < 13: - idx = 13 - override + override = self.qualityOverride + if override is not None and override < 16: + idx = 16 - override showQualityDialog(self.video, non_playback=self.nonPlayback, selected_idx=idx) elif result == 'kodi_video': xbmc.executebuiltin('ActivateWindow(OSDVideoSettings)') @@ -202,137 +223,47 @@ def editSetting(self): self.parent.hidePPIDialog() else: #xbmc.executebuiltin('Action(PlayerProcessInfo)') - self.parent.showPPIDialog() + if self.parent._playerDebugActive: + xbmc.executebuiltin('Action(playerdebug)') + self.parent._playerDebugActive = False + elif self.parent._playerNativePPIActive: + xbmc.executebuiltin('Action(playerprocessinfo)') + self.parent._playerNativePPIActive = False + else: + self.parent.showPPIDialog() self.doClose() return self.showSettings() -class SelectDialog(kodigui.BaseDialog, util.CronReceiver): - xmlFile = 'script-plex-settings_select_dialog.xml' - path = util.ADDON.getAddonInfo('path') - theme = 'Main' - res = '1080i' - width = 1920 - height = 1080 - - OPTIONS_LIST_ID = 100 - - def __init__(self, *args, **kwargs): - kodigui.BaseDialog.__init__(self, *args, **kwargs) - self.heading = kwargs.get('heading') - self.options = kwargs.get('options') - self.selectedIdx = kwargs.get('selected_idx') - self.choice = None - self.nonPlayback = kwargs.get('non_playback') - self.lastSelectedItem = self.selectedIdx if self.selectedIdx is not None else 0 - self.roundRobin = kwargs.get('round_robin', True) - - def onFirstInit(self): - self.optionsList = kodigui.ManagedControlList(self, self.OPTIONS_LIST_ID, 8) - self.setProperty('heading', self.heading) - self.showOptions() - util.CRON.registerReceiver(self) - - def onAction(self, action): - try: - if not xbmc.getCondVisibility('Player.HasMedia') and not self.nonPlayback: - self.doClose() - return - except: - util.ERROR() - - if self.roundRobin and action in (xbmcgui.ACTION_MOVE_UP, xbmcgui.ACTION_MOVE_DOWN) and \ - self.getFocusId() == self.OPTIONS_LIST_ID: - to_pos = None - last_index = self.optionsList.size() - 1 - - if last_index > 0: - if action == xbmcgui.ACTION_MOVE_UP and self.lastSelectedItem == 0 and self.optionsList.topHasFocus(): - to_pos = last_index - - elif action == xbmcgui.ACTION_MOVE_DOWN and self.lastSelectedItem == last_index \ - and self.optionsList.bottomHasFocus(): - to_pos = 0 - - if to_pos is not None: - self.optionsList.setSelectedItemByPos(to_pos) - self.lastSelectedItem = to_pos - return - - self.lastSelectedItem = self.optionsList.control.getSelectedPosition() - - kodigui.BaseDialog.onAction(self, action) - - def onClick(self, controlID): - if controlID == self.OPTIONS_LIST_ID: - self.setChoice() - - def onClosed(self): - util.CRON.cancelReceiver(self) - - def tick(self): - if self.nonPlayback: - return - - if not xbmc.getCondVisibility('Player.HasMedia'): - self.doClose() - return - - def setChoice(self): - mli = self.optionsList.getSelectedItem() - if not mli: - return - - self.choice = self.options[self.optionsList.getSelectedPosition()][0] - self.doClose() - - def showOptions(self): - items = [] - for ds, title1 in self.options: - title2 = '' - if isinstance(title1, (list, set, tuple)): - title1, title2 = title1 - item = kodigui.ManagedListItem(title1, plexnet.util.trimString(title2, limit=40), data_source=ds) - items.append(item) - - self.optionsList.reset() - self.optionsList.addItems(items) - - if self.selectedIdx is not None: - self.optionsList.selectItem(self.selectedIdx) - - self.setFocusId(self.OPTIONS_LIST_ID) - - -def showOptionsDialog(heading, options, non_playback=False, selected_idx=None): - w = SelectDialog.open(heading=heading, options=options, non_playback=non_playback, selected_idx=selected_idx) - choice = w.choice - del w - util.garbageCollect() - return choice - - -def showAudioDialog(video, non_playback=False): +def showAudioDialog(video, non_playback=False, session_id=None): options = [] idx = None for i, s in enumerate(video.audioStreams): if s.isSelected(): idx = i options.append((s, (s.getTitle(metadata.apiTranslate), s.title))) - choice = showOptionsDialog(T(32395, 'Audio'), options, non_playback=non_playback, selected_idx=idx) + choice = showOptionsDialog(T(32395, 'Audio'), options, non_playback=non_playback, selected_idx=idx, + trim=False) if choice is None: return - video.selectStream(choice, from_session=not non_playback) + video.selectStream(choice, from_session=not non_playback, session_id=session_id) + video.clearCache() -def showSubtitlesDialog(video, non_playback=False): +def showSubtitlesDialog(video, non_playback=False, session_id=None): options = [(plexnet.plexstream.NoneStream(), 'None')] idx = None + sss = video.selectedSubtitleStream( + forced_subtitles_override=util.getSetting("forced_subtitles_override") and plexnet.util.ACCOUNT.subtitlesForced == 0, + deselect_subtitles=util.getSetting("disable_subtitle_languages") + ) for i, s in enumerate(video.subtitleStreams): - if s.isSelected(): + if s == sss: + #for i, s in enumerate(video.subtitleStreams): + # if s.isSelected(): idx = i + 1 options.append((s, s.getTitle(metadata.apiTranslate))) @@ -340,21 +271,44 @@ def showSubtitlesDialog(video, non_playback=False): if choice is None: return - video.selectStream(choice, from_session=not non_playback) + video.selectStream(choice, from_session=not non_playback, session_id=session_id) + video.clearCache() video.manually_selected_sub_stream = choice.id def showQualityDialog(video, non_playback=False, selected_idx=None): - options = [(13 - i, T(l)) for (i, l) in enumerate((32001, 32002, 32003, 32004, 32005, 32006, 32007, 32008, 32009, - 32010, 32011))] + options = [] + video_bitrate = video.mediaChoice.media.bitrate.asInt() + + if video.settings.getPreference('clamp_video_bitrates', True): + bitrates = list(reversed(video.settings.getGlobal("transcodeVideoBitrates")))[1:] + for (i, l) in enumerate((32017, 32002, 32016, 32003, 32004, 32005, 32015, 32006, 32007, 32008, 32009, 32010, + 32011)): + br_in_list = int(bitrates[i]) + if br_in_list > video_bitrate: + if selected_idx is not None: + selected_idx -= 1 + continue + + options.append((15 - i, T(l))) + else: + options = [(15 - i, T(l)) for (i, l) in enumerate((32017, 32002, 32016, 32003, 32004, 32005, 32015, 32006, + 32007, 32008, 32009, 32010, 32011))] + + + options.insert(0, (16, u'{0} {1} ({2})'.format( + plexnet.util.bitrateToString(video_bitrate * 1000), + video.mediaChoice.media.getVideoResolutionString(), + T(32001, 'Original') + ))) choice = showOptionsDialog('Quality', options, non_playback=non_playback, selected_idx=selected_idx) if choice is None: return - video.settings.setPrefOverride('local_quality', choice) - video.settings.setPrefOverride('remote_quality', choice) - video.settings.setPrefOverride('online_quality', choice) + video.settings.setPrefOverride('local_quality2', choice) + video.settings.setPrefOverride('remote_quality2', choice) + video.settings.setPrefOverride('online_quality2', choice) def showDialog(video, non_playback=False, via_osd=False, parent=None): diff --git a/script.plexmod/lib/windows/playlist.py b/script.plexmod/lib/windows/playlist.py index 160ccffc79..99a0f052c2 100644 --- a/script.plexmod/lib/windows/playlist.py +++ b/script.plexmod/lib/windows/playlist.py @@ -7,6 +7,7 @@ from kodi_six import xbmcgui from six.moves import range +from plexnet import signalsmixin from lib import backgroundthread from lib import player from lib import util @@ -20,8 +21,6 @@ from . import windowutils PLAYLIST_PAGE_SIZE = 500 -PLAYLIST_INITIAL_SIZE = 100 - class ChunkRequestTask(backgroundthread.Task): WINDOW = None @@ -58,7 +57,7 @@ def run(self): util.DEBUG_LOG('404 on playlist: {0}', lambda: repr(self.WINDOW.playlist.title)) -class PlaylistWindow(kodigui.ControlledWindow, windowutils.UtilMixin): +class PlaylistWindow(kodigui.ControlledWindow, windowutils.UtilMixin, signalsmixin.SignalsMixin): xmlFile = 'script-plex-playlist.xml' path = util.ADDON.getAddonInfo('path') theme = 'Main' @@ -84,16 +83,20 @@ class PlaylistWindow(kodigui.ControlledWindow, windowutils.UtilMixin): def __init__(self, *args, **kwargs): kodigui.ControlledWindow.__init__(self, *args, **kwargs) + signalsmixin.SignalsMixin.__init__(self) self.playlist = kwargs.get('playlist') self.exitCommand = None self.tasks = backgroundthread.Tasks() self.isPlaying = False + self.video_progress = {} ChunkRequestTask.WINDOW = self def onFirstInit(self): self.playlistListControl = kodigui.ManagedControlList(self, self.PLAYLIST_LIST_ID, 5) self.setProperties() player.PLAYER.on('new.video', self.onNewVideo) + player.PLAYER.on('video.progress', self.onVideoProgress) + self.on('playlist.filled', self.onPlaylistFilled) self.fillPlaylist() self.setFocusId(self.PLAYLIST_LIST_ID) @@ -116,6 +119,14 @@ def onNewVideo(self, *args, **kwargs): video = kwargs.get("video") self.playlist.setCurrent(self.playlist.getPosFromItem(video)) + def onVideoProgress(self, data=None, **kwargs): + if not data: + return + + util.DEBUG_LOG("Storing video progress data: {}", data) + gprk, prk, rk, state = data + self.video_progress[rk] = state + def onAction(self, action): try: if action in (xbmcgui.ACTION_NAV_BACK, xbmcgui.ACTION_PREVIOUS_MENU): @@ -143,8 +154,10 @@ def onClick(self, controlID): elif controlID == self.SEARCH_BUTTON_ID: self.searchButtonClicked() - def doClose(self): + def doClose(self, **kw): player.PLAYER.off('new.video', self.onNewVideo) + player.PLAYER.off('video.progress', self.onVideoProgress) + self.off('playlist.filled', self.onPlaylistFilled) kodigui.ControlledWindow.doClose(self) self.tasks.cancel() ChunkRequestTask.reset() @@ -199,7 +212,7 @@ def playlistListClicked(self, no_item=False, shuffle=False, resume=None, play=Fa self.tasks.cancel() player.PLAYER.stop() # Necessary because if audio is already playing, it will close the window when that is stopped if self.playlist.playlistType == 'audio': - if self.playlist.leafCount.asInt() <= PLAYLIST_INITIAL_SIZE: + if self.playlist.leafCount.asInt() <= util.addonSettings.playlistMaxSize: self.playlist.setShuffle(shuffle) self.playlist.setCurrent(mli and mli.pos() or 0) self.showAudioPlayer(track=mli and mli.dataSource or self.playlist.current(), playlist=self.playlist) @@ -212,9 +225,11 @@ def playlistListClicked(self, no_item=False, shuffle=False, resume=None, play=Fa elif self.playlist.playlistType == 'video': if not util.addonSettings.playlistVisitMedia or play: if resume is None and mli and bool(mli.dataSource.viewOffset.asInt()): - return self.plItemPlaybackMenu(select_choice='resume') + if not util.getSetting('assume_resume'): + return self.plItemPlaybackMenu(select_choice='resume') + resume = True - if self.playlist.leafCount.asInt() <= PLAYLIST_INITIAL_SIZE: + if self.playlist.leafCount.asInt() <= util.addonSettings.playlistMaxSize: self.playlist.setShuffle(shuffle) self.playlist.setCurrent(mli and mli.pos() or 0) videoplayer.play(play_queue=self.playlist, resume=resume) @@ -236,6 +251,7 @@ def playlistListClicked(self, no_item=False, shuffle=False, resume=None, play=Fa finally: self.isPlaying = False self.restartFill() + self.video_progress = {} def restartFill(self): threading.Thread(target=self._restartFill).start() @@ -252,7 +268,8 @@ def _restartFill(self): else: break # Update the progress for videos - elif mli.dataSource.type in ('episode', 'movie', 'clip'): + elif mli.dataSource.type in ('episode', 'movie', 'clip') and mli.dataSource.ratingKey in self.video_progress: + mli.dataSource.clearCache() mli.dataSource.reload() self.updateListItem(idx, mli.dataSource) else: @@ -325,8 +342,8 @@ def createEpisodeListItem(self, mli, episode): mli.setThumbnailImage(episode.thumb.asTranscodedImageURL(*self.LI_AR16X9_THUMB_DIM)) mli.setProperty('track.duration', util.durationToShortText(episode.duration.asInt())) mli.setProperty('video', '1') - mli.setProperty('watched', episode.isWatched and '1' or '') - mli.setProperty('unwatched', episode.isWatched and '' or '1') + mli.setProperty('watched', episode.isFullyWatched and '1' or '') + mli.setProperty('unwatched', episode.isFullyWatched and '' or '1') def createMovieListItem(self, mli, movie): mli.setLabel(movie.defaultTitle) @@ -337,6 +354,23 @@ def createMovieListItem(self, mli, movie): mli.setProperty('watched', movie.isWatched and '1' or '') mli.setProperty('unwatched', movie.isWatched and '' or '1') + + def onPlaylistFilled(self, *args, **kwargs): + start = kwargs.get("start", None) + item_count = kwargs.get("item_count", None) + uc = self.playlist.userCurrent() + item_pos = self.playlist.getPosFromItem(uc) + + if item_pos > -1 and start is not None and item_count is not None and start <= item_pos < start + item_count: + util.DEBUG_LOG("Playlist: Relevant task finished, selecting " + "user-relevant current item: {} (pos: {}, range: {}-{})", uc, item_pos, start, start + item_count) + self.playlist.setCurrent(item_pos) + success = self.playlistListControl.setSelectedItemByDataSource(self.playlist.current()) + if not success: + util.LOG("Playlist: Couldn't find item in playlist (current: {}, user-current: {})", + self.playlist.current(), self.playlist.userCurrent()) + + @busy.dialog() def fillPlaylist(self): total = self.playlist.leafCount.asInt() @@ -347,7 +381,7 @@ def fillPlaylist(self): if total < len(self.playlist): total = actualPlaylistLength - endoffirst = min(PLAYLIST_INITIAL_SIZE, PLAYLIST_PAGE_SIZE, total) + endoffirst = min(util.addonSettings.playlistMaxSize, PLAYLIST_PAGE_SIZE, total) items = [self.updateListItem(i, pi, kodigui.ManagedListItem()) for i, pi in enumerate(self.playlist.extend(0, endoffirst))] items += [kodigui.ManagedListItem() for i in range(total - endoffirst)] @@ -355,16 +389,17 @@ def fillPlaylist(self): self.playlistListControl.reset() self.playlistListControl.addItems(items) - self.playlist.setCurrent(self.playlist.getPosFromItem(self.playlist.userCurrent())) - self.playlistListControl.setSelectedItemByDataSource(self.playlist.current()) - - if total <= min(PLAYLIST_INITIAL_SIZE, PLAYLIST_PAGE_SIZE): + if total <= min(util.addonSettings.playlistMaxSize, PLAYLIST_PAGE_SIZE): + self.trigger('playlist.filled', start=0, item_count=total) return - for start in range(endoffirst, total, PLAYLIST_PAGE_SIZE): + batchSize = min(util.addonSettings.playlistMaxSize, PLAYLIST_PAGE_SIZE) + self.trigger('playlist.filled', start=0, item_count=batchSize) + + for start in range(endoffirst, total, batchSize): if util.MONITOR.abortRequested(): break - self.tasks.add(ChunkRequestTask().setup(start, PLAYLIST_PAGE_SIZE)) + self.tasks.add(ChunkRequestTask().setup(start, batchSize)) backgroundthread.BGThreader.addTasksToFront(self.tasks) @@ -375,3 +410,5 @@ def chunkCallback(self, items, start): idx = start + i self.updateListItem(idx, pi) + + self.trigger('playlist.filled', start=start, item_count=len(items)) diff --git a/script.plexmod/lib/windows/preplay.py b/script.plexmod/lib/windows/preplay.py index 3ace7d6c9f..d54ba952bc 100644 --- a/script.plexmod/lib/windows/preplay.py +++ b/script.plexmod/lib/windows/preplay.py @@ -1,8 +1,10 @@ from __future__ import absolute_import +import os + from kodi_six import xbmc from kodi_six import xbmcgui -from plexnet import plexplayer, media +from plexnet import plexplayer, media, util as pnUtil, plexapp, plexlibrary from lib import metadata from lib import util @@ -18,7 +20,12 @@ from . import search from . import videoplayer from . import windowutils -from .mixins import RatingsMixin, PlaybackBtnMixin +from .mixins.ratings import RatingsMixin +from .mixins.playbackbtn import PlaybackBtnMixin +from .mixins.thememusic import ThemeMusicMixin +from .mixins.watchlist import WatchlistUtilsMixin, removeFromWatchlistBlind +from .mixins.roles import RolesMixin +from .mixins.common import CommonMixin VIDEO_RELOAD_KW = dict(includeExtras=1, includeExtrasCount=10, includeChapters=1, includeReviews=1) @@ -28,7 +35,8 @@ def getData(self, offset, amount): return self.parentWindow.video.getRelated(offset=offset, limit=amount) -class PrePlayWindow(kodigui.ControlledWindow, windowutils.UtilMixin, RatingsMixin, PlaybackBtnMixin): +class PrePlayWindow(kodigui.ControlledWindow, windowutils.UtilMixin, RatingsMixin, PlaybackBtnMixin, ThemeMusicMixin, + RolesMixin, CommonMixin, WatchlistUtilsMixin): xmlFile = 'script-plex-pre_play.xml' path = util.ADDON.getAddonInfo('path') theme = 'Main' @@ -36,8 +44,10 @@ class PrePlayWindow(kodigui.ControlledWindow, windowutils.UtilMixin, RatingsMixi width = 1920 height = 1080 + supportsAutoPlay = True + THUMB_POSTER_DIM = util.scaleResolution(347, 518) - RELATED_DIM = util.scaleResolution(268, 397) + RELATED_DIM = util.scaleResolution(268, 402) EXTRA_DIM = util.scaleResolution(329, 185) ROLES_DIM = util.scaleResolution(334, 334) PREVIEW_DIM = util.scaleResolution(343, 193) @@ -53,6 +63,7 @@ class PrePlayWindow(kodigui.ControlledWindow, windowutils.UtilMixin, RatingsMixi HOME_BUTTON_ID = 201 SEARCH_BUTTON_ID = 202 + MAIN_BUTTON_GROUP_ID = 300 INFO_BUTTON_ID = 304 PLAY_BUTTON_ID = 302 TRAILER_BUTTON_ID = 303 @@ -60,13 +71,21 @@ class PrePlayWindow(kodigui.ControlledWindow, windowutils.UtilMixin, RatingsMixi OPTIONS_BUTTON_ID = 306 MEDIA_BUTTON_ID = 307 + POSSIBLE_PLAY_BUTTON_IDS = [302, 2302, 2303, 2304, 2305] + PLAYER_STATUS_BUTTON_ID = 204 def __init__(self, *args, **kwargs): kodigui.ControlledWindow.__init__(self, *args, **kwargs) PlaybackBtnMixin.__init__(self) + WatchlistUtilsMixin.__init__(self) self.video = kwargs.get('video') self.parentList = kwargs.get('parent_list') + self.fromWatchlist = kwargs.get('from_watchlist', False) + self.isExternal = kwargs.get('external_item', False) + self.directlyFromWatchlist = kwargs.get('directly_from_watchlist') + self.is_watchlisted = kwargs.get('is_watchlisted', False) + self.startOver = kwargs.get('start_over') self.videos = None self.exitCommand = None self.trailer = None @@ -74,8 +93,12 @@ def __init__(self, *args, **kwargs): self.lastNonOptionsFocusID = None self.initialized = False self.relatedPaginator = None + self.openedWithAutoPlay = False + self.needs_related_divider = False + self.fromPlayback = False + self.useBGM = False - def doClose(self): + def doClose(self, **kw): self.relatedPaginator = None kodigui.ControlledWindow.doClose(self) @@ -84,34 +107,60 @@ def onFirstInit(self): self.relatedListControl = kodigui.ManagedControlList(self, self.RELATED_LIST_ID, 5) self.rolesListControl = kodigui.ManagedControlList(self, self.ROLES_LIST_ID, 5) self.reviewsListControl = kodigui.ManagedControlList(self, self.REVIEWS_LIST_ID, 5) + self.setBoolProperty("is_watchlisted", self.is_watchlisted) self.progressImageControl = self.getControl(self.PROGRESS_IMAGE_ID) self.setup() self.initialized = True - def doAutoPlay(self): + if not util.getSetting("slow_connection") and not self.openedWithAutoPlay: + self.themeMusicInit(self.video, locations=[os.path.dirname(s.part.file) for s in self.video.videoStreams]) + + def doAutoPlay(self, blind=False): # First reload the video to get all the other info self.video.reload(checkFiles=1, **VIDEO_RELOAD_KW) + self.openedWithAutoPlay = True return self.playVideo(from_auto_play=True) @busy.dialog() def onReInit(self): PlaybackBtnMixin.onReInit(self) + self.themeMusicReinit(self.video) self.initialized = False - if util.getSetting("slow_connection", False): + if util.getSetting("slow_connection"): self.progressImageControl.setWidth(1) self.setProperty('remainingTime', T(32914, "Loading")) self.video.reload(checkFiles=1, fromMediaChoice=self.video.mediaChoice is not None, **VIDEO_RELOAD_KW) + removed_from_wl = False + if self.fromPlayback: + removed_from_wl = self.wl_auto_remove(self.video) + self.fromPlayback = False self.refreshInfo(from_reinit=True) + + if not removed_from_wl: + self.checkIsWatchlisted(self.video) self.initialized = True + def onBlindClose(self): + if self.fromPlayback and self.openedWithAutoPlay and not self.started: + self.video.reload(checkFiles=1, fromMediaChoice=self.video.mediaChoice is not None, **VIDEO_RELOAD_KW) + if self.video.isFullyWatched: + removeFromWatchlistBlind(self.video.guid) + def refreshInfo(self, from_reinit=False): oldFocusId = self.getFocusId() util.setGlobalProperty('hide.resume', '' if self.video.viewOffset.asInt() else '1') # skip setting background when coming from reinit (other window) if we've focused something other than main self.setInfo(skip_bg=from_reinit and not (self.PLAY_BUTTON_ID <= oldFocusId <= self.MEDIA_BUTTON_ID)) - self.fillRelated() + + if not from_reinit: + show_reviews = util.getSetting('show_reviews1') + if show_reviews: + if "watched" in show_reviews and "unwatched" not in show_reviews: + self.fillReviews() + + self.fillRelated(self.needs_related_divider) xbmc.sleep(100) if oldFocusId == self.PLAY_BUTTON_ID: @@ -125,6 +174,10 @@ def onAction(self, action): self.setFocusId(self.lastFocusID) if action == xbmcgui.ACTION_CONTEXT_MENU: + if controlID == self.PLAY_BUTTON_ID: + self.playVideo(force_resume_menu=True) + return + if not xbmc.getCondVisibility('ControlGroup({0}).HasFocus(0)'.format(self.OPTIONS_GROUP_ID)): self.lastNonOptionsFocusID = self.lastFocusID self.setFocusId(self.OPTIONS_GROUP_ID) @@ -143,12 +196,16 @@ def onAction(self, action): self.setFocusId(self.OPTIONS_GROUP_ID) return - elif action == xbmcgui.ACTION_LAST_PAGE and xbmc.getCondVisibility('ControlGroup(300).HasFocus(0)'): + elif self.isWatchedAction(action) and xbmc.getCondVisibility('ControlGroup({}).HasFocus(0)'.format(self.MAIN_BUTTON_GROUP_ID)): + self.toggleWatched(self.video) + return + + elif action == xbmcgui.ACTION_LAST_PAGE and xbmc.getCondVisibility('ControlGroup({}).HasFocus(0)'.format(self.MAIN_BUTTON_GROUP_ID)): next(self) elif action == xbmcgui.ACTION_NEXT_ITEM: self.setFocusId(300) next(self) - elif action == xbmcgui.ACTION_FIRST_PAGE and xbmc.getCondVisibility('ControlGroup(300).HasFocus(0)'): + elif action == xbmcgui.ACTION_FIRST_PAGE and xbmc.getCondVisibility('ControlGroup({}).HasFocus(0)'.format(self.MAIN_BUTTON_GROUP_ID)): self.prev() elif action == xbmcgui.ACTION_PREV_ITEM: self.setFocusId(300) @@ -178,9 +235,17 @@ def onClick(self, controlID): elif controlID == self.RELATED_LIST_ID: self.openItem(self.relatedListControl) elif controlID == self.ROLES_LIST_ID: - self.roleClicked() + if self.fromWatchlist: + return + if not self.roleClicked(): + return elif controlID == self.PLAY_BUTTON_ID: self.playVideo() + elif controlID in self.WL_RELEVANT_BTNS and self.fromWatchlist and self.wl_availability: + self.wl_item_opener(self.video, self.openItem) + elif controlID in self.WL_BTN_STATE_BTNS: + is_watchlisted = self.toggleWatchlist(self.video) + self.waitAndSetFocus(self.WL_BTN_STATE_WATCHLISTED if is_watchlisted else self.WL_BTN_STATE_NOT_WATCHLISTED) elif controlID == self.PLAYER_STATUS_BUTTON_ID: self.showAudioPlayer() elif controlID == self.INFO_BUTTON_ID: @@ -210,6 +275,17 @@ def onFocus(self, controlID): elif xbmc.getCondVisibility('ControlGroup(50).HasFocus(0) + !ControlGroup(300).HasFocus(0)'): self.setProperty('on.extras', '1') + def toggleWatched(self, item, state=None, **kw): + watched = super(PrePlayWindow, self).toggleWatched(item, state=state, **kw) + if watched is None: + return + + if watched: + self.wl_auto_remove(self.video) + self.checkIsWatchlisted(self.video) + self.refreshInfo() + util.MONITOR.watchStatusChanged() + def searchButtonClicked(self): self.processCommand(search.dialog(self, section_id=self.video.getLibrarySectionId() or None)) @@ -251,8 +327,15 @@ def optionsButtonClicked(self): if self.video.type in ('episode', 'movie'): options.append({'key': 'to_section', 'display': T(32324, u'Go to {0}').format(self.video.getLibrarySectionTitle())}) - if self.video.server.allowsMediaDeletion: - options.append({'key': 'delete', 'display': T(32322, 'Delete')}) + if plexapp.ACCOUNT.isAdmin: + options.append(dropdown.SEPARATOR) + options.append({'key': 'refresh', 'display': T(33719, 'Refresh metadata')}) + + if self.video.server.allowsMediaDeletion: + options.append({'key': 'delete', 'display': T(32322, 'Delete')}) + + if 'items' in util.getSetting('cache_requests'): + options.append({'key': 'cache_reset', 'display': T(33728, "Clear cache for item")}) # if xbmc.getCondVisibility('Player.HasAudio') and self.section.TYPE == 'artist': # options.append({'key': 'add_to_queue', 'display': 'Add To Queue'}) @@ -270,21 +353,31 @@ def optionsButtonClicked(self): if choice['key'] == 'play_next': xbmc.executebuiltin('PlayerControl(Next)') elif choice['key'] == 'mark_watched': - self.video.markWatched(**VIDEO_RELOAD_KW) - self.refreshInfo() - util.MONITOR.watchStatusChanged() + self.toggleWatched(self.video, state=True, **VIDEO_RELOAD_KW) elif choice['key'] == 'mark_unwatched': - self.video.markUnwatched(**VIDEO_RELOAD_KW) - self.refreshInfo() - util.MONITOR.watchStatusChanged() + self.toggleWatched(self.video, state=False, **VIDEO_RELOAD_KW) elif choice['key'] == 'to_season': self.processCommand(opener.open(self.video.parentRatingKey)) elif choice['key'] == 'to_show': self.processCommand(opener.open(self.video.grandparentRatingKey)) elif choice['key'] == 'to_section': - self.goHome(self.video.getLibrarySectionId()) + self.cameFrom = "library" + section = plexlibrary.LibrarySection.fromFilter(self.video) + self.processCommand(opener.sectionClicked(section, + came_from=self.video.ratingKey) + ) elif choice['key'] == 'delete': self.delete() + elif choice['key'] == 'refresh': + self.video.refresh() + self.refreshInfo() + elif choice["key"] == "cache_reset": + try: + util.DEBUG_LOG('Clearing requests cache for {}...', self.video) + self.video.clearCache() + self.refreshInfo() + except Exception as e: + util.DEBUG_LOG("Couldn't clear cache: {}", e) def mediaButtonClicked(self): options = [] @@ -302,6 +395,7 @@ def mediaButtonClicked(self): self.video.setMediaChoice(choice['key']) choice['key'].set('selected', 1) + pnUtil.INTERFACE.playbackManager(self.video, key="media_version", value=choice['key'].id) self.setInfo() def delete(self): @@ -326,32 +420,6 @@ def _delete(self): util.LOG('Media DELETE: {0} - {1}', self.video, success and 'SUCCESS' or 'FAILED') return success - def roleClicked(self): - mli = self.rolesListControl.getSelectedItem() - if not mli: - return - - sectionRoles = busy.widthDialog(mli.dataSource.sectionRoles, '') - - if not sectionRoles: - util.DEBUG_LOG('No sections found for actor') - return - - if len(sectionRoles) > 1: - x, y = self.getRoleItemDDPosition() - - options = [{'role': r, 'display': r.reasonTitle} for r in sectionRoles] - choice = dropdown.showDropdown(options, (x, y), pos_is_bottom=True, close_direction='bottom') - - if not choice: - return - - role = choice['role'] - else: - role = sectionRoles[0] - - self.processCommand(opener.open(role)) - def getVideos(self): if not self.videos: if self.video.TYPE == 'episode': @@ -430,34 +498,7 @@ def _prev(self): return True - def getRoleItemDDPosition(self): - y = 200 - if xbmc.getCondVisibility('Control.IsVisible(500)'): - y += 360 - if xbmc.getCondVisibility('Control.IsVisible(501)'): - y += 520 - if xbmc.getCondVisibility('!String.IsEmpty(Window.Property(on.extras))'): - y -= 300 - if xbmc.getCondVisibility('Integer.IsGreater(Window.Property(hub.focus),0) + Control.IsVisible(500)'): - y -= 500 - if xbmc.getCondVisibility('Integer.IsGreater(Window.Property(hub.focus),1) + Control.IsVisible(501)'): - y -= 500 - if xbmc.getCondVisibility('Integer.IsGreater(Window.Property(hub.focus),2) + Control.IsVisible(502)'): - y -= 500 - - tries = 0 - focus = xbmc.getInfoLabel('Container(400).Position') - while tries < 2 and focus == '': - focus = xbmc.getInfoLabel('Container(400).Position') - xbmc.sleep(250) - tries += 1 - - focus = int(focus) - - x = ((focus + 1) * 304) - 100 - return x, y - - def playVideo(self, from_auto_play=False): + def playVideo(self, from_auto_play=False, force_resume_menu=False): if self.playBtnClicked: return @@ -466,41 +507,49 @@ def playVideo(self, from_auto_play=False): return resume = False - if self.video.viewOffset.asInt(): - choice = dropdown.showDropdown( - options=[ - {'key': 'resume', 'display': T(32429, 'Resume from {0}').format(util.timeDisplay(self.video.viewOffset.asInt()).lstrip('0').lstrip(':'))}, - {'key': 'play', 'display': T(32317, 'Play from beginning')} - ], - pos=(660, 441), - close_direction='none', - set_dropdown_prop=False, - header=T(32314, 'In Progress'), - dialog_props=from_auto_play and self.dialogProps or None - ) - - if not choice: - return + if self.video.viewOffset.asInt() and not self.startOver: + if not util.getSetting('assume_resume') or force_resume_menu: + choice = dropdown.showDropdown( + options=[ + {'key': 'resume', 'display': T(32429, 'Resume from {0}').format(util.timeDisplay(self.video.viewOffset.asInt()).lstrip('0').lstrip(':'))}, + {'key': 'play', 'display': T(32317, 'Play from beginning')} + ], + pos=(660, 441), + close_direction='none', + set_dropdown_prop=False, + header=T(32314, 'In Progress'), + dialog_props=from_auto_play and self.dialogProps or None + ) + + if not choice: + return - if choice['key'] == 'resume': + if choice['key'] == 'resume': + resume = True + else: resume = True if not from_auto_play: self.playBtnClicked = True - self.processCommand(videoplayer.play(video=self.video, resume=resume)) + self.fromPlayback = True + self.processCommand(videoplayer.play(video=self.video, resume=resume, bgm=self.useBGM)) return True - def openItem(self, control=None, item=None): + def openItem(self, control=None, item=None, inherit_from_watchlist=True, server=None, is_watchlisted=False, **kw): if not item: mli = control.getSelectedItem() if not mli: return item = mli.dataSource - self.processCommand(opener.open(item)) + self.processCommand(opener.open(item, from_watchlist=self.fromWatchlist if inherit_from_watchlist else False, + server=server, is_watchlisted=is_watchlisted, **kw)) - def focusPlayButton(self): + def focusPlayButton(self, extended=False): + if extended: + self.setFocusId(self.wl_play_button_id) + return try: if not self.getFocusId() == self.PLAY_BUTTON_ID: self.setFocusId(self.PLAY_BUTTON_ID) @@ -510,6 +559,7 @@ def focusPlayButton(self): @busy.dialog() def setup(self): self.focusPlayButton() + self.watchlist_setup(self.video) util.DEBUG_LOG('PrePlay: Showing video info: {0}', self.video) if self.video.type == 'episode': @@ -517,6 +567,9 @@ def setup(self): elif self.video.type == 'movie': self.setProperty('preview.no', '1') + if self.isExternal: + # fixme, multiple? choice? + self.video.related_source = "more-from-credits" self.video.reload(checkFiles=1, **VIDEO_RELOAD_KW) try: self.relatedPaginator = RelatedPaginator(self.relatedListControl, leaf_count=int(self.video.relatedCount), @@ -524,21 +577,28 @@ def setup(self): except ValueError: raise util.NoDataException + if self.fromWatchlist: + self.watchlistItemAvailable(self.video, shortcut_watchlisted=self.directlyFromWatchlist) + if not self.directlyFromWatchlist: + self.checkIsWatchlisted(self.video) + self.setInfo() self.setBoolProperty("initialized", True) hasRoles = self.fillRoles() hasReviews = self.fillReviews() hasExtras = self.fillExtras() - self.fillRelated(hasRoles and not hasExtras and not hasReviews) + self.needs_related_divider = hasRoles and not hasExtras and not hasReviews + self.fillRelated(self.needs_related_divider) def setInfo(self, skip_bg=False): if not skip_bg: self.updateBackgroundFrom(self.video) self.setProperty('title', self.video.title) - self.setProperty('duration', util.durationToText(self.video.duration.asInt())) + self.setProperty('duration', self.video.duration and util.durationToText(self.video.duration.asInt())) self.setProperty('summary', self.video.summary.strip().replace('\t', ' ')) self.setProperty('unwatched', not self.video.isWatched and '1' or '') self.setBoolProperty('watched', self.video.isFullyWatched) + self.setBoolProperty('disable_playback', self.fromWatchlist) directors = u' / '.join([d.tag for d in self.video.directors()][:3]) directorsLabel = len(self.video.directors) > 1 and T(32401, u'DIRECTORS').upper() or T(32383, u'DIRECTOR').upper() @@ -563,13 +623,17 @@ def setInfo(self, skip_bg=False): genres = u' / '.join([g.tag for g in self.video.genres()][:3]) self.setProperty('info', genres) self.setProperty('date', self.video.year) + if self.fromWatchlist and not self.wl_availability: + self.setProperty('wl_server_availability_verbose', util.cleanLeadingZeros(self.video.originallyAvailableAt.asDatetime('%B %d, %Y'))) self.setProperty('content.rating', self.video.contentRating.split('/', 1)[-1]) cast = u' / '.join([r.tag for r in self.video.roles()][:5]) castLabel = 'CAST' self.setProperty('cast', cast and u'{0} {1}'.format(castLabel, cast) or '') - self.setProperty('related.header', T(32404, 'Related Movies')) + self.setProperty('related.header', T(32404, 'Related Movies') if not self.fromWatchlist else T(34018, 'Related Media')) + if self.fromWatchlist: + self.setProperty('studios', u' / '.join([r.tag for r in self.video.studios()][:2])) self.setProperty('video.res', self.video.resolutionString()) self.setProperty('audio.codec', self.video.audioCodecString()) self.setProperty('video.codec', self.video.videoCodecString()) @@ -579,20 +643,21 @@ def setInfo(self, skip_bg=False): self.populateRatings(self.video, self) - self.setAudioAndSubtitleInfo() + if not self.fromWatchlist: + self.setAudioAndSubtitleInfo() - self.setProperty('unavailable', all(not v.isAccessible() for v in self.video.media()) and '1' or '') + self.setProperty('unavailable', all(not v.isAccessible() for v in self.video.media()) and '1' or '') - if self.video.viewOffset.asInt(): - width = self.video.viewOffset.asInt() and (1 + int((self.video.viewOffset.asInt() / self.video.duration.asFloat()) * self.width)) or 1 - self.progressImageControl.setWidth(width) - else: - self.progressImageControl.setWidth(1) + if self.video.viewOffset.asInt(): + width = self.video.viewOffset.asInt() and (1 + int((self.video.viewOffset.asInt() / self.video.duration.asFloat()) * self.width)) or 1 + self.progressImageControl.setWidth(width) + else: + self.progressImageControl.setWidth(1) - if self.video.viewOffset.asInt(): - self.setProperty('remainingTime', T(33615, "{time} left").format(time=self.video.remainingTimeString)) - else: - self.setProperty('remainingTime', '') + if self.video.viewOffset.asInt(): + self.setProperty('remainingTime', T(33615, "{time} left").format(time=self.video.remainingTimeString)) + else: + self.setProperty('remainingTime', '') def setAudioAndSubtitleInfo(self): sas = self.video.selectedAudioStream() @@ -608,7 +673,8 @@ def setAudioAndSubtitleInfo(self): self.setProperty('audio', sas and sas.getTitle(metadata.apiTranslate) or T(32309, 'None')) sss = self.video.selectedSubtitleStream( - forced_subtitles_override=util.getSetting("forced_subtitles_override", False)) + forced_subtitles_override=util.getSetting("forced_subtitles_override") and pnUtil.ACCOUNT.subtitlesForced == 0, + deselect_subtitles=util.getSetting("disable_subtitle_languages")) if sss: if len(self.video.subtitleStreams) > 1: self.setProperty( @@ -631,10 +697,14 @@ def fillExtras(self, has_prev=False): idx = 0 if not self.video.extras: - self.extraListControl.reset() - return False + if self.fromWatchlist: + self.video.fetchExternalExtras() - for extra in self.video.extras(): + if not self.video.extras: + self.extraListControl.reset() + return False + + for extra in self.video.extras: if not self.trailer and extra.extraType.asInt() == media.METADATA_RELATED_TRAILER: self.trailer = extra self.setProperty('trailer.button', '1') @@ -646,6 +716,7 @@ def fillExtras(self, has_prev=False): mli.setProperty( 'thumb.fallback', 'script.plex/thumb_fallbacks/{0}.png'.format(extra.type in ('show', 'season', 'episode') and 'show' or 'movie') ) + mli.setProperty('extra.duration', extra.duration and util.simplifiedTimeDisplay(extra.duration.asInt())) items.append(mli) idx += 1 @@ -655,8 +726,6 @@ def fillExtras(self, has_prev=False): self.extraListControl.reset() self.extraListControl.addItems(items) - self.setProperty('divider.{0}'.format(self.EXTRA_LIST_ID), has_prev and '1' or '') - return True def fillRelated(self, has_prev=False): @@ -669,8 +738,6 @@ def fillRelated(self, has_prev=False): if not items: return False - self.setProperty('divider.{0}'.format(self.RELATED_LIST_ID), has_prev and '1' or '') - return True def fillRoles(self, has_prev=False): @@ -681,8 +748,10 @@ def fillRoles(self, has_prev=False): self.rolesListControl.reset() return False - for role in self.video.roles(): - mli = kodigui.ManagedListItem(role.tag, role.role, thumbnailImage=role.thumb.asTranscodedImageURL(*self.ROLES_DIM), data_source=role) + for role in self.video.combined_roles: + mli = kodigui.ManagedListItem(role.tag, role.role or util.TRANSLATED_ROLES[role.translated_role], + thumbnailImage=role.thumb.asTranscodedImageURL(*self.ROLES_DIM), + data_source=role) mli.setProperty('index', str(idx)) items.append(mli) idx += 1 @@ -698,7 +767,12 @@ def fillReviews(self, has_prev=False): items = [] idx = 0 - if not self.video.reviews: + show_reviews = util.getSetting('show_reviews1') + fully_watched = self.video.isFullyWatched + + if (not show_reviews or not self.video.reviews or + ("unwatched" not in show_reviews and not fully_watched) or + ("watched" not in show_reviews and fully_watched)): self.reviewsListControl.reset() return False @@ -715,3 +789,6 @@ def fillReviews(self, has_prev=False): self.reviewsListControl.reset() self.reviewsListControl.addItems(items) return True + +class PrePlayWindowWL(PrePlayWindow): + xmlFile = 'script-plex-pre_play-wl.xml' diff --git a/script.plexmod/lib/windows/search.py b/script.plexmod/lib/windows/search.py index d84a77890c..77463f1141 100644 --- a/script.plexmod/lib/windows/search.py +++ b/script.plexmod/lib/windows/search.py @@ -115,7 +115,7 @@ def __init__(self, *args, **kwargs): self.resultsThread = None self.updateResultsTimeout = 0 self.isActive = True - self.useKodiKbd = util.getSetting('search_use_kodi_kbd', False) + self.useKodiKbd = util.getSetting('search_use_kodi_kbd') def onFirstInit(self): self.hubControls = ( diff --git a/script.plexmod/lib/windows/seekdialog.py b/script.plexmod/lib/windows/seekdialog.py index 2aff52733f..de0fc6bc97 100644 --- a/script.plexmod/lib/windows/seekdialog.py +++ b/script.plexmod/lib/windows/seekdialog.py @@ -7,6 +7,7 @@ from kodi_six import xbmc from kodi_six import xbmcgui + from plexnet import plexapp from plexnet.util import AttributeDict from plexnet.exceptions import ServerNotOwned, NotFound @@ -20,8 +21,11 @@ from . import busy from . import dropdown from . import kodigui +from . import windowutils from . import playersettings -from .mixins import SpoilersMixin +from . import optionsdialog +from .mixins.spoilers import SpoilersMixin +from .mixins.subtitledl import PlexSubtitleDownloadMixin KEY_MOVE_SET = frozenset( ( @@ -50,6 +54,7 @@ "countdown": None, "countdown_initial": None, "skipped": False, + "hidden": False, # attrs "markerAutoSkip": "autoSkipIntro", @@ -65,6 +70,7 @@ "countdown": None, "countdown_initial": None, "skipped": False, + "hidden": False, "markerAutoSkip": "autoSkipCredits", "markerAutoSkipped": False, @@ -85,7 +91,7 @@ class Marker(AttributeDict): MARKER_END_JUMP_OFF = 1000 -class SeekDialog(kodigui.BaseDialog): +class SeekDialog(kodigui.BaseDialog, windowutils.GoHomeMixin, PlexSubtitleDownloadMixin): """ fixme: This is a convoluted mess. """ @@ -136,10 +142,12 @@ class SeekDialog(kodigui.BaseDialog): HIDE_DELAY = 4 # This uses the Cron tick so is +/- 1 second accurate OSD_HIDE_ANIMATION_DURATION = 0.2 + OSD_HIDE_ACTION_THRESHOLD = 0.5 SKIP_STEPS = {"negative": [-10000], "positive": [30000]} def __init__(self, *args, **kwargs): super(SeekDialog, self).__init__(*args, **kwargs) + PlexSubtitleDownloadMixin.__init__(self, *args, **kwargs) # fixme: heyo, there's a lot of disorder in here. self.handler = kwargs.get('handler') @@ -152,6 +160,7 @@ def __init__(self, *args, **kwargs): self.baseOffset = 0 self._duration = 0 self.offset = 0 + self.playbackTime = 0 self.selectedOffset = 0 self.bigSeekOffset = 0 self.bigSeekChanged = False # attention, with chapters this can become an integer for the True state @@ -174,7 +183,7 @@ def __init__(self, *args, **kwargs): self._delayedSeekThread = None self._delayedSeekTimeout = 0 self._osdHideAnimationTimeout = 0 - self._hideDelay = self.HIDE_DELAY + self._hideDelay = util.addonSettings.osdHideDelay if util.SKIN_PLEXTUARY else 4 self._autoSeekDelay = util.addonSettings.autoSeek and util.addonSettings.autoSeekDelay or 0 self._atSkipStep = -1 self._lastSkipDirection = None @@ -185,9 +194,13 @@ def __init__(self, *args, **kwargs): self._ignoreInput = False self._ignoreTick = False self._abortBufferWait = False - self.no_spoilers = util.getSetting('no_episode_spoilers3', ["unwatched"]) - self.no_time_no_osd_spoilers = util.getSetting('no_osd_time_spoilers', False) - self.clientLikePlex = util.getSetting('player_official', True) + self._playerDebugActive = False + self._playerNativePPIActive = False + self._item_states = {} + self.no_spoilers = util.getSetting('no_episode_spoilers4') + self.no_time_no_osd_spoilers = util.getSetting('no_osd_time_spoilers') + self.clientLikePlex = util.getSetting('player_official') + self.fastPauseResume = self.clientLikePlex and util.getUserSetting('fast_pause_resume', []) or [] self._videoBelowOneHour = False self.timeFmtKodi = util.timeFormatKN @@ -198,11 +211,12 @@ def __init__(self, *args, **kwargs): self.timeKeeper = None self.timeKeeperTime = None self.idleTime = None - self.stopPlaybackOnIdle = util.getSetting('player_stop_on_idle', 0) - self.resumeSeekBehind = util.getSetting('resume_seek_behind', 0) - self.resumeSeekBehindPause = util.getSetting('resume_seek_behind_pause', False) - self.resumeSeekBehindAfter = util.getSetting('resume_seek_behind_after', 0) / 1000.0 - self.resumeSeekBehindOnlyDP = util.getSetting('resume_seek_behind_onlydp', True) + self.stopPlaybackOnIdle = util.getSetting('player_stop_on_idle') + self.resumeSeekBehind = util.getSetting('resume_seek_behind') + self.resumeSeekBehindPause = util.getSetting('resume_seek_behind_pause') + self.resumeSeekBehindAfter = util.getSetting('resume_seek_behind_after') / 1000.0 + self.resumeSeekBehindOnlyDP = util.getSetting('resume_seek_behind_onlydp') + self.useAlternateSeek = util.getSetting('use_alternate_seek2') self.pausedAt = None self.isDirectPlay = True self.isTranscoded = False @@ -234,7 +248,7 @@ def __init__(self, *args, **kwargs): self.player.video.server.on("np:timelineResponse", self.timelineResponseCallback) - if util.kodiSkipSteps and util.addonSettings.kodiSkipStepping: + if util.kodiSkipSteps and util.addonSettings.kodiSkipStepping and not self.handler.useAlternateSeek: self.skipSteps = {"negative": [], "positive": []} for step in util.kodiSkipSteps: key = "negative" if step < 0 else "positive" @@ -299,9 +313,9 @@ def DPPlayerOffset(self): def trueOffset(self): if self.isDirectPlay: - return self.DPPlayerOffset + self.offset + return self.DPPlayerOffset + (self.offset if self.offset is not None else 0) else: - return self.baseOffset + self.offset + return self.baseOffset + (self.offset if self.offset is not None else 0) @property def markers(self): @@ -372,7 +386,7 @@ def getCurrentMarkerDef(self, offset=None): # show intro skip early? (only if intro is during the first X minutes) if self.showIntroSkipEarly and markerDef["marker_type"] == "intro" and \ - startTimeOffset <= util.addonSettings.skipIntroButtonShowEarlyThreshold1 * 1000: + startTimeOffset <= util.addonSettings.skipIntroButtonShowEarlyThreshold2 * 1000: startTimeOffset = 0 markerDef["overrideStartOff"] = 0 @@ -393,6 +407,11 @@ def onFirstInit(self): except RuntimeError: util.ERROR(hide_tb=True) self.started = False + except AttributeError: + self.started = False + # early exit probably during dialog setup + self.handler.player._ignorePlaybackFailure = True + self.stop() def _onFirstInit(self): util.DEBUG_LOG("SeekDialog: onFirstInit") @@ -402,6 +421,7 @@ def _onFirstInit(self): if self.handler.playlist: self.handler.playlist.on('change', self.updateProperties) + self.handler.playlist.on('current.changed', self.updateProperties) self.seekbarControl = self.getControl(self.SEEK_IMAGE_ID) self.positionControl = self.getControl(self.POSITION_IMAGE_ID) @@ -427,17 +447,17 @@ def _onFirstInit(self): self.setBoolProperty('nav.repeat', showRepeat) self.setBoolProperty('nav.ffwdrwd', showFfwdRwd) self.setBoolProperty('nav.shuffle', showShuffle) - navPlaylist = util.getSetting('video_show_playlist', 'eponly') + navPlaylist = util.getSetting('video_show_playlist') self.setBoolProperty('nav.playlist', (navPlaylist == "eponly" and - (self.player.video.type == 'episode' or self.handler.playlist)) or + ((self.player.video and self.player.video.type == 'episode') or (self.handler and self.handler.playlist))) or navPlaylist == "always") if not self.getProperty('nav.playlist'): self.subtitleButtonLeft += self.NAVBAR_BTN_SIZE - navPrevNext = util.getSetting('video_show_prevnext', 'eponly') + navPrevNext = util.getSetting('video_show_prevnext') self.setBoolProperty('nav.prevnext', (navPrevNext == "eponly" and - (self.player.video.type == 'episode' or self.handler.playlist)) or + ((self.player.video and self.player.video.type == 'episode') or (self.handler and self.handler.playlist))) or navPrevNext == "always") if showQuickSubs: @@ -468,7 +488,7 @@ def setup(self, duration, meta, offset=0, bif_url=None, title='', title2='', cha """ this is called by our handler and occurs earlier than onFirstInit. """ - util.DEBUG_LOG("SeekDialog: setup, keepMarkerDef={}", keepMarkerDef) + util.DEBUG_LOG("SeekDialog: setup, keepMarkerDef={}, offset={}", keepMarkerDef, offset) self._duration = duration self.title = title self.title2 = title2 @@ -485,6 +505,8 @@ def setup(self, duration, meta, offset=0, bif_url=None, title='', title2='', cha self.killTimeKeeper() + self.playbackTime = 0 + if not self.getProperty('nav.playlist'): self.subtitleButtonLeft += self.NAVBAR_BTN_SIZE @@ -529,9 +551,8 @@ def setup(self, duration, meta, offset=0, bif_url=None, title='', title2='', cha self.timeFmtKodi = self.timeFmtKodi.replace("hh:", "") self._ignoreTick = False self._ignoreInput = False - if not self.showChapters: - self.bifURL = bif_url - self.hasBif = bool(self.bifURL) + self.bifURL = bif_url + self.hasBif = bool(self.bifURL) if self.hasBif: self.baseURL = re.sub(r'/\d+\?', '/{0}?', self.bifURL) @@ -550,6 +571,10 @@ def update(self, offset=None, from_seek=False): self.updateProgress() + def closeWithCommand(self, command): + self.exitCommand = command + self.stop() + def onAction(self, action): if xbmc.getCondVisibility('Window.IsActive(selectdialog)'): if self.doKodiSelectDialogHack(action): @@ -591,6 +616,16 @@ def onAction(self, action): self.player.playState == self.player.STATE_PLAYING: self.hideOSD() + if action == xbmcgui.ACTION_CONTEXT_MENU or (self.getProperty('show.PPI') and action in (xbmcgui.ACTION_MOVE_LEFT, xbmcgui.ACTION_MOVE_RIGHT)): + if self.getProperty('show.PPI') and not self._playerDebugActive and not self._playerNativePPIActive: + if action == xbmcgui.ACTION_MOVE_LEFT: + self.showPPIDialog(real_ppi=True, debug=True) + else: + self.showPPIDialog(real_ppi=True) + return + if self._playerNativePPIActive and action in (xbmcgui.ACTION_MOVE_UP, xbmcgui.ACTION_MOVE_DOWN): + self._playerNativePPIActive = False + passThroughMain = False if controlID == self.SKIP_MARKER_BUTTON_ID: if action == xbmcgui.ACTION_SELECT_ITEM: @@ -598,18 +633,20 @@ def onAction(self, action): if markerDef["marker"]: marker = markerDef["marker"] final = getattr(marker, "final", False) - markerOff = -FINAL_MARKER_NEGOFF if final else MARKER_END_JUMP_OFF + + if final: + return self.handleFinalMarker(markerDef, immediate=False, context="MarkerSkip") util.DEBUG_LOG('MarkerSkip: Skipping marker' ' {} (final: {}, to: {}, offset: {})'.format(markerDef["marker"], - final, marker.endTimeOffset, markerOff)) + final, marker.endTimeOffset, MARKER_END_JUMP_OFF)) self.setProperty('show.markerSkip', '') self.setProperty('show.markerSkip_OSDOnly', '') markerDef["skipped"] = True - self.doSeek(marker.endTimeOffset + markerOff) + self.doSeek(marker.endTimeOffset + MARKER_END_JUMP_OFF) self.hideOSD(skipMarkerFocus=True) - if marker.type == "credits" and not final: + if marker.type == "credits": # non-final marker setattr(self, markerDef["markerAutoSkipShownTimer"], None) self.resetAutoSeekTimer(None) @@ -630,17 +667,33 @@ def onAction(self, action): if self.getProperty('show.markerSkip') and not self.getProperty('show.markerSkip_OSDOnly'): self.setProperty('show.markerSkip', '') self.setProperty('show.markerSkip_OSDOnly', '1') + markerDef = self._currentMarker + if markerDef: + markerDef["hidden"] = True return if controlID == self.MAIN_BUTTON_ID: # we're seeking from the timeline with the OSD open - do an actual timeline seek + # ignore seek actions for a split second when the OSD is hiding or was hiding + if (action.getId() in KEY_MOVE_SET and self._osdHideAnimationTimeout and + self._osdHideAnimationTimeout + self.OSD_HIDE_ACTION_THRESHOLD >= time.time()): + return + if action in (xbmcgui.ACTION_MOVE_RIGHT, xbmcgui.ACTION_STEP_FORWARD): + if self.handler.waitingForSOS: + util.DEBUG_LOG("SeekDialog: Ignoring seek action as we're waiting for SOS") + return + self.setProperty('show.chapters', '') if self.useDynamicStepsForTimeline: return self.skipForward() return self.seekByOffset(10000, auto_seek=self.useAutoSeek) elif action in (xbmcgui.ACTION_MOVE_LEFT, xbmcgui.ACTION_STEP_BACK): + if self.handler.waitingForSOS: + util.DEBUG_LOG("SeekDialog: Ignoring seek action as we're waiting for SOS") + return + self.setProperty('show.chapters', '') if self.useDynamicStepsForTimeline: return self.skipBack() return self.seekByOffset(-10000, auto_seek=self.useAutoSeek) @@ -679,7 +732,15 @@ def onAction(self, action): self.resetSeeking() elif controlID == self.NO_OSD_BUTTON_ID or passThroughMain: + # ignore seek actions for a split second when the OSD is hiding or was hiding + if (action.getId() in KEY_MOVE_SET and self._osdHideAnimationTimeout and + self._osdHideAnimationTimeout + self.OSD_HIDE_ACTION_THRESHOLD >= time.time()): + return + if action in (xbmcgui.ACTION_MOVE_RIGHT, xbmcgui.ACTION_MOVE_LEFT): + if self.handler.waitingForSOS: + util.DEBUG_LOG("SeekDialog: Ignoring seek action as we're waiting for SOS") + return # we're seeking from the timeline, with the OSD closed; act as we're skipping if not self._seeking: self.selectedOffset = self.trueOffset() @@ -722,7 +783,6 @@ def onAction(self, action): else: self.showPPIDialog() return - elif controlID == self.BIG_SEEK_LIST_ID: if action in (xbmcgui.ACTION_MOVE_RIGHT, xbmcgui.ACTION_BIG_STEP_FORWARD): return self.updateBigSeek(changed=True) @@ -745,21 +805,24 @@ def onAction(self, action): # Alt-right builtin.PlayerControl('tempoup') elif action == xbmcgui.ACTION_NEXT_ITEM: - self.sendTimeline(state=self.player.STATE_STOPPED) - self._ignoreTick = True - self.killTimeKeeper() - self.handler.next() + self.prepareNewPlayback(queuing_next=True, ignore_tick=True) + self.player.trigger("action", action="next") elif action == xbmcgui.ACTION_PREV_ITEM: - self.sendTimeline(state=self.player.STATE_STOPPED) - self._ignoreTick = True - self.killTimeKeeper() - self.handler.prev() + self.prepareNewPlayback(ignore_tick=True) + self.player.trigger("action", action="prev") if action in cancelActions + (xbmcgui.ACTION_SELECT_ITEM,): - if self.getProperty('show.PPI') and action in cancelActions: - self.hidePPIDialog() - self.hideOSD() - return + if action in cancelActions: + if self.getProperty('show.PPI'): + self.hidePPIDialog() + self.hideOSD() + return + if self._playerDebugActive: + xbmc.executebuiltin('Action(playerdebug)') + self._playerDebugActive = False + return + if self._playerNativePPIActive: + self._playerNativePPIActive = False # immediate marker timer actions if self.countingDownMarker: @@ -775,9 +838,9 @@ def onAction(self, action): # behaviour elif util.addonSettings.skipMarkerTimerImmediate \ and action == xbmcgui.ACTION_SELECT_ITEM and \ - self._currentMarker["countdown"] is not None and \ - self._currentMarker["countdown_initial"] is not None and \ - self._currentMarker["countdown"] < self._currentMarker["countdown_initial"]: + self._currentMarker["countdown"] is not None: + #self._currentMarker["countdown_initial"] is not None and \ + #self._currentMarker["countdown"] < self._currentMarker["countdown_initial"]: self.displayMarkers(immediate=True) self.hideOSD(skipMarkerFocus=True) return @@ -807,7 +870,15 @@ def onAction(self, action): if not self.playlistDialogVisible: self.hideOSD() else: - self.sendTimeline(state=self.player.STATE_STOPPED, ensureFinalTimelineEvent=True) + # were we in a credits marker that has been canceled? use its endTime for our timeline + # event + t = None + if (self._currentMarker and self._currentMarker["marker_type"] == "credits" and + self._currentMarker["hidden"]): + util.DEBUG_LOG("Using credits marker's endtime for timeline event as it's been " + "skipped and we're stopping playback") + t = self._currentMarker["marker"].endTimeOffset + self.sendTimeline(state=self.player.STATE_STOPPED, t=t, ensureFinalTimelineEvent=True) self.stop() return except: @@ -883,7 +954,15 @@ def onClick(self, controlID): # in that case, don't show the OSD if not self._currentMarker or not util.addonSettings.skipMarkerTimerImmediate or \ self._currentMarker["countdown"] is None: - self.showOSD() + # check if fast pause or resume are enabled and act accordingly instead of showing OSD + if "paused" in self.fastPauseResume and self.player.playState == self.player.STATE_PAUSED: + self.player.pause() + return + elif "playing" in self.fastPauseResume and self.player.playState == self.player.STATE_PLAYING: + self.player.pause() + return + else: + self.showOSD() else: # currently seeking without the OSD, apply the seek self.doSeek() @@ -900,17 +979,13 @@ def onClick(self, controlID): elif controlID == self.SHUFFLE_BUTTON_ID: self.shuffleButtonClicked() elif controlID == self.PREV_BUTTON_ID: - self.sendTimeline(state=self.player.STATE_STOPPED) - self._ignoreTick = True - self.handler.prev() + self.prepareNewPlayback(ignore_tick=True) + self.player.trigger("action", action="prev") elif controlID == self.NEXT_BUTTON_ID: if not self.handler.queuingNext: - self.sendTimeline(state=self.player.STATE_STOPPED) - self.handler.queuingNext = True - self._ignoreTick = True - self._ignoreInput = True - self.killTimeKeeper() - next(self.handler) + self.sendTimeline(state=self.player.STATE_STOPPED, ensureFinalTimelineEvent=True) + self.prepareNewPlayback(queuing_next=True, ignore_tick=True, ignore_input=True) + self.player.trigger("action", action="next") return elif controlID == self.PLAYLIST_BUTTON_ID: self.showPlaylistDialog() @@ -932,10 +1007,21 @@ def stop(self): self.handler.stoppedManually = True self.handler.player.stop() - def doClose(self, delete=False): + def prepareNewPlayback(self, queuing_next=False, queuing_specific=False, ignore_tick=False, ignore_input=False, + with_timeline=True): + if with_timeline: + self.sendTimeline(state=self.player.STATE_STOPPED) + self._ignoreTick = ignore_tick + self._ignoreInput = ignore_input + self.handler.queuingNext = queuing_next + self.handler.queuingSpecific = queuing_specific + self.killTimeKeeper() + + def doClose(self, delete=False, **kw): util.DEBUG_LOG("SeekDialog: Closing") if self.handler.playlist: self.handler.playlist.off('change', self.updateProperties) + self.handler.playlist.off('current.changed', self.updateProperties) try: if self.playlistDialog: @@ -943,13 +1029,26 @@ def doClose(self, delete=False): if delete: del self.playlistDialog self.playlistDialog = None + self.playlistDialogVisible = False util.garbageCollect() self.killTimeKeeper() finally: kodigui.BaseDialog.doClose(self) - def showPPIDialog(self): + def showPPIDialog(self, real_ppi=False, debug=False): + from lib.cache import kcm + if self.getProperty('show.PPI'): + if real_ppi: + self.setProperty('show.PPI', '') + if debug: + xbmc.executebuiltin('Action(playerdebug)') + self._playerDebugActive = True + else: + xbmc.executebuiltin('Action(playerprocessinfo)') + self._playerNativePPIActive = True + return + for attrib in SESSION_ATTRIBUTE_TYPES.values(): self.setProperty('ppi.%s' % attrib.label, "") @@ -957,8 +1056,7 @@ def showPPIDialog(self): self.setProperty('ppi.Status', 'Loading ...') def getVideoSession(currentVideo): - return currentVideo.server.findVideoSession(currentVideo.settings.getGlobal("clientIdentifier"), - currentVideo.ratingKey) + return currentVideo.server.findVideoSession(self.handler.sessionID, currentVideo.ratingKey) if util.KODI_BUILD_NUMBER < 2090821: try: @@ -967,8 +1065,20 @@ def getVideoSession(currentVideo): except: pass - while not self.player.started: + self.setProperty('ppi.BufferMB', str(kcm.memorySize)) + if kcm.readFactor > 0: + self.setProperty('ppi.ReadFactor', str(kcm.readFactor)) + else: + self.setProperty('ppi.AReadFactor', 'Adaptive') + + tries = 0 + while not self.player.started and tries < 50: util.MONITOR.waitForAbort(0.1) + tries += 1 + + if tries >= 50: + self.hidePPIDialog() + return info = None currentVideo = self.player.video @@ -987,7 +1097,7 @@ def getVideoSession(currentVideo): elapsed += 0.5 # fill attributes - info = VideoSessionInfo(videoSession, currentVideo) + info = VideoSessionInfo(videoSession, currentVideo, plexapp.SERVERMANAGER.selectedServer.anyLANConnection) except ServerNotOwned: # timeline response data fallback @@ -1000,7 +1110,9 @@ def getVideoSession(currentVideo): util.MONITOR.waitForAbort(0.1) elapsed += 0.1 - info = VideoSessionInfo(None, currentVideo, incompleteSessionData=self.lastTimelineResponse) + info = VideoSessionInfo(None, currentVideo, + plexapp.SERVERMANAGER.selectedServer.anyLANConnection, + incompleteSessionData=self.lastTimelineResponse) except NotFound: self.setProperty('ppi.Status', 'Info not available (data not found)') @@ -1008,6 +1120,7 @@ def getVideoSession(currentVideo): util.ERROR() except NotFound: + util.DEBUG_LOG("PPI: Couldn't find session: {}", self.handler.sessionID) self.setProperty('ppi.Status', 'Info not available (session not found)') except: @@ -1181,8 +1294,10 @@ def videoSettingsHaveChanged(self): self.initialVideoSettings = dict(self.player.video.settings.prefOverrides) self.initialAudioStream = self.player.video.selectedAudioStream() - sss = self.player.video.selectedSubtitleStream() + sss = self.player.video.selectedSubtitleStream(deselect_subtitles=util.getSetting("disable_subtitle_languages")) if sss != self.initialSubtitleStream: + util.DEBUG_LOG("Subtitle changed from {} to {} (deselect: {})", self.initialSubtitleStream, sss, + util.getSetting("disable_subtitle_languages")) self.initialSubtitleStream = sss changed.subtitle = True if self.isTranscoded: @@ -1230,6 +1345,8 @@ def optionsButtonClicked(self): # Button currently commented out. def subtitleButtonClicked(self): options = [] + sss = self.player.video.selectedSubtitleStream() + if self.isDirectPlay: options.append({'key': 'download', 'display': T(32405, 'Download Subtitles')}) @@ -1239,6 +1356,7 @@ def subtitleButtonClicked(self): selectIndex = 0 if self.player.video.hasSubtitles: + subsEnabled = xbmc.getCondVisibility('VideoPlayer.SubtitlesEnabled') and self.player.video.hasSubtitle if self.player.video.hasSubtitle: options.append({'key': 'delay', 'display': T(32406, 'Subtitle Delay')}) @@ -1264,17 +1382,30 @@ def subtitleButtonClicked(self): elif self.lastSubtitleNavAction == "download": selectIndex = 0 + if subsEnabled: + if sss and sss.canAutoSync.asBool(): + if sss.force_auto_sync is None: + auto_sync = self.player.video.playbackSettings.auto_sync + sss.should_auto_sync = auto_sync + options.append( + { + 'key': 'auto_sync', + 'display': + sss.should_auto_sync and + T(33658, 'Disable Auto-Sync') or T(33657, 'Enable Auto-Sync') + } + ) + options.append( { 'key': 'enable', - 'display': - xbmc.getCondVisibility('VideoPlayer.SubtitlesEnabled') and self.player.video.hasSubtitle and - T(32408, 'Disable Subtitles') or T(32409, 'Enable Subtitles') + 'display': subsEnabled and + T(32408, 'Disable Subtitles') or T(32409, 'Enable Subtitles') } ) # cheap and inaccurate approach to move the dropdown to the left based on how many buttons the user has hidden - choice = dropdown.showDropdown(options, (1360 - self.subtitleButtonLeft, 1060), close_direction='down', pos_is_bottom=True, + choice = dropdown.showDropdown(options, (1360 - self.subtitleButtonLeft, 1060), pos_is_bottom=True, close_on_playback_ended=True, select_index=selectIndex) if not choice: @@ -1282,44 +1413,81 @@ def subtitleButtonClicked(self): if choice['key'] == 'download': self.hideOSD() - if self.handler and self.handler.player and self.handler.player.playerObject \ - and util.getSetting('calculate_oshash', False): - meta = self.handler.player.playerObject.metadata - if not meta.size: - util.LOG("Can't calculate OpenSubtitles hash because we're transcoding") + subs_dl_source = util.getSetting('subtitle_download_from') + if subs_dl_source == 'ask': + button = optionsdialog.show( + T(33693, 'Download subtitles using'), + T(33704, 'Using which service?'), + 'Plex', + 'Kodi' + ) - else: - oss_hash = util.getOpenSubtitlesHash(meta.size, meta.streamUrls[0]) - if oss_hash: - util.DEBUG_LOG("OpenSubtitles hash: {}", oss_hash) - util.setGlobalProperty("current_oshash", oss_hash, base='videoinfo.{0}') - else: - util.setGlobalProperty("current_oshash", '', base='videoinfo.{0}') - self.lastSubtitleNavAction = "download" - - # remove the Year info from the current video info tag for better OSS search results - t = self.player.getVideoInfoTag() - changed_info_tag = False - item = xbmcgui.ListItem() - item.setPath(self.player.getPlayingFile()) - if t: - year = t.getYear() - if year: - item.setInfo("video", {"year": 0}) - self.player.updateInfoTag(item) - changed_info_tag = year + subs_dl_source = button == 0 and 'plex' or 'kodi' - builtin.ActivateWindow('SubtitleSearch') - # wait for the window to activate - while not xbmc.getCondVisibility('Window.IsActive(SubtitleSearch)'): - util.MONITOR.waitForAbort(0.1) - # wait for the window to close - while xbmc.getCondVisibility('Window.IsActive(SubtitleSearch)'): - util.MONITOR.waitForAbort(0.1) - if changed_info_tag: - item.setInfo("video", {"year": changed_info_tag}) - self.player.updateInfoTag(item) + if subs_dl_source == 'plex': + was_playing = False + if self.player.playState == self.player.STATE_PLAYING: + was_playing = True + self.player.pause() + downloaded = self.downloadPlexSubtitles(self.player.video) + if downloaded: + self.setSubtitles(honor_forced_subtitles_override=False, + honor_deselect_subtitles=False, ref=None) + elif downloaded is None: + if util.getSetting('subtitle_download_fallback'): + subs_dl_source = 'kodi' + if was_playing and self.player.playState == self.player.STATE_PAUSED: + self.player.pause() + + if subs_dl_source == 'kodi': + if self.handler and self.handler.player and self.handler.player.playerObject \ + and util.getSetting('calculate_oshash'): + meta = self.handler.player.playerObject.metadata + if not meta.size: + util.LOG("Can't calculate OpenSubtitles hash because we're transcoding") + + else: + oss_hash = util.getOpenSubtitlesHash(meta.size, meta.streamUrls[0]) + if oss_hash: + util.DEBUG_LOG("OpenSubtitles hash: {}", oss_hash) + util.setGlobalProperty("current_oshash", oss_hash, base='videoinfo.{0}') + else: + util.setGlobalProperty("current_oshash", '', base='videoinfo.{0}') + self.lastSubtitleNavAction = "download" + + # remove the Year info from the current video info tag for better OSS search results + t = self.player.getVideoInfoTag() + changed_info_tag = False + item = xbmcgui.ListItem() + item.setPath(self.player.getPlayingFile()) + if t: + year = t.getYear() + if year: + if util.KODI_VERSION_MAJOR >= 20: + vi = item.getVideoInfoTag() + vi.setYear(0) + else: + item.setInfo("video", {"year": 0}) + util.DEBUG_LOG("Removing videoInfo year for subtitle search") + self.player.updateInfoTag(item) + changed_info_tag = year + + builtin.ActivateWindow('SubtitleSearch') + # wait for the window to activate + while not xbmc.getCondVisibility('Window.IsActive(SubtitleSearch)'): + util.MONITOR.waitForAbort(0.1) + # wait for the window to close + while xbmc.getCondVisibility('Window.IsActive(SubtitleSearch)'): + util.MONITOR.waitForAbort(0.1) + + if changed_info_tag: + if util.KODI_VERSION_MAJOR >= 20: + vi = item.getVideoInfoTag() + vi.setYear(changed_info_tag) + else: + item.setInfo("video", {"year": changed_info_tag}) + self.player.updateInfoTag(item) elif choice['key'] == 'delay': self.hideOSD() @@ -1332,8 +1500,32 @@ def subtitleButtonClicked(self): self.cycleSubtitles(forward=False) self.lastSubtitleNavAction = "backward" elif choice['key'] == 'enable': + if self.player.playState == self.player.STATE_PLAYING: + self.hideOSD() enabled = self.toggleSubtitles() self.lastSubtitleNavAction = "forward" + elif choice['key'] == 'auto_sync': + should_auto_sync = not sss.should_auto_sync_unforced + + # force auto sync for session + if sss.force_auto_sync is not None: + sss.force_auto_sync = not sss.force_auto_sync + else: + sss.force_auto_sync = should_auto_sync + sss.should_auto_sync = should_auto_sync + util.DEBUG_LOG("Setting subtitle auto-sync for session to: {}".format(sss.should_auto_sync)) + + # self.player.video isn't the same as the mediachoice representation + if self.player.playerObject.choice.subtitleStream: + self.player.playerObject.choice.subtitleStream.should_auto_sync = sss.should_auto_sync + self.player.playerObject.choice.subtitleStream.force_auto_sync = sss.force_auto_sync + if self.player.playState == self.player.STATE_PLAYING: + self.hideOSD() + if self.isDirectPlay: + self.setSubtitles(honor_forced_subtitles_override=False, honor_deselect_subtitles=False) + else: + self.doSeek(self.trueOffset(), settings_changed=True) + self.lastSubtitleNavAction = "auto_sync" def toggleSubtitles(self): """ @@ -1343,12 +1535,19 @@ def toggleSubtitles(self): self.disableSubtitles() return False else: - self.cycleSubtitles() + self.enableSubtitles() return True def disableSubtitles(self): - self.player.video.disableSubtitles() + self.player.video.disableSubtitles(sync_to_server=False) + self.setSubtitles() + if self.isTranscoded: + self.doSeek(self.trueOffset(), settings_changed=True) + + def enableSubtitles(self): + stream = self.player.video.enableSubtitles(sync_to_server=False) self.setSubtitles() + util.showNotification(str(stream), time_ms=1500, header=util.T(32396, "Subtitles")) if self.isTranscoded: self.doSeek(self.trueOffset(), settings_changed=True) @@ -1356,14 +1555,16 @@ def cycleSubtitles(self, forward=True): """ Selects the first subtitle or the next one """ - stream = self.player.video.cycleSubtitles(forward=forward) - self.setSubtitles(honor_forced_subtitles_override=False) + stream = self.player.video.cycleSubtitles(forward=forward, sync_to_server=False) + self.setSubtitles(honor_forced_subtitles_override=False, honor_deselect_subtitles=False) util.showNotification(str(stream), time_ms=1500, header=util.T(32396, "Subtitles")) if self.isTranscoded: self.doSeek(self.trueOffset(), settings_changed=True) - def setSubtitles(self, do_sleep=False, honor_forced_subtitles_override=False): - self.handler.setSubtitles(do_sleep=do_sleep, honor_forced_subtitles_override=honor_forced_subtitles_override) + def setSubtitles(self, do_sleep=False, honor_forced_subtitles_override=False, honor_deselect_subtitles=False, + ref="_current_subtitle_idx"): + self.handler.setSubtitles(do_sleep=do_sleep, honor_forced_subtitles_override=honor_forced_subtitles_override, + honor_deselect_subtitles=honor_deselect_subtitles, ref=ref) if self.player.video.current_subtitle_is_embedded: # this is an embedded stream, seek back a second after setting the subtitle due to long standing kodi # issue: https://github.com/xbmc/xbmc/issues/21086 @@ -1374,12 +1575,13 @@ def setSubtitles(self, do_sleep=False, honor_forced_subtitles_override=False): util.DEBUG_LOG("Waiting for seekOnStart to apply: {}", self.handler.seekOnStart) waited = 0 - while self.handler.seekOnStart and waited < 20: - xbmc.sleep(100) + while self.handler.seekOnStart and waited < 40 and not util.MONITOR.abortRequested(): + util.MONITOR.waitForAbort(0.1) waited += 1 - if waited < 20: - self.doSeek(max(self.trueOffset() - 100, 100)) + if waited < 40: + seekBack = 1500 if self.useAlternateSeek else 100 + self.doSeek(max(self.trueOffset() - seekBack, seekBack)) return util.LOG("Tried switching embedded subtitle stream to the correct one, but we've waited too long for " "seekOnStart.") @@ -1412,11 +1614,11 @@ def showSettings(self): # On CoreELEC changing the audio stream causes the audio to stutter or delay # so this small seek helps sync things back up. But we also need to pause the # video for a short time if it's playing or the seek doesn't work - if util.isCoreELEC and changed.audio: + if self.useAlternateSeek and changed.audio: if not xbmc.getCondVisibility('Player.Paused'): self.videoPausedForAudioStreamChange = True self.handler.player.control('pause') - self.doSeek(offset=((self.handler.player.getTime() * 1000) - 1000)) + self.doSeek(offset=max(self.handler.player.getTime() * 1000 - 1500, 1500)) return True util.LOG("Media settings have changed and are not directly applicable, restarting video: {}", changed) @@ -1450,11 +1652,11 @@ def updateBigSeek(self, changed=False): if changed and not self.showChapters: self.bigSeekChanged = True self.selectedOffset = self.bigSeekControl.getSelectedItem().dataSource + self.bigSeekOffset - self.updateProgress(set_to_current=False) + self.updateProgress(set_to_current=False, no_osd=True) elif self.showChapters: # when hovering chapters, show its corresponding time on the timeline, but don't act like we're seeking self.updateProgress(set_to_current=False, offset=self.bigSeekControl.getSelectedItem().dataSource, - onlyTimeIndicator=True) + onlyTimeIndicator=True, no_osd=True) self.resetSkipSteps() def bigSeekSelected(self): @@ -1569,7 +1771,7 @@ def updateChapters(self): chaps.append((st, thumb, chapter.tag or T(33607, 'Chapter {}').format(index + 1))) # fake chapters by using markers - if util.getSetting('virtual_chapters', True) and self.markers: + if util.getUserSetting('virtual_chapters', True) and self.markers: if not self.chapters: self.setProperty('chapters.label', T(33606, 'Virtual Chapters').upper()) else: @@ -1592,9 +1794,9 @@ def updateChapters(self): preparedMarkers.append((marker.startTimeOffset, label, True)) # add staggered virtual markers - preparedMarkers.append((int(self.duration * 0.25), "25 %", False)) - preparedMarkers.append((int(self.duration * 0.50), "50 %", False)) - preparedMarkers.append((int(self.duration * 0.75), "75 %", False)) + #preparedMarkers.append((int(self.duration * 0.25), "25 %", False)) + #preparedMarkers.append((int(self.duration * 0.50), "50 %", False)) + #preparedMarkers.append((int(self.duration * 0.75), "75 %", False)) credCnt = 1 for offset, label, credits in sorted(preparedMarkers): @@ -1646,6 +1848,7 @@ def updateChapters(self): def updateCurrent(self, update_position_control=True, atOffset=None): ratio = self.trueOffset() / float(self.duration) + w = None if update_position_control: w = int(ratio * self.SEEK_IMAGE_WIDTH) self.positionControl.setWidth(w) @@ -1653,7 +1856,8 @@ def updateCurrent(self, update_position_control=True, atOffset=None): # update cache/buffer bar if util.addonSettings.playerShowBuffer and self.isDirectPlay and util.KODI_VERSION_MAJOR > 18: cache_w = int(xbmc.getInfoLabel("Player.ProgressCache")) * self.SEEK_IMAGE_WIDTH // 100 - self.cacheControl.setWidth(cache_w) + w = w or self.positionControl.getWidth() + self.cacheControl.setWidth(max(cache_w, w+5)) if self.isTranscoded: to = atOffset if atOffset is not None else self.trueOffset() @@ -1707,7 +1911,8 @@ def seekByOffset(self, offset, auto_seek=False, without_osd=False): self._seeking = True self._seekingWithoutOSD = without_osd - self.selectedOffset += offset + sign = offset > 0 and 1 or -1 + self.selectedOffset += max(abs(offset), self.useAlternateSeek and util.addonSettings.altseekValidSeekWindow or 0) * sign # Don't skip past 5 seconds from end if self.selectedOffset > self.duration - 5000: # offset = +100, at = 80000, duration = 80007, realoffset = 2 @@ -1719,7 +1924,7 @@ def seekByOffset(self, offset, auto_seek=False, without_osd=False): self._forcedLastSkipAmount = 1 - lastSelectedOffset self.selectedOffset = 1 - self.updateProgress(set_to_current=False) + self.updateProgress(set_to_current=False, no_osd=without_osd) self.setBigSeekShift() if auto_seek: self.resetAutoSeekTimer() @@ -1755,7 +1960,7 @@ def duration(self): except RuntimeError: # Not playing return 1 - def updateProgress(self, set_to_current=True, offset=None, onlyTimeIndicator=False): + def updateProgress(self, set_to_current=True, offset=None, onlyTimeIndicator=False, no_osd=False): """ Updates the progress bars (seek and position) and the currently-selected-time-label for the current position or seek state on the timeline. @@ -1764,7 +1969,7 @@ def updateProgress(self, set_to_current=True, offset=None, onlyTimeIndicator=Fal selected position depending on the direction of the seek :return: None """ - if not self.initialized: + if not all((self.initialized, self.handler.player, self.handler.player.playerObject)): return offset = offset if offset is not None else \ @@ -1794,12 +1999,19 @@ def updateProgress(self, set_to_current=True, offset=None, onlyTimeIndicator=Fal self.setProperty('time.selection', util.simplifiedTimeDisplay(offset)) self.selectionIndicatorImage.setWidth(101) + self.setProperty('bif.image', "") if onlyTimeIndicator: return - if self.hasBif: - self.setProperty('bif.image', self.handler.player.playerObject.getBifUrl(offset)) - self.bifImageControl.setPosition(bifx, 752) + if not no_osd or (no_osd and not self.no_time_no_osd_spoilers): + if self.hasBif: + bifUrl = self.handler.player.playerObject.getBifUrl(offset) + if "blur_chapters" in self.no_spoilers: + bifUrl = self.player.video.server.getImageTranscodeURL(bifUrl, + *PlaylistDialog.LI_AR16X9_THUMB_DIM, + **{"blur": util.addonSettings.episodeNoSpoilerBlur}) + self.setProperty('bif.image', bifUrl) + self.bifImageControl.setPosition(bifx, 752) self.seekbarControl.setPosition(0, self.seekbarControl.getPosition()[1]) if set_to_current: @@ -1929,10 +2141,12 @@ def waitForBuffer(self): def seekBehind(self): to = self.trueOffset() + # make sure we're at least seeking behind 1500ms when we're using alternate seek + amount = max(self.resumeSeekBehind, 1500) if self.useAlternateSeek else self.resumeSeekBehind if ((not self.resumeSeekBehindOnlyDP or self.isDirectPlay) - and self.resumeSeekBehind and to > self.resumeSeekBehind): - util.DEBUG_LOG("SeekDialog: Seeking back from {} to {}", to, to - self.resumeSeekBehind) - self.doSeek(to - self.resumeSeekBehind) + and self.resumeSeekBehind and to > amount): + util.DEBUG_LOG("SeekDialog: Seeking back from {} to {}", to, to - amount) + self.doSeek(max(to - amount, 0)) def onPlayBackResumed(self): util.DEBUG_LOG("SeekDialog: OnPlaybackResumed") @@ -1953,7 +2167,7 @@ def onAVChange(self): util.DEBUG_LOG("SeekDialog: OnAVChange: DPO: {0}, offset: {1}", self.DPPlayerOffset, self.offset) # wait for buffer if we're not expecting a seek - if not self.handler.seekOnStart and util.getSetting("slow_connection", False) and not self.waitingForBuffer: + if not self.handler.seekOnStart and util.getSetting("slow_connection") and not self.waitingForBuffer: # fixme: not sure why this is necessary, but something breaks when playing back a next item from playback # that doesn't have a seek value. Adding a slight delay here fixes that. Timing issue? xbmc.sleep(100) @@ -1978,7 +2192,7 @@ def onPlayBackPaused(self): util.DEBUG_LOG("SeekDialog: OnPlaybackPaused") # Need to resume the video when changing streams on CoreELEC - if util.isCoreELEC and self.videoPausedForAudioStreamChange: + if self.useAlternateSeek and self.videoPausedForAudioStreamChange: self.videoPausedForAudioStreamChange = False self.handler.player.control('play') return @@ -2011,7 +2225,16 @@ def syncTimeKeeper(self): if not self.handler.player.playerObject: return - self.timeKeeperTime = self.trueOffset()#int(self.handler.player.getTime() * 1000) + if not self.handler.player.isExternal: + self.timeKeeperTime = self.trueOffset()#int(self.handler.player.getTime() * 1000) + else: + # special case for external players - we don't know the actual progress, but we can make an educated guess + # for the start point + if not self.timeKeeperTime: + self.timeKeeperTime = self.baseOffset or self.handler.seekOnStart + if self.timeKeeperTime is None: + self.timeKeeperTime = 0 + if not self.timeKeeper: self.timeKeeper = plexapp.util.RepeatingCounterTimer(1.0, self.onTimeKeeperCallback) self.onTimeKeeperCallback(tick=False) @@ -2030,21 +2253,32 @@ def onTimeKeeperCallback(self, tick=True): """ called by playbackTimer periodically, sets playback time/ends in UI """ - # we might be a little early on slower systems - if not self.started or not self.handler.player.playerObject: - return + force_tick = self.handler.player.isExternal - if self.stopPlaybackOnIdle: - if self.idleTime and time.time() - self.idleTime >= self.stopPlaybackOnIdle: - util.LOG("Player has been idle for {}s, stopping.", int(time.time() - self.idleTime)) - self.handler.player.stopAndWait() + # we might be a little early on slower systems + if not force_tick: + if not self.started or not self.handler.player.playerObject: return - if not self.idleTime and xbmc.getCondVisibility('Player.Paused'): - self.idleTime = time.time() + if self.stopPlaybackOnIdle: + if self.idleTime and time.time() - self.idleTime >= self.stopPlaybackOnIdle: + util.LOG("Player has been idle for {}s, stopping.", int(time.time() - self.idleTime)) + self.handler.stoppedManually = True + self.sendTimeline(state=self.player.STATE_STOPPED) + self.handler.player.stopAndWait() + return - if tick and xbmc.getCondVisibility('Player.HasVideo + Player.Playing'): + if not self.idleTime and xbmc.getCondVisibility('Player.Paused'): + self.idleTime = time.time() + + # force_tick is enabled when we're using an external player. In this case we simply count the time spent while + # the external player is open and report that to the PMS + if tick and (xbmc.getCondVisibility('Player.HasVideo + Player.Playing') or force_tick): self.timeKeeperTime += 1000 + self.playbackTime += 1000 + + if force_tick: + return # Update buffer state in PPI if open and old Kodi version if util.KODI_BUILD_NUMBER < 2090821 and self.getProperty('show.PPI'): @@ -2074,10 +2308,45 @@ def countingDownMarker(self, val): self._currentMarker["countdown"] = None self.setProperty('marker.countdown', '') + def handleFinalMarker(self, marker_def, immediate=False, context="MarkerAutoSkip"): + # final marker is _not_ at the end of video, seek and do nothing + if marker_def["marker"].endTimeOffset < self.duration - FINAL_MARKER_NEGOFF: + target = marker_def["marker"].endTimeOffset + util.DEBUG_LOG( + "{}: Skipping final marker, its endTime is too early, " + "though, seeking and playing back", context) + self.doSeek(target) + return False + + # tell plex we've arrived at the end of the video + self.sendTimeline(state=self.player.STATE_STOPPED, t=self.duration - 1000) + + # go to next video immediately (post play or next episode on bingeMode) + if self.handler.playlist and self.handler.playlist.hasNext(): + if self.bingeMode or self.skipPostPlay: + if not self.handler.queuingNext: + # skip final marker + util.DEBUG_LOG("{}: {} final marker, going to next video", context, + immediate and "Immediately skipping" or "Skipping") + self.prepareNewPlayback(queuing_next=True, ignore_tick=True, ignore_input=True, with_timeline=False) + self.player.stop() + return True + else: + util.DEBUG_LOG("{}: Skipping final marker in episode, stopping", context) + self.handler.endedManually = True + self.player.stop() + return True + else: + util.DEBUG_LOG("{}: Skipping final marker, stopping", context) + self.stop() + return False + def sendTimeline(self, state=None, t=None, ensureFinalTimelineEvent=True): self.handler.updateNowPlaying(state=state, t=t, overrideChecks=True) if ensureFinalTimelineEvent: self.handler.ignoreTimelines = True + # kill previous timeline data + plexapp.util.APP.nowplayingmanager.reset() def displayMarkers(self, cancelTimer=False, immediate=False, onlyReturnIntroMD=False, setSkipped=False, offset=None): @@ -2092,6 +2361,7 @@ def displayMarkers(self, cancelTimer=False, immediate=False, onlyReturnIntroMD=F # this might be counter intuitive, but self._currentMarker is a reference to a dict if self._currentMarker: self._currentMarker["countdown"] = None + setattr(self, self._currentMarker["markerAutoSkipShownTimer"], None) self._currentMarker = None return False @@ -2106,6 +2376,14 @@ def displayMarkers(self, cancelTimer=False, immediate=False, onlyReturnIntroMD=F not self.handler.playlist.hasNext(): markerAutoSkip = False + # hint handler + if markerDef["marker_type"] == "credits": + if not self.handler.creditMarkerHit: + self.handler.creditMarkerHit = "first" + else: + if getattr(markerDef["marker"], "final", False): + self.handler.creditMarkerHit = "final" + markerAutoSkipped = markerDef["markerAutoSkipped"] sTOffWThres = startTimeOff + util.addonSettings.autoSkipOffset * 1000 @@ -2121,6 +2399,7 @@ def displayMarkers(self, cancelTimer=False, immediate=False, onlyReturnIntroMD=F if cancelTimer and self.countingDownMarker: self.countingDownMarker = False markerDef["markerAutoSkipped"] = True + markerDef["hidden"] = True setattr(self, markerDef["markerAutoSkipShownTimer"], None) self.setProperty('show.markerSkip', '') return False @@ -2144,34 +2423,7 @@ def displayMarkers(self, cancelTimer=False, immediate=False, onlyReturnIntroMD=F self.countingDownMarker = False if getattr(markerDef["marker"], "final", False): - # final marker is _not_ at the end of video, seek and do nothing - if markerDef["marker"].endTimeOffset < self.duration - FINAL_MARKER_NEGOFF: - target = markerDef["marker"].endTimeOffset - util.DEBUG_LOG( - "MarkerAutoSkip: Skipping final marker, its endTime is too early, " - "though, seeking and playing back") - self.doSeek(target) - return False - - # tell plex we've arrived at the end of the video, playing back - self.sendTimeline(state=self.player.STATE_STOPPED, t=self.duration - 1000) - - # go to next video immediately if on bingeMode - if self.handler.playlist and self.handler.playlist.hasNext(): - if not self.handler.queuingNext: - # skip final marker - util.DEBUG_LOG("MarkerAutoSkip: {} final marker, going to next video".format( - immediate and "Immediately skipping" or "Skipping")) - self.handler.queuingNext = True - self._ignoreTick = True - self._ignoreInput = True - self.killTimeKeeper() - self.player.stop() - return True - else: - util.DEBUG_LOG("MarkerAutoSkip: Skipping final marker, stopping") - self.stop() - return False + return self.handleFinalMarker(markerDef, immediate=immediate) util.DEBUG_LOG('MarkerAutoSkip: Skipping marker {}', markerDef["marker"]) self.doSeek(markerDef["marker"].endTimeOffset + MARKER_END_JUMP_OFF) @@ -2179,6 +2431,7 @@ def displayMarkers(self, cancelTimer=False, immediate=False, onlyReturnIntroMD=F # got a marker, display logic # hide marker into OSD after a timeout + # fixme: "markerAutoSkipShownTimer" should be "markerSkipShownTimer" timer = getattr(self, markerDef["markerAutoSkipShownTimer"]) if timer is None or self.player.playState == self.player.STATE_PAUSED: @@ -2189,12 +2442,15 @@ def displayMarkers(self, cancelTimer=False, immediate=False, onlyReturnIntroMD=F if not self.getProperty('show.markerSkip_OSDOnly'): if timer + getattr(self, markerDef["markerSkipBtnTimeout"]) <= time.time(): self.setProperty('show.markerSkip_OSDOnly', '1') + markerDef["hidden"] = True else: self.setProperty('show.markerSkip_OSDOnly', '') # no marker auto skip and not yet skipped or not yet auto skipped, normal display if (markerAutoSkip and not markerAutoSkipped) or (not markerAutoSkip and not markerDef["skipped"]): self.setProperty('show.markerSkip', '1') + if not markerDef["hidden"]: + self.setProperty('show.markerSkip_OSDOnly', '') # marker auto skip and already skipped, or no autoskip and manually skipped - hide in OSD else: self.setProperty('show.markerSkip_OSDOnly', '1') @@ -2247,6 +2503,9 @@ def tick(self, offset=None, waitForBuffer=False): Called ~1/s; can be wildly inaccurate. """ + if self.handler and self.handler.player and self.handler.player.isExternal: + return + # we might be called with an offset for seekOnStart even before we're initialized (onFirstInit) # in that case, skip all functionality and just seekOnStart if (not offset and not self.initialized) or self._ignoreTick: @@ -2264,13 +2523,16 @@ def tick(self, offset=None, waitForBuffer=False): self.pausedAt = None return + if self.player.playState == self.player.STATE_PLAYING: + self.idleTime = None + # invisibly sync low-drift timer to current playback every X seconds, as Player.getTime() can be wildly off if self.ldTimer and not self.osdVisible() and self.timeKeeper and self.timeKeeper.ticks >= 60: self.syncTimeKeeper() cancelTick = False # don't auto skip while we're initializing and waiting for the handler to seek on start - if offset is None and not self.handler.seekOnStart: + if offset is None and not self.handler.seekOnStart and not self.handler.waitingForSOS: cancelTick = self.displayMarkers() if cancelTick: @@ -2281,7 +2543,10 @@ def tick(self, offset=None, waitForBuffer=False): xbmc.executebuiltin('Dialog.Close(busydialog,1)') if not self.hasDialog and not self.playlistDialogVisible and self.osdVisible(): - if time.time() > self.timeout: + t = time.time() + # with a customizable OSD hide timeout, OSD hide timeout might happen before autoSeekTimeout; + # in case we're still waiting for a seek, postpone OSD hiding + if t > self.timeout and (not self.autoSeekTimeout or self.autoSeekTimeout < self.timeout < t): xbmc.executebuiltin('Dialog.Close(videoosd,true)') xbmc.executebuiltin('Dialog.Close(seekbar,true)') if not xbmc.getCondVisibility('Window.IsActive(videoosd) | Player.Rewinding | Player.Forwarding'): @@ -2296,11 +2561,13 @@ def tick(self, offset=None, waitForBuffer=False): if offset or (self.autoSeekTimeout and time.time() >= self.autoSeekTimeout and self.offset != self.selectedOffset): - self.resetAutoSeekTimer(None) #off = offset is not None and offset or None #self.doSeek(off) - self.doSeek() - return True + if not self.useAlternateSeek or (((self.selectedOffset and abs(self.selectedOffset - self.offset) >= util.addonSettings.altseekValidSeekWindow) or not self.selectedOffset) and not self.handler.waitingForSOS): + util.DEBUG_LOG("SeekDialog: Tick: Seek: {}, {}, {}", self.offset, self.selectedOffset, util.addonSettings.altseekValidSeekWindow) + self.resetAutoSeekTimer(None) + self.doSeek() + return True if self.isDirectPlay or not self.ldTimer: self.updateCurrent(update_position_control=not self._seeking and not self._applyingSeek) @@ -2315,14 +2582,15 @@ def playlistDialogVisible(self, value): self.setProperty('playlist.visible', '1' if value else '') def showPlaylistDialog(self): - if not self.playlistDialog: - self.playlistDialog = PlaylistDialog.create(show=False, handler=self.handler) - + self.playlistDialog = PlaylistDialog.create(show=False, handler=self.handler, item_states=self._item_states) self.playlistDialogVisible = True self.playlistDialog.doModal() self.resetTimeout() + if self.playlistDialog: + self.playlistDialog.doClose() + self.setFocusId(self.PLAYLIST_BUTTON_ID) + self.playlistDialog = None self.playlistDialogVisible = False - self.setFocusId(self.PLAYLIST_BUTTON_ID) def osdVisible(self): return xbmc.getCondVisibility('Control.IsVisible(801)') @@ -2337,6 +2605,7 @@ def showOSD(self, focusButton=True): self.setFocusId(self.PLAY_PAUSE_BUTTON_ID) def hideOSD(self, skipMarkerFocus=False, closing=False): + util.DEBUG_LOG("SeekDialog: HideOSD: {}, {}", skipMarkerFocus, closing) self.setProperty('show.OSD', '') if closing: return @@ -2352,6 +2621,7 @@ def hideOSD(self, skipMarkerFocus=False, closing=False): if self.playlistDialog: self.playlistDialog.doClose() self.playlistDialogVisible = False + self.playlistDialog = None class PlaylistDialog(kodigui.BaseDialog, SpoilersMixin): @@ -2366,11 +2636,13 @@ class PlaylistDialog(kodigui.BaseDialog, SpoilersMixin): LI_SQUARE_THUMB_DIM = (100, 100) PLAYLIST_LIST_ID = 101 + PLAYLIST_SCROLLBAR_ID = 152 def __init__(self, *args, **kwargs): kodigui.BaseDialog.__init__(self, *args, **kwargs) SpoilersMixin.__init__(self, *args, **kwargs) self.handler = kwargs.get('handler') + self.item_states = kwargs.get('item_states', {}) self.playlist = self.handler.playlist def onFirstInit(self): @@ -2385,19 +2657,36 @@ def onReInit(self): self.updatePlayingItem() self.setFocusId(self.PLAYLIST_LIST_ID) + def doClose(self, **kw): + if self.handler: + self.handler.player.off('playlist.changed', self.playQueueCallback) + self.handler.player.off('session.ended', self.sessionEnded) + self.handler = None + self.playlist = None + super(PlaylistDialog, self).doClose() + def onClick(self, controlID): if controlID == self.PLAYLIST_LIST_ID: self.playlistListClicked() + def onAction(self, action): + controlID = self.getFocusId() + if action == xbmcgui.ACTION_MOVE_LEFT: + if controlID == self.PLAYLIST_LIST_ID: + self.doClose() + return + elif controlID == self.PLAYLIST_SCROLLBAR_ID: + self.setFocusId(self.PLAYLIST_LIST_ID) + super(PlaylistDialog, self).onAction(action) + def playlistListClicked(self): mli = self.playlistListControl.getSelectedItem() if not mli: return - self.handler.playAt(mli.pos()) - self.updatePlayingItem() + self.handler.player.trigger("action", action="playAt", pos=mli.pos()) def sessionEnded(self, **kwargs): - util.DEBUG_LOG('Video OSD: Session ended - closing') + util.DEBUG_LOG('PlaylistDialog: Session ended - closing') self.doClose() def createListItem(self, pi): @@ -2477,9 +2766,17 @@ def fillPlaylist(self): items = [] idx = 1 for pi in self.playlist.items(): + # mark watched items in playlist during current playback session + util.DEBUG_LOG("ROLLER: %r %r %r" % (self.handler.getProgressForItem(str(pi.ratingKey), None), self.handler._progressHld, pi.ratingKey)) + if self.handler.getProgressForItem(str(pi.ratingKey), None) is True: + pi.set('viewCount',pi.get('viewCount', 0).asInt() + 1) + pi.set('viewOffset', 0) + mli = self.createListItem(pi) if mli: mli.setProperty('track.number', str(idx)) + mli.setProperty('progress', util.getProgressImage(mli.dataSource, + view_offset=self.handler.getProgressForItem(str(pi.ratingKey), None))) items.append(mli) idx += 1 diff --git a/script.plexmod/lib/windows/settings.py b/script.plexmod/lib/windows/settings.py index b02f17235b..11a6dcbfe4 100644 --- a/script.plexmod/lib/windows/settings.py +++ b/script.plexmod/lib/windows/settings.py @@ -4,12 +4,15 @@ import json import sys +import datetime +import types import plexnet from kodi_six import xbmc from kodi_six import xbmcgui from kodi_six import xbmcaddon from threading import Timer +from iso639 import languages import lib.cache from lib import util @@ -22,6 +25,8 @@ UNDEF = "__UNDEF__" +#MAIN_LANGUAGES = [l for l in languages if l.part1] +#SEP_LANGUAGES= [l for l in languages if l.part2t and l not in MAIN_LANGUAGES] class Setting(object): type = None @@ -32,10 +37,14 @@ class Setting(object): userAware = False isThemeRelevant = False backport_from = None + show_cb = None def translate(self, val): return str(val) + def should_show(self): + return self.show_cb() if self.show_cb else True + def get(self, *args, **kwargs): _id = kwargs.pop("_id", UNDEF) use_id = _id if _id != UNDEF else self.ID @@ -86,13 +95,16 @@ def __repr__(self): class BasicSetting(Setting): - def __init__(self, ID, label, default, desc='', theme_relevant=False, backport_from=None): + def __init__(self, ID, label, default, desc='', theme_relevant=False, backport_from=None, show_cb=None): self.ID = ID self.label = label self.default = default self.desc = desc self.isThemeRelevant = theme_relevant self.backport_from = backport_from + if show_cb: + self.show_cb = show_cb + util.DEFAULT_SETTINGS[ID] = default def description(self, desc): self.desc = desc @@ -119,10 +131,13 @@ def set(self, val, skip_get=False): class QualitySetting(ListSetting): options = ( T(32001), + T(32017), T(32002), + T(32016), T(32003), T(32004), T(32005), + T(32015), T(32006), T(32007), T(32008), @@ -228,6 +243,9 @@ def __init__(self, ID, label, default, options, none_option=None, **kwargs): self.noneOption = none_option util.JSON_SETTINGS.append(self.ID) + # fixme: not sure why we initialize default with an empty list in the supercall + util.DEFAULT_SETTINGS[ID] = default + def get(self, *args, **kwargs): with_default = kwargs.pop('with_default', True) val = super(MultiOptionsSetting, self).get(*args, default=DEFAULT, **kwargs) @@ -322,6 +340,8 @@ def __init__(self, ID, label, info): self.info = info def valueLabel(self): + if isinstance(self.info, types.FunctionType): + return self.info() return self.info @@ -407,7 +427,6 @@ def get(self, as_code=False, *args, **kwargs): ak = actions.ActionKey(int(code)) return ak - class Settings(object): SETTINGS = { 'main': ( @@ -429,20 +448,57 @@ class Settings(object): 'search_use_kodi_kbd', T(32955, 'Use Kodi keyboard for searching'), False ), ThemeMusicSetting('theme_music', T(32480, 'Theme music'), 5), - PlayedThresholdSetting('played_threshold', T(33501, 'Video played threshold'), 1).description( + BoolSetting( + 'theme_music_loop', T(33737, 'Loop theme music'), False + ), + PlayedThresholdSetting('played_threshold', T(33501, 'Video played threshold'), 1, + show_cb=lambda: plexnet.plexapp.SERVERMANAGER.selectedServer.prefs.get("LibraryVideoPlayedThreshold", None) is None + ).description( T( 33502, "Set this to the same value as your Plex server (Settings>Library>Video played threshold) to av" "oid certain pitfalls, Default: 90 %" ) - ) + ), + OptionsSetting( + 'played_threshold_behaviour', + T(34022, 'Video play completion behaviour'), + 3, + ( + (0, T(34024, 'at selected threshold percentage')), + (1, T(34025, 'at final credits marker position')), + (2, T(34025, 'at first credits marker position')), + (3, T(34026, 'earliest between threshold percent and first credits marker')), + ), + show_cb=lambda: plexnet.plexapp.SERVERMANAGER.selectedServer.prefs.get( + "LibraryVideoPlayedAtBehaviour", None) is None + ).description(T(34023, "Decide whether to use end credits markers to determine the 'watched' " + "state of video items. When markers are not available the selected threshold " + "percentage will be used.")), + BoolSetting('use_alternate_seek2', T(33667, 'Use alternate seek'), util.altSeekRecommended).description( + T(33668, 'ATTENTION: Only enable this if you have reproducible audio issues after ' + 'seeking/resuming.\n\nUse an alternative seek method in videos, which can help in ' + 'problematic scenarios; brings its own issues/quirks. Disabled by default (enabled by ' + 'default for CoreELEC and LG WebOS)' + )), + BoolSetting( + 'assume_resume', T(33711, 'Always resume media'), True + ).description( + T(33712, 'When playback of an in-progress media is requested, resume it by default instead' + ' of asking whether to resume or start from the beginning.') + ), + BoolSetting( + 'home_inprogress_resume', T(33713, 'Home: Resume in-progress items'), False + ).description( + T(33714, 'Resume in-progress items directly instead of visiting the media.') + ), ) ), 'video': ( T(32053, 'Video'), ( - QualitySetting('local_quality', T(32020, 'Local Quality'), 13), - QualitySetting('remote_quality', T(32021, 'Remote Quality'), 13), - QualitySetting('online_quality', T(32022, 'Online Quality'), 13), + QualitySetting('local_quality2', T(32020, 'Local Quality'), 16), + QualitySetting('remote_quality2', T(32021, 'Remote Quality'), 16), + QualitySetting('online_quality2', T(32022, 'Online Quality'), 16), MultiOptionsSetting( 'playback_features', T(33058, ''), ["playback_directplay", "playback_remux", "allow_4k"], @@ -456,6 +512,15 @@ class Settings(object): desc_ds=T(32979, ''), feature_4k=T(32036, ''), desc_4k=T(32102, ''))), + BoolSetting( + 'disable_hdr', T(33660, 'Disable HDR'), False + ).description(T(33661, "If you don't want your client to handle HDR (or HDR-fallback), " + "enable this to force transcoding. Doesn't apply to DV Profile 5.") + ), + BoolSetting( + 'clamp_video_bitrates', T(33685, 'Clamp video bitrate'), True + ).description(T(33686, "Only show bitrate targets lower than the current video's bitrate.") + ), MultiOptionsSetting( 'allowed_codecs', T(33059, ''), ["allow_hevc", "allow_vc1"], @@ -496,10 +561,54 @@ class Settings(object): T(32065, 'When force AC3 settings are enabled, treat DTS the same as AC3 ' '(useful for Optical passthrough)') ), + MultiOptionsSetting( + 'audio_disabled_codecs', T(33665, 'Disable audio codecs'), + [], + list(sorted([(a, "{} ({})".format(b, a)) for a, b in plexnet.util.AUDIO_CODECS_VERB.items()])) + ).description( + T(33666, "Audio codecs you can't play back. Disables Direct Play for such media items, " + "enables Direct Stream if possible, transcodes audio stream to compatible format.") + ), + OptionsSetting( + 'audio_transcode_codec', T(33733, 'Transcode target codec'), + "default", + [("default", T(32030, 'Auto'))] + list(sorted([(a, "{} ({})".format(b, a)) for a, b in plexnet.util.AUDIO_CODECS_TC_VERB.items()])) + ).description( + T(33734, "Sets the target codec when transcoding/direct streaming. Overridden when " + "\"Transcode audio to AC3\" is set.") + ), BoolSetting('audio_hires', T(33079, ''), True).description( T(33080, '') ), + MultiOptionsSetting( + 'disable_subtitle_languages', T(33691, "Native languages"), + [], + [(b, a) for a, b in sorted([(l.name, l.part2t) for l in languages if l.part1])] # + + # [(b, a) for a, b in sorted([(l.name, l.part2t) for l in SEP_LANGUAGES])] + ).description( + T(33692, "When you usually watch things in a different language with subtitles, but are a" + " native speaker of other languages, which you don't need subtitles for, prevent Plex " + "from auto-selecting subtitles for those languages.") + ), + OptionsSetting( + 'subtitle_download_from', + T(33693, 'Download subtitles using'), + 'plex', + ( + ('ask', T(33694, 'Ask')), + ('plex', 'Plex'), + ('kodi', 'Kodi'), + ) + ).description(T(33695, "Where do you want to download subtitles from? Note: Currently this " + "only applies to the subtitle quick actions in the player. The subtitle download" + " in stream settings always uses Plex as a source.")), + BoolSetting('subtitle_download_fallback', T(33701, 'Fallback to Kodi'), + True).description( + T(33702, "When no subtitles are found via the Plex subtitle search, fall back to Kodi " + "subtitle search. Note: Currently this only applies to the subtitle quick actions in the " + "player. The subtitle download in stream settings always uses Plex as a source.") + ), OptionsSetting( 'burn_subtitles', T(32031, 'Burn-in Subtitles'), @@ -513,6 +622,11 @@ class Settings(object): 'transcoding the video stream). If disabled it will not touch the video stream, but ' 'will convert the subtitle to unstyled text.') ), + BoolUserSetting('auto_sync', T(33655, 'Auto-Sync Subtitles'), + True).description( + T(33656, 'Only for External SRT subtitles. The PMS setting for voice activity detection ' + 'has to be enabled for this to work.') + ), BoolSetting('forced_subtitles_override', T(32941, 'Forced subtitles fix'), False).description( T(32493, 'When a media file has a forced/foreign subtitle for a subtitle-enabled language, the Plex' @@ -537,7 +651,7 @@ class Settings(object): ('modern-dotted', T(32986, 'Modern (dotted)')), ('modern-colored', T(32989, 'Modern (colored)')), ('classic', T(32987, 'Classic')), - ('custom', T(32988, 'Custom')), + #('custom', T(32988, 'Custom')), ), theme_relevant=True ).description( T(32984, 'stub') @@ -564,14 +678,45 @@ class Settings(object): ).description( T(33078, "") ), + BoolUserSetting( + 'use_watchlist', T(34007, 'Use Watchlist'), True + ).description( + T(34008, "Activates the current user's Plex watchlist as a section item. Adds watchlist " + "functionality to certain media screens. Per-user setting. Default: On") + ), + BoolUserSetting( + 'watchlist_auto_remove', T(34009, 'Watchlist auto-remove'), True + ).description( + T(34010, "Automatically remove fully watched items from watchlist. Default: On") + ), MultiOptionsSetting( - 'no_episode_spoilers3', T(33006, ''), - ['unwatched'], + 'show_ratings', T(33709, 'Show ratings for'), + ["series", "movies"], + [ + ('series', T(32393, 'TV Shows')), + ('movies', T(32348, 'Movies')), + ] + ), + MultiOptionsSetting( + 'show_reviews1', T(33710, 'Show reviews for'), + ["watched", "unwatched"], + [ + ('watched', T(33718, 'Watched')), + ('unwatched', T(33010, 'Unwatched')), + ] + ), + MultiOptionsSetting( + 'no_episode_spoilers4', T(33006, ''), + ['unwatched', 'blur_images', 'hide_summary'], ( ('unwatched', T(33010, '')), ('in_progress', T(33011, '')), ('no_unwatched_episode_titles', T(33012, '')), + ('blur_images', T(33706, '')), + ('blur_resume_images', T(33707, '')), ('blur_chapters', T(33081, '')), + ('hide_summary', T(33708, '')), + ('hide_ratings', T(33705, '')), ) ).description(T(33007, "")), MultiOptionsSetting( @@ -580,10 +725,22 @@ class Settings(object): [(g, g) for g in genres.GENRES_TV] ).description(T(33017, "")), BoolSetting( - 'hubs_use_new_continue_watching', T(32998, ''), False + 'hubs_use_new_continue_watching', T(32998, ''), True ).description( T(32999, "") ), + BoolSetting( + 'home_confirm_actions', T(33663, 'Home: Confirm item actions'), True + ).description( + T(33664, "When acting on items in the Home view, such as mark played, hide from continue " + "watching etc., show a confirmation dialog.") + ), + BoolSetting( + 'hub_season_thumbnails', T(33740, 'Home: Episodes season thumbnails'), True + ).description( + T(33741, "Use season thumbnails/posters when displaying episodes in hubs instead of " + "the TV show's.") + ), BoolSetting( 'hubs_round_robin', T(33043, ''), False ).description( @@ -620,6 +777,16 @@ class Settings(object): ('skip_credits', T(32496, 'Skip Credits')), ) ).description(T(32939, 'Only applies to video player UI')), + MultiUAOptionsSetting( + 'fast_pause_resume', T(34012, 'Fast pause/resume'), + [], + ( + ('paused', T(34013, 'when paused')), + ('playing', T(34014, 'when playing')), + ) + ).description(T(34015, 'User-specific. Use OK/ENTER button to pause instead of showing the OSD' + ' (which can then only be accessed using DOWN), or resume when paused. ' + 'Only works with \'Behave like official Plex clients\' enabled.')), OptionsSetting( 'video_show_playlist', T(32936, 'Show playlist button'), 'eponly', ( @@ -720,7 +887,7 @@ class Settings(object): 'ing setting applies. Doesn\'t override enabled binge mode.\nCan be disabled/enabled per TV show.') ), BoolUserSetting( - 'skip_post_play_tv', T(32973, 'Episodes: Skip Post Play screen'), False + 'skip_post_play_tv', T(32973, 'Episodes: Continuous playback'), False ).description( T(32974, 'When finishing an episode, don\'t show Post Play but go to the next one immediately.' '\nCan be disabled/enabled per TV show. Doesn\'t override enabled binge mode. ' @@ -757,6 +924,9 @@ class Settings(object): ).description( T(32992, 'stub') ), + BoolSetting( + 'force_pd_mapping', T(34038, 'Force plex.direct mapping'), False + ).description(T(34039, 'stub')), IPSetting('manual_ip_0', T(32044, 'Connection 1 IP'), ''), IntegerSetting('manual_port_0', T(32045, 'Connection 1 Port'), 32400), IPSetting('manual_ip_1', T(32046, 'Connection 2 IP'), ''), @@ -765,7 +935,44 @@ class Settings(object): ), 'system': ( T(33600, 'System'), ( - + BoolSetting('auto_update_check', T(33672, 'Check for updates'), True) + .description(T(33673, "Automatically check for updates periodically. If installed from a " + "Kodi repository and the Update Source setting is set to Repository, Kodi " + "itself will handle the updating of this addon. " + "Needs a Kodi restart when changed.")) if not util.FROM_KODI_REPOSITORY else None, + BoolSetting('update_check_startup', T(33674, 'Check for updates on start'), True) + .description(T(33675, "Automatically check for updates on startup. " + "Doesn't do much when Update source is Repository." + "Needs a Kodi restart when changed.")) if not util.FROM_KODI_REPOSITORY else None, + OptionsSetting( + 'update_source', + T(33676, 'Update source'), + 'repository', + (('beta', T(33678, 'Beta')), ('stable', T(33679, 'Stable')), + ('repository', T(33680, 'Repository'))) + ).description(T(33677, 'Specifies the update mode. Will immediately check for a new version ' + 'when changed and closing settings.\nDefault: Repository\n\nBeta: Bleeding ' + 'edge (possibly unstable)\nStable: Stable branch (faster than Repository)\n' + 'Repository: Kodi repository (official (slow) or Don\'t Panic)') + ) if not util.FROM_KODI_REPOSITORY else None, + MultiOptionsSetting( + 'cache_requests', T(33724, 'Cache Plex data for'), + [], + [ + ('items', T(33723, 'Media Items')), + ('libraries', T(33722, 'Libraries')), + ] + ).description(T(33727, "Store Plex server responses for items and library views in a local " + "SQLite database. Doesn't cache anything else (Home/Hubs are always up to date)." + " Massively speeds up consecutive visits to items and libraries. Certain " + "important events, such as watch state changes, automatically delete the item " + "cache and its corresponding library cache. The complete cache gets cleared " + "when exiting the addon. (Default: Off)")), + BoolSetting('persist_requests_cache', T(33725, 'Persist cached Plex data'), False) + .description(T(33726, "Instead of clearing the cache when exiting the addon, persist it " + "instead. Warning: You'll most likely encounter missing items in libraries " + "or outdated data. Use the corresponding menu functionalities to clear the " + "cache for specific items or libraries.")), BoolSetting('exit_default_is_quit', T(32965, 'Start Plex On Kodi Startup'), False) .description(T(32966, "stub")), BoolSetting('path_mapping', T(33000, ''), True).description(T(33001, '')), @@ -808,7 +1015,7 @@ class Settings(object): OptionsSetting( 'action_on_wake', T(33070, 'Action on Wake event'), - util.isCoreELEC and 'wait_5' or 'wait_1', + util.altSeekRecommended and 'wait_5' or 'wait_1', [('none', T(32702, 'Nothing')), ('restart', T(33071, 'Restart PM4K'))] + [('wait_{}'.format(s), T(33072, '').format(s)) for s in [1, 2, 3] + list(range(5, 65, 5))] ).description(T(33075, '')), @@ -831,6 +1038,12 @@ class Settings(object): InfoSetting('addon_path', T(33616, 'Addon Path'), util.ADDON.getAddonInfo("path")), InfoSetting('userdata_path', T(33617, 'Userdata/Profile Path'), util.translatePath("special://profile")), + InfoSetting('service_status', T(33689, 'Service running'), + lambda: "{} ({})".format( + util.getGlobalProperty("service.started") and T(32328, "Yes") or T(32329, "No"), + util.getGlobalProperty("service.version"))), + InfoSetting('i_last_update_check', T(33690, "Last update check"), + lambda: util.getGlobalProperty('last_update_check', datetime.datetime.fromtimestamp(0).strftime('%Y-%m-%dT%H:%M:%S.%f'))), ) ), } @@ -867,6 +1080,7 @@ def onFirstInit(self): self.showSections() self.setFocusId(75) self.lastSection = None + self.lastFocusID = None self.checkSection() def onAction(self, action): @@ -886,8 +1100,14 @@ def onAction(self, action): # elif not xbmc.getCondVisibility('ControlGroup({0}).HasFocus(0)'.format(self.TOP_GROUP_ID)): # self.setFocusId(self.TOP_GROUP_ID) # return - elif action == xbmcgui.ACTION_MOVE_RIGHT and controlID == 150: - self.editSetting(from_right=True) + elif action == xbmcgui.ACTION_MOVE_RIGHT: + if self.lastFocusID == self.SECTION_LIST_ID: + if self.lastSection != 'about': + self.setFocusId(self.SETTINGS_LIST_ID) + return + elif self.lastFocusID == self.SETTINGS_LIST_ID: + self.editSetting(from_right=True) + return except: util.ERROR() @@ -895,7 +1115,8 @@ def onAction(self, action): def onClick(self, controlID): if controlID == self.SECTION_LIST_ID: - self.setFocusId(self.SETTINGS_LIST_ID) + if self.lastSection != 'about': + self.setFocusId(self.SETTINGS_LIST_ID) elif controlID == self.SETTINGS_LIST_ID: self.editSetting() elif controlID == self.OPTIONS_LIST_ID: @@ -905,6 +1126,9 @@ def onClick(self, controlID): elif controlID == self.PLAYER_STATUS_BUTTON_ID: self.showAudioPlayer() + def onFocus(self, controlID): + self.lastFocusID = controlID + def checkSection(self): mli = self.sectionList.getSelectedItem() if not mli: @@ -935,7 +1159,7 @@ def showSettings(self, section): items = [] for setting in settings: - if setting is None: + if setting is None or not setting.should_show(): continue item = kodigui.ManagedListItem(setting.label, setting.type != 'BOOL' and setting.valueLabel() or '', @@ -1126,6 +1350,9 @@ def setProperty(self, key, value): xbmcgui.WindowXML.setProperty(self, key, value) except RuntimeError: xbmc.log('kodigui.BaseWindow.setProperty: Missing window', xbmc.LOGDEBUG) + except TypeError: + # python 2.7 + pass def onAction(self, action): code = action.getButtonCode() @@ -1232,16 +1459,16 @@ def showSubtitlesDialog(video): def showQualityDialog(video): - options = [(13 - i, T(l)) for (i, l) in enumerate((32001, 32002, 32003, 32004, 32005, 32006, 32007, 32008, 32009, - 32010, 32011))] + options = [(16 - i, T(l)) for (i, l) in enumerate((32001, 32017, 32002, 32016, 32003, 32004, 32005, 32015, 32006, + 32007, 32008, 32009, 32010, 32011))] choice = showOptionsDialog(T(32397, 'Quality'), options) if choice is None: return - video.settings.setPrefOverride('local_quality', choice) - video.settings.setPrefOverride('remote_quality', choice) - video.settings.setPrefOverride('online_quality', choice) + video.settings.setPrefOverride('local_quality2', choice) + video.settings.setPrefOverride('remote_quality2', choice) + video.settings.setPrefOverride('online_quality2', choice) def openWindow(): diff --git a/script.plexmod/lib/windows/subitems.py b/script.plexmod/lib/windows/subitems.py index d55064ada1..2a253f7d16 100644 --- a/script.plexmod/lib/windows/subitems.py +++ b/script.plexmod/lib/windows/subitems.py @@ -4,10 +4,9 @@ from kodi_six import xbmc from kodi_six import xbmcgui -from plexnet import playlist +from plexnet import playlist, util as pnUtil, plexapp, plexlibrary from lib import metadata -from lib import player from lib import util from lib.util import T from . import busy @@ -23,7 +22,14 @@ from . import tracks from . import videoplayer from . import windowutils -from .mixins import SeasonsMixin, DeleteMediaMixin, RatingsMixin, PlaybackBtnMixin +from .mixins.seasons import SeasonsMixin +from .mixins.delete_media import DeleteMediaMixin +from .mixins.ratings import RatingsMixin +from .mixins.playbackbtn import PlaybackBtnMixin +from .mixins.watchlist import WatchlistUtilsMixin +from .mixins.thememusic import ThemeMusicMixin +from .mixins.roles import RolesMixin +from .mixins.common import CommonMixin class RelatedPaginator(pagination.BaseRelatedPaginator): @@ -32,7 +38,8 @@ def getData(self, offset, amount): class ShowWindow(kodigui.ControlledWindow, windowutils.UtilMixin, SeasonsMixin, DeleteMediaMixin, RatingsMixin, - PlaybackBtnMixin, playbacksettings.PlaybackSettingsMixin): + RolesMixin, PlaybackBtnMixin, WatchlistUtilsMixin, ThemeMusicMixin, CommonMixin, + playbacksettings.PlaybackSettingsMixin): xmlFile = 'script-plex-seasons.xml' path = util.ADDON.getAddonInfo('path') theme = 'Main' @@ -41,7 +48,7 @@ class ShowWindow(kodigui.ControlledWindow, windowutils.UtilMixin, SeasonsMixin, height = 1080 EXTRA_DIM = util.scaleResolution(329, 185) - RELATED_DIM = util.scaleResolution(268, 397) + RELATED_DIM = util.scaleResolution(268, 402) ROLES_DIM = util.scaleResolution(334, 334) SUB_ITEM_LIST_ID = 400 @@ -58,6 +65,7 @@ class ShowWindow(kodigui.ControlledWindow, windowutils.UtilMixin, SeasonsMixin, PROGRESS_IMAGE_ID = 250 + MAIN_BUTTON_GROUP_ID = 300 INFO_BUTTON_ID = 301 PLAY_BUTTON_ID = 302 SHUFFLE_BUTTON_ID = 303 @@ -68,9 +76,15 @@ def __init__(self, *args, **kwargs): SeasonsMixin.__init__(*args, **kwargs) DeleteMediaMixin.__init__(*args, **kwargs) PlaybackBtnMixin.__init__(self, *args, **kwargs) + WatchlistUtilsMixin.__init__(self) + ThemeMusicMixin.__init__(self) self.mediaItem = kwargs.get('media_item') self.parentList = kwargs.get('parent_list') self.cameFrom = kwargs.get('came_from') + self.fromWatchlist = kwargs.get('from_watchlist', False) + self.isExternal = kwargs.get('external_item', False) + self.directlyFromWatchlist = kwargs.get('directly_from_watchlist') + self.is_watchlisted = kwargs.get('is_watchlisted') self.mediaItems = None self.exitCommand = None self.lastFocusID = None @@ -78,12 +92,14 @@ def __init__(self, *args, **kwargs): self.manuallySelectedSeason = False self.initialized = False self.relatedPaginator = None + self.useBGM = False - def doClose(self): + def doClose(self, **kw): self.relatedPaginator = None kodigui.ControlledWindow.doClose(self) def onFirstInit(self): + self.focusPlayButton() self.subItemListControl = kodigui.ManagedControlList(self, self.SUB_ITEM_LIST_ID, 5) self.rolesListControl = kodigui.ManagedControlList(self, self.ROLES_LIST_ID, 5) self.extraListControl = kodigui.ManagedControlList(self, self.EXTRA_LIST_ID, 5) @@ -93,28 +109,28 @@ def onFirstInit(self): self.setup() self.initialized = True - - self.setFocusId(self.PLAY_BUTTON_ID) - - def onInit(self): - super(ShowWindow, self).onInit() - if self.mediaItem.theme and (not self.cameFrom or self.cameFrom != self.mediaItem.ratingKey) \ - and not util.getSetting("slow_connection", False): - self.cameFrom = self.mediaItem.ratingKey - volume = self.mediaItem.settings.getThemeMusicValue() - if volume > 0: - player.PLAYER.playBackgroundMusic(self.mediaItem.theme.asURL(True), volume, - self.mediaItem.ratingKey) + self.themeMusicInit(self.mediaItem) def onReInit(self): PlaybackBtnMixin.onReInit(self) + self.wl_auto_remove(self.mediaItem) + self.checkIsWatchlisted(self.mediaItem) + self.themeMusicReinit(self.mediaItem) def setup(self): + if self.isExternal: + # fixme, multiple? choice? + self.mediaItem.related_source = "more-from-credits" self.mediaItem.reload(includeExtras=1, includeExtrasCount=10, includeOnDeck=1) - self.relatedPaginator = RelatedPaginator(self.relatedListControl, leaf_count=int(self.mediaItem.relatedCount), parent_window=self) + self.watchlist_setup(self.mediaItem) + if self.fromWatchlist: + self.watchlistItemAvailable(self.mediaItem, shortcut_watchlisted=self.directlyFromWatchlist) + if not self.directlyFromWatchlist: + self.checkIsWatchlisted(self.mediaItem) + self.updateProperties() self.setBoolProperty("initialized", True) self.fill() @@ -130,13 +146,15 @@ def updateProperties(self): self.setProperty('duration', util.durationToText(self.mediaItem.fixedDuration())) self.setProperty('info', '') self.setProperty('date', self.mediaItem.year) + self.setBoolProperty('disable_playback', self.fromWatchlist) if not self.mediaItem.isWatched: self.setProperty('unwatched.count', str(self.mediaItem.unViewedLeafCount) or '') + self.setBoolProperty('unwatched.count.large', self.mediaItem.unViewedLeafCount > 999) else: self.setBoolProperty('watched', self.mediaItem.isWatched) self.setProperty('extras.header', T(32305, 'Extras')) - self.setProperty('related.header', T(32306, 'Related Shows')) + self.setProperty('related.header', T(32306, 'Related Shows') if not self.fromWatchlist else T(34018, 'Related Media')) if self.mediaItem.creator: self.setProperty('directors', u'{0} {1}'.format(T(32418, 'Creator').upper(), self.mediaItem.creator)) @@ -150,13 +168,18 @@ def updateProperties(self): genres = self.mediaItem.genres() self.setProperty('info', genres and (u' / '.join([g.tag for g in genres][:3])) or '') + if self.fromWatchlist and not self.wl_availability: + self.setProperty('wl_server_availability_verbose', + util.cleanLeadingZeros(self.mediaItem.originallyAvailableAt.asDatetime('%B %d, %Y'))) + self.populateRatings(self.mediaItem, self) sas = self.mediaItem.selectedAudioStream() self.setProperty('audio', sas and sas.getTitle() or 'None') sss = self.mediaItem.selectedSubtitleStream( - forced_subtitles_override=util.getSetting("forced_subtitles_override", False)) + forced_subtitles_override=util.getSetting("forced_subtitles_override") and pnUtil.ACCOUNT.subtitlesForced == 0, + deselect_subtitles=util.getSetting("disable_subtitle_languages")) self.setProperty('subtitles', sss and sss.getTitle() or 'None') leafcount = self.mediaItem.leafCount.asFloat() @@ -171,6 +194,16 @@ def updateProperties(self): width = (int(wBase * self.width)) or 1 self.progressImageControl.setWidth(width) + def focusPlayButton(self, extended=False): + if extended: + self.setFocusId(self.wl_play_button_id) + return + try: + if not self.getFocusId() == self.PLAY_BUTTON_ID: + self.setFocusId(self.PLAY_BUTTON_ID) + except (SystemError, RuntimeError): + self.setFocusId(self.PLAY_BUTTON_ID) + def onAction(self, action): try: controlID = self.getFocusId() @@ -182,7 +215,7 @@ def onAction(self, action): self.manuallySelectedSeason = True elif action == xbmcgui.ACTION_CONTEXT_MENU: - if controlID == self.SUB_ITEM_LIST_ID: + if controlID == self.SUB_ITEM_LIST_ID and not self.isExternal: self.optionsButtonClicked(from_item=True) return elif not xbmc.getCondVisibility('ControlGroup({0}).HasFocus(0)'.format(self.OPTIONS_GROUP_ID)): @@ -195,6 +228,14 @@ def onAction(self, action): self.lastNonOptionsFocusID = None return + elif controlID == self.SUB_ITEM_LIST_ID and self.isWatchedAction(action): + item = self.subItemListControl.getSelectedItem() + if not item.dataSource: + return + + self.toggleWatched(item.dataSource) + return + elif action in (xbmcgui.ACTION_NAV_BACK, xbmcgui.ACTION_CONTEXT_MENU): if not xbmc.getCondVisibility('ControlGroup({0}).HasFocus(0)'.format( self.OPTIONS_GROUP_ID)) and \ @@ -213,13 +254,16 @@ def onAction(self, action): elif action == xbmcgui.ACTION_PREV_ITEM: self.setFocusId(300) self.prev() + elif self.isWatchedAction(action) and xbmc.getCondVisibility('ControlGroup({}).HasFocus(0)'.format(self.MAIN_BUTTON_GROUP_ID)): + self.toggleWatched(self.mediaItem) + return if action == xbmcgui.ACTION_MOVE_UP and (controlID == self.SUB_ITEM_LIST_ID or self.INFO_BUTTON_ID <= controlID <= self.OPTIONS_BUTTON_ID): self.updateBackgroundFrom(self.mediaItem) if controlID == self.RELATED_LIST_ID: - if self.relatedPaginator.boundaryHit: + if self.relatedPaginator and self.relatedPaginator.boundaryHit: self.relatedPaginator.paginate() return elif action in (xbmcgui.ACTION_MOVE_LEFT, xbmcgui.ACTION_MOVE_RIGHT): @@ -234,7 +278,13 @@ def onClick(self, controlID): if controlID == self.HOME_BUTTON_ID: self.goHome() elif controlID == self.SUB_ITEM_LIST_ID: - self.subItemListClicked() + if not self.fromWatchlist: + self.subItemListClicked() + else: + mli = self.subItemListControl.getSelectedItem() + if not mli: + return + self.wl_item_opener(mli.dataSource, self.openItem) elif controlID == self.PLAYER_STATUS_BUTTON_ID: self.showAudioPlayer() elif controlID == self.EXTRA_LIST_ID: @@ -242,11 +292,18 @@ def onClick(self, controlID): elif controlID == self.RELATED_LIST_ID: self.openItem(self.relatedListControl) elif controlID == self.ROLES_LIST_ID: - self.roleClicked() + if not self.fromWatchlist: + if not self.roleClicked(): + return elif controlID == self.INFO_BUTTON_ID: self.infoButtonClicked() elif controlID == self.PLAY_BUTTON_ID: self.playButtonClicked() + elif controlID in self.WL_RELEVANT_BTNS and self.fromWatchlist and self.wl_availability: + self.wl_item_opener(self.mediaItem, self.openItem) + elif controlID in self.WL_BTN_STATE_BTNS: + is_watchlisted = self.toggleWatchlist(self.mediaItem) + self.waitAndSetFocus(self.WL_BTN_STATE_WATCHLISTED if is_watchlisted else self.WL_BTN_STATE_NOT_WATCHLISTED) elif controlID == self.SHUFFLE_BUTTON_ID: self.shuffleButtonClicked() elif controlID == self.OPTIONS_BUTTON_ID: @@ -268,8 +325,17 @@ def onFocus(self, controlID): elif xbmc.getCondVisibility('ControlGroup(50).HasFocus(0) + !ControlGroup(300).HasFocus(0)'): self.setProperty('on.extras', '1') - if player.PLAYER.bgmPlaying and player.PLAYER.handler.currentlyPlaying != self.mediaItem.ratingKey: - player.PLAYER.stopAndWait() + def toggleWatched(self, item, state=None, **kw): + watched = super(ShowWindow, self).toggleWatched(item, state=state, **kw) + if watched is None: + return + + if watched: + self.wl_auto_remove(self.mediaItem) + self.checkIsWatchlisted(self.mediaItem) + self.updateItems() + self.updateProperties() + util.MONITOR.watchStatusChanged() def getMediaItems(self): return False @@ -345,14 +411,15 @@ def _prev(self): def searchButtonClicked(self): self.processCommand(search.dialog(self, section_id=self.mediaItem.getLibrarySectionId() or None)) - def openItem(self, control=None, item=None): + def openItem(self, control=None, item=None, inherit_from_watchlist=True, server=None, is_watchlisted=False, **kw): if not item: mli = control.getSelectedItem() if not mli: return item = mli.dataSource - self.processCommand(opener.open(item)) + self.processCommand(opener.open(item, from_watchlist=self.fromWatchlist if inherit_from_watchlist else False, + server=server, is_watchlisted=is_watchlisted, **kw)) def subItemListClicked(self): mli = self.subItemListControl.getSelectedItem() @@ -363,7 +430,8 @@ def subItemListClicked(self): w = None if self.mediaItem.type == 'show': - w = episodes.EpisodesWindow.open(season=mli.dataSource, show=self.mediaItem, parent_list=self.subItemListControl) + w = episodes.EpisodesWindow.open(season=mli.dataSource, show=self.mediaItem, + parent_list=self.subItemListControl, from_watchlist=self.fromWatchlist) update = True elif self.mediaItem.type == 'artist': w = tracks.AlbumWindow.open(album=mli.dataSource, parent_list=self.subItemListControl) @@ -413,7 +481,7 @@ def playButtonClicked(self, shuffle=False): if self.playBtnClicked: return - items = self.mediaItem.all() + items = self.mediaItem.all(unwatched=True) pl = playlist.LocalPlaylist(items, self.mediaItem.getServer()) resume = False if not shuffle and self.mediaItem.type == 'show': @@ -423,7 +491,7 @@ def playButtonClicked(self, shuffle=False): self.playBtnClicked = True pl.shuffle(shuffle, first=True) - videoplayer.play(play_queue=pl, resume=resume) + videoplayer.play(play_queue=pl, resume=resume, bgm=self.useBGM) def shuffleButtonClicked(self): self.playButtonClicked(shuffle=True) @@ -453,12 +521,16 @@ def optionsButtonClicked(self, from_item=None): options.append(dropdown.SEPARATOR) options.append({'key': 'playback_settings', 'display': T(32925, 'Playback Settings')}) - if item.server.allowsMediaDeletion: + if plexapp.ACCOUNT.isAdmin and item.server.allowsMediaDeletion: options.append(dropdown.SEPARATOR) + if plexapp.ACCOUNT.isAdmin: + options.append({'key': 'refresh', 'display': T(33719, 'Refresh metadata')}) options.append({'key': 'delete', 'display': T(32322, 'Delete')}) elif item.type == "season": - if item.server.allowsMediaDeletion: + if plexapp.ACCOUNT.isAdmin and item.server.allowsMediaDeletion: options.append(dropdown.SEPARATOR) + if plexapp.ACCOUNT.isAdmin: + options.append({'key': 'refresh', 'display': T(33719, 'Refresh metadata')}) options.append({'key': 'delete', 'display': T(32975, 'Delete Season')}) # if xbmc.getCondVisibility('Player.HasAudio') and self.section.TYPE == 'artist': @@ -470,6 +542,10 @@ def optionsButtonClicked(self, from_item=None): options.append(dropdown.SEPARATOR) options.append({'key': 'to_section', 'display': u'Go to {0}'.format(self.mediaItem.getLibrarySectionTitle())}) + + if 'items' in util.getSetting('cache_requests'): + options.append({'key': 'cache_reset', 'display': T(33728, "Clear cache for item")}) + pos = (880, 618) if from_item: viewPos = self.subItemListControl.getViewPosition() @@ -484,17 +560,15 @@ def optionsButtonClicked(self, from_item=None): if choice['key'] == 'play_next': xbmc.executebuiltin('PlayerControl(Next)') elif choice['key'] == 'mark_watched': - item.markWatched() - self.updateItems() - self.updateProperties() - util.MONITOR.watchStatusChanged() + self.toggleWatched(item, state=True) elif choice['key'] == 'mark_unwatched': - item.markUnwatched() - self.updateItems() - self.updateProperties() - util.MONITOR.watchStatusChanged() + self.toggleWatched(item, state=False) elif choice['key'] == 'to_section': - self.goHome(self.mediaItem.getLibrarySectionId()) + self.cameFrom = "library" + section = plexlibrary.LibrarySection.fromFilter(self.mediaItem) + self.processCommand(opener.sectionClicked(section, + came_from=self.mediaItem.ratingKey) + ) elif choice['key'] == 'playback_settings': self.playbackSettings(self.mediaItem, pos, False) elif choice['key'] == 'delete': @@ -506,53 +580,30 @@ def optionsButtonClicked(self, from_item=None): self.setup() self.initialized = True self.setFocusId(self.PLAY_BUTTON_ID) + elif choice['key'] == 'refresh': + item.refresh() + self.updateItems() + self.updateProperties() - def roleClicked(self): - mli = self.rolesListControl.getSelectedItem() - if not mli: - return - - sectionRoles = busy.widthDialog(mli.dataSource.sectionRoles, '') - - if not sectionRoles: - util.DEBUG_LOG('No sections found for actor') - return - - if len(sectionRoles) > 1: - x, y = self.getRoleItemDDPosition() - - options = [{'role': r, 'display': r.reasonTitle} for r in sectionRoles] - choice = dropdown.showDropdown(options, (x, y), pos_is_bottom=True, close_direction='bottom') - - if not choice: - return - - role = choice['role'] - else: - role = sectionRoles[0] - - self.processCommand(opener.open(role)) + elif choice["key"] == "cache_reset": + try: + util.DEBUG_LOG('Clearing requests cache for {}...', item) + item.clearCache() + self.updateItems() + self.updateProperties() + except Exception as e: + util.DEBUG_LOG("Couldn't clear cache: {}", e) - def getRoleItemDDPosition(self): + def getRoleItemDDPosition(self, *args, **kwargs): y = 980 if xbmc.getCondVisibility('Control.IsVisible(500)'): y += 380 if xbmc.getCondVisibility('!String.IsEmpty(Window.Property(on.extras))'): y -= 200 if xbmc.getCondVisibility('Integer.IsGreater(Window.Property(hub.focus),0) + Control.IsVisible(500)'): - y -= 500 + y -= 650 - tries = 0 - focus = xbmc.getInfoLabel('Container(401).Position') - while tries < 2 and focus == '': - focus = xbmc.getInfoLabel('Container(401).Position') - xbmc.sleep(250) - tries += 1 - - focus = int(focus) - - x = ((focus + 1) * 304) - 100 - return x, y + return super(ShowWindow, self).getRoleItemDDPosition(y=y, container_id="401") def updateItems(self): self.fill(update=True) @@ -605,8 +656,6 @@ def fillRelated(self, has_prev=False): self.relatedListControl.reset() return has_prev - self.setProperty('divider.{0}'.format(self.RELATED_LIST_ID), has_prev and '1' or '') - items = self.relatedPaginator.paginate() if not items: @@ -621,10 +670,10 @@ def fillRoles(self, has_prev=False): self.rolesListControl.reset() return has_prev - self.setProperty('divider.{0}'.format(self.ROLES_LIST_ID), has_prev and '1' or '') - - for role in self.mediaItem.roles(): - mli = kodigui.ManagedListItem(role.tag, role.role, thumbnailImage=role.thumb.asTranscodedImageURL(*self.ROLES_DIM), data_source=role) + for role in self.mediaItem.combined_roles: + mli = kodigui.ManagedListItem(role.tag, role.role or util.TRANSLATED_ROLES[role.translated_role], + thumbnailImage=role.thumb.asTranscodedImageURL(*self.ROLES_DIM), + data_source=role) mli.setProperty('index', str(idx)) items.append(mli) idx += 1 @@ -661,8 +710,7 @@ def setup(self): def playButtonClicked(self, shuffle=False): pl = playlist.LocalPlaylist(self.mediaItem.all(), self.mediaItem.getServer(), self.mediaItem) pl.startShuffled = shuffle - w = musicplayer.MusicPlayerWindow.open(track=pl.current(), playlist=pl) - del w + self.processCommand(opener.handleOpen(musicplayer.MusicPlayerWindow, track=pl.current(), playlist=pl)) def updateProperties(self): self.setProperty('summary', self.mediaItem.summary) diff --git a/script.plexmod/lib/windows/tracks.py b/script.plexmod/lib/windows/tracks.py index c94baf3233..fd6d83a559 100644 --- a/script.plexmod/lib/windows/tracks.py +++ b/script.plexmod/lib/windows/tracks.py @@ -70,7 +70,7 @@ def setup(self): self.updateProperties() self.fillTracks() - def doClose(self): + def doClose(self, **kw): player.PLAYER.off('started.audio', self.onPlayingTrackChanged) kodigui.ControlledWindow.doClose(self) diff --git a/script.plexmod/lib/windows/userselect.py b/script.plexmod/lib/windows/userselect.py index 9053c76cec..a5e232719c 100644 --- a/script.plexmod/lib/windows/userselect.py +++ b/script.plexmod/lib/windows/userselect.py @@ -135,7 +135,7 @@ def shutdownClicked(self): options = [] options.append({'key': 'sign_out', 'display': T(32421, 'Sign Out')}) options.append({'key': 'exit', 'display': T(32422, 'Exit')}) - if util.getSetting('kiosk.mode', False): + if util.getSetting('kiosk.mode'): if xbmc.getCondVisibility('System.CanPowerDown'): options.append({'key': 'shutdown', 'display': T(32423, 'Shutdown')}) if xbmc.getCondVisibility('System.CanSuspend'): diff --git a/script.plexmod/lib/windows/videoplayer.py b/script.plexmod/lib/windows/videoplayer.py index db5db975ba..dc79c8778e 100644 --- a/script.plexmod/lib/windows/videoplayer.py +++ b/script.plexmod/lib/windows/videoplayer.py @@ -3,6 +3,8 @@ import math import threading import time +import traceback +import uuid from kodi_six import xbmc from kodi_six import xbmcgui @@ -12,6 +14,7 @@ from lib import player from lib import util from lib.util import T +from plexnet.serverdecision import DecisionFailure from . import busy from . import dropdown from . import kodigui @@ -19,7 +22,8 @@ from . import pagination from . import search from . import windowutils -from .mixins import SpoilersMixin +from .mixins.spoilers import SpoilersMixin +from .mixins.roles import RolesMixin PASSOUT_PROTECTION_DURATION_SECONDS = 7200 PASSOUT_LAST_VIDEO_DURATION_MILLIS = 1200000 @@ -34,6 +38,8 @@ def getData(self, offset, amount): class OnDeckPaginator(pagination.MCLPaginator): + initialPageSize = 8 + def readyForPaging(self): return self.parentWindow.postPlayInitialized @@ -66,12 +72,17 @@ def createListItem(self, ondeck): def getData(self, offset, amount): data = (self.parentWindow.prev or self.parentWindow.next).sectionOnDeck(offset=offset, limit=amount) + skipRKs = [] if self.parentWindow.next: - return list(filter(lambda x: x.ratingKey != self.parentWindow.next.ratingKey, data)) + skipRKs.append(self.parentWindow.next.ratingKey) + if self.parentWindow.prev: + skipRKs.append(self.parentWindow.prev.ratingKey) + if skipRKs: + return list(filter(lambda x: x.ratingKey not in skipRKs, data)) return data -class VideoPlayerWindow(kodigui.ControlledWindow, windowutils.UtilMixin, SpoilersMixin): +class VideoPlayerWindow(kodigui.ControlledWindow, windowutils.UtilMixin, RolesMixin, SpoilersMixin): xmlFile = 'script-plex-video_player.xml' path = util.ADDON.getAddonInfo('path') theme = 'Main' @@ -82,7 +93,7 @@ class VideoPlayerWindow(kodigui.ControlledWindow, windowutils.UtilMixin, Spoiler NEXT_DIM = util.scaleResolution(537, 303) PREV_DIM = util.scaleResolution(462, 259) ONDECK_DIM = util.scaleResolution(329, 185) - RELATED_DIM = util.scaleResolution(268, 397) + RELATED_DIM = util.scaleResolution(268, 402) ROLES_DIM = util.scaleResolution(334, 334) OPTIONS_GROUP_ID = 200 @@ -107,6 +118,9 @@ def __init__(self, *args, **kwargs): self.video = kwargs.get('video') self.resume = bool(kwargs.get('resume')) + if util.platformFlavor == "CoreELEC": + self.defer_init = True + self.postPlayMode = False self.prev = None self.playlist = None @@ -124,17 +138,32 @@ def __init__(self, *args, **kwargs): self.lastNonOptionsFocusID = None self.playBackStarted = False self.handleBGM = kwargs.get('bgm') + self.lastItem = None + self.earlyAbortRequested = False + self.sessionID = None - def doClose(self): + def doClose(self, force=False): util.DEBUG_LOG('VideoPlayerWindow: Closing') self.timeout = None self.relatedPaginator = None self.onDeckPaginator = None + self.lastItem = None + if self.earlyAbortRequested: + player.PLAYER._ignorePlaybackFailure = True + if player.PLAYER.isPlayingVideo(): + player.PLAYER.close() + if player.PLAYER.handler: + player.PLAYER.handler.stoppedManually = True + player.PLAYER.stop() + kodigui.ControlledWindow.doClose(self) - player.PLAYER.handler.sessionEnded() + + if player.PLAYER.handler: + player.PLAYER.handler.sessionEnded() def onFirstInit(self): player.PLAYER.on('session.ended', self.sessionEnded) + player.PLAYER.on('videowindow.closed', self.videoWindowClosed) player.PLAYER.on('av.started', self.playerPlaybackStarted) player.PLAYER.on('starting.video', self.onVideoStarting) player.PLAYER.on('started.video', self.onVideoStarted) @@ -142,11 +171,13 @@ def onFirstInit(self): player.PLAYER.on('post.play', self.postPlay) player.PLAYER.on('change.background', self.changeBackground) + self.sessionID = str(uuid.uuid4()) + self.onDeckListControl = kodigui.ManagedControlList(self, self.ONDECK_LIST_ID, 5) self.relatedListControl = kodigui.ManagedControlList(self, self.RELATED_LIST_ID, 5) self.rolesListControl = kodigui.ManagedControlList(self, self.ROLES_LIST_ID, 5) - util.DEBUG_LOG('VideoPlayerWindow: Starting session (ID: {0})', id(self)) + util.DEBUG_LOG('VideoPlayerWindow: Starting session (ID: {0})', self.sessionID) self.resetPassoutProtection() self.play(resume=self.resume) @@ -161,7 +192,9 @@ def onVideoChanged(self, *args, **kwargs): pass def onReInit(self): - self.setBackground() + util.DEBUG_LOG('VideoPlayerWindow: Reinitializing') + if not self.earlyAbortRequested: + self.setBackground() def onAction(self, action): try: @@ -202,6 +235,26 @@ def onAction(self, action): if self.onDeckPaginator.boundaryHit: self.onDeckPaginator.paginate() return + + mli = self.onDeckListControl.getSelectedItem() + if not mli or mli.getProperty("is.boundary"): + return + + lastItem = self.lastItem + + if action in (xbmcgui.ACTION_MOVE_RIGHT, xbmcgui.ACTION_MOVE_LEFT) and lastItem: + items = self.onDeckPaginator.wrap(mli, lastItem, action) + xbmc.sleep(100) + if items: + # wrapped with new data + return True + + if mli != self.lastItem and not mli.getProperty("is.boundary"): + self.lastItem = mli + else: + if action in(xbmcgui.ACTION_NAV_BACK, xbmcgui.ACTION_PREVIOUS_MENU, xbmcgui.ACTION_STOP): + util.DEBUG_LOG('VideoPlayerWindow: Abort requested, setting flag') + self.earlyAbortRequested = True except: util.ERROR() @@ -210,6 +263,10 @@ def onAction(self, action): def playerPlaybackStarted(self, *args, **kwargs): self.playBackStarted = True + if self.earlyAbortRequested: + util.DEBUG_LOG('VideoPlayerWindow: Abort flag set, closing') + self.doClose() + def onClick(self, controlID): if not self.postPlayMode: return @@ -226,7 +283,8 @@ def onClick(self, controlID): elif controlID == self.RELATED_LIST_ID: self.openItem(self.relatedListControl) elif controlID == self.ROLES_LIST_ID: - self.roleClicked() + if not self.roleClicked(): + return elif controlID == self.PREV_BUTTON_ID: self.playVideo(prev=True) elif controlID == self.NEXT_BUTTON_ID: @@ -256,33 +314,7 @@ def onFocus(self, controlID): def searchButtonClicked(self): self.processCommand(search.dialog(self, section_id=self.prev.getLibrarySectionId() or None)) - def roleClicked(self): - mli = self.rolesListControl.getSelectedItem() - if not mli: - return - - sectionRoles = busy.widthDialog(mli.dataSource.sectionRoles, '') - - if not sectionRoles: - util.DEBUG_LOG('No sections found for actor') - return - - if len(sectionRoles) > 1: - x, y = self.getRoleItemDDPosition() - - options = [{'role': r, 'display': r.reasonTitle} for r in sectionRoles] - choice = dropdown.showDropdown(options, (x, y), pos_is_bottom=True, close_direction='bottom') - - if not choice: - return - - role = choice['role'] - else: - role = sectionRoles[0] - - self.processCommand(opener.open(role)) - - def getRoleItemDDPosition(self): + def getRoleItemDDPosition(self, *args, **kwargs): y = 1000 if xbmc.getCondVisibility('Control.IsVisible(500)'): y += 360 @@ -295,17 +327,7 @@ def getRoleItemDDPosition(self): if xbmc.getCondVisibility('Integer.IsGreater(Window.Property(hub.focus),1) + Control.IsVisible(501)'): y -= 500 - tries = 0 - focus = xbmc.getInfoLabel('Container(403).Position') - while tries < 2 and focus == '': - focus = xbmc.getInfoLabel('Container(403).Position') - xbmc.sleep(250) - tries += 1 - - focus = int(focus) - - x = ((focus + 1) * 304) - 100 - return x, y + return super(VideoPlayerWindow, self).getRoleItemDDPosition(y=y, container_id="403") def setBackground(self): video = self.video if self.video else self.playQueue.current() @@ -316,16 +338,25 @@ def changeBackground(self, url, **kwargs): self.windowSetBackground(url) def sessionEnded(self, session_id=None, **kwargs): - if session_id != id(self): - util.DEBUG_LOG('VideoPlayerWindow: Ignoring session end (ID: {0} - SessionID: {1})', id(self), session_id) + if session_id != self.sessionID: + util.DEBUG_LOG('VideoPlayerWindow: Ignoring session end (ID: {0} - SessionID: {1})', self.sessionID, session_id) return - util.DEBUG_LOG('VideoPlayerWindow: Session ended - closing (ID: {0})', id(self)) + util.DEBUG_LOG('VideoPlayerWindow: Session ended - closing (ID: {0})', self.sessionID) self.doClose() + def videoWindowClosed(self, session_id=None, video=None, **kwargs): + if session_id != self.sessionID: + return + + video.clearCache() + def play(self, resume=False, handler=None): + util.DEBUG_LOG("VideoPlayerWindow: play() called") self.hidePostPlay() + player.PLAYER.dontRequeueBGM = True + def anyOtherVPlayer(): return any(list(filter(lambda x: x['playerid'] > 0, kodijsonrpc.rpc.Player.GetActivePlayers()))) @@ -349,24 +380,47 @@ def anyOtherVPlayer(): # wait for BGM to end if it's playing or queued if self.handleBGM: - while not player.PLAYER.bgmPlaying and player.PLAYER.bgmStarting: + util.DEBUG_LOG("Checking BGM") + ct = 0 + while not player.PLAYER.bgmPlaying and player.PLAYER.bgmStarting and ct < 20: util.DEBUG_LOG("Waiting for BGM to start as it has been queued") util.MONITOR.waitForAbort(0.1) + ct += 1 if player.PLAYER.bgmPlaying: util.DEBUG_LOG("Stopping BGM before starting playback") player.PLAYER.stopAndWait() - while player.PLAYER.bgmPlaying: + if player.PLAYER.isPlayingAudio(): + player.PLAYER.stopAndWait() + + ct = 0 + while (player.PLAYER.bgmPlaying or player.PLAYER.isPlayingAudio()) and ct < 20: util.MONITOR.waitForAbort(0.1) + ct += 1 + util.DEBUG_LOG("BGM check done") self.setBackground() - if self.playQueue: - player.PLAYER.playVideoPlaylist(self.playQueue, resume=resume or self.resume, session_id=id(self), - handler=handler) - elif self.video: - player.PLAYER.playVideo(self.video, resume=resume or self.resume, force_update=True, session_id=id(self), - handler=handler) + + self.sessionID = self.sessionID or str(uuid.uuid4()) + + try: + if self.playQueue: + player.PLAYER.playVideoPlaylist(self.playQueue, resume=resume or self.resume, session_id=self.sessionID, + handler=handler) + elif self.video: + player.PLAYER.playVideo(self.video, resume=resume or self.resume, force_update=True, session_id=self.sessionID, + handler=handler) + except DecisionFailure: + util.LOG("Can't play this media.") + self.doClose() + + except Exception as e: + util.LOG("Playback failed: {}", traceback.format_exc()) + self.doClose() + + util.DEBUG_LOG("VideoPlayerWindow: Playback initialized; returning from play()") + def openItem(self, control=None, item=None): if not item: @@ -420,7 +474,7 @@ def postPlay(self, video=None, playlist=None, handler=None, stoppedManually=Fals util.DEBUG_LOG('PostPlay: Showing video info') if self.next: - self.next.reload(includeExtras=1, includeExtrasCount=10) + self.next.reload(includeChapters=1, includeExtras=1, includeExtrasCount=10) self.relatedPaginator = RelatedPaginator(self.relatedListControl, leaf_count=int((self.prev or self.next).relatedCount), @@ -429,7 +483,7 @@ def postPlay(self, video=None, playlist=None, handler=None, stoppedManually=Fals vid = self.prev or self.next if vid.sectionOnDeckCount: self.onDeckPaginator = OnDeckPaginator(self.onDeckListControl, - leaf_count=int(vid.sectionOnDeckCount), + leaf_count=int(vid.sectionOnDeckCount) - 1 if self.next else 0, parent_window=self) self.setInfo() @@ -492,13 +546,13 @@ def countdown(self): elif self.timeout is not None: cd = min(abs(util.addonSettings.postplayTimeout - 1), int((self.timeout or now) - now)) base = 15 / float(util.addonSettings.postplayTimeout - 1) - self.setProperty('countdown', str(int(math.ceil(base*cd)))) + self.setProperty('countdown', str(15 - int(math.ceil(base*cd)))) def getHubs(self): try: self.hubs = self.prev.postPlay() except: - util.ERROR("No data - disconnected?", notify=True, time_ms=5000) + util.ERROR("No data - deleted or server disconnected?", notify=True, time_ms=5000) self.doClose() return @@ -517,7 +571,7 @@ def getHubs(self): def setInfo(self): hide_spoilers = False if self.next and self.next.type == "episode": - hide_spoilers = self.hideSpoilers(self.next, use_cache=False) + hide_spoilers = self.hideSpoilers(self.next, fully_watched=False, watched=False, use_cache=False) if self.next: self.setProperty( 'post.play.background', @@ -607,8 +661,6 @@ def fillRelated(self, has_prev=False): if not items: return False - - self.setProperty('divider.{0}'.format(self.RELATED_LIST_ID), has_prev and '1' or '') return True def fillRoles(self, has_prev=False): @@ -630,8 +682,6 @@ def fillRoles(self, has_prev=False): if not items: return False - self.setProperty('divider.{0}'.format(self.ROLES_LIST_ID), has_prev and '1' or '') - self.rolesListControl.reset() self.rolesListControl.addItems(items) return True @@ -686,11 +736,13 @@ def playVideo(self, prev=False): def play(video=None, play_queue=None, resume=False, bgm=False, **kwargs): try: - w = VideoPlayerWindow.open(video=video, play_queue=play_queue, resume=resume, bgm=bgm) + w = VideoPlayerWindow.open(video=video, play_queue=play_queue, resume=resume, bgm=bgm, aggressive=True) except util.NoDataException: raise finally: + util.DEBUG_LOG("VideoPlayer Window exit") player.PLAYER.off('session.ended', w.sessionEnded) + player.PLAYER.off('videowindow.closed', w.videoWindowClosed) player.PLAYER.off('post.play', w.postPlay) player.PLAYER.off('av.started', w.playerPlaybackStarted) player.PLAYER.off('starting.video', w.onVideoStarting) diff --git a/script.plexmod/lib/windows/windowutils.py b/script.plexmod/lib/windows/windowutils.py index 7910ded9b6..d7166721e8 100644 --- a/script.plexmod/lib/windows/windowutils.py +++ b/script.plexmod/lib/windows/windowutils.py @@ -8,10 +8,7 @@ HOME = None -class UtilMixin(): - def __init__(self): - self.exitCommand = None - +class GoHomeMixin(): def goHome(self, section=None, with_root=False): HOME.go_root = with_root @@ -22,8 +19,13 @@ def goHome(self, section=None, with_root=False): HOME.show() - def openItem(self, obj): - self.processCommand(opener.open(obj)) + +class UtilMixin(GoHomeMixin): + def __init__(self): + self.exitCommand = None + + def openItem(self, obj, **kwargs): + self.processCommand(opener.open(obj, **kwargs)) def openWindow(self, window_class, **kwargs): self.processCommand(opener.handleOpen(window_class, **kwargs)) @@ -49,24 +51,27 @@ def getNextShowEp(self, pl, items, title): if in_progress: n = in_progress[0] pl.setCurrent(n) - choice = dropdown.showDropdown( - options=[ - {'key': 'resume', 'display': T(32429, 'Resume from {0}').format( - util.timeDisplay(n.viewOffset.asInt()).lstrip('0').lstrip(':'))}, - {'key': 'play', 'display': T(32317, 'Play from beginning')} - ], - pos=(660, 441), - close_direction='none', - set_dropdown_prop=False, - header=u'{0} - {1} \u2022 {2}'.format(title, - T(32310, 'S').format(n.parentIndex), - T(32311, 'E').format(n.index)) - ) - - if not choice: - return None - - if choice['key'] == 'resume': + + if not util.getSetting('assume_resume'): + choice = dropdown.showDropdown( + options=[ + {'key': 'resume', 'display': T(32429, 'Resume from {0}').format( + util.timeDisplay(n.viewOffset.asInt()).lstrip('0').lstrip(':'))}, + {'key': 'play', 'display': T(32317, 'Play from beginning')} + ], + pos=(660, 441), + set_dropdown_prop=False, + header=u'{0} - {1} \u2022 {2}'.format(title, + T(32310, 'S').format(n.parentIndex), + T(32311, 'E').format(n.index)) + ) + + if not choice: + return None + + if choice['key'] == 'resume': + return True + else: return True return False @@ -89,6 +94,7 @@ def getNextShowEp(self, pl, items, title): return False + def shutdownHome(): global HOME if HOME: diff --git a/script.plexmod/resources/language/resource.language.cs_cz/strings.po b/script.plexmod/resources/language/resource.language.cs_cz/strings.po index 189e635a4b..782e42e231 100644 --- a/script.plexmod/resources/language/resource.language.cs_cz/strings.po +++ b/script.plexmod/resources/language/resource.language.cs_cz/strings.po @@ -23,36 +23,36 @@ msgid "Original" msgstr "Originální" msgctxt "#32002" -msgid "20 Mbps 1080p" -msgstr "20 Mb/s 1080p" +msgid "20 Mbps" +msgstr "20 Mb/s" msgctxt "#32003" -msgid "12 Mbps 1080p" -msgstr "12 Mb/s 1080p" +msgid "12 Mbps" +msgstr "12 Mb/s" msgctxt "#32004" -msgid "10 Mbps 1080p" -msgstr "10 Mb/s 1080p" +msgid "10 Mbps" +msgstr "10 Mb/s" msgctxt "#32005" -msgid "8 Mbps 1080p" -msgstr "8 Mb/s 1080p" +msgid "8 Mbps" +msgstr "8 Mb/s" msgctxt "#32006" -msgid "4 Mbps 720p" -msgstr "4 Mb/s 720p" +msgid "4 Mbps" +msgstr "4 Mb/s" msgctxt "#32007" -msgid "3 Mbps 720p" -msgstr "3 Mb/s 720p" +msgid "3 Mbps" +msgstr "3 Mb/s" msgctxt "#32008" -msgid "2 Mbps 720p" -msgstr "2 Mb/s 720p" +msgid "2 Mbps" +msgstr "2 Mb/s" msgctxt "#32009" -msgid "1.5 Mbps 480p" -msgstr "1.5 Mb/s 480p" +msgid "1.5 Mbps" +msgstr "1.5 Mb/s" msgctxt "#32010" msgid "720 kbps" diff --git a/script.plexmod/resources/language/resource.language.de_de/strings.po b/script.plexmod/resources/language/resource.language.de_de/strings.po index cd1ee55ec9..1d80d1f9ef 100644 --- a/script.plexmod/resources/language/resource.language.de_de/strings.po +++ b/script.plexmod/resources/language/resource.language.de_de/strings.po @@ -19,43 +19,43 @@ msgstr "Original" #: msgctxt "#32002" -msgid "20 Mbps 1080p" -msgstr "20 Mbit/s 1080p" +msgid "20 Mbps" +msgstr "20 Mbit/s" #: msgctxt "#32003" -msgid "12 Mbps 1080p" -msgstr "12 Mbit/s 1080p" +msgid "12 Mbps" +msgstr "12 Mbit/s" #: msgctxt "#32004" -msgid "10 Mbps 1080p" -msgstr "10 Mbit/s 1080p" +msgid "10 Mbps" +msgstr "10 Mbit/s" #: msgctxt "#32005" -msgid "8 Mbps 1080p" -msgstr "8 Mbit/s 1080p" +msgid "8 Mbps" +msgstr "8 Mbit/s" #: msgctxt "#32006" -msgid "4 Mbps 720p" -msgstr "4 Mbit/s 720p" +msgid "4 Mbps" +msgstr "4 Mbit/s" #: msgctxt "#32007" -msgid "3 Mbps 720p" -msgstr "3 Mbit/s 720p" +msgid "3 Mbps" +msgstr "3 Mbit/s" #: msgctxt "#32008" -msgid "2 Mbps 720p" -msgstr "2 Mbit/s 720p" +msgid "2 Mbps" +msgstr "2 Mbit/s" #: msgctxt "#32009" -msgid "1.5 Mbps 480p" -msgstr "1.5 Mbit/s 480p" +msgid "1.5 Mbps" +msgstr "1.5 Mbit/s" #: msgctxt "#32010" @@ -82,6 +82,18 @@ msgctxt "#32014" msgid "64 kbps" msgstr "64 kbit/s" +msgctxt "#32015" +msgid "6 Mbps" +msgstr "6 Mbit/s" + +msgctxt "#32016" +msgid "16 Mbps" +msgstr "16 Mbit/s" + +msgctxt "#32017" +msgid "26 Mbps" +msgstr "26 Mbit/s" + #: msgctxt "#32020" msgid "Local Quality" @@ -1274,6 +1286,11 @@ msgctxt "#32492" msgid "Kodi Subtitle Settings" msgstr "Kodi-Untertiteleinstellungen" +#: +msgctxt "#32493" +msgid "When a media file has a forced/foreign subtitle for a subtitle-enabled language, the Plex Media Server preselects it. This behaviour is usually not necessary and not configurable. This setting fixes that by ignoring the PMSs decision and selecting the same language without a forced flag if possible." +msgstr "Hat eine Mediendatei erzwungenge Untertitel für eine Sprache, bei der Untertitel erwünscht sind, wählt der Plex Media Server diesen standardmäßig aus. Das Verhalten ist normalerweise unerwünscht und nicht konfigurierbar. Diese Einstellung behebt das Problem, indem versucht wird, einen Untertitel der selben Sprache ohne Erzwungen-Markierung zu wählen." + #: msgctxt "#32495" msgid "Skip intro" @@ -1454,11 +1471,6 @@ msgctxt "#32067" msgid "Only force multichannel audio to AC3" msgstr "Nur Mehrkanal-Audio als AC3 erzwingen" -#: -msgctxt "#32493" -msgid "When a media file has a forced/foreign subtitle for a subtitle-enabled language, the Plex Media Server preselects it. This behaviour is usually not necessary and not configurable. This setting fixes that by ignoring the PMSs decision and selecting the same language without a forced flag if possible." -msgstr "Hat eine Mediendatei erzwungenge Untertitel für eine Sprache, bei der Untertitel erwünscht sind, wählt der Plex Media Server diesen standardmäßig aus. Das Verhalten ist normalerweise unerwünscht und nicht konfigurierbar. Diese Einstellung behebt das Problem, indem versucht wird, einen Untertitel der selben Sprache ohne Erzwungen-Markierung zu wählen." - #: msgctxt "#32523" msgid "Automatically skip intros if available. Doesn't override enabled binge mode.\n" @@ -1517,8 +1529,8 @@ msgstr "Deaktiviert" #: msgctxt "#33509" -msgid "Early intro skip threshold (default: < 60s/1m)" -msgstr "Grenzwert für 'Früher Intro überspringen' (Standardwert < 60s)" +msgid "Early intro skip threshold (default: < 120s/2m)" +msgstr "Grenzwert für 'Früher Intro überspringen' (Standardwert < 120s)" #: msgctxt "#33510" @@ -1947,7 +1959,7 @@ msgctxt "#32940" msgid "Video Player" msgstr "Video-Player" -#: +#: msgctxt "#32941" msgid "Forced subtitles fix" msgstr "Erzwungene Untertitel beheben" @@ -2108,8 +2120,8 @@ msgstr "Diese Anzahl an Medieninhalten pro gestückelter Anfrage in der Biblioth #: msgctxt "#32973" -msgid "Episodes: Skip Post Play screen" -msgstr "Direkt zur nächsten Episode springen" +msgid "Episodes: Continuous playback" +msgstr "Episoden: Nahtlose Wiedergabe" #: msgctxt "#32974" @@ -2282,8 +2294,8 @@ msgstr "Keine TV-Spoiler" #: msgctxt "#33007" -msgid "Select in which cases to blur TV episode thumbnails, previews, redact summaries, hide episode titles and whether to blur chapter images. When the Addon Setting \"Use episode thumbnails in continue hub\" is enabled, blur them as well." -msgstr "Wähle aus, in welchen Fällen Vorschaubilder von TV-Episoden verschleiert, Zusammenfassungen zensiert, Episodentitel versteckt und Kapitelbilder verschleiert werden sollen. Wenn die Addon-Einstellung \"Vorschaubild für Episoden im Fortsetzen-Hub verwenden\" aktiviert ist, diese ebenfalls verschleiern." +msgid "Select in which cases to blur TV episode thumbnails, previews, redact summaries, hide episode titles etc." +msgstr "Wähle aus, in welchen Fällen Vorschaubilder von TV-Episoden verschleiert, Zusammenfassungen zensiert, Episodentitel versteckt usw. werden sollen." #: msgctxt "#33008" @@ -2292,12 +2304,12 @@ msgstr "[Spoiler entfernt]" #: msgctxt "#33009" -msgid "Blur amount for unwatched/in-progress episodes" +msgid "Blur amount for unplayed/in-progress episodes" msgstr "Stärke der Verschleierung nicht geschauter Episoden" #: msgctxt "#33010" -msgid "Unwatched" +msgid "Unplayed" msgstr "Nicht geschaut" #: @@ -2307,7 +2319,7 @@ msgstr "Angefangen" #: msgctxt "#33012" -msgid "No unwatched episode titles" +msgid "No unplayed episode titles" msgstr "Keine Episodentitel für nicht geschaute Folgen" #: @@ -2382,14 +2394,14 @@ msgstr "Aktion wählen" #: msgctxt "#33022" -msgid "Watched indicators" +msgid "Played indicators" msgstr "Geschaut-Indikatoren" #: msgctxt "#33023" -msgid "Classic: Show orange triangle for unwatched items\n" -"Modern: Show green checkmark for watched items\n" -"Modern (2024): Show white checkmark for watched items\n" +msgid "Classic: Show orange triangle for unplayed items\n" +"Modern: Show green checkmark for played items\n" +"Modern (2024): Show white checkmark for played items\n" "(default: Modern (2024))" msgstr "Klassisch: Zeige oranges Dreieck für nicht geschaute Elemente\n" "Modern: Zeige grünen Haken für geschaute Elemente\n" @@ -2403,7 +2415,7 @@ msgstr "Hintergrund moderner Intikatoren verstecken" #: msgctxt "#33025" -msgid "When the above is enabled, hide the black backdrop of the watched state." +msgid "When the above is enabled, hide the black backdrop of the played state." msgstr "Wenn die vorherige Option aktiviert ist, den schwarzen Hintergrund nicht darstellen." #: @@ -2463,8 +2475,8 @@ msgstr "Maximales erwägtes Intro-Offset" #: msgctxt "#33038" -msgid "When encountering an intro marker with a start time offset greater than this, ignore it (default: 600s/10m)" -msgstr "Wenn ein Intro-Marker mit einem Startzeitpunkt größer als diese Einstellung ist, diesen ignorieren (default: 600s/10m)" +msgid "When encountering an intro marker with a start time offset greater than this, ignore it (default: 1400s/23m)" +msgstr "Wenn ein Intro-Marker mit einem Startzeitpunkt größer als diese Einstellung ist, diesen ignorieren (standard: 1400s/23m)" #: msgctxt "#33039" @@ -2845,13 +2857,13 @@ msgstr "Nur plex.direct Hosts behandeln, wenn das Attribut des Servers publicAdd #: msgctxt "#33649" -msgid "CoreELEC: Resume-fix wait for seek" -msgstr "CoreELEC: Fortsetzen-Fix Sprungwartezeit" +msgid "\"Use alternate seek\" wait for seek" +msgstr "\"Alternatives Spulen\" Sprungwartezeit" #: msgctxt "#33650" -msgid "This adjusts the delay between seek-tries on CoreELEC, which fixes resume not always working or double-seeking. When you have resume/double-seek issues, increase this. 100ms should be stable as well. Default: 350ms" -msgstr "Dies passt die Wartezeit zwischen Sprungversuchen auf CoreELEC an, welches den Fehler behebt, dass das Fortsetzen manchmal nicht funktioniert, oder doppelt gesprungen wird. Solltest Du Fortsetzen-/Doppelsprungprobleme haben, diesen Wert erhöhen. 100ms sollten ebenfalls stabil sein. Standard: 350ms" +msgid "This adjusts the delay between seek-tries on problematic platforms when \"Use alternate seek\" is enabled, which fixes resume not always working or double-seeking. When you have resume/double-seek issues, increase this. Default: 500ms" +msgstr "Dies passt die Wartezeit zwischen Sprungversuchen auf problematischen Plattformen an, wenn \"Alternatives Spulen\" aktiv ist, welches den Fehler behebt, dass das Fortsetzen manchmal nicht funktioniert, oder doppelt gesprungen wird. Solltest Du Fortsetzen-/Doppelsprungprobleme haben, diesen Wert erhöhen. Standard: 500ms" #: msgctxt "#33651" @@ -2863,3 +2875,537 @@ msgctxt "#33652" msgid "Never show Post Play" msgstr "Niemals Post-Play-Fenster anzeigen" +msgctxt "#33653" +msgid "Background image resolution scaling %" +msgstr "Hintergrundbilder Auflösungsskalierung %" + +msgctxt "#33654" +msgid "In percent, based on 1080p. Scales the resolution of all backgrounds for better image quality. May impact PMS/PM4K performance, will increase the cache usage accordingly. Can lead to crashes if your hardware is low on RAM. Needs addon restart. Use 400% for 4K. ATTENTION: In order for this to work you need to add 2160 and 2160 to your advancedsettings.xml." +msgstr "In Prozent, basierend auf 1080p. Skaliert die Auflösung aller Hintergründe für bessere Bildqualität. Kann die PMS/PM4K Performance beeinflussen; wird die Cache-Nutzung dementsprechend erhöhen. Kann zu Abstürzen führen, wenn die Hardware zu wenig Arbeitsspeicher besitzt. Benötigt Addon-Neustart. 400% für 4K benutzen. ACHTUNG: Damit dies funktioniert, müssen folgende Werte in die advancedsettings.xml eingetragen werden: 2160 2160" + +msgctxt "#33655" +msgid "Auto-Sync Subtitles" +msgstr "Untertitel autom. Sync." + +msgctxt "#33656" +msgid "Only for External SRT subtitles. The PMS setting for voice activity detection has to be enabled for this to work." +msgstr "Nur für externe SRT-Untertitel. Die PMS-Einstellung für Sprachaktivitätserkennung muss aktiviert sein, damit dies funktioniert." + +msgctxt "#33657" +msgid "Enable Auto-Sync" +msgstr "Auto-Sync aktiv." + +msgctxt "#33658" +msgid "Disable Auto-Sync" +msgstr "Auto-Sync deakt." + +msgctxt "#33659" +msgid "Hide hub: {}" +msgstr "Hub verstecken: {}" + +msgctxt "#33660" +msgid "Disable HDR" +msgstr "HDR deaktivieren" + +msgctxt "#33661" +msgid "If you don't want your client to handle HDR (or HDR-fallback), enable this to force transcoding. Doesn't apply to DV Profile 5." +msgstr "Wenn Dein Client HDR (oder HDR-Fallback) nicht abspielen soll, aktivieren um Transkodieren zu forcieren. Wird bei DV Profil 5 nicht angewandt." + +msgctxt "#33662" +msgid "Remove from Continue Watching" +msgstr "Von Fortsetzen entfernen" + +msgctxt "#33663" +msgid "Home: Confirm item actions" +msgstr "Start: Aktionen bestätigen" + +msgctxt "#33664" +msgid "When acting on items in the Home view, such as mark played, hide from continue watching etc., show a confirmation dialog." +msgstr "Werden in der Start-Ansicht gewisse Aktionen auf Elementen ausgeführt, wie z. B. als gesehen markieren, von Fortsetzen entfernen, einen Bestätigungsdialog anzeigen." + +msgctxt "#33665" +msgid "Disable audio codecs" +msgstr "Deaktivierte Audio-Codecs" + +msgctxt "#33666" +msgid "Audio codecs you can't play back. Disables Direct Play for such media items, enables Direct Stream if possible, transcodes audio stream to compatible format." +msgstr "Audio-Codecs, die Du nicht wiedergeben kannst. Deaktiviert Direktes Abspielen für betroffene Mediendateien, aktiviert Direktes Streamen, wenn möglich, transkodiert den Audio-Stream zu einem kompatiblen Format." + +msgctxt "#33667" +msgid "Use alternate seek" +msgstr "Alternatives Spulen" + +msgctxt "#33668" +msgid "ATTENTION: Only enable this if you have reproducible audio issues after seeking/resuming.\n\nUse an alternative seek method in videos, which can help in problematic scenarios; brings its own issues/quirks. Disabled by default (enabled by default for CoreELEC and LG WebOS)" +msgstr "ACHTUNG: Ausschließlich verwenden, wenn reproduzierbare Audio-Probleme beim Spulen/Fortsetzen auftreten.\n\nVerwende eine alternative Methode zum Spulen beim Video-Abspielen, die in bestimmten problematischen Szenarios helfen kann. Kann selbst Probleme verursachen. Standardm. deaktiviert (standardm. aktiviert bei CoreELEC und LG WebOS)" + +msgctxt "#33669" +msgid "Really sign out?" +msgstr "Wirklich abmelden?" + +msgctxt "#33670" +msgid "Update available" +msgstr "Update verfügbar" + +msgctxt "#33671" +msgid "Current: {current_version}\nNew: {new_version}\n\nChangelog:\n{changelog}" +msgstr "Aktuell: {current_version}\Neu: {new_version}\n\nÄnderungen:\n{changelog}" + +msgctxt "#33672" +msgid "Check for updates" +msgstr "Auf Updates prüfen" + +msgctxt "#33673" +msgid "Automatically check for updates periodically. If installed from a Kodi repository and the Update Source setting is set to Repository, Kodi itself will handle the updating of this addon. Needs a Kodi restart when changed." +msgstr "Automatisch regelmäßig auf Updates überprüfen. Ist das Addon von einem Kodi Repository installiert worden und die Update-Quelle auf Repository gestellt, wird Kodi sich selbst um die Aktualisierung des addons kümmern. Benötigt einen Kodi Neustart wenn geändert." + +msgctxt "#33674" +msgid "Check for updates on start" +msgstr "Beim Start auf Updates prüfen" + +msgctxt "#33675" +msgid "Automatically check for updates on startup. Doesn't do much when Update source is Repository. Needs a Kodi restart when changed." +msgstr "Automatisch beim Start auf Updates prüfen. Bewirkt nicht viel, wenn die Update-Quelle \"Repository\" ist. Benötigt einen Kodi Neustart wenn geändert." + +msgctxt "#33676" +msgid "Update source" +msgstr "Update-Quelle" + +msgctxt "#33677" +msgid "Specifies the update mode. Will immediately check for a new version when changed and closing settings.\nDefault: Repository\n\nBeta: Bleeding edge (possibly unstable)\nStable: Stable branch (faster than Repository)\nRepository: Kodi repository (official (slow) or Don't Panic)" +msgstr "Definiert den Update-Modus. Wird sofort nach einer neuen Version schauen, sobald geändert und die Einstellungen geschlossen werden.\nStandard: Repository\n\nBeta: Bleeding Edge (möglw. instabil)\nStable: Stable branch (faster than Repository)\nRepository: Kodi Repository (offiziell (langsam) oder Don't Panic)" + +msgctxt "#33678" +msgid "Beta" +msgstr "Beta" + +#: Update mode as in (beta, stable, repository) +msgctxt "#33679" +msgid "Stable" +msgstr "Stable" + +msgctxt "#33680" +msgid "Repository" +msgstr "Repository" + +msgctxt "#33681" +msgid "Service updated" +msgstr "Service aktualisiert" + +msgctxt "#33682" +msgid "The update {} has had changes to the updater itself. In order for the updated updater service to work, a Kodi restart is necessary. The addon will work normally, though. Do you still want to run the addon?" +msgstr "Das Update {} beinhaltet Änderungen am Updater selbst. Damit der aktualisierte Updater funktioniert, ist ein Kodi-Neustart nötig. Das Addon wird allerdings normal funktionieren. Möchtest Du das Addon trotzdem starten?" + +msgctxt "#33683" +msgid "Exit, download and install" +msgstr "Beenden, herunterladen und installieren" + +msgctxt "#33684" +msgid "Later" +msgstr "Später" + +msgctxt "#33685" +msgid "Clamp video bitrate" +msgstr "Video-Bitrate beschränken" + +msgctxt "#33686" +msgid "Only show transcode bitrate targets lower than the current video's bitrate." +msgstr "Nur Transcode-Bitrates anzeigen, die kleiner als die Bitrate des aktuellen Videos sind." + +msgctxt "#33687" +msgid "Translation updated" +msgstr "Übersetzung aktualisiert" + +msgctxt "#33688" +msgid "The currently in-use translation has been updated. In order to load the new translation, a Kodi restart is necessary. The addon will still run properly, but you might see badly or untranslated strings. Do you still want to run the addon?" +msgstr "Die aktuell verwendete Übersetzung wurde aktualisiert. Damit die neue Übersetzung geladen wird, ist ein Kodi-Neustart nötig. Das Addon wird trotzdem funktionieren, aber Du könntest schlechte oder nicht übersetzte Texte sehen. Möchtest Du das Addon trotzdem starten?" + +msgctxt "#33689" +msgid "Service running" +msgstr "Service läuft" + +msgctxt "#33690" +msgid "Last update check" +msgstr "Letzte Update-Überprüfung" + +msgctxt "#33691" +msgid "Native languages" +msgstr "Muttersprachen" + +msgctxt "#33692" +msgid "When you usually watch things in a different language with subtitles, but are a native speaker of other languages, which you don't need subtitles for, prevent Plex from auto-selecting subtitles for those languages." +msgstr "Schaust Du normalerweise in einer anderen Sprache mit Untertiteln, bist aber Muttersprachler anderer Sprachen, für die Du keine Untertitel benötigst, halte Plex davon ab, Untertitel für diese Sprachen automatisch auszuwählen." + +msgctxt "#33693" +msgid "Download subtitles using" +msgstr "Untertitel herunterladen mit" + +msgctxt "#33694" +msgid "Ask" +msgstr "Fragen" + +msgctxt "#33695" +msgid "Where do you want to download subtitles from? Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "Woher möchtest du Untertitel herunterladen? Achtung: Das gilt aktuell nur für die Untertitel-Schnellaktionen im Player. Die Untertitel-Download Funktion in den Stream-Einstellungen benutzt immer Plex als Quelle." + +msgctxt "#33696" +msgid "No subtitles found." +msgstr "Keine Untertitel gefunden." + +#: things in curly brackets are variables, don't translate them! +msgctxt "#33697" +msgid "{provider_title}, Score: {subtitle_score}{subtitle_info}" +msgstr "{provider_title}, Punktzahl: {subtitle_score}{subtitle_info}" + +#: hearing impaired subtitle flag +msgctxt "#33698" +msgid "HI" +msgstr "HI" + +#: forced subtitle flag +msgctxt "#33699" +msgid "forced" +msgstr "erzwungen" + +msgctxt "#33700" +msgid "Download subtitles: {}" +msgstr "Untertitel herunterladen: {}" + +msgctxt "#33701" +msgid "Fallback to Kodi" +msgstr "Falle auf Kodi zurück" + +msgctxt "#33702" +msgid "When no subtitles are found via the Plex subtitle search, fall back to Kodi subtitle search. Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "Werden keine Untertitel über die Plex-Untertitel-Suche gefunden, auf Kodi zurück fallen. Achtung: Das gilt aktuell nur für die Untertitel-Schnellaktionen im Player. Die Untertitel-Download Funktion in den Stream-Einstellungen benutzt immer Plex als Quelle." + +msgctxt "#33703" +msgid "Download subtitles" +msgstr "Untertitel herunterladen" + +msgctxt "#33704" +msgid "Using which service?" +msgstr "Mit welchem Service?" + +msgctxt "#33705" +msgid "Hide ratings" +msgstr "Bewertungen verstecken" + +msgctxt "#33706" +msgid "Blur images" +msgstr "Bilder verschleiern" + +msgctxt "#33707" +msgid "Blur images on home (in progress)" +msgstr "Bilder auf Home verschleiern (fortsetzen)" + +msgctxt "#33708" +msgid "Hide summaries" +msgstr "Zusammenfassungen verstecken" + +msgctxt "#33709" +msgid "Show ratings for" +msgstr "Bewertungen anzeigen für" + +msgctxt "#33710" +msgid "Show reviews for" +msgstr "Kritiken anzeigen für" + +msgctxt "#33711" +msgid "Always resume media" +msgstr "Medien immer fortsetzen" + +msgctxt "#33712" +msgid "When playback of an in-progress media is requested, resume it by default instead of asking whether to resume or start from the beginning." +msgstr "Bei einem bereits begonnenen Medium nicht fragen, ob es fortgesetzt oder vom Start abgespielt werden soll." + +msgctxt "#33713" +msgid "Home: Resume in-progress items" +msgstr "Start: Angef. Items fortsetzen" + +msgctxt "#33714" +msgid "Resume in-progress items directly instead of visiting the media." +msgstr "Setze angefangene Items direkt fort, anstatt die Mediendetails aufzurufen." + +msgctxt "#33715" +msgid "OSD hide-delay" +msgstr "OSD Versteck-Verzögerung" + +msgctxt "#33716" +msgid "How long to wait until hiding the OSD. Only works with Plextuary skin or others with configurable OSD timeout. Default: 4s (+/- 1s)" +msgstr "Nach wieviel Sekunden das OSD automatisch versteckt werden soll. Funktioniert nur mit dem Plextuary Skin oder anderen Skins mit konfigurierbarer OSD Verzögerung. Standard: 4s (+/- 1s)" + +msgctxt "#33718" +msgid "Played" +msgstr "Geschaut" + +msgctxt "#33719" +msgid "Refresh metadata" +msgstr "Metadaten aktualisieren" + +msgctxt "#33720" +msgid "Clear all caches" +msgstr "Alle Caches leeren" + +msgctxt "#33721" +msgid "Clear library cache (not items)" +msgstr "Bibliothek-Cache löschen (ausg. Items)" + +msgctxt "#33722" +msgid "Libraries" +msgstr "Bibliotheken" + +msgctxt "#33723" +msgid "Media Items" +msgstr "Medien" + +msgctxt "#33724" +msgid "Cache Plex data for" +msgstr "Plex-Daten cachen für" + +msgctxt "#33725" +msgid "Persist cached Plex data" +msgstr "Gecachte Plex-Daten persistieren" + +msgctxt "#33726" +msgid "Instead of clearing the cache when exiting the addon, persist it instead. Warning: You'll most likely encounter missing items in libraries or outdated data. Use the corresponding menu functionalities to clear the cache for specific items or libraries." +msgstr "Anstatt den Cache beim Beenden zu löschen, behalte ihn. Achtung: Es wird sehr wahrscheinlich zu fehlenden Items in Bibliotheken oder abgelaufene Daten kommen. Die korrespondierenden Menüfunktionalitäten benutzen um den Cache für spezifische Items oder Bibliotheken zu leeren." + +msgctxt "#33727" +msgid "Store Plex server responses for items and library views in a local SQLite database. Doesn't cache anything else (Home/Hubs are always up to date). Massively speeds up consecutive visits to items and libraries. Certain important events, such as watch state changes, automatically delete the item cache and its corresponding library cache. (Default: Off)" +msgstr "Speichere die Plex-Server-Antworten für Items und Bibliothekansichten in einer lokalen SQLite-Datenbank. Gilt nur für diese zwei Datenbereiche (Start/Hubs sind immer aktuell). Erzeugt massiven Geschwindigkeitsvorteil bei aufeinanderfolgenden Besuchen von Items und Bibliotheken. Bestimmte, wichtige Ereignisse, wie z. B. Abgespielt-Status, löschen automatisch den Item-Cache und die dazugehörigen Bibliothek-Caches. (Standard: Aus)" + +msgctxt "#33728" +msgid "Clear cache for item" +msgstr "Cache für Medium löschen" + +msgctxt "#33729" +msgid "Expire cached Plex data after (hours)" +msgstr "Ablauf gecachter Plex-Daten (Stunden)" + +msgctxt "#33730" +msgid "Randomly" +msgstr "Zufällig" + +msgctxt "#33731" +msgid "By Bitrate" +msgstr "Nach Bitrate" + +msgctxt "#33732" +msgid "Bitrate" +msgstr "Bitrate" + +msgctxt "#33733" +msgid "Transcode target codec" +msgstr "Transkodieren Ziel-Codec" + +msgctxt "#33734" +msgid "Sets the target codec when transcoding/direct streaming. Overridden when \"Transcode audio to AC3\" is set." +msgstr "Setzt den Ziel-Codec beim Transkodieren/Direkt Streamen. Wird überschrieben wenn \"Transkodiere Ton zu AC3\" aktiv ist." + +msgctxt "#33735" +msgid "Always auto-start" +msgstr "Immer automatisch starten" + +msgctxt "#33736" +msgid "By default PM4K won't auto-start when the current window isn't a Home-derived window (e.g.: Settings). Enable this if you always want to auto-start PM4K regardless of the current window. Can also improve start-up time." +msgstr "PM4K startet nicht, solange das aktuelle Fenster kein Home-bezogenes Fenster ist (z. B.: Einstellungen). Aktivieren, wenn PM4K immer automatisch gestartet werden soll, egal, welches Fenster aktuell aktiv ist. Kann die Auto-Start-Zeit verbessern." + +msgctxt "#33737" +msgid "Loop theme music" +msgstr "Titelmusik-Dauerschleife" + +msgctxt "#33738" +msgid "Max playlist size" +msgstr "Max. Playlist Größe" + +msgctxt "#33739" +msgid "How many items to load into a Kodi playlist before chunking them remotely using Plex PlayQueues (default: 500); Reduce on memory issues/crashes." +msgstr "Wie groß soll eine Playlist maximal sein, bevor diese durch eine gestückelte Plex-seitige PlayQueue ersetzt wird? (Standard: 500); Bei Speicherproblemen/Abstürzen reduzieren." + +msgctxt "#33740" +msgid "Home: Episodes season thumbnails" +msgstr "Start: Staffelposter verwenden" + +msgctxt "#33741" +msgid "Use season thumbnails/posters when displaying episodes in hubs instead of the TV show's." +msgstr "Staffel-Thumbnails/Poster für Episoden in Hubs anstatt die der TV Serie verwenden." + +msgctxt "#34000" +msgid "Watchlist" +msgstr "Merkliste" + +msgctxt "#34001" +msgid "Released" +msgstr "Veröffentlicht" + +msgctxt "#34002" +msgid "Movies & Shows" +msgstr "Filme & Serien" + +#: e.g.: 8 seasons +msgctxt "#34003" +msgid "{} seasons" +msgstr "{} Staffeln" + +msgctxt "#34004" +msgid "Choose server" +msgstr "Server auswählen" + +msgctxt "#34005" +msgid "Availability" +msgstr "Verfügbarkeit" + +#: e.g.: 1 season +msgctxt "#34006" +msgid "{} season" +msgstr "{} Staffel" + +msgctxt "#34007" +msgid "Use Watchlist" +msgstr "Merkliste verwenden" + +msgctxt "#34008" +msgid "Activates the current user's Plex watchlist as a section item. Adds watchlist functionality to certain media screens. Per-user setting. Default: On" +msgstr "Aktiviert die Plex Merkliste des aktuellen Nutzers. Fügt Merklistenfunktionalität zu bestimmten Ansichten hinzu. Pro-Nutzer Einstellung. Standard: An" + +msgctxt "#34009" +msgid "Watchlist auto-remove" +msgstr "Merkliste Auto-Löschen" + +msgctxt "#34010" +msgid "Automatically remove fully watched items from watchlist. Default: On" +msgstr "Automatisch vollständig geschaute Medien von der Merkliste entfernen. Standard: An" + +msgctxt "#34011" +msgid "Remove from watchlist" +msgstr "Von Merkliste entfernen" + +msgctxt "#34012" +msgid "Fast pause/resume" +msgstr "Schnelles Pausieren/Fortsetzen" + +msgctxt "#34013" +msgid "when paused" +msgstr "wenn pausiert" + +msgctxt "#34014" +msgid "when playing" +msgstr "beim Abspielen" + +msgctxt "#34015" +msgid "User-specific. Use OK/ENTER button to pause instead of showing the OSD (which can then only be accessed using DOWN), or resume when paused. Only works with 'Behave like official Plex clients' enabled." +msgstr "Benutzerspezifisch. OK/ENTER Knopf zum schnellen Pausieren verwenden anstatt das OSD anzuzeigen (welches dann nur noch über den RUNTER Knopf erreichbar ist), oder zum Fortsetzen, wenn pausiert. Funktioniert nur, wenn 'Verhalten wie Plex Clients' aktiviert ist." + +msgctxt "#34016" +msgid "Max shutdown wait" +msgstr "Max. Herunterfahrzeit" + +msgctxt "#34017" +msgid "The plugin will try to hard exit once this time limit is reached (default: 5 seconds)" +msgstr "Das Plugin versucht sich nach dem Ablauf dieser Zeit hart zu beenden" + +msgctxt "#34018" +msgid "Related Media" +msgstr "Ähnliche Medien" + +#: watchlist discover hub title prefix +msgctxt "#34019" +msgid "Discover: {}" +msgstr "" + +msgctxt "#34020" +msgid "Loading external hubs" +msgstr "Lade externe Hubs" + +msgctxt "#34021" +msgid "Please wait ..." +msgstr "Bitte warten ..." + +msgctxt "#34022" +msgid "Video play completion behaviour" +msgstr "Verhalten für \"Video zuende abgespielt\"" + +msgctxt "#34023" +msgid "Decide whether to use end credits markers to determine the 'watched' state of video items. When markers are not available the selected threshold percentage will be used." +msgstr "Wähle, ob das Video bei Erreichen der Abpann-Position als komplett \"gesehen\" betrachtet wird. Wenn keine Abspann-Position bekannt ist, wird statt dessen der gewählte Abspielfortschritt als Schwelle benutzt." + +msgctxt "#34024" +msgid "at selected threshold percentage" +msgstr "beim Erreichen der Fortschrittsgrenze" + +msgctxt "#34025" +msgid "at final credits marker position" +msgstr "an der finalen Abspannmarkierung" + +msgctxt "#34026" +msgid "at first credits marker position" +msgstr "an der ersten Abspannmarkierung" + +msgctxt "#34027" +msgid "earliest between threshold percent and first credits marker" +msgstr "frühester Wert zwischen Fortschrittsgrenze und erster Abspannmarkierung" + +msgctxt "#34028" +msgid "\"Use alternate seek\" valid seek window" +msgstr "\"Alternatives Spulen\" valides Spulfenster" + +msgctxt "#34029" +msgid "When \"Use alternate seek\" is enabled, any seek amount outside of this threshold window will be applied, otherwise the seek attempt will be ignored. If you encounter any seek issues, play with this value (ms, default: 2000)" +msgstr "Mit aktiviertem \"Alternatives Spulen\", wird jeder Spulversuch größer als dieser Wert angewandt, ansonsten wird der Spulversuch ignoriert. Sollten Probleme beim Spulen auftreten, diesen Wert verändern (ms, Standard: 2000)" + +msgctxt "#34030" +msgid "Unlock Direct Play for above 4K" +msgstr "Direct Play für über 4K freischalten" + +msgctxt "#34031" +msgid "Producer" +msgstr "Produzent" + +msgctxt "#34032" +msgid "Audio Language" +msgstr "Tonspur Sprache" + +msgctxt "#34033" +msgid "Subtitle Language" +msgstr "Untertitel Sprache" + +msgctxt "#34034" +msgid "Folder Location" +msgstr "Speicherort" + +msgctxt "#34035" +msgid "Editions" +msgstr "Edition" + +msgctxt "#34036" +msgid "DOVI" +msgstr "DOVI" + +msgctxt "#34037" +msgid "HDR" +msgstr "HDR" + +msgctxt "#34038" +msgid "Force plex.direct mapping" +msgstr "plex.direct-Zuordnung erzwingen" + +msgctxt "#34039" +msgid "If you (still) don't see any posters: Under certain circumstances when Kodi still can't resolve hostnames when loading assets, even though the host resolves natively, enable this to force plex.direct mapping via advancedsettings.xml." +msgstr "Sind (weiterhin) keine Poster sichtbar: In bestimmten Fällen kann Kodi weiterhin keine Hostnamen auflösen. In dem Fall aktivieren um die plex.direct-Zuordnung via advancedsettings.xml zu erzwingen." + +msgctxt "#34040" +msgid "By Progress" +msgstr "Nach Fortschritt" + +msgctxt "#34041" +msgid "Progress" +msgstr "Fortschritt" + +msgctxt "#34042" +msgid "By Album" +msgstr "Nach Album" + +msgctxt "#34043" +msgid "Album" +msgstr "Album" diff --git a/script.plexmod/resources/language/resource.language.en_gb/strings.po b/script.plexmod/resources/language/resource.language.en_gb/strings.po index 0e75dc59d3..6342d064fd 100644 --- a/script.plexmod/resources/language/resource.language.en_gb/strings.po +++ b/script.plexmod/resources/language/resource.language.en_gb/strings.po @@ -22,35 +22,35 @@ msgid "Original" msgstr "" msgctxt "#32002" -msgid "20 Mbps 1080p" +msgid "20 Mbps" msgstr "" msgctxt "#32003" -msgid "12 Mbps 1080p" +msgid "12 Mbps" msgstr "" msgctxt "#32004" -msgid "10 Mbps 1080p" +msgid "10 Mbps" msgstr "" msgctxt "#32005" -msgid "8 Mbps 1080p" +msgid "8 Mbps" msgstr "" msgctxt "#32006" -msgid "4 Mbps 720p" +msgid "4 Mbps" msgstr "" msgctxt "#32007" -msgid "3 Mbps 720p" +msgid "3 Mbps" msgstr "" msgctxt "#32008" -msgid "2 Mbps 720p" +msgid "2 Mbps" msgstr "" msgctxt "#32009" -msgid "1.5 Mbps 480p" +msgid "1.5 Mbps" msgstr "" msgctxt "#32010" @@ -73,6 +73,18 @@ msgctxt "#32014" msgid "64 kbps" msgstr "" +msgctxt "#32015" +msgid "6 Mbps" +msgstr "" + +msgctxt "#32016" +msgid "16 Mbps" +msgstr "" + +msgctxt "#32017" +msgid "26 Mbps" +msgstr "" + msgctxt "#32020" msgid "Local Quality" msgstr "" @@ -1231,7 +1243,7 @@ msgid "Disabled" msgstr "" msgctxt "#33509" -msgid "Early intro skip threshold (default: < 60s/1m)" +msgid "Early intro skip threshold (default: < 120s/2m)" msgstr "" msgctxt "#33510" @@ -1695,7 +1707,7 @@ msgid "Request this amount of media items per chunk request in library view (+6- msgstr "" msgctxt "#32973" -msgid "Episodes: Skip Post Play screen" +msgid "Episodes: Continuous playback" msgstr "" msgctxt "#32974" @@ -1831,7 +1843,7 @@ msgid "No TV spoilers" msgstr "" msgctxt "#33007" -msgid "Select in which cases to blur TV episode thumbnails, previews, redact summaries, hide episode titles and whether to blur chapter images. When the Addon Setting \"Use episode thumbnails in continue hub\" is enabled, blur them as well." +msgid "Select in which cases to blur TV episode thumbnails, previews, redact summaries, hide episode titles etc." msgstr "" msgctxt "#33008" @@ -1839,11 +1851,11 @@ msgid "[Spoilers removed]" msgstr "" msgctxt "#33009" -msgid "Blur amount for unwatched/in-progress episodes" +msgid "Blur amount for unplayed/in-progress episodes" msgstr "" msgctxt "#33010" -msgid "Unwatched" +msgid "Unplayed" msgstr "" msgctxt "#33011" @@ -1851,7 +1863,7 @@ msgid "In progress" msgstr "" msgctxt "#33012" -msgid "No unwatched episode titles" +msgid "No unplayed episode titles" msgstr "" msgctxt "#33013" @@ -1891,11 +1903,11 @@ msgid "Choose action" msgstr "" msgctxt "#33022" -msgid "Watched indicators" +msgid "Played indicators" msgstr "" msgctxt "#33023" -msgid "Classic: Show orange triangle for unwatched items\nModern: Show green checkmark for watched items\nModern (2024): Show white checkmark for watched items\n(default: Modern (2024))" +msgid "Classic: Show orange triangle for unplayed items\nModern: Show green checkmark for played items\nModern (2024): Show white checkmark for played items\n(default: Modern (2024))" msgstr "" msgctxt "#33024" @@ -1903,7 +1915,7 @@ msgid "Hide background in modern indicators" msgstr "" msgctxt "#33025" -msgid "When the above is enabled, hide the black backdrop of the watched state." +msgid "When the above is enabled, hide the black backdrop of the played state." msgstr "" msgctxt "#33026" @@ -1955,7 +1967,7 @@ msgid "Maximum intro offset to consider" msgstr "" msgctxt "#33038" -msgid "When encountering an intro marker with a start time offset greater than this, ignore it (default: 600s/10m)" +msgid "When encountering an intro marker with a start time offset greater than this, ignore it (default: 1400s/23m)" msgstr "" msgctxt "#33039" @@ -2255,11 +2267,11 @@ msgid "Only handle plex.direct hosts when the server's attributes publicAddressM msgstr "" msgctxt "#33649" -msgid "CoreELEC: Resume-fix wait for seek" +msgid "\"Use alternate seek\" wait for seek" msgstr "" msgctxt "#33650" -msgid "This adjusts the delay between seek-tries on CoreELEC, which fixes resume not always working or double-seeking. When you have resume/double-seek issues, increase this. 100ms should be stable as well. Default: 350ms" +msgid "This adjusts the delay between seek-tries on problematic platforms when \"Use alternate seek\" is enabled, which fixes resume not always working or double-seeking. When you have resume/double-seek issues, increase this. Default: 500ms" msgstr "" msgctxt "#33651" @@ -2269,3 +2281,544 @@ msgstr "" msgctxt "#33652" msgid "Never show Post Play" msgstr "" + +msgctxt "#33653" +msgid "Background image resolution scaling %" +msgstr "" + +msgctxt "#33654" +msgid "In percent, based on 1080p. Scales the resolution of all backgrounds for better image quality. May impact PMS/PM4K performance, will increase the cache usage accordingly. Can lead to crashes if your hardware is low on RAM. Needs addon restart. Use 400% for 4K. ATTENTION: In order for this to work you need to add 2160 and 2160 to your advancedsettings.xml." +msgstr "" + +msgctxt "#33655" +msgid "Auto-Sync Subtitles" +msgstr "" + +msgctxt "#33656" +msgid "Only for External SRT subtitles. The PMS setting for voice activity detection has to be enabled for this to work." +msgstr "" + +#: in player quick settings UI, needs to be short +msgctxt "#33657" +msgid "Enable Auto-Sync" +msgstr "" + +#: in player quick settings UI, needs to be short +msgctxt "#33658" +msgid "Disable Auto-Sync" +msgstr "" + +msgctxt "#33659" +msgid "Hide hub: {}" +msgstr "" + +msgctxt "#33660" +msgid "Disable HDR" +msgstr "" + +msgctxt "#33661" +msgid "If you don't want your client to handle HDR (or HDR-fallback), enable this to force transcoding. Doesn't apply to DV Profile 5." +msgstr "" + +msgctxt "#33662" +msgid "Remove from Continue Watching" +msgstr "" + +msgctxt "#33663" +msgid "Home: Confirm item actions" +msgstr "" + +msgctxt "#33664" +msgid "When acting on items in the Home view, such as mark played, hide from continue watching etc., show a confirmation dialog." +msgstr "" + +msgctxt "#33665" +msgid "Disable audio codecs" +msgstr "" + +msgctxt "#33666" +msgid "Audio codecs you can't play back. Disables Direct Play for such media items, enables Direct Stream if possible, transcodes audio stream to compatible format." +msgstr "" + +msgctxt "#33667" +msgid "Use alternate seek" +msgstr "" + +msgctxt "#33668" +msgid "ATTENTION: Only enable this if you have reproducible audio issues after seeking/resuming.\n\nUse an alternative seek method in videos, which can help in problematic scenarios; brings its own issues/quirks. Disabled by default (enabled by default for CoreELEC and LG WebOS)" +msgstr "" + +msgctxt "#33669" +msgid "Really sign out?" +msgstr "" + +msgctxt "#33670" +msgid "Update available" +msgstr "" + +msgctxt "#33671" +msgid "Current: {current_version}\nNew: {new_version}\n\nChangelog:\n{changelog}" +msgstr "" + +msgctxt "#33672" +msgid "Check for updates" +msgstr "" + +msgctxt "#33673" +msgid "Automatically check for updates periodically. If installed from a Kodi repository and the Update Source setting is set to Repository, Kodi itself will handle the updating of this addon. Needs a Kodi restart when changed." +msgstr "" + +msgctxt "#33674" +msgid "Check for updates on start" +msgstr "" + +msgctxt "#33675" +msgid "Automatically check for updates on startup. Doesn't do much when Update source is Repository. Needs a Kodi restart when changed." +msgstr "" + +msgctxt "#33676" +msgid "Update source" +msgstr "" + +msgctxt "#33677" +msgid "Specifies the update mode. Will immediately check for a new version when changed and closing settings.\nDefault: Repository\n\nBeta: Bleeding edge (possibly unstable)\nStable: Stable branch (faster than Repository)\nRepository: Kodi repository (official (slow) or Don't Panic)" +msgstr "" + +msgctxt "#33678" +msgid "Beta" +msgstr "" + +#: Update mode as in (beta, stable, repository) +msgctxt "#33679" +msgid "Stable" +msgstr "" + +msgctxt "#33680" +msgid "Repository" +msgstr "" + +msgctxt "#33681" +msgid "Service updated" +msgstr "" + +msgctxt "#33682" +msgid "The update {} has had changes to the updater itself. In order for the updated updater service to work, a Kodi restart is necessary. The addon will work normally, though. Do you still want to run the addon?" +msgstr "" + +msgctxt "#33683" +msgid "Exit, download and install" +msgstr "" + +msgctxt "#33684" +msgid "Later" +msgstr "" + +msgctxt "#33685" +msgid "Clamp video bitrate" +msgstr "" + +msgctxt "#33686" +msgid "Only show transcode bitrate targets lower than the current video's bitrate." +msgstr "" + +msgctxt "#33687" +msgid "Translation updated" +msgstr "" + +msgctxt "#33688" +msgid "The currently in-use translation has been updated. In order to load the new translation, a Kodi restart is necessary. The addon will still run properly, but you might see badly or untranslated strings. Do you still want to run the addon?" +msgstr "" + +msgctxt "#33689" +msgid "Service running" +msgstr "" + +msgctxt "#33690" +msgid "Last update check" +msgstr "" + +msgctxt "#33691" +msgid "Native languages" +msgstr "" + +msgctxt "#33692" +msgid "When you usually watch things in a different language with subtitles, but are a native speaker of other languages, which you don't need subtitles for, prevent Plex from auto-selecting subtitles for those languages." +msgstr "" + +msgctxt "#33693" +msgid "Download subtitles using" +msgstr "" + +msgctxt "#33694" +msgid "Ask" +msgstr "" + +msgctxt "#33695" +msgid "Where do you want to download subtitles from? Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "" + +msgctxt "#33696" +msgid "No subtitles found." +msgstr "" + +#: things in curly brackets are variables, don't translate them! +msgctxt "#33697" +msgid "{provider_title}, Score: {subtitle_score}{subtitle_info}" +msgstr "" + +#: hearing impaired subtitle flag +msgctxt "#33698" +msgid "HI" +msgstr "" + +#: forced subtitle flag +msgctxt "#33699" +msgid "forced" +msgstr "" + +msgctxt "#33700" +msgid "Download subtitles: {}" +msgstr "" + +msgctxt "#33701" +msgid "Fallback to Kodi" +msgstr "" + +msgctxt "#33702" +msgid "When no subtitles are found via the Plex subtitle search, fall back to Kodi subtitle search. Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "" + +msgctxt "#33703" +msgid "Download subtitles" +msgstr "" + +msgctxt "#33704" +msgid "Using which service?" +msgstr "" + +msgctxt "#33705" +msgid "Hide ratings" +msgstr "" + +msgctxt "#33706" +msgid "Blur images" +msgstr "" + +msgctxt "#33707" +msgid "Blur images on home (in progress)" +msgstr "" + +msgctxt "#33708" +msgid "Hide summaries" +msgstr "" + +msgctxt "#33709" +msgid "Show ratings for" +msgstr "" + +msgctxt "#33710" +msgid "Show reviews for" +msgstr "" + +msgctxt "#33711" +msgid "Always resume media" +msgstr "" + +msgctxt "#33712" +msgid "When playback of an in-progress media is requested, resume it by default instead of asking whether to resume or start from the beginning." +msgstr "" + +msgctxt "#33713" +msgid "Home: Resume in-progress items" +msgstr "" + +msgctxt "#33714" +msgid "Resume in-progress items directly instead of visiting the media." +msgstr "" + +msgctxt "#33715" +msgid "OSD hide-delay" +msgstr "" + +msgctxt "#33716" +msgid "How long to wait until hiding the OSD. Only works with Plextuary skin or others with configurable OSD timeout. Default: 4s (+/- 1s)" +msgstr "" + +msgctxt "#33717" +msgid "Debug requests" +msgstr "" + +msgctxt "#33718" +msgid "Played" +msgstr "" + +msgctxt "#33719" +msgid "Refresh metadata" +msgstr "" + +msgctxt "#33720" +msgid "Clear all caches" +msgstr "" + +msgctxt "#33721" +msgid "Clear library cache (not items)" +msgstr "" + +msgctxt "#33722" +msgid "Libraries" +msgstr "" + +msgctxt "#33723" +msgid "Media Items" +msgstr "" + +msgctxt "#33724" +msgid "Cache Plex data for" +msgstr "" + +msgctxt "#33725" +msgid "Persist cached Plex data" +msgstr "" + +msgctxt "#33726" +msgid "Instead of clearing the cache when exiting the addon, persist it instead. Warning: You'll most likely encounter missing items in libraries or outdated data. Use the corresponding menu functionalities to clear the cache for specific items or libraries." +msgstr "" + +msgctxt "#33727" +msgid "Store Plex server responses for items and library views in a local SQLite database. Doesn't cache anything else (Home/Hubs are always up to date). Massively speeds up consecutive visits to items and libraries. Certain important events, such as watch state changes, automatically delete the item cache and its corresponding library cache. (Default: Off)" +msgstr "" + +msgctxt "#33728" +msgid "Clear cache for item" +msgstr "" + +msgctxt "#33729" +msgid "Expire cached Plex data after (hours)" +msgstr "" + +msgctxt "#33730" +msgid "Randomly" +msgstr "" + +msgctxt "#33731" +msgid "By Bitrate" +msgstr "" + +msgctxt "#33732" +msgid "Bitrate" +msgstr "" + +msgctxt "#33733" +msgid "Transcode target codec" +msgstr "" + +msgctxt "#33734" +msgid "Sets the target codec when transcoding/direct streaming. Overridden when \"Transcode audio to AC3\" is set." +msgstr "" + +msgctxt "#33735" +msgid "Always auto-start" +msgstr "" + +msgctxt "#33736" +msgid "By default PM4K won't auto-start when the current window isn't a Home-derived window (e.g.: Settings). Enable this if you always want to auto-start PM4K regardless of the current window. Can also improve start-up time." +msgstr "" + +msgctxt "#33737" +msgid "Loop theme music" +msgstr "" + +msgctxt "#33738" +msgid "Max playlist size" +msgstr "" + +msgctxt "#33739" +msgid "How many items to load into a Kodi playlist before chunking them remotely using Plex PlayQueues (default: 500); Reduce on memory issues/crashes." +msgstr "" + +msgctxt "#33740" +msgid "Home: Episodes season thumbnails" +msgstr "" + +msgctxt "#33741" +msgid "Use season thumbnails/posters when displaying episodes in hubs instead of the TV show's." +msgstr "" + +msgctxt "#34000" +msgid "Watchlist" +msgstr "" + +msgctxt "#34001" +msgid "Released" +msgstr "" + +msgctxt "#34002" +msgid "Movies & Shows" +msgstr "" + +#: e.g.: 8 seasons +msgctxt "#34003" +msgid "{} seasons" +msgstr "" + +msgctxt "#34004" +msgid "Choose server" +msgstr "" + +msgctxt "#34005" +msgid "Availability" +msgstr "" + +#: e.g.: 1 season +msgctxt "#34006" +msgid "{} season" +msgstr "" + +msgctxt "#34007" +msgid "Use Watchlist" +msgstr "" + +msgctxt "#34008" +msgid "Activates the current user's Plex watchlist as a section item. Adds watchlist functionality to certain media screens. Per-user setting. Default: On" +msgstr "" + +msgctxt "#34009" +msgid "Watchlist auto-remove" +msgstr "" + +msgctxt "#34010" +msgid "Automatically remove fully watched items from watchlist. Default: On" +msgstr "" + +msgctxt "#34011" +msgid "Remove from watchlist" +msgstr "" + +msgctxt "#34012" +msgid "Fast pause/resume" +msgstr "" + +msgctxt "#34013" +msgid "when paused" +msgstr "" + +msgctxt "#34014" +msgid "when playing" +msgstr "" + +msgctxt "#34015" +msgid "User-specific. Use OK/ENTER button to pause instead of showing the OSD (which can then only be accessed using DOWN), or resume when paused. Only works with 'Behave like official Plex clients' enabled." +msgstr "" + +msgctxt "#34016" +msgid "Max shutdown wait" +msgstr "" + +msgctxt "#34017" +msgid "The plugin will try to hard exit once this time limit is reached (default: 5 seconds)" +msgstr "" + +msgctxt "#34018" +msgid "Related Media" +msgstr "" + +#: watchlist discover hub title prefix +msgctxt "#34019" +msgid "Discover: {}" +msgstr "" + +msgctxt "#34020" +msgid "Loading external hubs" +msgstr "" + +msgctxt "#34021" +msgid "Please wait ..." +msgstr "" + +msgctxt "#34022" +msgid "Video play completion behaviour" +msgstr "" + +msgctxt "#34023" +msgid "Decide whether to use end credits markers to determine the 'watched' state of video items. When markers are not available the selected threshold percentage will be used." +msgstr "" + +msgctxt "#34024" +msgid "at selected threshold percentage" +msgstr "" + +msgctxt "#34025" +msgid "at final credits marker position" +msgstr "" + +msgctxt "#34026" +msgid "at first credits marker position" +msgstr "" + +msgctxt "#34027" +msgid "earliest between threshold percent and first credits marker" +msgstr "" + +msgctxt "#34028" +msgid "\"Use alternate seek\" valid seek window" +msgstr "" + +msgctxt "#34029" +msgid "When \"Use alternate seek\" is enabled, any seek amount outside of this threshold window will be applied, otherwise the seek attempt will be ignored. If you encounter any seek issues, play with this value (ms, default: 2000)" +msgstr "" + +msgctxt "#34030" +msgid "Unlock Direct Play for above 4K" +msgstr "" + +msgctxt "#34031" +msgid "Producer" +msgstr "" + +msgctxt "#34032" +msgid "Audio Language" +msgstr "" + +msgctxt "#34033" +msgid "Subtitle Language" +msgstr "" + +msgctxt "#34034" +msgid "Folder Location" +msgstr "" + +msgctxt "#34035" +msgid "Editions" +msgstr "" + +msgctxt "#34036" +msgid "DOVI" +msgstr "" + +msgctxt "#34037" +msgid "HDR" +msgstr "" + +msgctxt "#34038" +msgid "Force plex.direct mapping" +msgstr "" + +msgctxt "#34039" +msgid "If you (still) don't see any posters: Under certain circumstances when Kodi still can't resolve hostnames when loading assets, even though the host resolves natively, enable this to force plex.direct mapping via advancedsettings.xml." +msgstr "" + +msgctxt "#34040" +msgid "By Progress" +msgstr "" + +msgctxt "#34041" +msgid "Progress" +msgstr "" + +msgctxt "#34042" +msgid "By Album" +msgstr "" + +msgctxt "#34043" +msgid "Album" +msgstr "" diff --git a/script.plexmod/resources/language/resource.language.es_es/strings.po b/script.plexmod/resources/language/resource.language.es_es/strings.po index 8a5987b2c0..b49390ca2f 100644 --- a/script.plexmod/resources/language/resource.language.es_es/strings.po +++ b/script.plexmod/resources/language/resource.language.es_es/strings.po @@ -17,46 +17,6 @@ msgctxt "#32001" msgid "Original" msgstr "Original" -#: -msgctxt "#32002" -msgid "20 Mbps 1080p" -msgstr "20 Mbps 1080p" - -#: -msgctxt "#32003" -msgid "12 Mbps 1080p" -msgstr "12 Mbps 1080p" - -#: -msgctxt "#32004" -msgid "10 Mbps 1080p" -msgstr "10 Mbps 1080p" - -#: -msgctxt "#32005" -msgid "8 Mbps 1080p" -msgstr "8 Mbps 1080p" - -#: -msgctxt "#32006" -msgid "4 Mbps 720p" -msgstr "4 Mbps 720p" - -#: -msgctxt "#32007" -msgid "3 Mbps 720p" -msgstr "3 Mbps 720p" - -#: -msgctxt "#32008" -msgid "2 Mbps 720p" -msgstr "2 Mbps 720p" - -#: -msgctxt "#32009" -msgid "1.5 Mbps 480p" -msgstr "1.5 Mbps 480p" - #: msgctxt "#32010" msgid "720 kbps" @@ -107,16 +67,6 @@ msgctxt "#32024" msgid "Debug Logging" msgstr "Log de Depuración" -#: -msgctxt "#32025" -msgid "Direct Play" -msgstr "Reproducción directa" - -#: -msgctxt "#32026" -msgid "Direct Stream" -msgstr "Transmisión directa" - #: msgctxt "#32027" msgid "Force" @@ -157,16 +107,6 @@ msgctxt "#32035" msgid "Always" msgstr "Siempre" -#: -msgctxt "#32036" -msgid "4K" -msgstr "4K" - -#: -msgctxt "#32037" -msgid "HEVC (h265)" -msgstr "HEVC (h265)" - #: msgctxt "#32038" msgid "Automatically Sign In" @@ -292,11 +232,6 @@ msgctxt "#32100" msgid "Skip user selection and pin entry on startup." msgstr "Omitir la selección de usuario y PIN al iniciar." -#: -msgctxt "#32101" -msgid "If enabled, when playback ends and there is a 'Next Up' item available, it will be automatically be played after a {} second delay." -msgstr "Si está activada, cuando finalice la reproducción y haya un elemento 'Siguiente' disponible, éste se reproducirá automáticamente tras un retardo de {} segundos." - #: msgctxt "#32102" msgid "Enable this if your hardware can handle 4K playback. Disable it to force transcoding." @@ -637,16 +572,6 @@ msgctxt "#32356" msgid "Date Viewed" msgstr "Fecha en la que se vio" -#: -msgctxt "#32357" -msgid "By Title" -msgstr "Por título" - -#: -msgctxt "#32358" -msgid "Title" -msgstr "Título" - #: msgctxt "#32359" msgid "By Rating" @@ -1347,11 +1272,6 @@ msgctxt "#32543" msgid "Ends at" msgstr "Termina en" -#: -msgctxt "#32601" -msgid "AV1" -msgstr "AV1" - #: msgctxt "#32602" msgid "Enable this if your hardware can handle AV1. Disable it to force transcoding." @@ -1496,13 +1416,6 @@ msgctxt "#33505" msgid "Show intro skip button early" msgstr "Mostrar el botón de salto de introducción antes de tiempo" -#: -msgctxt "#33506" -msgid "Show the intro skip button from the start of a video with an intro marker. The auto-skipping setting applies. Doesn't override enabled binge mode.\n" -"Can be disabled/enabled per TV show." -msgstr "Mostrar el botón de salto de introducción desde el inicio de un vídeo con un marcador de introducción. Se aplica la configuración de salto automático. No anula el modo maratón activado.\n" -"Puede desactivarse/activarse por programa de TV." - #: msgctxt "#33507" msgid "Enabled" @@ -1513,11 +1426,6 @@ msgctxt "#33508" msgid "Disabled" msgstr "Desactivado" -#: -msgctxt "#33509" -msgid "Early intro skip threshold (default: < 60s/1m)" -msgstr "Umbral de salto de introducción temprana (por defecto: < 60s/1m)" - #: msgctxt "#33510" msgid "When showing the intro skip button early, only do so if the intro occurs within the first X seconds." @@ -1618,23 +1526,6 @@ msgctxt "#33618" msgid "TV binge-viewing mode" msgstr "Modo \"maratón\" de TV" -#: -msgctxt "#33619" -msgid "Automatically skips episode intros, credits and tries to skip episode recaps. Doesn't skip the intro of the first episode of a season and doesn't skip the final credits of a show.\n\nCan be disabled/enabled per TV show.\nOverrides any setting below." -msgstr "Se salta automaticamente las intros de los episodios, los créditos e intenta saltarse los resúmenes de los episodios. No salta la intro del primer episodio de una temporada y no salta los créditos finales de un programa.\n" -"Puede ser desactivado/activado por programa de TV.\n" -"Anula cualquier configuración de abajo." - -#: -msgctxt "#33620" -msgid "Plex server connect timeout" -msgstr "Tiempo de espera de conexión al servidor Plex" - -#: -msgctxt "#33621" -msgid "Sets the maximum amount of time to connect to a Plex Server in seconds. Default: 5" -msgstr "Establece la cantidad máxima de tiempo para conectarse a un servidor Plex en segundos. Predeterminado: 5" - #: msgctxt "#33622" msgid "LAN reachability timeout (ms)" @@ -1899,26 +1790,6 @@ msgctxt "#32931" msgid "Audio/Subtitles" msgstr "Audio/Subtítulos" -#: -msgctxt "#32932" -msgid "Subtitle quick-actions" -msgstr "Acciones rápidas de subtítulos" - -#: -msgctxt "#32933" -msgid "FFWD/RWD" -msgstr "FFWD/RWD" - -#: -msgctxt "#32934" -msgid "Repeat" -msgstr "Repetir" - -#: -msgctxt "#32935" -msgid "Shuffle" -msgstr "Aleatorio" - #: msgctxt "#32936" msgid "Show playlist button" @@ -1929,18 +1800,6 @@ msgctxt "#32937" msgid "Show prev/next button" msgstr "Mostrar botón anterior/siguiente" -#: -msgctxt "#32939" -msgid "User-specific.\n" -"Only applies to video player UI" -msgstr "Específico del usuario.\n" -"Sólo se aplica a la interfaz de usuario del reproductor de vídeo" - -#: -msgctxt "#32940" -msgid "Video Player" -msgstr "Reproductor de vídeo" - #: msgctxt "#32941" msgid "Forced subtitles fix" @@ -2122,11 +1981,6 @@ msgctxt "#32976" msgid "Adaptive" msgstr "Adaptable" -#: -msgctxt "#32977" -msgid "VC1" -msgstr "VC1" - #: msgctxt "#32978" msgid "Enable this if your hardware can handle VC1. Disable it to force transcoding." @@ -2137,13 +1991,11 @@ msgctxt "#32979" msgid "Allows the server to only transcode streams of a video that need transcoding, while streaming the others unaltered. If disabled, force the server to transcode everything not direct playable." msgstr "Permitir al servidor solo transcodificar la reproducción de video que necesita de transcodificación, mientras se reproduce el resto sin cambios. Si se deshabilita, fuerza al servidor a transcodificar todo lo que no sea directamente reproducible." -#. Refrescar Usuarios o Recargar Usuarios #: msgctxt "#32980" msgid "Refresh Users" msgstr "Volver a cargar usuarios" -#. In this sentence Workers should not be translated to Trabajadores in Spanish because there's no real translation for it in the programming context. #: msgctxt "#32981" msgid "Background worker count" @@ -2154,18 +2006,6 @@ msgctxt "#32982" msgid "Depending on how many cores your CPU has and how much it can handle, increasing this might improve certain situations. If you experience crashes or other annormalities, leave this at its default (3). Needs an addon restart." msgstr "Según cuántos núcleos tenga tu procesador y cuánto pueda manejar, incrementar esto podría mejorar ciertas situaciones. Si experimentas cierres repentinos u otros errores, déjalo con su configuración por defecto (3). Necesita un reinicio del Addon." -#: -msgctxt "#32983" -msgid "Theme" -msgstr "Tema" - -#: -msgctxt "#32984" -msgid "Sets the theme. Currently only customizes all control buttons. ATTENTION: [I]Might[/I] need an addon restart.\n" -"In order to customize this, copy one of the xml's in script.plexmod/resources/skins/Main/1080i/templates to addon_data/script.plexmod/templates/{templatename}_custom.xml and adjust it to your liking, then select \"Custom\" as your theme." -msgstr "Configura el tema. Actualmente sólo personaliza todos los botones de control. ATENCIÓN: [I]Podría[/I] necesitar un reinicio del addon.\n" -"Para personalizar esto, copia uno de los xml's en script.plexmod/resources/skins/Main/1080i/templates a addon_data/script.plexmod/templates/{templatename}_custom.xml y ajústalo a tu gusto, luego selecciona \"Personalizado\" como tu tema." - #: msgctxt "#32985" msgid "Modern" @@ -2201,7 +2041,6 @@ msgctxt "#32991" msgid "Notify" msgstr "Notificar" -#. Haven't heard about an actual translation for "Rebind" in Spanish. These techie words are usually not translated in Spanish. #: msgctxt "#32992" msgid "When using servers with a plex.direct connection (most of them), should we automatically adjust advancedsettings.xml to cope with plex.direct domains? If not, you might want to add plex.direct to your router's DNS rebind exemption list." @@ -2232,13 +2071,11 @@ msgctxt "#32997" msgid "OK" msgstr "OK" -#. Apartado was the best interpretation for Hub that I could think of #: msgctxt "#32998" msgid "Use new Continue Watching hub on Home" msgstr "Utilizar el nuevo apartado de Continuar Viendo en Inicio" -#. Cartelera was the best interpretation for On Deck that I could think of #: msgctxt "#32999" msgid "Instead of separating Continue Watching and On Deck hubs, behave like the modern Plex clients, which combine those two types of hubs into one Continue Watching hub." @@ -2249,11 +2086,6 @@ msgctxt "#33000" msgid "Enable path mapping" msgstr "Activar mapeo de rutas" -#: -msgctxt "#33001" -msgid "Long-press (or context menu) on a library item or: Honor path_mapping.json in the addon_data/script.plexmod folder when DirectPlaying media. This can be used to stream using other techniques such as SMB/NFS/etc. instead of the default HTTP handler. path_mapping.example.json is included in the addon's main directory." -msgstr "Pulsación larga (o menú contextual) en un elemento de la biblioteca o: Honor path_mapping.json en la carpeta addon_data/script.plexmod cuando DirectPlaying medios de comunicación. Esto puede usarse para transmitir usando otras técnicas como SMB/NFS/etc. en lugar del manejador HTTP por defecto. path_mapping.example.json está incluido en el directorio principal del addon." - #: msgctxt "#33002" msgid "Verify mapped files exist" @@ -2279,36 +2111,11 @@ msgctxt "#33006" msgid "No TV spoilers" msgstr "Sin Spoilers de TV" -#: -msgctxt "#33007" -msgid "Select in which cases to blur TV episode thumbnails, previews, redact summaries, hide episode titles and whether to blur chapter images. When the Addon Setting \"Use episode thumbnails in continue hub\" is enabled, blur them as well." -msgstr "Seleccione en qué casos desea difuminar las miniaturas de los episodios de TV, los avances, redactar los resúmenes, ocultar los títulos de los episodios y si desea difuminar las imágenes de los capítulos. Si la opción del addon \"Usar miniaturas de episodios en el hub de continuación\" está activada, difumínalas también." - #: msgctxt "#33008" msgid "[Spoilers removed]" msgstr "[Spoilers removidos]" -#: -msgctxt "#33009" -msgid "Blur amount for unwatched/in-progress episodes" -msgstr "Cantidad de desenfoque para episodios No vistos/En progreso" - -#: -msgctxt "#33010" -msgid "Unwatched" -msgstr "No vistos" - -#: -msgctxt "#33011" -msgid "In progress" -msgstr "En curso" - -#: -msgctxt "#33012" -msgid "No unwatched episode titles" -msgstr "Títulos de Episodios no vistos" - #: msgctxt "#33013" msgid "When the above is anything but \"off\", hide episode titles as well." @@ -2324,16 +2131,6 @@ msgctxt "#33015" msgid "When checking for plex.direct host mapping, ignore local Docker IPv4 addresses (172.16.0.0/12)." msgstr "Cuando se verifica el mapeo de Host de plex.direct, ignorar las direcciones locales IPv4 de Docker (172.16.0.0/12)." -#: -msgctxt "#33016" -msgid "Allow TV spoilers for" -msgstr "Permitir spoilers de TV para" - -#: -msgctxt "#33017" -msgid "Overrides the above for specific genres. Default: Reality, Game Show, Documentary, Sport" -msgstr "Anula lo anterior para géneros específicos. Por defecto: Reality, Game Show, Documental, Deporte" - #: msgctxt "#32303" msgid "Season {}" @@ -2379,32 +2176,6 @@ msgctxt "#33021" msgid "Choose action" msgstr "Elegir acción" -#: -msgctxt "#33022" -msgid "Watched indicators" -msgstr "Indicadores de vigilancia" - -#: -msgctxt "#33023" -msgid "Classic: Show orange triangle for unwatched items\n" -"Modern: Show green checkmark for watched items\n" -"Modern (2024): Show white checkmark for watched items\n" -"(default: Modern (2024))" -msgstr "Clásico: Mostrar triángulo naranja para los elementos no vistos\n" -"Moderno: Mostrar una marca verde para los elementos vigilados\n" -"Moderno (2024): Mostrar marca blanca para los elementos vigilados\n" -"(por defecto: Modern (2024))" - -#: -msgctxt "#33024" -msgid "Hide background in modern indicators" -msgstr "Ocultar fondo en indicadores modernos" - -#: -msgctxt "#33025" -msgid "When the above is enabled, hide the black backdrop of the watched state." -msgstr "Cuando se activa lo anterior, oculta el fondo negro del estado vigilado." - #: msgctxt "#33026" msgid "Map path: {}" @@ -2460,11 +2231,6 @@ msgctxt "#33037" msgid "Maximum intro offset to consider" msgstr "Desplazamiento de introducción máximo a considerar" -#: -msgctxt "#33038" -msgid "When encountering an intro marker with a start time offset greater than this, ignore it (default: 600s/10m)" -msgstr "Cuando encuentre un marcador de introducción con un desfase de tiempo de inicio superior a este valor, ignórelo (por defecto: 600s/10m)" - #: msgctxt "#33039" msgid "Move" @@ -2480,6 +2246,16 @@ msgctxt "#33034" msgid "Library settings" msgstr "Configuración de la biblioteca" +#: +msgctxt "#32357" +msgid "By Title" +msgstr "Por título" + +#: +msgctxt "#32358" +msgid "Title" +msgstr "Título" + #: msgctxt "#33041" msgid "Show hub: {}" @@ -2490,69 +2266,148 @@ msgctxt "#33042" msgid "Episode Date Added" msgstr "Fecha del episodio añadida" +#: +msgctxt "#33001" +msgid "Long-press (or context menu) on a library item or: Honor path_mapping.json in the addon_data/script.plexmod folder when DirectPlaying media. This can be used to stream using other techniques such as SMB/NFS/etc. instead of the default HTTP handler. path_mapping.example.json is included in the addon's main directory." +msgstr "Pulsación larga (o menú contextual) en un elemento de la biblioteca o: Honor path_mapping.json en la carpeta addon_data/script.plexmod cuando DirectPlaying medios de comunicación. Esto puede usarse para transmitir usando otras técnicas como SMB/NFS/etc. en lugar del manejador HTTP por defecto. path_mapping.example.json está incluido en el directorio principal del addon." + #: msgctxt "#33043" msgid "Hubs round-robin" msgstr "Hubs round-robin" -#: -msgctxt "#33044" -msgid "Allow round-robining in hubs. Attention: Loads a lot of items. Depending on the hub size, this can lead to crashes. Current limit: {}; can be adjusted in addon settings." -msgstr "Permitir round-robin en hubs. Atención: Carga muchos elementos. Dependiendo del tamaño del hub, esto puede provocar fallos. Límite actual: {}; se puede ajustar en la configuración del addon." - #: msgctxt "#33045" msgid "Behave like official Plex clients" msgstr "Comportarse como clientes oficiales de Plex" #: -msgctxt "#33046" -msgid "When pressing up/down while the OSD isn't shown, show OSD instead of skipping chapters. Additionally, when pressing down while the OSD is shown, open the chapters (if available)." -msgstr "Al presionar arriba/abajo mientras el OSD no está mostrado, mostrar el OSD en lugar de saltar capítulos. Además, al presionar abajo mientras el OSD está mostrado, abrir los capítulos (si están disponibles)." +msgctxt "#32025" +msgid "Direct Play" +msgstr "Reproducción directa" #: -msgctxt "#33047" -msgid "Hubs round-robin item limit" -msgstr "Límite de elementos round-robin en hubs" +msgctxt "#32026" +msgid "Direct Stream" +msgstr "Transmisión directa" #: -msgctxt "#33048" -msgid "When hubs round-robin is enabled, only round-robin until this item limit. If the hub size exceeds this limit, the remaining items will be lazy loaded as usual. Tested minimum safe value on NVIDIA SHIELD 2019 is 1000. Default: 250" -msgstr "Cuando el round-robin en hubs está habilitado, solo hacer round-robin hasta este límite de elementos. Si el tamaño del hub excede este límite, los elementos restantes se cargarán de manera diferida como de costumbre. El valor mínimo seguro probado en NVIDIA SHIELD 2019 es 1000. Por defecto: 250." +msgctxt "#32036" +msgid "4K" +msgstr "4K" #: -msgctxt "#33049" -msgid "Max retries" -msgstr "Reintentos máximos" +msgctxt "#32037" +msgid "HEVC (h265)" +msgstr "HEVC (h265)" #: -msgctxt "#33050" -msgid "The maximum number of retries each connection should attempt. The default (3) helps with hibernation/wakeup issues. Set higher for bad connections, setting this to 0 was the old default before 0.7.9" -msgstr "El número máximo de reintentos que debe intentar cada conexión. El valor por defecto (3) ayuda con problemas de hibernación/despertar. Establézcalo más alto para malas conexiones, establecerlo a 0 era el antiguo valor por defecto antes de 0.7.9" +msgctxt "#32601" +msgid "AV1" +msgstr "AV1" #: -msgctxt "#33051" -msgid "Use CA certificate bundle" -msgstr "Utilizar paquete de certificados CA" +msgctxt "#32932" +msgid "Subtitle quick-actions" +msgstr "Acciones rápidas de subtítulos" #: -msgctxt "#33052" -msgid "Which CA certificate bundle to use for request HTTPS verification. Default is \"system\", which uses the system-provided certificate bundle. \"plex.direct\" uses an extremely small bundle provided with the addon, which only applies to plex.direct connections (any other connections will use the system bundle) and might slightly improve performance. \"custom\" allows for a certificate bundle in userdata/addon_data/script.plexmod/custom_bundle.crt" -msgstr "Qué paquete de certificados CA utilizar para la verificación HTTPS de la solicitud. Por defecto es \"system\", que utiliza el paquete de certificados proporcionado por el sistema. \"plex.direct \" utiliza un paquete extremadamente pequeño proporcionado con el complemento, que sólo se aplica a las conexiones plex.direct (cualquier otra conexión utilizará el paquete del sistema) y podría mejorar ligeramente el rendimiento. \"custom\" permite un paquete de certificados en userdata/addon_data/script.plexmod/custom_bundle.crt" +msgctxt "#32933" +msgid "FFWD/RWD" +msgstr "FFWD/RWD" #: -msgctxt "#33053" -msgid "System" -msgstr "Sistema" +msgctxt "#32934" +msgid "Repeat" +msgstr "Repetir" #: -msgctxt "#33054" -msgid "plex.direct (addon-supplied)" -msgstr "plex.direct (suministrado por el addon)" +msgctxt "#32935" +msgid "Shuffle" +msgstr "Aleatorio" #: -msgctxt "#33055" -msgid "Custom" +msgctxt "#32939" +msgid "User-specific.\n" +"Only applies to video player UI" +msgstr "Específico del usuario.\n" +"Sólo se aplica a la interfaz de usuario del reproductor de vídeo" + +#: +msgctxt "#32977" +msgid "VC1" +msgstr "VC1" + +#: +msgctxt "#32983" +msgid "Theme" +msgstr "Tema" + +#: +msgctxt "#32984" +msgid "Sets the theme. Currently only customizes all control buttons. ATTENTION: [I]Might[/I] need an addon restart.\n" +"In order to customize this, copy one of the xml's in script.plexmod/resources/skins/Main/1080i/templates to addon_data/script.plexmod/templates/{templatename}_custom.xml and adjust it to your liking, then select \"Custom\" as your theme." +msgstr "Configura el tema. Actualmente sólo personaliza todos los botones de control. ATENCIÓN: [I]Podría[/I] necesitar un reinicio del addon.\n" +"Para personalizar esto, copia uno de los xml's en script.plexmod/resources/skins/Main/1080i/templates a addon_data/script.plexmod/templates/{templatename}_custom.xml y ajústalo a tu gusto, luego selecciona \"Personalizado\" como tu tema." + +#: +msgctxt "#33011" +msgid "In progress" +msgstr "En curso" + +#: +msgctxt "#33016" +msgid "Allow TV spoilers for" +msgstr "Permitir spoilers de TV para" + +#: +msgctxt "#33017" +msgid "Overrides the above for specific genres. Default: Reality, Game Show, Documentary, Sport" +msgstr "Anula lo anterior para géneros específicos. Por defecto: Reality, Game Show, Documental, Deporte" + +#: +msgctxt "#33024" +msgid "Hide background in modern indicators" +msgstr "Ocultar fondo en indicadores modernos" + +#: +msgctxt "#33044" +msgid "Allow round-robining in hubs. Attention: Loads a lot of items. Depending on the hub size, this can lead to crashes. Current limit: {}; can be adjusted in addon settings." +msgstr "Permitir round-robin en hubs. Atención: Carga muchos elementos. Dependiendo del tamaño del hub, esto puede provocar fallos. Límite actual: {}; se puede ajustar en la configuración del addon." + +#: +msgctxt "#33046" +msgid "When pressing up/down while the OSD isn't shown, show OSD instead of skipping chapters. Additionally, when pressing down while the OSD is shown, open the chapters (if available)." +msgstr "Al presionar arriba/abajo mientras el OSD no está mostrado, mostrar el OSD en lugar de saltar capítulos. Además, al presionar abajo mientras el OSD está mostrado, abrir los capítulos (si están disponibles)." + +#: +msgctxt "#33047" +msgid "Hubs round-robin item limit" +msgstr "Límite de elementos round-robin en hubs" + +#: +msgctxt "#33048" +msgid "When hubs round-robin is enabled, only round-robin until this item limit. If the hub size exceeds this limit, the remaining items will be lazy loaded as usual. Tested minimum safe value on NVIDIA SHIELD 2019 is 1000. Default: 250" +msgstr "Cuando el round-robin en hubs está habilitado, solo hacer round-robin hasta este límite de elementos. Si el tamaño del hub excede este límite, los elementos restantes se cargarán de manera diferida como de costumbre. El valor mínimo seguro probado en NVIDIA SHIELD 2019 es 1000. Por defecto: 250." + +#: +msgctxt "#33049" +msgid "Max retries" +msgstr "Reintentos máximos" + +#: +msgctxt "#33051" +msgid "Use CA certificate bundle" +msgstr "Utilizar paquete de certificados CA" + +#: +msgctxt "#33053" +msgid "System" +msgstr "Sistema" + +#: +msgctxt "#33055" +msgid "Custom" msgstr "Personalizado" #: @@ -2652,11 +2507,6 @@ msgctxt "#33074" msgid "Waiting {} second(s)" msgstr "Esperando {} segundo(s)" -#: -msgctxt "#33075" -msgid "Do something after waking up. Waiting for a time until resuming normal execution helps with networks coming back up after sleep events. Default: 1 second (5 seconds, CoreELEC)." -msgstr "Hacer algo después de despertar. Esperar un tiempo hasta reanudar la ejecución normal ayuda con las redes que vuelven a funcionar después de eventos de reposo. Por defecto: 1 segundo (5 segundos, CoreELEC)." - #: msgctxt "#33076" msgid "Modern (2024)" @@ -2712,6 +2562,31 @@ msgctxt "#33086" msgid "Press the key you want to map to go to home within {} seconds" msgstr "Presiona la tecla que deseas asignar para ir a inicio dentro de {} segundos" +#: +msgctxt "#33620" +msgid "Plex server connect timeout" +msgstr "Tiempo de espera de conexión al servidor Plex" + +#: +msgctxt "#33621" +msgid "Sets the maximum amount of time to connect to a Plex Server in seconds. Default: 5" +msgstr "Establece la cantidad máxima de tiempo para conectarse a un servidor Plex en segundos. Predeterminado: 5" + +#: +msgctxt "#32940" +msgid "Video Player" +msgstr "Reproductor de vídeo" + +#: +msgctxt "#33050" +msgid "The maximum number of retries each connection should attempt. The default (3) helps with hibernation/wakeup issues. Set higher for bad connections, setting this to 0 was the old default before 0.7.9" +msgstr "El número máximo de reintentos que debe intentar cada conexión. El valor por defecto (3) ayuda con problemas de hibernación/despertar. Establézcalo más alto para malas conexiones, establecerlo a 0 era el antiguo valor por defecto antes de 0.7.9" + +#: +msgctxt "#33075" +msgid "Do something after waking up. Waiting for a time until resuming normal execution helps with networks coming back up after sleep events. Default: 1 second (5 seconds, CoreELEC)." +msgstr "Hacer algo después de despertar. Esperar un tiempo hasta reanudar la ejecución normal ayuda con las redes que vuelven a funcionar después de eventos de reposo. Por defecto: 1 segundo (5 segundos, CoreELEC)." + #: msgctxt "#33087" msgid "Bind a key to immediately go to the Home view. Cancel using BACK/PREVIOUS_MENU. Clear bound key using the STOP or CONTEXT_MENU (long press OK/Enter) button on the setting." @@ -2792,21 +2667,11 @@ msgctxt "#33638" msgid "Plex.tv connect timeout" msgstr "Tiempo de espera de conexión de Plex.tv" -#: -msgctxt "#33639" -msgid "Plex.tv: Sets the maximum amount of time to connect to Plex.tv in seconds. Default: 5" -msgstr "Plex.tv: Establece la cantidad máxima de tiempo para conectarse a Plex.tv en segundos. Predeterminado: 5" - #: msgctxt "#33640" msgid "Plex.tv read timeout" msgstr "Tiempo de espera de lectura de Plex.tv" -#: -msgctxt "#33641" -msgid "Plex.tv: Sets the maximum amount of time to read from Plex.tv in seconds. Default: 20" -msgstr "Plex.tv: Establece la cantidad máxima de tiempo para leer de Plex.tv en segundos. Predeterminado: 20" - #: msgctxt "#33642" msgid "Dump config" @@ -2843,14 +2708,9 @@ msgid "Only handle plex.direct hosts when the server's attributes publicAddressM msgstr "Sólo maneja hosts plex.direct cuando los atributos publicAddressMatches=1 del servidor. Puede no funcionar en ciertas situaciones. Desactivar si tiene problemas de conexión. Por defecto: En" #: -msgctxt "#33649" -msgid "CoreELEC: Resume-fix wait for seek" -msgstr "CoreELEC: Reanudar y corregir la espera de búsqueda" - -#: -msgctxt "#33650" -msgid "This adjusts the delay between seek-tries on CoreELEC, which fixes resume not always working or double-seeking. When you have resume/double-seek issues, increase this. 100ms should be stable as well. Default: 350ms" -msgstr "Ajusta el retardo entre los intentos de búsqueda en CoreELEC, lo que corrige la reanudación que no siempre funciona o la doble búsqueda. Cuando tenga problemas de reanudación/doble búsqueda, auméntelo. 100ms debería ser estable también. Por defecto: 350ms" +msgctxt "#32101" +msgid "If enabled, when playback ends and there is a 'Next Up' item available, it will be automatically be played after a {} second delay." +msgstr "Si está activada, cuando finalice la reproducción y haya un elemento 'Siguiente' disponible, éste se reproducirá automáticamente tras un retardo de {} segundos." #: msgctxt "#33651" @@ -2862,3 +2722,848 @@ msgctxt "#33652" msgid "Never show Post Play" msgstr "No mostrar nunca la reproducción posterior" +#: +msgctxt "#33506" +msgid "Show the intro skip button from the start of a video with an intro marker. The auto-skipping setting applies. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "Mostrar el botón de salto de introducción desde el inicio de un vídeo con un marcador de introducción. Se aplica la configuración de salto automático. No anula el modo maratón activado.\n" +"Puede desactivarse/activarse por programa de TV." + +#: +msgctxt "#33619" +msgid "Automatically skips episode intros, credits and tries to skip episode recaps. Doesn't skip the intro of the first episode of a season and doesn't skip the final credits of a show.\n" +"\n" +"Can be disabled/enabled per TV show.\n" +"Overrides any setting below." +msgstr "Se salta automaticamente las intros de los episodios, los créditos e intenta saltarse los resúmenes de los episodios. No salta la intro del primer episodio de una temporada y no salta los créditos finales de un programa.\n" +"Puede ser desactivado/activado por programa de TV.\n" +"Anula cualquier configuración de abajo." + +#: +msgctxt "#33052" +msgid "Which CA certificate bundle to use for request HTTPS verification. \"system\" uses the system-provided certificate bundle. \"ACME\" uses an extremely small bundle provided with the addon, which includes root certificates for Letsencrypt (plex.direct) and ZeroSSL. \"custom\" allows for a ca-certificate bundle in userdata/addon_data/script.plexmod/custom_bundle.crt" +msgstr "Qué paquete de certificados CA utilizar para solicitar la verificación HTTPS. \"system\" utiliza el paquete de certificados proporcionado por el sistema. \"ACME\" utiliza un paquete extremadamente pequeño proporcionado con el addon, que incluye certificados raíz para Letsencrypt (plex.direct) y ZeroSSL. \"custom\" permite un paquete de certificados ca en userdata/addon_data/script.plexmod/custom_bundle.crt" + +#: +msgctxt "#33054" +msgid "ACME (addon-supplied)" +msgstr "ACME (addon suministrado)" + +#: +msgctxt "#33639" +msgid "Plex.tv: Sets the maximum amount of time to connect to Plex.tv in seconds. Default: 1" +msgstr "Plex.tv: Establece la cantidad máxima de tiempo para conectarse a Plex.tv en segundos. Predeterminado: 1" + +#: +msgctxt "#33641" +msgid "Plex.tv: Sets the maximum amount of time to read from Plex.tv in seconds. Default: 2" +msgstr "Plex.tv: Establece la cantidad máxima de tiempo para leer de Plex.tv en segundos. Predeterminado: 2" + +#: +msgctxt "#33653" +msgid "Background image resolution scaling %" +msgstr "Escalado de resolución de imagen de fondo %." + +#: +msgctxt "#33654" +msgid "In percent, based on 1080p. Scales the resolution of all backgrounds for better image quality. May impact PMS/PM4K performance, will increase the cache usage accordingly. Can lead to crashes if your hardware is low on RAM. Needs addon restart. Use 400% for 4K. ATTENTION: In order for this to work you need to add 2160 and 2160 to your advancedsettings.xml." +msgstr "En porcentaje, basado en 1080p. Escala la resolución de todos los fondos para una mejor calidad de imagen. Puede afectar al rendimiento de PMS/PM4K, por lo que aumentará el uso de la caché. Puede provocar fallos si su hardware tiene poca RAM. Necesita reiniciar el addon. Usa 400% para 4K. ATENCIÓN: Para que esto funcione es necesario añadir 2160 and 2160 a su advancedsettings.xml." + +#: +msgctxt "#33655" +msgid "Auto-Sync Subtitles" +msgstr "Sincronización automática de subtítulos" + +#: +msgctxt "#33656" +msgid "Only for External SRT subtitles. The PMS setting for voice activity detection has to be enabled for this to work." +msgstr "Sólo para subtítulos SRT externos. El ajuste PMS para la detección de actividad de voz tiene que estar activado para que esto funcione." + +#: in player quick settings UI, needs to be short +msgctxt "#33657" +msgid "Enable Auto-Sync" +msgstr "Activar Auto-Sync" + +#: in player quick settings UI, needs to be short +msgctxt "#33658" +msgid "Disable Auto-Sync" +msgstr "Desactivar Auto-Sync" + +#: +msgctxt "#33659" +msgid "Hide hub: {}" +msgstr "Ocultar centro: {}" + +#: +msgctxt "#33660" +msgid "Disable HDR" +msgstr "Desactivar HDR" + +#: +msgctxt "#33661" +msgid "If you don't want your client to handle HDR (or HDR-fallback), enable this to force transcoding. Doesn't apply to DV Profile 5." +msgstr "Si no desea que su cliente gestione HDR (o HDR-fallback), active esta opción para forzar la transcodificación. No se aplica a DV Profile 5." + +#: +msgctxt "#33662" +msgid "Remove from Continue Watching" +msgstr "Quitar de Seguir viendo" + +#: +msgctxt "#33663" +msgid "Home: Confirm item actions" +msgstr "Inicio: Confirmar acciones" + +#: +msgctxt "#33664" +msgid "When acting on items in the Home view, such as mark played, hide from continue watching etc., show a confirmation dialog." +msgstr "Al actuar sobre elementos de la vista de inicio, como marcar como reproducido, ocultar para que no se siga viendo, etc., mostrar un cuadro de diálogo de confirmación." + +#: +msgctxt "#33665" +msgid "Disable audio codecs" +msgstr "Desactivar códecs de audio" + +#: +msgctxt "#33666" +msgid "Audio codecs you can't play back. Disables Direct Play for such media items, enables Direct Stream if possible, transcodes audio stream to compatible format." +msgstr "Códecs de audio que no puedes reproducir. Desactiva la reproducción directa de dichos elementos multimedia, activa Direct Stream si es posible, transcodifica el flujo de audio a un formato compatible." + +#: +msgctxt "#32002" +msgid "20 Mbps" +msgstr "20 Mbps" + +#: +msgctxt "#32003" +msgid "12 Mbps" +msgstr "12 Mbps" + +#: +msgctxt "#32004" +msgid "10 Mbps" +msgstr "10 Mbps" + +#: +msgctxt "#32005" +msgid "8 Mbps" +msgstr "8 Mbps" + +#: +msgctxt "#32006" +msgid "4 Mbps" +msgstr "4 Mbps" + +#: +msgctxt "#32007" +msgid "3 Mbps" +msgstr "3 Mbps" + +#: +msgctxt "#32008" +msgid "2 Mbps" +msgstr "2 Mbps" + +#: +msgctxt "#32009" +msgid "1.5 Mbps" +msgstr "1.5 Mbps" + +#: +msgctxt "#33669" +msgid "Really sign out?" +msgstr "¿Cerrar sesión de verdad??" + +#: +msgctxt "#33670" +msgid "Update available" +msgstr "Actualización disponible" + +#: +msgctxt "#33672" +msgid "Check for updates" +msgstr "Buscar actualizaciones" + +#: +msgctxt "#33673" +msgid "Automatically check for updates periodically. If installed from a Kodi repository and the Update Source setting is set to Repository, Kodi itself will handle the updating of this addon. Needs a Kodi restart when changed." +msgstr "Buscar actualizaciones automáticamente de forma periódica. Si se instala desde un repositorio de Kodi y el ajuste Fuente de actualización está establecido en Repositorio, Kodi mismo se encargará de la actualización de este complemento. Necesita un reinicio de Kodi cuando se cambia." + +#: +msgctxt "#33674" +msgid "Check for updates on start" +msgstr "Buscar actualizaciones al inicio" + +#: +msgctxt "#33675" +msgid "Automatically check for updates on startup. Doesn't do much when Update source is Repository. Needs a Kodi restart when changed." +msgstr "Comprobación automática de actualizaciones al inicio. No hace mucho cuando la fuente de actualización es el repositorio. Necesita reiniciar Kodi cuando se cambia." + +#: +msgctxt "#33676" +msgid "Update source" +msgstr "Actualizar fuente" + +#: +msgctxt "#33677" +msgid "Specifies the update mode. Will immediately check for a new version when changed and closing settings.\n" +"Default: Repository\n" +"\n" +"Beta: Bleeding edge (possibly unstable)\n" +"Stable: Stable branch (faster than Repository)\n" +"Repository: Kodi repository (official (slow) or Don't Panic)" +msgstr "Especifica el modo de actualización. Comprobará inmediatamente si hay una nueva versión al cambiar y cerrar la configuración.\n" +"Por defecto: Repositorio\n" +"\n" +"Beta: Bleeding edge (posiblemente inestable)\n" +"Estable: Rama estable (más rápida que Repositorio)\n" +"Repositorio: Repositorio Kodi (oficial (lento) o Don't Panic)" + +#: +msgctxt "#33678" +msgid "Beta" +msgstr "Beta" + +#: Update mode as in (beta, stable, repository) +msgctxt "#33679" +msgid "Stable" +msgstr "Estable" + +#: +msgctxt "#33680" +msgid "Repository" +msgstr "Repositorio" + +#: +msgctxt "#33683" +msgid "Exit, download and install" +msgstr "Salir, descargar e instalar" + +#: +msgctxt "#33684" +msgid "Later" +msgstr "Más tarde" + +#: +msgctxt "#32015" +msgid "6 Mbps" +msgstr "6 Mbps" + +#: +msgctxt "#32016" +msgid "16 Mbps" +msgstr "16 Mbps" + +#: +msgctxt "#32017" +msgid "26 Mbps" +msgstr "26 Mbps" + +#: +msgctxt "#33681" +msgid "Service updated" +msgstr "Servicio actualizado" + +#: +msgctxt "#33682" +msgid "The update {} has had changes to the updater itself. In order for the updated updater service to work, a Kodi restart is necessary. The addon will work normally, though. Do you still want to run the addon?" +msgstr "La actualización {} ha tenido cambios en el propio actualizador. Para que el servicio actualizado del actualizador funcione, es necesario reiniciar Kodi. Sin embargo, el addon funcionará normalmente. ¿Todavía quieres ejecutar el addon?" + +#: +msgctxt "#33685" +msgid "Clamp video bitrate" +msgstr "Tasa de bits de vídeo" + +#: +msgctxt "#33686" +msgid "Only show transcode bitrate targets lower than the current video's bitrate." +msgstr "Mostrar sólo objetivos de bitrate de transcodificación inferiores al bitrate del vídeo actual." + +#: +msgctxt "#33687" +msgid "Translation updated" +msgstr "Traducción actualizada" + +#: +msgctxt "#33688" +msgid "The currently in-use translation has been updated. In order to load the new translation, a Kodi restart is necessary. The addon will still run properly, but you might see badly or untranslated strings. Do you still want to run the addon?" +msgstr "La traducción actualmente en uso ha sido actualizada. Para cargar la nueva traducción, es necesario reiniciar Kodi. El addon seguirá funcionando correctamente, pero es posible que veas cadenas mal traducidas o sin traducir. ¿Todavía quieres ejecutar el addon?" + +#: +msgctxt "#33509" +msgid "Early intro skip threshold (default: < 120s/2m)" +msgstr "Umbral de salto de introducción temprana (por defecto: < 120s/2m)" + +#: +msgctxt "#33007" +msgid "Select in which cases to blur TV episode thumbnails, previews, redact summaries, hide episode titles etc." +msgstr "Seleccione en qué casos desea difuminar las miniaturas de los episodios de TV, los avances, redactar resúmenes, ocultar los títulos de los episodios, etc." + +#: +msgctxt "#33009" +msgid "Blur amount for unplayed/in-progress episodes" +msgstr "Cantidad de desenfoque para episodios no reproducidos/en curso" + +#: +msgctxt "#33010" +msgid "Unplayed" +msgstr "Sin reproducir" + +#: +msgctxt "#33012" +msgid "No unplayed episode titles" +msgstr "No hay títulos de episodios sin reproducir" + +#: +msgctxt "#33022" +msgid "Played indicators" +msgstr "Indicadores de reproducción" + +#: +msgctxt "#33023" +msgid "Classic: Show orange triangle for unplayed items\n" +"Modern: Show green checkmark for played items\n" +"Modern (2024): Show white checkmark for played items\n" +"(default: Modern (2024))" +msgstr "Clásico: Mostrar triángulo naranja para los objetos no jugados\n" +"Moderno: Muestra una marca verde para los objetos jugados\n" +"Moderno (2024): Marca blanca para los elementos jugados\n" +"(por defecto: Modern (2024))" + +#: +msgctxt "#33025" +msgid "When the above is enabled, hide the black backdrop of the played state." +msgstr "Cuando lo anterior está activado, oculta el fondo negro del estado reproducido." + +#: +msgctxt "#33689" +msgid "Service running" +msgstr "Servicio en funcionamiento" + +#: +msgctxt "#33690" +msgid "Last update check" +msgstr "Comprobación de la última actualización" + +#: +msgctxt "#33691" +msgid "Native languages" +msgstr "Lenguas nativas" + +#: +msgctxt "#33692" +msgid "When you usually watch things in a different language with subtitles, but are a native speaker of other languages, which you don't need subtitles for, prevent Plex from auto-selecting subtitles for those languages." +msgstr "Si sueles ver cosas en otro idioma con subtítulos, pero eres hablante nativo de otros idiomas para los que no necesitas subtítulos, evita que Plex seleccione automáticamente subtítulos para esos idiomas." + +#: +msgctxt "#33693" +msgid "Download subtitles using" +msgstr "Descargar subtítulos con" + +#: +msgctxt "#33694" +msgid "Ask" +msgstr "Consulte" + +#: +msgctxt "#33695" +msgid "Where do you want to download subtitles from? Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "¿De dónde quieres descargar los subtítulos? Nota: Actualmente esto sólo se aplica a las acciones rápidas de subtítulos en el reproductor. La descarga de subtítulos en la configuración del stream siempre utiliza Plex como fuente." + +#: +msgctxt "#33696" +msgid "No subtitles found." +msgstr "No se han encontrado subtítulos." + +#: things in curly brackets are variables, don't translate them! +msgctxt "#33697" +msgid "{provider_title}, Score: {subtitle_score}{subtitle_info}" +msgstr "{provider_title}, Puntuación: {subtitle_score}{subtitle_info}" + +#: hearing impaired subtitle flag +msgctxt "#33698" +msgid "HI" +msgstr "HOLA" + +#: forced subtitle flag +msgctxt "#33699" +msgid "forced" +msgstr "forzado" + +#: +msgctxt "#33700" +msgid "Download subtitles: {}" +msgstr "Descargar subtítulos: {}" + +#: +msgctxt "#33701" +msgid "Fallback to Kodi" +msgstr "Volver a Kodi" + +#: +msgctxt "#33702" +msgid "When no subtitles are found via the Plex subtitle search, fall back to Kodi subtitle search. Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "Cuando no se encuentran subtítulos a través de la búsqueda de subtítulos de Plex, vuelve a la búsqueda de subtítulos de Kodi. Nota: Actualmente esto sólo se aplica a las acciones rápidas de subtítulos en el reproductor. La descarga de subtítulos en la configuración del stream siempre utiliza Plex como fuente." + +#: +msgctxt "#33703" +msgid "Download subtitles" +msgstr "Descargar subtítulos" + +#: +msgctxt "#33704" +msgid "Using which service?" +msgstr "¿Qué servicio utilizas?" + +#: +msgctxt "#33705" +msgid "Hide ratings" +msgstr "Ocultar valoraciones" + +#: +msgctxt "#33706" +msgid "Blur images" +msgstr "Difuminar imágenes" + +#: +msgctxt "#33707" +msgid "Blur images on home (in progress)" +msgstr "Difuminar imágenes en casa (en curso)" + +#: +msgctxt "#33708" +msgid "Hide summaries" +msgstr "Ocultar resúmenes" + +#: +msgctxt "#33709" +msgid "Show ratings for" +msgstr "Mostrar clasificaciones de" + +#: +msgctxt "#33710" +msgid "Show reviews for" +msgstr "Mostrar reseñas de" + +#: +msgctxt "#33711" +msgid "Always resume media" +msgstr "Reanudar siempre los medios" + +#: +msgctxt "#33712" +msgid "When playback of an in-progress media is requested, resume it by default instead of asking whether to resume or start from the beginning." +msgstr "Cuando se solicita la reproducción de un medio en curso, reanudarla por defecto en lugar de preguntar si se desea reanudar o empezar desde el principio." + +#: +msgctxt "#33713" +msgid "Home: Resume in-progress items" +msgstr "Inicio: Reanudar elementos en curso" + +#: +msgctxt "#33714" +msgid "Resume in-progress items directly instead of visiting the media." +msgstr "Reanude directamente los elementos en curso en lugar de visitar los medios." + +#: +msgctxt "#33715" +msgid "OSD hide-delay" +msgstr "Retardo de ocultación OSD" + +#: +msgctxt "#33716" +msgid "How long to wait until hiding the OSD. Only works with Plextuary skin or others with configurable OSD timeout. Default: 4s (+/- 1s)" +msgstr "Cuánto tiempo esperar hasta ocultar el OSD. Sólo funciona con el skin Plextuary u otros con tiempo de espera OSD configurable. Por defecto: 4s (+/- 1s)" + +#: +msgctxt "#33717" +msgid "Debug requests" +msgstr "Depurar solicitudes" + +#: +msgctxt "#33718" +msgid "Played" +msgstr "Reproducido" + +#: +msgctxt "#33719" +msgid "Refresh metadata" +msgstr "Actualizar metadatos" + +#: +msgctxt "#33038" +msgid "When encountering an intro marker with a start time offset greater than this, ignore it (default: 1400s/23m)" +msgstr "Al encontrar un marcador de intro con un desplazamiento de tiempo de inicio mayor a este, ignorarlo (predeterminado: 1400 s/23 m)" + +#: +msgctxt "#33649" +msgid "\"Use alternate seek\" wait for seek" +msgstr "\"Usar búsqueda alternativa\" esperar la búsqueda" + +#: +msgctxt "#33650" +msgid "This adjusts the delay between seek-tries on problematic platforms when \"Use alternate seek\" is enabled, which fixes resume not always working or double-seeking. When you have resume/double-seek issues, increase this. Default: 500ms" +msgstr "Esto ajusta el retraso entre intentos de búsqueda en plataformas problemáticas cuando \"Usar búsqueda alternativa\" está habilitado, lo cual arregla que el resumir no siempre funcione o la búsqueda doble. Cuando tienes problemas con resumir/búsqueda doble, incrementar esto. Por defecto: 500ms" + +#: +msgctxt "#33667" +msgid "Use alternate seek" +msgstr "Usar búsqueda alternativa" + +#: +msgctxt "#33668" +msgid "ATTENTION: Only enable this if you have reproducible audio issues after seeking/resuming.\n" +"\n" +"Use an alternative seek method in videos, which can help in problematic scenarios; brings its own issues/quirks. Disabled by default (enabled by default for CoreELEC and LG WebOS)" +msgstr "ATENCIÓN: Solamente habilita esto si has tenido problemas de reproducción de audio después de buscar/reanudar.\n" +"\n" +"Usar un método de búsqueda alternativo en videos, el cual puede ayudar en situaciones problemáticas; trae sus propios problemas/peculiaridades. Deshabilitado por defecto (habilitado por defecto para CoreELEC y LG WerbOS)" + +#: +msgctxt "#33720" +msgid "Clear all caches" +msgstr "Borrar todas las cachés" + +#: +msgctxt "#33721" +msgid "Clear library cache (not items)" +msgstr "Borrar caché de la librería (no incluye elementos)" + +#: +msgctxt "#33722" +msgid "Libraries" +msgstr "Librerías" + +#: +msgctxt "#33723" +msgid "Media Items" +msgstr "Elementos multimedia" + +#: +msgctxt "#33724" +msgid "Cache Plex data for" +msgstr "La data de Plex en caché para" + +#: +msgctxt "#33725" +msgid "Persist cached Plex data" +msgstr "Data de Plex en caché persistente" + +#: +msgctxt "#33726" +msgid "Instead of clearing the cache when exiting the addon, persist it instead. Warning: You'll most likely encounter missing items in libraries or outdated data. Use the corresponding menu functionalities to clear the cache for specific items or libraries." +msgstr "Persistir el caché al salir del complemento en lugar de borrarlo. Precaución: Probablemente encontrarás elementos que desaparecieron en librerías o data obsoleta. Usa las funcionalidades del menú correspondientes para borrar el caché para elementos específicos o librerías." + +#: +msgctxt "#33727" +msgid "Store Plex server responses for items and library views in a local SQLite database. Doesn't cache anything else (Home/Hubs are always up to date). Massively speeds up consecutive visits to items and libraries. Certain important events, such as watch state changes, automatically delete the item cache and its corresponding library cache. (Default: Off)" +msgstr "Guardar las respuestas del servidor de Plex para vistas de elementos y de librería en una base de datos local SQLite. No guarda nada más en caché (Home/Hubs siempre están actualizados). Acelera masivamente las visitas consecutivas a elementos y librerías. Algunos eventos importantes, como cambios del estado de visto, automáticamente eliminan el elemento caché y su caché de librería correspondiente. (Por defecto: Apagado)" + +#: +msgctxt "#33728" +msgid "Clear cache for item" +msgstr "Borrar caché para elemento" + +#: +msgctxt "#33729" +msgid "Expire cached Plex data after (hours)" +msgstr "Expirar la data de Plex en caché después de (horas)" + +#: +msgctxt "#33730" +msgid "Randomly" +msgstr "Aleatoriamente" + +#: +msgctxt "#33731" +msgid "By Bitrate" +msgstr "Por tasa de bits" + +#: +msgctxt "#33732" +msgid "Bitrate" +msgstr "Tasa de bits" + +#: +msgctxt "#33733" +msgid "Transcode target codec" +msgstr "Códec de destino de la transcodificación" + +#: +msgctxt "#33734" +msgid "Sets the target codec when transcoding/direct streaming. Overridden when \"Transcode audio to AC3\" is set." +msgstr "Establece el códec de destino cuando se transcodifica/transmisión directa. Es ignorado cuando se establece \"Transcodificar el sonido a AC3\"." + +#: +msgctxt "#33735" +msgid "Always auto-start" +msgstr "Siempre inicio automático" + +#: +msgctxt "#33736" +msgid "By default PM4K won't auto-start when the current window isn't a Home-derived window (e.g.: Settings). Enable this if you always want to auto-start PM4K regardless of the current window. Can also improve start-up time." +msgstr "Por defecto PM4K no iniciará automáticamente cuando la ventana actual no es una ventana derivada de Inicio/Home (ejemplo: Ajustes). Habilita esto si siempre quieres iniciar automáticamente PM4K sin importar de cuál sea la ventana actual. Además, puede mejorar el tiempo de inicio." + +#: +msgctxt "#33737" +msgid "Loop theme music" +msgstr "Repite la música temática" + +#: +msgctxt "#33671" +msgid "Current: {current_version}\n" +"New: {new_version}\n" +"\n" +"Changelog:\n" +"{changelog}" +msgstr "Actual: {current_version}\n" +"Nuevo: {new_version}\n" +"\n" +"Log de cambios:\n" +"{changelog}" + +#: +msgctxt "#33738" +msgid "Max playlist size" +msgstr "Tamaño máximo de una lista de reproducción" + +#: +msgctxt "#33739" +msgid "How many items to load into a Kodi playlist before chunking them remotely using Plex PlayQueues (default: 500); Reduce on memory issues/crashes." +msgstr "Cuántos elementos se cargan en una lista de reproducción de Kodi antes de fragmentarlas remotamente usando Plex PlayQueues (por defecto: 500); Reduce problemas/fallos en la memoria." + +#: +msgctxt "#33740" +msgid "Home: Episodes season thumbnails" +msgstr "Inicio: Miniaturas de Episodios en temporada" + +#: +msgctxt "#33741" +msgid "Use season thumbnails/posters when displaying episodes in hubs instead of the TV show's." +msgstr "Utilizar miniaturas/carteles de las temporadas al mostrar episodios en las secciones en lugar de los del programa de TV." + +#: +msgctxt "#34000" +msgid "Watchlist" +msgstr "Lista de Seguimiento" + +#: +msgctxt "#34001" +msgid "Released" +msgstr "Estrenado" + +#: +msgctxt "#34002" +msgid "Movies & Shows" +msgstr "Películas y Series" + +#: e.g.: 8 seasons +msgctxt "#34003" +msgid "{} seasons" +msgstr "{} temporadas" + +#: +msgctxt "#34004" +msgid "Choose server" +msgstr "Elegir servidor" + +#: +msgctxt "#34005" +msgid "Availability" +msgstr "Disponibilidad" + +#: e.g.: 1 season +msgctxt "#34006" +msgid "{} season" +msgstr "{} temporada" + +#: +msgctxt "#34007" +msgid "Use Watchlist" +msgstr "Utilizar Lista de Seguimiento" + +#: +msgctxt "#34008" +msgid "Activates the current user's Plex watchlist as a section item. Adds watchlist functionality to certain media screens. Per-user setting. Default: On" +msgstr "Activa la lista de seguimiento de Plex del usuario actual como un elemento de sección. Añade la funcionalidad de lista de seguimiento a ciertas pantallas multimedia. Configuración por usuario. Predeterminado: Activo" + +#: +msgctxt "#34009" +msgid "Watchlist auto-remove" +msgstr "Eliminación automática de la lista de seguimiento" + +#: +msgctxt "#34010" +msgid "Automatically remove fully watched items from watchlist. Default: On" +msgstr "Eliminar automáticamente los elementos que fueron completamente vistos de la lista de seguimiento. Predeterminado: Activo" + +#: +msgctxt "#34011" +msgid "Remove from watchlist" +msgstr "Eliminar de la lista de seguimiento" + +#: +msgctxt "#34012" +msgid "Fast pause/resume" +msgstr "Pausa/reanudar rápida" + +#: +msgctxt "#34013" +msgid "when paused" +msgstr "Cuando en pausa" + +#: +msgctxt "#34014" +msgid "when playing" +msgstr "Cuando en reproducción" + +#: +msgctxt "#34015" +msgid "User-specific. Use OK/ENTER button to pause instead of showing the OSD (which can then only be accessed using DOWN), or resume when paused. Only works with 'Behave like official Plex clients' enabled." +msgstr "Específico de usuario. Use el botón OK/ENTER para pausar en lugar de mostrar el OSD (al que solo se podrá acceder con la tecla ABAJO) o para reanudar la reproducción tras la pausa. Solo funciona con la opción 'Comportarse como los clientes oficiales de Plex' activada." + +#: +msgctxt "#34016" +msgid "Max shutdown wait" +msgstr "Máxima espera para apagar" + +#: +msgctxt "#34017" +msgid "The plugin will try to hard exit once this time limit is reached (default: 5 seconds)" +msgstr "El complemento intentará salir forzosamente una vez que se alcance este límite de tiempo (predeterminado: 5 segundos)" + +#: +msgctxt "#34018" +msgid "Related Media" +msgstr "Media relacionada" + +#: watchlist discover hub title prefix +msgctxt "#34019" +msgid "Discover: {}" +msgstr "Descubrir: {}" + +#: +msgctxt "#34020" +msgid "Loading external hubs" +msgstr "Cargando secciones externas" + +#: +msgctxt "#34021" +msgid "Please wait ..." +msgstr "Por favor espere..." + +#: +msgctxt "#34022" +msgid "Video play completion behaviour" +msgstr "Comportamiento de completar la reproducción del video" + +#: +msgctxt "#34023" +msgid "Decide whether to use end credits markers to determine the 'watched' state of video items. When markers are not available the selected threshold percentage will be used." +msgstr "Decidir si usar marcadores de créditos finales para determinar si los elementos de video han sido vistos. Si los marcadores no están disponibles, se usará el porcentaje de umbral seleccionado." + +#: +msgctxt "#34024" +msgid "at selected threshold percentage" +msgstr "en el umbral de porcentaje seleccionado" + +#: +msgctxt "#34025" +msgid "at final credits marker position" +msgstr "en la posición final del marcador de créditos" + +#: +msgctxt "#34026" +msgid "at first credits marker position" +msgstr "en la primera posición del marcador de créditos" + +#: +msgctxt "#34027" +msgid "earliest between threshold percent and first credits marker" +msgstr "más temprano entre umbral de porcentaje y el primer marcador de créditos" + +#: +msgctxt "#32973" +msgid "Episodes: Continuous playback" +msgstr "" + +#: +msgctxt "#34028" +msgid "\"Use alternate seek\" valid seek window" +msgstr "" + +#: +msgctxt "#34029" +msgid "When \"Use alternate seek\" is enabled, any seek amount outside of this threshold window will be applied, otherwise the seek attempt will be ignored. If you encounter any seek issues, play with this value (ms, default: 2000)" +msgstr "" + +#: +msgctxt "#34030" +msgid "Unlock Direct Play for above 4K" +msgstr "" + +#: +msgctxt "#34031" +msgid "Producer" +msgstr "" + +#: +msgctxt "#34032" +msgid "Audio Language" +msgstr "" + +#: +msgctxt "#34033" +msgid "Subtitle Language" +msgstr "" + +#: +msgctxt "#34034" +msgid "Folder Location" +msgstr "" + +#: +msgctxt "#34035" +msgid "Editions" +msgstr "" + +#: +msgctxt "#34036" +msgid "DOVI" +msgstr "" + +#: +msgctxt "#34037" +msgid "HDR" +msgstr "" + +#: +msgctxt "#34038" +msgid "Force plex.direct mapping" +msgstr "" + +#: +msgctxt "#34039" +msgid "If you (still) don't see any posters: Under certain circumstances when Kodi still can't resolve hostnames when loading assets, even though the host resolves natively, enable this to force plex.direct mapping via advancedsettings.xml." +msgstr "" + +#: +msgctxt "#34040" +msgid "By Progress" +msgstr "" + +#: +msgctxt "#34041" +msgid "Progress" +msgstr "" + +#: +msgctxt "#34042" +msgid "By Album" +msgstr "" + +#: +msgctxt "#34043" +msgid "Album" +msgstr "" + diff --git a/script.plexmod/resources/language/resource.language.fr_fr/strings.po b/script.plexmod/resources/language/resource.language.fr_fr/strings.po index b4ec81eca1..4bc254495a 100644 --- a/script.plexmod/resources/language/resource.language.fr_fr/strings.po +++ b/script.plexmod/resources/language/resource.language.fr_fr/strings.po @@ -1,953 +1,3571 @@ -# XBMC Media Center language file msgid "" msgstr "" -"Project-Id-Version: XBMC-Addons\n" -"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n" -"POT-Creation-Date: 2013-12-12 22:56+0000\n" -"PO-Revision-Date: 2017-05-29 16:47++0200\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"X-Generator: POEditor.com\n" +"Project-Id-Version: PM4K / PlexMod for Kodi\n" "Language: fr\n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" +#: +#, fuzzy msgctxt "#32000" msgid "Main" -msgstr "Main" +msgstr "Principal" +#: msgctxt "#32001" msgid "Original" msgstr "Original" -msgctxt "#32002" -msgid "20 Mbps 1080p" -msgstr "20 Mbps 1080p" - -msgctxt "#32003" -msgid "12 Mbps 1080p" -msgstr "12 Mbps 1080p" - -msgctxt "#32004" -msgid "10 Mbps 1080p" -msgstr "10 Mbps 1080p" - -msgctxt "#32005" -msgid "8 Mbps 1080p" -msgstr "8 Mbps 1080p" - -msgctxt "#32006" -msgid "4 Mbps 720p" -msgstr "4 Mbps 720p" - -msgctxt "#32007" -msgid "3 Mbps 720p" -msgstr "3 Mbps 720p" - -msgctxt "#32008" -msgid "2 Mbps 720p" -msgstr "2 Mbps 720p" - -msgctxt "#32009" -msgid "1.5 Mbps 480p" -msgstr "1.5 Mbps 480p" - +#: msgctxt "#32010" msgid "720 kbps" msgstr "720 kbps" +#: msgctxt "#32011" msgid "320 kbps" msgstr "320 kbps" +#: msgctxt "#32012" msgid "208 kbps" msgstr "208 kbps" +#: msgctxt "#32013" msgid "96 kbps" msgstr "96 kbps" +#: msgctxt "#32014" msgid "64 kbps" msgstr "64 kbps" +#: msgctxt "#32020" msgid "Local Quality" msgstr "Qualité locale" +#: msgctxt "#32021" msgid "Remote Quality" msgstr "Qualité distante" +#: msgctxt "#32022" msgid "Online Quality" -msgstr "Qualité online" +msgstr "Qualité Internet" +#: msgctxt "#32023" msgid "Transcode Format" msgstr "Format transcodage" +#: msgctxt "#32024" msgid "Debug Logging" -msgstr "Debug Logging" - -msgctxt "#32025" -msgid "Allow Direct Play" -msgstr "Autoriser Direct Play" - -msgctxt "#32026" -msgid "Allow Direct Stream" -msgstr "Autoriser Direct Stream" +msgstr "Journal débogage" +#: msgctxt "#32027" msgid "Force" -msgstr "Force" +msgstr "Forcer" +#: msgctxt "#32028" msgid "Always" msgstr "Toujours" +#: msgctxt "#32029" msgid "Only Image Formats" -msgstr "Seulement formats d'image" +msgstr "Formats d'image seulement" +#: msgctxt "#32030" msgid "Auto" msgstr "Auto" -msgctxt "#32031" -msgid "Burn Subtitles (Direct Play Only)" -msgstr "Incruster sous-titres (pour Direct Play)" - +#: msgctxt "#32032" msgid "Allow Insecure Connections" msgstr "Autoriser connexions non sécurisées" +#: msgctxt "#32033" msgid "Never" msgstr "Jamais" +#: msgctxt "#32034" msgid "On Same network" -msgstr "Sur le même réseau" +msgstr "Même réseau" +#: msgctxt "#32035" msgid "Always" msgstr "Toujours" -msgctxt "#32036" -msgid "Allow 4K" -msgstr "Autoriser 4K" - -msgctxt "#32037" -msgid "Allow HEVC (h265)" -msgstr "Autoriser HEVC (h265)" - +#: msgctxt "#32038" msgid "Automatically Sign In" msgstr "Connexion automatique" +#: msgctxt "#32039" msgid "Post Play Auto Play" -msgstr "Post Play Auto Play" +msgstr "Lecture auto après lecture" +#: msgctxt "#32040" msgid "Enable Subtitle Downloading" -msgstr "Activer le téléchargement des sous-titres" +msgstr "Activer le téléchargement de sous-titres" +#: msgctxt "#32041" msgid "Enable Subtitle Downloading" -msgstr "Activer le téléchargement des sous-titres" +msgstr "Activer le téléchargement de sous-titres" +#: msgctxt "#32042" msgid "Server Discovery (GDM)" -msgstr "Server Discovery (GDM)" +msgstr "Découverte réseau (GDM)" +#: msgctxt "#32043" msgid "Start Plex On Kodi Startup" msgstr "Démarrer Plex au démarrage de Kodi" +#: msgctxt "#32044" msgid "Connection 1 IP" msgstr "Connexion 1 IP" +#: msgctxt "#32045" msgid "Connection 1 Port" msgstr "Connexion 1 Port" +#: msgctxt "#32046" msgid "Connection 2 IP" msgstr "Connexion 2 IP" +#: msgctxt "#32047" msgid "Connection 2 Port" msgstr "Connexion 2 Port" +#: msgctxt "#32048" msgid "Audio" msgstr "Audio" +#: msgctxt "#32049" msgid "Advanced" -msgstr "Avancée" +msgstr "Avancées" +#: msgctxt "#32050" msgid "Manual Servers" msgstr "Serveurs manuels" +#: msgctxt "#32051" msgid "Privacy" -msgstr "Privacy" +msgstr "Confidentialité" +#: msgctxt "#32052" msgid "About" -msgstr "A propos" +msgstr "À propos" +#: msgctxt "#32053" msgid "Video" -msgstr "Video" +msgstr "Vidéo" +#: msgctxt "#32054" msgid "Addon Version" -msgstr "Version de l'addon" +msgstr "Version de l'extension" +#: msgctxt "#32055" msgid "Kodi Version" -msgstr "Version de kodi" +msgstr "Version de Kodi" +#: msgctxt "#32056" msgid "Screen Resolution" -msgstr "Résolution de l'écran" +msgstr "Résolution d'écran" +#: msgctxt "#32057" msgid "Current Server Version" msgstr "Version actuelle du serveur" +#: +msgctxt "#32058" +msgid "Never exceed original audio codec" +msgstr "Ne pas dépasser le codec audio original" + +#: +msgctxt "#32059" +msgid "When transcoding audio, never exceed the original audio bitrate or channel count on the same codec." +msgstr "En transcodage audio, ne pas excéder le débit du codec audio original ou le nombre de canaux sur le même codec." + +#: +msgctxt "#32060" +msgid "Use Kodi audio channels" +msgstr "Utiliser les canaux audio de Kodi" +#: +msgctxt "#32064" +msgid "Treat DTS like AC3" +msgstr "Traiter le DTS comme l'AC3" + +#: msgctxt "#32100" msgid "Skip user selection and pin entry on startup." -msgstr "Passer l'écran de sélection de l'utilisateur et du PIN au démarrage" - -msgctxt "#32101" -msgid "If enabled, when playback ends and there is a 'Next Up' item available, it will be automatically be played after a 15 second delay." -msgstr "Si actif, à la fin de la lecture, les éléments suivants seront joués après un délai de 15s." +msgstr "Passer le choix de l'utilisateur et le code PIN au démarrage." +#: msgctxt "#32102" msgid "Enable this if your hardware can handle 4K playback. Disable it to force transcoding." -msgstr "Activer cette option si votre hardware est capable de lire la 4K. Désactiver pour forcer le transcodage." +msgstr "Activez ceci si votre matériel peut lire la 4K. Désactivez pour forcer le transcodage." +#: msgctxt "#32103" msgid "Enable this if your hardware can handle HEVC/h265. Disable it to force transcoding." -msgstr "Activer cette option si votre hardware est capable de décoder l'HEVC/h265. Désactiver pour forcer le transcodage." +msgstr "Activez ceci si votre matériel peut décoder l'HEVC/h265. Désactivez pour forcer le transcodage." +#: msgctxt "#32104" msgid "When to connect to servers with no secure connections.[CR][CR]* [B]Never[/B]: Never connect to a server insecurely[CR]* [B]On Same Network[/B]: Allow if on the same network[CR]* [B]Always[/B]: Allow same network and remote connections" -msgstr "Pour se connecter à un serveur via une connexion non sécurisée.[CR][CR]* [B]Attention[/B]: Ne jamais se connecter à un serveur via une connexion son sécurisée[CR]* [B]Sur le même réseau[/B]: Autoriser si même réseau[CR]* [B]Toujours[/B]: Autoriser même réseau et connexions distantes" - +msgstr "Quand se connecter à un serveur sans connexion sécurisée.[CR][CR]* [B]Jamais[/B] : Ne jamais se connecter à un serveur non sécurisé[CR]* [B]Même réseau[/B] : Autoriser sur le même réseau[CR]* [B]Toujours[/B] : Autoriser sur le même réseau et en connexion distante" +#: msgctxt "#32201" msgid "Trailer" msgstr "Bande-annonce" +#: msgctxt "#32202" msgid "Deleted Scene" -msgstr "Supprimer scène" +msgstr "Scène supprimée" +#: msgctxt "#32203" msgid "Interview" -msgstr "Interview" +msgstr "Entretien" +#: msgctxt "#32204" msgid "Music Video" -msgstr "Vidéos Musiques" +msgstr "Clip musical" +#: msgctxt "#32205" msgid "Behind the Scenes" -msgstr "Dans les coulisses" +msgstr "En coulisses" +#: msgctxt "#32206" msgid "Scene" -msgstr "Scene" +msgstr "Scène" +#: msgctxt "#32207" msgid "Live Music Video" -msgstr "Vidéos Musiques Live" +msgstr "Clip musical sur scène" +#: msgctxt "#32208" msgid "Lyric Music Video" -msgstr "Paroles vidéos musiques" +msgstr "Clip musical avec paroles" +#: msgctxt "#32209" msgid "Concert" msgstr "Concert" +#: msgctxt "#32210" msgid "Featurette" -msgstr "Featurette" +msgstr "Reportage" +#: msgctxt "#32211" msgid "Short" -msgstr "Courte" +msgstr "Court-métrage" +#: msgctxt "#32212" msgid "Other" -msgstr "Autres" +msgstr "Autre" +#: msgctxt "#32300" msgid "Go to Album" msgstr "Voir l'album" +#: msgctxt "#32301" msgid "Go to Artist" msgstr "Voir l'artiste" +#: msgctxt "#32302" msgid "Go to {0}" msgstr "Aller à {0}" -msgctxt "#32303" -msgid "Season" -msgstr "Saison" - -msgctxt "#32304" -msgid "Episode" -msgstr "Episode" - +#: msgctxt "#32305" msgid "Extras" -msgstr "Extras" +msgstr "Bonus" +#: msgctxt "#32306" msgid "Related Shows" -msgstr "Liens" +msgstr "Séries similaires" +#: msgctxt "#32307" msgid "More" msgstr "Plus" +#: msgctxt "#32308" msgid "Available" msgstr "Disponible" +#: msgctxt "#32309" msgid "None" msgstr "Aucun" -msgctxt "#32310" -msgid "S" -msgstr "S" - -msgctxt "#32311" -msgid "E" -msgstr "E" - +#: msgctxt "#32312" msgid "Unavailable" msgstr "Non disponible" +#: msgctxt "#32313" msgid "This item is currently unavailable." -msgstr "Cet élément n'est actuellement pas disponible." +msgstr "Cet élément n'est pas disponible actuellement." +#: msgctxt "#32314" msgid "In Progress" msgstr "En cours" +#: msgctxt "#32315" msgid "Resume playback?" -msgstr "Poursuivre la lecture ?" +msgstr "Reprendre la lecture ?" +#: msgctxt "#32316" msgid "Resume" -msgstr "Resume" +msgstr "Reprendre" +#: msgctxt "#32317" msgid "Play from beginning" msgstr "Lire depuis le début" +#: msgctxt "#32318" msgid "Mark Unplayed" msgstr "Marquer comme non vu" +#: msgctxt "#32319" msgid "Mark Played" msgstr "Marquer comme vu" +#: msgctxt "#32320" msgid "Mark Season Unplayed" msgstr "Marquer saison comme non vue" +#: msgctxt "#32321" msgid "Mark Season Played" msgstr "Marquer saison comme vue" +#: msgctxt "#32322" msgid "Delete" msgstr "Supprimer" +#: msgctxt "#32323" msgid "Go To Show" -msgstr "Voir le Show" +msgstr "Voir la série" +#: msgctxt "#32324" msgid "Go To {0}" msgstr "Aller à {0}" +#: msgctxt "#32325" msgid "Play Next" -msgstr "Lire le media suivant" +msgstr "Média suivant" +#: msgctxt "#32326" msgid "Really Delete?" -msgstr "Supprimer?" +msgstr "Supprimer ?" +#: msgctxt "#32327" msgid "Are you sure you really want to delete this media?" -msgstr "Etes-vous sûr de vouloir supprimer ce media ?" +msgstr "Voulez-vous vraiment supprimer ce média ?" +#: msgctxt "#32328" msgid "Yes" msgstr "Oui" +#: msgctxt "#32329" msgid "No" msgstr "Non" +#: msgctxt "#32330" msgid "Message" msgstr "Message" +#: msgctxt "#32331" msgid "There was a problem while attempting to delete the media." -msgstr "Erreur : impossible de supprimer le media" +msgstr "La suppression de ce média a échoué." +#: msgctxt "#32332" msgid "Home" -msgstr "Home" +msgstr "Accueil" +#: msgctxt "#32333" msgid "Playlists" -msgstr "Playlists" +msgstr "Listes de lecture" +#: msgctxt "#32334" msgid "Confirm Exit" -msgstr "Quitter" +msgstr "Confirmer la fermeture" +#: msgctxt "#32335" msgid "Are you ready to exit Plex?" msgstr "Souhaitez-vous quitter Plex ?" +#: msgctxt "#32336" msgid "Exit" msgstr "Quitter" +#: msgctxt "#32337" msgid "Cancel" msgstr "Annuler" +#: msgctxt "#32338" msgid "No Servers Found" msgstr "Aucun serveur trouvé" +#: msgctxt "#32339" msgid "Server is not accessible" -msgstr "Serveur non accessible" +msgstr "Serveur inaccessible" +#: msgctxt "#32340" msgid "Connection tests are in progress. Please wait." msgstr "Tests de connexion en cours. Merci de patienter." +#: msgctxt "#32341" msgid "Server is not accessible. Please sign into your server and check your connection." -msgstr "Serveur non joignable. Merci de vérifier l'accessibilité du serveur." +msgstr "Serveur inaccessible. Merci de vérifier vos identifiants et votre connexion." +#: msgctxt "#32342" msgid "Switch User" msgstr "Changer d'utilisateur" +#: msgctxt "#32343" msgid "Settings" msgstr "Paramètres" +#: msgctxt "#32344" msgid "Sign Out" msgstr "Déconnexion" +#: msgctxt "#32345" msgid "All" msgstr "Tous" +#: msgctxt "#32346" msgid "By Name" msgstr "Par Nom" +#: msgctxt "#32347" msgid "Artists" -msgstr "Artites" +msgstr "Artistes" +#: msgctxt "#32348" -msgid "movies" -msgstr "films" +msgid "Movies" +msgstr "Films" +#: msgctxt "#32349" msgid "photos" -msgstr "photos" +msgstr "Photos" +#: msgctxt "#32350" msgid "Shows" msgstr "Séries" +#: msgctxt "#32351" msgid "By Date Added" msgstr "Par date d'ajout" +#: msgctxt "#32352" msgid "Date Added" msgstr "Date d'ajout" +#: msgctxt "#32353" msgid "By Release Date" msgstr "Par date de sortie" +#: msgctxt "#32354" msgid "Release Date" msgstr "Date de sortie" +#: msgctxt "#32355" msgid "By Date Viewed" msgstr "Par date de visionnage" +#: msgctxt "#32356" msgid "Date Viewed" -msgstr "Date du visionnage" - -msgctxt "#32357" -msgid "By Name" -msgstr "Par nom" - -msgctxt "#32358" -msgid "Name" -msgstr "Nom" +msgstr "Date de visionnage" +#: msgctxt "#32359" msgid "By Rating" msgstr "Par note" +#: msgctxt "#32360" msgid "Rating" msgstr "Note" +#: msgctxt "#32361" msgid "By Resolution" msgstr "Par qualité" +#: msgctxt "#32362" msgid "Resolution" msgstr "Qualité" +#: msgctxt "#32363" msgid "By Duration" msgstr "Par durée" +#: msgctxt "#32364" msgid "Duration" msgstr "Durée" +#: msgctxt "#32365" msgid "By First Aired" msgstr "Par première diffusion" +#: msgctxt "#32366" msgid "First Aired" msgstr "Première diffusion" +#: msgctxt "#32367" msgid "By Unplayed" msgstr "Par non vus" +#: msgctxt "#32368" msgid "Unplayed" msgstr "Non vus" +#: msgctxt "#32369" msgid "By Date Played" msgstr "Par date de lecture" +#: msgctxt "#32370" msgid "Date Played" msgstr "Date de lecture" +#: msgctxt "#32371" msgid "By Play Count" msgstr "Par nombre de lectures" +#: msgctxt "#32372" msgid "Play Count" msgstr "Nombre de lectures" +#: msgctxt "#32373" msgid "By Date Taken" msgstr "Par date de prise de vue" +#: msgctxt "#32374" msgid "Date Taken" msgstr "Date de prise de vue" +#: msgctxt "#32375" msgid "No filters available" -msgstr "Aucuns filtres disponibles" +msgstr "Aucun filtre disponible" +#: msgctxt "#32376" msgid "Clear Filter" -msgstr "Effacer filtres" +msgstr "Effacer le filtre" +#: msgctxt "#32377" msgid "Year" msgstr "Année" +#: msgctxt "#32378" msgid "Decade" msgstr "Décennie" +#: msgctxt "#32379" msgid "Genre" msgstr "Genre" +#: msgctxt "#32380" msgid "Content Rating" -msgstr "Note du contenu" +msgstr "Classification" +#: msgctxt "#32381" msgid "Network" msgstr "Réseau" +#: msgctxt "#32382" msgid "Collection" msgstr "Collection" +#: msgctxt "#32383" msgid "Director" msgstr "Réalisateur" +#: msgctxt "#32384" msgid "Actor" msgstr "Acteur" +#: msgctxt "#32385" msgid "Country" msgstr "Pays" +#: msgctxt "#32386" msgid "Studio" msgstr "Studio" +#: msgctxt "#32387" msgid "Labels" msgstr "Labels" +#: msgctxt "#32388" msgid "Camera Make" -msgstr "Marque appareil photo" +msgstr "Marque caméra" +#: msgctxt "#32389" msgid "Camera Model" msgstr "Modèle caméra" +#: msgctxt "#32390" msgid "Aperture" -msgstr "Aperture" +msgstr "Ouverture" +#: msgctxt "#32391" msgid "Shutter Speed" msgstr "Vitesse d'obturation" +#: msgctxt "#32392" msgid "Lens" -msgstr "Lentille" +msgstr "Objectif" +#: msgctxt "#32393" msgid "TV Shows" msgstr "Séries TV" +#: msgctxt "#32394" msgid "Music" -msgstr "Musiques" +msgstr "Musique" +#: msgctxt "#32395" msgid "Audio" msgstr "Audio" +#: msgctxt "#32396" msgid "Subtitles" msgstr "Sous-titres" +#: msgctxt "#32397" msgid "Quality" msgstr "Qualité" +#: msgctxt "#32398" msgid "Kodi Video Settings" -msgstr "Paramètres vidéo Kodi" +msgstr "Réglages vidéo Kodi" +#: msgctxt "#32399" msgid "Kodi Audio Settings" -msgstr "Paramètres audio Kodi" +msgstr "Réglages audio Kodi" +#: msgctxt "#32400" msgid "Go To Season" msgstr "Aller à la saison" +#: msgctxt "#32401" msgid "Directors" msgstr "Réalisateurs" +#: msgctxt "#32402" msgid "Writer" msgstr "Auteur" +#: msgctxt "#32403" msgid "Writers" msgstr "Auteurs" +#: msgctxt "#32404" msgid "Related Movies" msgstr "Films liés" +#: msgctxt "#32405" msgid "Download Subtitles" msgstr "Télécharger sous-titres" +#: msgctxt "#32406" msgid "Subtitle Delay" msgstr "Délai sous-titres" +#: msgctxt "#32407" msgid "Next Subtitle" msgstr "Sous-titres suivants" +#: msgctxt "#32408" msgid "Disable Subtitles" msgstr "Désactiver sous-titres" +#: msgctxt "#32409" msgid "Enable Subtitles" msgstr "Activer sous-titres" +#: msgctxt "#32410" msgid "Platform Version" msgstr "Version de la plateforme" +#: msgctxt "#32411" msgid "Unknown" msgstr "Inconnu" +#: msgctxt "#32412" msgid "Edit Or Clear" -msgstr "Editer ou Effacer" +msgstr "Éditer ou Effacer" +#: msgctxt "#32413" msgid "Edit IP address or clear the current setting?" -msgstr "Editer adresse IP ou effacer les paramètres actuels ?" +msgstr "Éditer l'adresse IP ou effacer les réglages actuels ?" +#: msgctxt "#32414" msgid "Clear" msgstr "Effacer" +#: msgctxt "#32415" msgid "Edit" -msgstr "Editer" +msgstr "Éditer" +#: msgctxt "#32416" msgid "Enter IP Address" -msgstr "Saisir adresse IP" +msgstr "Saisir l'adresse IP" +#: msgctxt "#32417" msgid "Enter Port Number" msgstr "Saisir le port" +#: msgctxt "#32418" msgid "Creator" -msgstr "Creator" +msgstr "Créateur" +#: msgctxt "#32419" msgid "Cast" -msgstr "Casting" +msgstr "Distribution" +#: msgctxt "#32420" msgid "Disc" -msgstr "Disc" +msgstr "Disque" +#: msgctxt "#32421" msgid "Sign Out" msgstr "Déconnexion" +#: msgctxt "#32422" msgid "Exit" msgstr "Quitter" +#: msgctxt "#32423" msgid "Shutdown" -msgstr "Eteindre" +msgstr "Éteindre" +#: msgctxt "#32424" msgid "Suspend" msgstr "Veille" +#: msgctxt "#32425" msgid "Hibernate" msgstr "Veille prolongée" +#: msgctxt "#32426" msgid "Reboot" msgstr "Redémarrer" +#: msgctxt "#32427" msgid "Failed" -msgstr "Echec" +msgstr "Échec" +#: msgctxt "#32428" msgid "Login failed!" -msgstr "Impossible de se connecter" +msgstr "Connexion impossible !" +#: msgctxt "#32429" msgid "Resume from {0}" msgstr "Reprendre à {0}" +#: msgctxt "#32430" msgid "Discovery" -msgstr "Recherche" +msgstr "Découverte" +#: msgctxt "#32431" msgid "Search" msgstr "Recherche" +#: msgctxt "#32432" msgid "Space" msgstr "Espace" +#: msgctxt "#32433" msgid "Clear" msgstr "Effacer" +#: msgctxt "#32434" msgid "Searching..." msgstr "Recherche en cours..." +#: msgctxt "#32435" msgid "No Results" msgstr "Aucun résultat" +#: msgctxt "#32436" msgid "Paused" -msgstr "Pause" +msgstr "En pause" +#: msgctxt "#32437" msgid "Welcome" msgstr "Bienvenue" +#: msgctxt "#32438" msgid "Previous" msgstr "Précédent" +#: msgctxt "#32439" msgid "Playing Next" -msgstr "A regarder ensuite" +msgstr "À suivre" +#: msgctxt "#32440" msgid "On Deck" msgstr "Découvrir" +#: msgctxt "#32441" msgid "Unknown" msgstr "Inconnu" +#: msgctxt "#32442" msgid "Embedded" msgstr "Intégré" +#: msgctxt "#32443" msgid "Forced" msgstr "Forcé" +#: msgctxt "#32444" msgid "Lyrics" -msgstr "Parole" +msgstr "Paroles" +#: msgctxt "#32445" msgid "Mono" msgstr "Mono" +#: msgctxt "#32446" msgid "Stereo" -msgstr "Stereo" +msgstr "Stéréo" +#: msgctxt "#32447" msgid "None" msgstr "Aucun" +#: msgctxt "#32448" msgid "Playback Failed!" -msgstr "Echec de lecture!" +msgstr "Lecture impossible !" +#: msgctxt "#32449" msgid "Can't connect to plex.tv[CR]Check your internet connection and try again." -msgstr "Impossible de se connecter à plex.tv[CR]Vérifiez votre connexion et réessayez." +msgstr "Connexion à plex.tv impossible[CR]Vérifiez votre connexion et réessayez." +#: msgctxt "#32450" msgid "Choose Version" msgstr "Choisir une version" +#: msgctxt "#32451" msgid "Play Version..." msgstr "Lire version..." +#: msgctxt "#32452" msgid "No Content available in this library" -msgstr "" +msgstr "Pas de contenu dans cette bibliothèque" +#: msgctxt "#32453" msgid "Please add content and/or check that 'Include in dashboard' is enabled." -msgstr "" +msgstr "Veuillez ajouter du contenu et/ou vérifier que 'Inclure au tableau de bord' est activé." +#: msgctxt "#32454" msgid "No Content available for this filter" -msgstr "Aucun contenu disponible dans cette librairie" +msgstr "Aucun contenu disponible avec ce filtre" +#: msgctxt "#32455" msgid "Please change change or remove the current filter" -msgstr "Merci de modifier ou supprimer le filtre courant" +msgstr "Veuillez modifier ou supprimer le filtre actuel" +#: msgctxt "#32456" msgid "Show" -msgstr "" +msgstr "Série" +#: msgctxt "#32457" msgid "By Show" -msgstr "" +msgstr "Par Série" +#: msgctxt "#32458" msgid "Episodes" -msgstr "" +msgstr "Épisodes" +#: msgctxt "#32459" msgid "Offline Mode" msgstr "Mode Hors-Ligne" +#: msgctxt "#32460" msgid "Sign In" msgstr "Se connecter" +#: msgctxt "#32461" msgid "Albums" -msgstr "" +msgstr "Albums" +#: msgctxt "#32462" msgid "Artist" -msgstr "" +msgstr "Artiste" +#: msgctxt "#32463" msgid "By Artist" +msgstr "Par Artiste" + +#: +#, fuzzy +msgctxt "#32464" +msgid "Player" +msgstr "Lecteur" + +#: +msgctxt "#32465" +msgid "Use skip step settings from Kodi" +msgstr "Utiliser les réglages de saut de Kodi" + +#: +#, fuzzy +msgctxt "#32466" +msgid "Automatically seek selected position after a delay" +msgstr "Recherche automatique de la position après un délai" + +#: +msgctxt "#32467" +msgid "User Interface" +msgstr "Interface Utilisateur" + +#: +msgctxt "#32468" +msgid "Show dynamic background art" +msgstr "Afficher un arrière-plan dynamique" + +#: +msgctxt "#32469" +msgid "Background art blur amount" +msgstr "Floutage de l'arrière-plan" + +#: +msgctxt "#32470" +msgid "Background art opacity" +msgstr "Opacité de l'arrière-plan" + +#: +msgctxt "#32471" +msgid "Use Plex/Kodi steps for timeline" +msgstr "Utiliser les intervalles de Plex/Kodi pour la progression" + +#: +msgctxt "#32480" +msgid "Theme music" +msgstr "Thème musical" + +#: +msgctxt "#32481" +msgid "Off" +msgstr "Désac." + +#: +msgctxt "#32482" +msgid "%(percentage)s %%" +msgstr "%(percentage)s %%" + +#: +msgctxt "#32483" +msgid "Hide Stream Info" +msgstr "Masquer infos de flux" + +#: +msgctxt "#32484" +msgid "Show Stream Info" +msgstr "Afficher infos de flux" + +#: +msgctxt "#32485" +msgid "Go back instantly with the previous menu action in scrolled views" +msgstr "Revenir immédiatement avec l'action de menu précédent depuis une vue déroulée" + +#: +msgctxt "#32487" +msgid "Seek Delay" +msgstr "Délai de recherche" + +#: +msgctxt "#32488" +msgid "Screensaver" +msgstr "Écran de veille" + +#: +msgctxt "#32489" +msgid "Quiz Mode" +msgstr "Mode Quiz" + +#: +msgctxt "#32490" +msgid "Collections" +msgstr "Collections" + +#: +msgctxt "#32491" +msgid "Folders" +msgstr "Dossiers" + +#: +msgctxt "#32492" +msgid "Kodi Subtitle Settings" +msgstr "Réglages des sous-titres de Kodi" + +#: +msgctxt "#32495" +msgid "Skip intro" +msgstr "Passer l'intro" + +#: +msgctxt "#32496" +msgid "Skip credits" +msgstr "Passer le générique" + +#: +msgctxt "#32500" +msgid "Always show post-play screen (even for short videos)" +msgstr "Toujours afficher l'écran post-lecture (même pour les vidéos courtes)" + +#: +msgctxt "#32501" +msgid "Time-to-wait between videos on post-play" +msgstr "Temps d'attente entre les vidéos en post-lecture" + +#: +msgctxt "#32505" +msgid "Visit media in video playlist instead of playing it" +msgstr "Accéder au média dans la liste de lecture vidéo plutôt que de le lire" + +#: +msgctxt "#32521" +msgid "Skip Intro Button Timeout" +msgstr "Temps d'attente du bouton 'Passer l'intro'" + +#: +msgctxt "#32522" +msgid "Automatically Skip Intro" +msgstr "Passer automatiquement l'intro" + +#: +msgctxt "#32524" +msgid "Set how long the skip intro button shows for." +msgstr "Régler le temps d'apparition du bouton pour passer l'intro." + +#: +msgctxt "#32525" +msgid "Skip Credits Button Timeout" +msgstr "Temps d'attente du bouton 'Passer les crédits'" + +#: +msgctxt "#32526" +msgid "Automatically Skip Credits" +msgstr "Passer automatiquement le générique" + +#: +msgctxt "#32528" +msgid "Set how long the skip credits button shows for." +msgstr "Régler le temps d'apparition du bouton pour passer le générique." + +#: +msgctxt "#32540" +msgid "Show when the current video will end in player" +msgstr "Montrer quand la vidéo en cours se termine dans le lecteur" + +#: +msgctxt "#32541" +msgid "Shows time left and at which time the media will end." +msgstr "Montrer le temps restant et à quelle heure le média se termine." + +#: +msgctxt "#32542" +msgid "Show \"Ends at\" label for the end-time as well" +msgstr "" + +#: +msgctxt "#32543" +msgid "Ends at" +msgstr "Termine à" + +#: +msgctxt "#32602" +msgid "Enable this if your hardware can handle AV1. Disable it to force transcoding." +msgstr "Activer si votre matériel peut décoder le AV1. Désactiver pour forcer son transcodage." + +#: +msgctxt "#33101" +msgid "By Audience Rating" +msgstr "Par note des spectateurs" + +#: +msgctxt "#33102" +msgid "Audience Rating" +msgstr "Notes des spectateurs" + +#: +msgctxt "#33103" +msgid "By my Rating" +msgstr "Par ma note" + +#: +msgctxt "#33104" +msgid "My Rating" +msgstr "Ma note" + +#: +msgctxt "#33105" +msgid "By Content Rating" +msgstr "Par notation du contenu" + +#: +msgctxt "#33106" +msgid "Content Rating" +msgstr "Notation du contenu" + +#: +msgctxt "#33107" +msgid "By Critic Rating" +msgstr "Par note des critiques" + +#: +msgctxt "#33108" +msgid "Critic Rating" +msgstr "Note critiques" + +#: +msgctxt "#33200" +msgid "Background Color" +msgstr "Couleur de fond" + +#: +msgctxt "#33201" +msgid "Specify solid Background Color instead of using media images" +msgstr "Spécifier une couleur de fond au lieu d'utiliser les images des médias" + +#: +msgctxt "#33400" +msgid "Use old compatibility profile" +msgstr "Utiliser l'ancien profile de compatibilité" + +#: +msgctxt "#33401" +msgid "Uses the Chrome client profile instead of the custom one. Might fix rare issues with 3D playback." +msgstr "Utiliser le profile du client Chrome à la place de celui personnalisé. Peut régler des problèmes rares en lecture 3D." + +#: +msgctxt "#32031" +msgid "Burn-in Subtitles" +msgstr "Sous-titres incrustés" + +#: +msgctxt "#32061" +msgid "When transcoding audio, target the audio channels set in Kodi." +msgstr "Lors d'un transcodage audio, cibler les canaux audio réglés dans Kodi" + +#: +msgctxt "#32062" +msgid "Transcode audio to AC3" +msgstr "Transcoder l'audio en AC3" + +#: +msgctxt "#32063" +msgid "Transcode audio to AC3 in certain conditions (useful for passthrough)." +msgstr "Transcoder l'audio en AC3 dans certaines conditions (utile pour le passthrough)." + +#: +msgctxt "#32065" +msgid "When any of the force AC3 settings are enabled, treat DTS the same as AC3 (useful for Optical passthrough)" +msgstr "Lorsque l'un des réglages pour forcer l'AC3 est activé, traiter le DTS comme l'AC3 (utile pour le passthrough optique)" + +#: +msgctxt "#32066" +msgid "Force audio to AC3" +msgstr "Forcer l'audio en AC3" + +#: +msgctxt "#32067" +msgid "Only force multichannel audio to AC3" +msgstr "Ne forcer que l'audio multicanal en AC3" + +#: +#, fuzzy +msgctxt "#32493" +msgid "When a media file has a forced/foreign subtitle for a subtitle-enabled language, the Plex Media Server preselects it. This behaviour is usually not necessary and not configurable. This setting fixes that by ignoring the PMSs decision and selecting the same language without a forced flag if possible." +msgstr "Lorsqu'un média a un sous-titre forcé/étranger pour un langage dont les sous-titres sont activés, le serveur Plex le préselectionne. Ce comportement est habituellement non nécessaire et non configurable. Ce réglage règle cela en ignorant la décision du serveur Plex et en choisissant la même langue sans flag \"forced\" si possible." + +#: +#, fuzzy +msgctxt "#32523" +msgid "Automatically skip intros if available. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "Passer automatiquement les intros si disponible. N'outrepasse pas le mode binge watching.\n" +"Peut être désactivé/activé par Série." + +#: +msgctxt "#32527" +msgid "Automatically skip credits if available. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "Passer automatiquement les génériques si disponible. N'outrepasse pas le mode binge watching.\n" +"Peut être désactivé/activé par Série." + +#: +#, fuzzy +msgctxt "#33501" +msgid "Video played threshold" +msgstr "Seuil de vidéo lue" + +#: +#, fuzzy +msgctxt "#33502" +msgid "Set this to the same value as your Plex server (Settings>Library>Video played threshold) to avoid certain pitfalls, Default: 90 %" +msgstr "Régler à la même valeur que votre serveur Plex (Réglages>Bibliothèque>Seuil de vidéo lue) pour éviter les erreurs, Défaut : 90%" + +#: +msgctxt "#33503" +msgid "Use alternative hubs refresh" +msgstr "Utiliser l'actualisation alternative des hubs" + +#: +#, fuzzy +msgctxt "#33504" +msgid "Refreshes all hubs for all libraries after an item's watch-state has changed, instead of only those likely affected. Use this if you find a hub that doesn't update properly." +msgstr "Ré-actualiser tous les hubs pour toutes les bibliothèques après que le statut d'un objet a changé, plutôt que que seulement les hubs affectés. Utiliser si un hub ne s'actualise pas correctement." + +#: +msgctxt "#33505" +msgid "Show intro skip button early" +msgstr "" + +#: +msgctxt "#33507" +msgid "Enabled" +msgstr "Activé" + +#: +msgctxt "#33508" +msgid "Disabled" +msgstr "Désactivé" + +#: +#, fuzzy +msgctxt "#33510" +msgid "When showing the intro skip button early, only do so if the intro occurs within the first X seconds." +msgstr "Lorsque le bouton \"passer l'intro\" s'affiche tôt, ne le faire que si l'intro se déroule dans les X premières secondes." + +#: +msgctxt "#33600" +msgid "System" +msgstr "Système" + +#: +msgctxt "#33601" +msgid "Show video chapters" +msgstr "Afficher les chapitres de la vidéo" + +#: +msgctxt "#33602" +msgid "If available, show video chapters from the video-file instead of the timeline-big-seek-steps." +msgstr "" + +#: +msgctxt "#33603" +msgid "Use virtual chapters" +msgstr "Utiliser les chapitres virtuels" + +#: +msgctxt "#33604" +msgid "When the above is enabled and no video chapters are available, simulate them by using the markers identified by the Plex Server (Intro, Credits)." +msgstr "Lorsque l'option est activée et qu'il n'y a pas de chapitre vidéo disponible, les simuler en utilisant les marqueurs identifiés par le serveur Plex (Intro, Générique)." + +#: +msgctxt "#33605" +msgid "Video Chapters" +msgstr "Chapitres Video" + +#: +msgctxt "#33606" +msgid "Virtual Chapters" +msgstr "Chapitres virtuels" + +#: +msgctxt "#33607" +msgid "Chapter {}" +msgstr "Chapitre {}" + +#: +msgctxt "#33608" +msgid "Intro" +msgstr "Intro" + +#: +msgctxt "#33609" +msgid "Credits" +msgstr "Générique" + +#: +msgctxt "#33610" +msgid "Main" +msgstr "Principal" + +#: +msgctxt "#33611" +msgid "Chapters" +msgstr "Chapitres" + +#: +msgctxt "#33612" +msgid "Markers" +msgstr "Marqueurs" + +#: +msgctxt "#33613" +msgid "Kodi Buffer Size (MB)" +msgstr "Taille du buffer de Kodi (MB)" + +#: +msgctxt "#33614" +msgid "Set the Kodi Cache/Buffer size. Free: {} MB, Recommended: ~50 MB, Recommended max: {} MB, Default: 20 MB." +msgstr "Régler la taille du Cache/Buffer de Kodi. Vide: {}MB, Recommandé : ~50MB, Recommandé max : {}MB, Défaut : 20MB" + +#: +msgctxt "#33615" +msgid "{time} left" +msgstr "{time} restant" + +#: +msgctxt "#33616" +msgid "Addon Path" +msgstr "Chemin de l'add-on" + +#: +msgctxt "#33617" +msgid "Userdata/Profile Path" +msgstr "Chemin Userdata/Profile" + +#: +#, fuzzy +msgctxt "#33618" +msgid "TV binge-viewing mode" +msgstr "Mode binge-watching" + +#: +msgctxt "#33622" +msgid "LAN reachability timeout (ms)" +msgstr "" + +#: +msgctxt "#33623" +msgid "When checking for LAN reachability, use this timeout. Default: 10ms" +msgstr "" + +#: +msgctxt "#33624" +msgid "Network" +msgstr "Réseau" + +#: +msgctxt "#33625" +msgid "Smart LAN/local server discovery" +msgstr "Recherche intelligente en LAN/serveur local" + +#: +msgctxt "#33626" +msgid "Checks whether servers returned from Plex.tv are actually local/in your LAN. For specific setups (e.g. Docker) Plex.tv might not properly detect a local server.\n" +"\n" +"NOTE: Only works on Kodi 19 or above." +msgstr "Vérifie si les serveurs présentés par Plex.tv sont locaux/dans le réseau local. Pour des installations spécifiques, (par ex. Docker), Plex.tv peut ne pas détecter correctement un serveur local.\n" +"\n" +"NOTE : Ne marche qu'avec Kodi 19 ou supérieur." + +#: +msgctxt "#33627" +msgid "Prefer LAN/local servers over security" +msgstr "Préférer les serveurs locaux à une connexion sécurisée" + +#: +msgctxt "#33628" +msgid "Prioritizes local connections over secure ones. Needs the proper setting in \"Allow Insecure Connections\" and the Plex Server's \"Secure connections\" at \"Preferred\". Can be used to enforce manual servers." +msgstr "Prioriser les connections locales sur les connexions sécurisées. Nécessite le réglage correct dans \"Autoriser les Connexions non sécurisées\" et " + +#: +msgctxt "#33629" +msgid "Auto-skip intro/credits offset" +msgstr "" + +#: +msgctxt "#33630" +msgid "Intro/credits markers might be a little early in Plex. When auto skipping add (or subtract) this many seconds from the marker. This avoids cutting off content, while possibly skipping the marker a little late." +msgstr "" + +#: +msgctxt "#32631" +msgid "Playback (user-specific)" +msgstr "" + +#: +msgctxt "#33632" +msgid "Server connectivity check timeout (seconds)" +msgstr "" + +#: +msgctxt "#33633" +msgid "Set the maximum amount of time a server connection has to answer a connectivity request. Default: 2.5" +msgstr "" + +#: +msgctxt "#33634" +msgid "Combined Chapters" +msgstr "Chapitres combinés" + +#: +msgctxt "#33635" +msgid "Final Credits" +msgstr "Générique Final" + +#: +msgctxt "#32700" +msgid "Action on Sleep event" +msgstr "Action lors de l'événement de mise en veille" + +#: +msgctxt "#32701" +msgid "When Kodi receives a sleep event from the system, run the following action." +msgstr "Lorsque Kodi reçoit un événement de mise en veille du système, exécutez l'action suivante" + +#: +msgctxt "#32702" +msgid "Nothing" +msgstr "Rien" + +#: +msgctxt "#32703" +msgid "Stop playback" +msgstr "" + +#: +msgctxt "#32704" +msgid "Quit Kodi" +msgstr "Quitter Kodi" + +#: +msgctxt "#32705" +msgid "CEC Standby" +msgstr "Veille CEC" + +#: +msgctxt "#32800" +msgid "Skipping intro" +msgstr "Passer l'intro" + +#: +msgctxt "#32801" +msgid "Skipping credits" +msgstr "" + +#: +msgctxt "#32900" +msgid "While playing back an item and seeking on the seekbar, automatically seek to the selected position after a delay instead of having to confirm the selection." +msgstr "" + +#: +msgctxt "#32901" +msgid "Seek delay in seconds." +msgstr "" + +#: +msgctxt "#32902" +msgid "Kodi has its own skip step settings. Try to use them if they're configured instead of the default ones." +msgstr "" + +#: +msgctxt "#32903" +msgid "Use the above for seeking on the timeline as well." +msgstr "" + +#: +msgctxt "#32904" +msgid "In seconds." +msgstr "En secondes." + +#: +msgctxt "#32905" +msgid "Cancel post-play timer by pressing OK/SELECT" +msgstr "" + +#: +msgctxt "#32906" +msgid "Cancel skip marker timer with BACK" +msgstr "" + +#: +msgctxt "#32907" +msgid "When auto-skipping a marker, allow cancelling the timer by pressing BACK." +msgstr "" + +#: +msgctxt "#32908" +msgid "Immediately skip marker with OK/SELECT" +msgstr "Passer automatiquement un marqueur avec OK/SELECT" + +#: +msgctxt "#32909" +msgid "When auto-skipping a marker with a timer, allow skipping immediately by pressing OK/SELECT." +msgstr "" + +#: +msgctxt "#32912" +msgid "Show buffer-state on timeline" +msgstr "Afficher l'état du tampon sur la timeline" + +#: +msgctxt "#32913" +msgid "Shows the current Kodi buffer/cache state on the video player timeline." +msgstr "Affiche l'état actuel du tampon/cache de Kodi sur la timeline du lecteur vidéo" + +#: +msgctxt "#32914" +msgid "Loading" +msgstr "Chargement" + +#: +msgctxt "#32915" +msgid "Slow connection" +msgstr "Connection lente" + +#: +msgctxt "#32916" +msgid "Use with a wonky/slow connection, e.g. in a hotel room. Adjusts the UI to visually wait for item refreshes and waits for the buffer to fill when starting playback. Automatically sets readfactor=20, requires Kodi restart." +msgstr "Utiliser avec une connexion lente/instable, comme dans un hôtel. Ajuster l'interface pour attendre visuellement que les éléments s'actualisent et attendre que le buffer se remplisse pour commencer la lecture. Règle automatiquement le readfactor=20, nécessite un redémarrage de Kodi." + +#: +msgctxt "#32917" +msgid "Couldn't fill buffer in time ({}s)" +msgstr "" + +#: +msgctxt "#32918" +msgid "Buffer wait timeout (seconds)" +msgstr "" + +#: +msgctxt "#32919" +msgid "When slow connection is enabled in the addon, wait this long for the buffer to fill. Default: 120 s" +msgstr "Lorsque le mode connexion lente est activé dans l'add-on attendre cette durée pour que le buffer se remplisse. Défaut : 120s" + +#: +msgctxt "#32920" +msgid "Insufficient buffer wait (seconds)" +msgstr "\n" +"" + +#: +msgctxt "#32921" +msgid "When slow connection is enabled in the addon and the configured buffer isn't big enough for us to determine its fill state, wait this long when starting playback. Default: 10 s" +msgstr "Lorsque l'option de connexion lente est activée dans l'addon et que le tampon configuré n'est pas assez grand pour déterminer son état de remplissage, attendez ce délai au démarrage de la lecture. Par défaut : 10 s" + +#: +msgctxt "#32922" +msgid "Kodi Cache Readfactor" +msgstr "" + +#: +msgctxt "#32923" +msgid "Sets the Kodi cache readfactor value. Default: {0}, recommended: {1}. With \"Slow connection\" enabled this will be set to {2}, as otherwise the cache doesn't fill fast/aggressively enough." +msgstr "" + +#: +msgctxt "#32924" +msgid "Minimize" +msgstr "Minimiser" + +#: +msgctxt "#32925" +msgid "Playback Settings" +msgstr "Paramètres de lecture" + +#: +msgctxt "#32926" +msgid "Wrong pin entered!" +msgstr "Mauvais ping saisi!" + +#: +msgctxt "#32927" +msgid "Use episode thumbnails in continue hub" +msgstr "" + +#: +msgctxt "#32928" +msgid "Instead of using media artwork, use thumbnails for episodes in the continue hub on the home screen if available." +msgstr "" + +#: +msgctxt "#32929" +msgid "Use legacy background fallback image" +msgstr "" + +#: +msgctxt "#32930" +msgid "Previous Subtitle" +msgstr "Sous-titres précédents" + +#: +msgctxt "#32931" +msgid "Audio/Subtitles" +msgstr "Audio/Sous-titres" + +#: +msgctxt "#32936" +msgid "Show playlist button" +msgstr "Afficher le bouton de la playlist" + +#: +msgctxt "#32937" +msgid "Show prev/next button" +msgstr "Afficher le bouton précédent/suivant" + +#: +msgctxt "#32941" +msgid "Forced subtitles fix" +msgstr "" + +#: +msgctxt "#32942" +msgid "Other seasons" +msgstr "Autres saisons" + +#: +msgctxt "#32943" +msgid "Crossfade dynamic background art" +msgstr "" + +#: +msgctxt "#32944" +msgid "Burn-in SSA subtitles (DirectStream)" +msgstr "Sous-titres incrustés SSA (DirectStream)" + +#: +msgctxt "#32945" +msgid "When Direct Streaming instruct the Plex Server to burn in SSA/ASS subtitles (thus transcoding the video stream). If disabled it will not touch the video stream, but will convert the subtitle to unstyled text." +msgstr "" + +#: +msgctxt "#32946" +msgid "Stop video playback on idle after" +msgstr "Arrêter la lecture lorsqu'inactive après" + +#: +msgctxt "#32947" +msgid "Stop video playback on screensaver" +msgstr "Arrêter la lecture vidéo lorsque l'économiseur d'écran est actif" + +#: +msgctxt "#32948" +msgid "Allow auto-skip when transcoding" +msgstr "" + +#: +msgctxt "#32949" +msgid "When transcoding/DirectStreaming, allow auto-skip functionality." +msgstr "" + +#: +msgctxt "#32950" +msgid "Use extended title for subtitles" +msgstr "Utiliser le titre étendu pour les sous-titres" + +#: +msgctxt "#32951" +msgid "When displaying subtitles use the extendedDisplayTitle Plex exposes." +msgstr "" + +#: +msgctxt "#32953" +msgid "Reviews" +msgstr "Critiques" + +#: +msgctxt "#32954" +msgid "Needs Kodi restart. WARNING: This will overwrite advancedsettings.xml!\n" +"\n" +"To customize other cache/network-related values, copy \"script.plexmod/pm4k_cache_template.xml\" to profile folder and edit it to your liking. (See About section for the file paths)" +msgstr "Nécessite un redémarrage de Kodi. ATTENTION : Cela va remplacer le fichier advancedsettings.xml !\n" +"\n" +"Pour personnaliser d'autres valeurs de cache/relatives au réseau, copier \"script.plexmod/pm4k_cache_template.xml\" dans le dossier profile et modifiez-le selon vos préférences. (Voir la section A propos pour les chemins)" + +#: +msgctxt "#32955" +msgid "Use Kodi keyboard for searching" +msgstr "Utiliser le clavier Kodi pour les recherches" + +#: +msgctxt "#32956" +msgid "Poster resolution scaling %" +msgstr "Mise à l'échelle de résolution du poster en %" + +#: +msgctxt "#32957" +msgid "In percent. Scales the resolution of all posters/thumbnails for better image quality. May impact PMS/PM4K performance, will increase the cache usage accordingly. Recommended: 200-300 % for for big screens if your hardware can handle it. Needs addon restart." +msgstr "En pourcentage. Met à l'échelle tous les posters/aperçus pour une meilleure qualité d'image. Peut impacter les performances de Plex/PM4K, augmentera le cache en conséquence. Recommandé : 200-300% pour les grands écrans si votre matériel le permet. Nécessite un redémarrage de l'add-on." + +#: +msgctxt "#32958" +msgid "Calculate OpenSubtitles.com hash" +msgstr "Calculer le hash de OpenSubtitles.com" + +#: +msgctxt "#32959" +msgid "When opening the subtitle download feature, automatically calculate the OpenSubtitles.com hash for the given file. Can improve search results, downloads 2*64 KB of the video file to calculate the hash." +msgstr "" + +#: +msgctxt "#32960" +msgid "Similar Artists" +msgstr "Artistes Similaires" + +#: +msgctxt "#32961" +msgid "Show hub bifurcation lines" +msgstr "Afficher les lignes de bifurcation des hubs" + +#: +msgctxt "#32962" +msgid "Visually separate hubs horizontally using a thin line." +msgstr "Séparer visuellement les hubs horizontalement avec une ligne fine." + +#: +msgctxt "#32963" +msgid "Wait between videos (s)" +msgstr "Attente entre les vidéos (s)" + +#: +msgctxt "#32964" +msgid "When playing back consecutive videos (e.g. TV shows), wait this long before starting the next one in the queue. Might fix compatibility issues with certain configurations." +msgstr "" + +#: +msgctxt "#32965" +msgid "Quit Kodi on exit by default" +msgstr "Quitter Kodi à la fermeture par défaut" + +#: +msgctxt "#32966" +msgid "When exiting the addon, use \"Quit Kodi\" as default option. Can be dynamically switched using CONTEXT_MENU (often longpress SELECT)" +msgstr "" + +#: +msgctxt "#32967" +msgid "Kodi Colour Management" +msgstr "Gestion des couleurs de Kodi" + +#: +msgctxt "#32968" +msgid "Kodi Resolution Settings" +msgstr "Réglages de résolution de Kodi" + +#: +msgctxt "#32969" +msgid "Always request all library media items at once" +msgstr "" + +#: +msgctxt "#32970" +msgid "Retrieve all media in library up front instead of fetching it in chunks as the user navigates through the library" +msgstr "" + +#: +msgctxt "#32971" +msgid "Library item-request chunk size" +msgstr "" + +#: +msgctxt "#32972" +msgid "Request this amount of media items per chunk request in library view (+6-30 depending on view mode; less can be less straining for the UI at first, but puts more strain on the server)" +msgstr "" + +#: +msgctxt "#32973" +msgid "Episodes: Skip Post Play screen" +msgstr "" + +#: +msgctxt "#32974" +msgid "When finishing an episode, don't show Post Play but go to the next one immediately.\n" +"Can be disabled/enabled per TV show. Doesn't override enabled binge mode. Overrides the Post Play setting." +msgstr "" + +#: +msgctxt "#32975" +msgid "Delete Season" +msgstr "Supprimer la Saison" + +#: +msgctxt "#32976" +msgid "Adaptive" +msgstr "Adaptatif" + +#: +msgctxt "#32978" +msgid "Enable this if your hardware can handle VC1. Disable it to force transcoding." +msgstr "Activer si votre matériel peut supporter le VC1. Désactiver pour forcer le transcodage." + +#: +msgctxt "#32979" +msgid "Allows the server to only transcode streams of a video that need transcoding, while streaming the others unaltered. If disabled, force the server to transcode everything not direct playable." +msgstr "" + +#: +msgctxt "#32980" +msgid "Refresh Users" +msgstr "Actualiser Utilisateurs" + +#: +msgctxt "#32981" +msgid "Background worker count" +msgstr "Nombre de tâches en arrière-plan" + +#: +msgctxt "#32982" +msgid "Depending on how many cores your CPU has and how much it can handle, increasing this might improve certain situations. If you experience crashes or other annormalities, leave this at its default (3). Needs an addon restart." +msgstr "Selon le nombre de coeurs du CPU et sa capacité de calcul, augmenter cette valeur peut améliorer certaines situations. Si vous expérimentez des crashs ou d'autres anomalies, laissez-le à sa valeur par défaut (3). Nécessite un redémarrage de l'add-on." + +#: +msgctxt "#32985" +msgid "Modern" +msgstr "Moderne" + +#: +msgctxt "#32986" +msgid "Modern (dotted)" +msgstr "Moderne (pointillé)" + +#: +msgctxt "#32987" +msgid "Classic" +msgstr "Classique" + +#: +msgctxt "#32988" +msgid "Custom" +msgstr "Personnalisé" + +#: +msgctxt "#32989" +msgid "Modern (colored)" +msgstr "Moderne (coloré)" + +#: +msgctxt "#32990" +msgid "Handle plex.direct mapping" +msgstr "Gérer le mapping plex.direct" + +#: +msgctxt "#32991" +msgid "Notify" +msgstr "Notifier" + +#: +msgctxt "#32992" +msgid "When using servers with a plex.direct connection (most of them), should we automatically adjust advancedsettings.xml to cope with plex.direct domains? If not, you might want to add plex.direct to your router's DNS rebind exemption list." +msgstr "" + +#: +msgctxt "#32993" +msgid "{} unhandled plex.direct connections found" +msgstr "{} connexions plex.direct non gérées trouvées" + +#: +msgctxt "#32994" +msgid "In order for PM4K to work properly, we need to add special handling for plex.direct connections. We've found {} new unhandled connections. Do you want us to write those to Kodi's advancedsettings.xml automatically? If not, you might want to add plex.direct to your router's DNS rebind exemption list. This can be changed in the settings as well." +msgstr "" + +#: +msgctxt "#32995" +msgid "Advancedsettings.xml modified (plex.direct mappings)" +msgstr "Advancedsettings.xml modifié (mappages plex.direct)" + +#: +msgctxt "#32996" +msgid "The advancedsettings.xml file has been modified. Please restart Kodi for the changes to apply." +msgstr "Le fichier advancedsetings.xml a été modifié. Veuillez redémarrer Kodi pour appliquer les modifications." + +#: +msgctxt "#32997" +msgid "OK" +msgstr "OK" + +#: +msgctxt "#32998" +msgid "Use new Continue Watching hub on Home" +msgstr "Utiliser le nouveau hub \"Continuer à regarder\" sur l'écran d'accueil" + +#: +msgctxt "#32999" +msgid "Instead of separating Continue Watching and On Deck hubs, behave like the modern Plex clients, which combine those two types of hubs into one Continue Watching hub." +msgstr "" + +#: +msgctxt "#33000" +msgid "Enable path mapping" +msgstr "Activer le mappage de chemins." + +#: +msgctxt "#33002" +msgid "Verify mapped files exist" +msgstr "Vérifier que les fichiers mappés existent" + +#: +msgctxt "#33003" +msgid "When path mapping is enabled and we've successfully mapped a file, verify its existence." +msgstr "" + +#: +msgctxt "#33004" +msgid "No spoilers without OSD" +msgstr "" + +#: +msgctxt "#33005" +msgid "When seeking without the OSD open, hide all time-related information from the user." +msgstr "" + +#: +msgctxt "#33006" +msgid "No TV spoilers" +msgstr "" + +#: +msgctxt "#33008" +msgid "[Spoilers removed]" +msgstr "" + +#: +msgctxt "#33013" +msgid "When the above is anything but \"off\", hide episode titles as well." +msgstr "" + +#: +msgctxt "#33014" +msgid "Ignore plex.direct docker hosts" +msgstr "" + +#: +msgctxt "#33015" +msgid "When checking for plex.direct host mapping, ignore local Docker IPv4 addresses (172.16.0.0/12)." +msgstr "Lors de la vérification du mapping des hôtes plex.direct, ignorez les adresses IPv4 locales Docker (172.16.0.0/12)" + +#: +msgctxt "#32303" +msgid "Season {}" +msgstr "Saison {}" + +#: +msgctxt "#32304" +msgid "Episode {}" +msgstr "Épisode {}" + +#: +msgctxt "#32310" +msgid "S{}" +msgstr "S{}" + +#: +msgctxt "#32311" +msgid "E{}" +msgstr "E{}" + +#: +msgctxt "#32938" +msgid "Only for Episodes/Playlists" +msgstr "" + +#: +msgctxt "#33018" +msgid "Cache Plex Home users" +msgstr "" + +#: +msgctxt "#33019" +msgid "Visit media item" +msgstr "" + +#: +msgctxt "#33020" +msgid "Play" +msgstr "" + +#: +msgctxt "#33021" +msgid "Choose action" +msgstr "" + +#: +msgctxt "#33026" +msgid "Map path: {}" +msgstr "" + +#: +msgctxt "#33027" +msgid "Remove mapping: {}" +msgstr "" + +#: +msgctxt "#33028" +msgid "Hide library" +msgstr "Masquer la bibliothèque" + +#: +msgctxt "#33029" +msgid "Show library: {}" +msgstr "Afficher la bibliothèque : {}" + +#: +msgctxt "#33030" +msgid "Choose action for: {}" +msgstr "Choisir l'action pour : {}" + +#: +msgctxt "#33031" +msgid "Select Kodi source for {}" +msgstr "Sélectionner la source de Kodi pour {}" + +#: +msgctxt "#33032" +msgid "Show path mapping indicators" +msgstr "Afficher les indicateurs de mappage chemins" + +#: +msgctxt "#33033" +msgid "When path mapping is active for a library, display an indicator." +msgstr "Lorsque le mappage d'un chemin est actif pour une bibliothèque, afficher un indicateur." + +#: +msgctxt "#33035" +msgid "Delete {}: {}?" +msgstr "Supprimer {} : {}?" + +#: +msgctxt "#33036" +msgid "Delete episode S{0:02d}E{1:02d} from {2}?" +msgstr "Supprimer épisode S{0:02d}E{1:02d} de {2}?" + +#: +msgctxt "#33037" +msgid "Maximum intro offset to consider" +msgstr "Offset maximum de l'intro à considérer" + +#: +msgctxt "#33039" +msgid "Move" +msgstr "Déplacer" + +#: +msgctxt "#33040" +msgid "Reset library order" +msgstr "Réinitialiser l'ordre de la bibliothèque" + +#: +msgctxt "#33034" +msgid "Library settings" +msgstr "Réglages de la bibliothèque" + +#: +msgctxt "#32357" +msgid "By Title" +msgstr "Par titre" + +#: +msgctxt "#32358" +msgid "Title" +msgstr "Titre" + +#: +msgctxt "#33041" +msgid "Show hub: {}" +msgstr "" + +#: +msgctxt "#33042" +msgid "Episode Date Added" +msgstr "" + +#: +msgctxt "#33001" +msgid "Long-press (or context menu) on a library item or: Honor path_mapping.json in the addon_data/script.plexmod folder when DirectPlaying media. This can be used to stream using other techniques such as SMB/NFS/etc. instead of the default HTTP handler. path_mapping.example.json is included in the addon's main directory." +msgstr "" + +#: +msgctxt "#33043" +msgid "Hubs round-robin" +msgstr "" + +#: +msgctxt "#33045" +msgid "Behave like official Plex clients" +msgstr "" + +#: +msgctxt "#32025" +msgid "Direct Play" +msgstr "" + +#: +msgctxt "#32026" +msgid "Direct Stream" +msgstr "" + +#: +msgctxt "#32036" +msgid "4K" +msgstr "4K" + +#: +msgctxt "#32037" +msgid "HEVC (h265)" +msgstr "HEVC (h265)" + +#: +msgctxt "#32601" +msgid "AV1" +msgstr "AV1" + +#: +msgctxt "#32932" +msgid "Subtitle quick-actions" +msgstr "" + +#: +msgctxt "#32933" +msgid "FFWD/RWD" +msgstr "Avance rapide / Retour rapide" + +#: +msgctxt "#32934" +msgid "Repeat" +msgstr "Répéter" + +#: +msgctxt "#32935" +msgid "Shuffle" +msgstr "Lecture aléatoire" + +#: +msgctxt "#32939" +msgid "User-specific.\n" +"Only applies to video player UI" +msgstr "" + +#: +msgctxt "#32977" +msgid "VC1" +msgstr "" + +#: +msgctxt "#32983" +msgid "Theme" +msgstr "" + +#: +msgctxt "#32984" +msgid "Sets the theme. Currently only customizes all control buttons. ATTENTION: [I]Might[/I] need an addon restart.\n" +"In order to customize this, copy one of the xml's in script.plexmod/resources/skins/Main/1080i/templates to addon_data/script.plexmod/templates/{templatename}_custom.xml and adjust it to your liking, then select \"Custom\" as your theme." +msgstr "" + +#: +msgctxt "#33011" +msgid "In progress" +msgstr "En cours" + +#: +msgctxt "#33016" +msgid "Allow TV spoilers for" +msgstr "" + +#: +msgctxt "#33017" +msgid "Overrides the above for specific genres. Default: Reality, Game Show, Documentary, Sport" +msgstr "" + +#: +msgctxt "#33024" +msgid "Hide background in modern indicators" +msgstr "" + +#: +msgctxt "#33044" +msgid "Allow round-robining in hubs. Attention: Loads a lot of items. Depending on the hub size, this can lead to crashes. Current limit: {}; can be adjusted in addon settings." +msgstr "" + +#: +msgctxt "#33046" +msgid "When pressing up/down while the OSD isn't shown, show OSD instead of skipping chapters. Additionally, when pressing down while the OSD is shown, open the chapters (if available)." +msgstr "" + +#: +msgctxt "#33047" +msgid "Hubs round-robin item limit" +msgstr "" + +#: +msgctxt "#33048" +msgid "When hubs round-robin is enabled, only round-robin until this item limit. If the hub size exceeds this limit, the remaining items will be lazy loaded as usual. Tested minimum safe value on NVIDIA SHIELD 2019 is 1000. Default: 250" +msgstr "" + +#: +msgctxt "#33049" +msgid "Max retries" +msgstr "" + +#: +msgctxt "#33051" +msgid "Use CA certificate bundle" +msgstr "" + +#: +msgctxt "#33053" +msgid "System" +msgstr "" + +#: +msgctxt "#33055" +msgid "Custom" +msgstr "" + +#: +msgctxt "#33056" +msgid "None" +msgstr "Aucun" + +#: +msgctxt "#33057" +msgid "Show buttons" +msgstr "" + +#: +msgctxt "#33058" +msgid "Playback features" +msgstr "" + +#: +msgctxt "#33059" +msgid "Additional codecs" +msgstr "Codecs additionnels" + +#: +msgctxt "#33060" +msgid "{feature_ds}: {desc_ds}\n" +"{feature_4k}: {desc_4k}" +msgstr "" + +#: +msgctxt "#33061" +msgid "Enable certain codecs if your hardware supports them. Disable them to force transcoding." +msgstr "" + +#: +msgctxt "#33062" +msgid "Compiling templates" +msgstr "" + +#: +msgctxt "#33063" +msgid "Looking for custom templates" +msgstr "" + +#: +msgctxt "#33064" +msgid "Rendering: {}" +msgstr "" + +#: +msgctxt "#33065" +msgid "Complete" +msgstr "" + +#: +msgctxt "#33066" +msgid "Cache template files" +msgstr "" + +#: +msgctxt "#33067" +msgid "Doesn't throw away the template source files after compiling them. Uses slightly more memory but increases the speed of theme-related changes." +msgstr "" + +#: +msgctxt "#33068" +msgid "Always compile templates" +msgstr "" + +#: +msgctxt "#33069" +msgid "Recompiles all templates on every startup. Useful for template/theme development." +msgstr "" + +#: +msgctxt "#33070" +msgid "Action on Wake event" +msgstr "" + +#: +msgctxt "#33071" +msgid "Restart PM4K" +msgstr "Redémarrer PM4K" + +#: +msgctxt "#33072" +msgid "Wait for {}s" +msgstr "Attendre {} s" + +#: +msgctxt "#33073" +msgid "Wait after wakeup" +msgstr "" + +#: +msgctxt "#33074" +msgid "Waiting {} second(s)" +msgstr "Attendre {} seconde(s)" + +#: +msgctxt "#33076" +msgid "Modern (2024)" +msgstr "" + +#: +msgctxt "#33077" +msgid "Scale modern indicators" +msgstr "" + +#: +msgctxt "#33078" +msgid "Scale the modern indicators based on the poster size used. Default: On (tiny: 0.75, small: 1.0, medium: 1.175, big: 1.3)" +msgstr "" + +#: +msgctxt "#33079" +msgid "Hi-Res Music" +msgstr "" + +#: +msgctxt "#33080" +msgid "Allow DirectPlay of high resolution music (e.g. FLAC, >= 192 kHz)" +msgstr "Autoriser Direct Play sur la musique haute qualité (i.e. FLAC, >= 192 kHz)" + +#: +msgctxt "#33081" +msgid "Blur chapter images" +msgstr "Flouter les images de chapitres" + +#: +msgctxt "#33082" +msgid "Scan Library Files" +msgstr "Scanner les fichiers de la bibliothèque" + +#: +msgctxt "#33083" +msgid "Empty Trash" +msgstr "Vider la corbeille" + +#: +msgctxt "#33084" +msgid "Analyze" +msgstr "Analyser" + +#: +msgctxt "#33085" +msgid "Map key to home" +msgstr "" + +#: +msgctxt "#33086" +msgid "Press the key you want to map to go to home within {} seconds" +msgstr "" + +#: +msgctxt "#33620" +msgid "Plex server connect timeout" +msgstr "" + +#: +msgctxt "#33621" +msgid "Sets the maximum amount of time to connect to a Plex Server in seconds. Default: 5" +msgstr "" + +#: +msgctxt "#32940" +msgid "Video Player" +msgstr "Lecteur Vidéo" + +#: +msgctxt "#33050" +msgid "The maximum number of retries each connection should attempt. The default (3) helps with hibernation/wakeup issues. Set higher for bad connections, setting this to 0 was the old default before 0.7.9" +msgstr "" + +#: +msgctxt "#33075" +msgid "Do something after waking up. Waiting for a time until resuming normal execution helps with networks coming back up after sleep events. Default: 1 second (5 seconds, CoreELEC)." +msgstr "" + +#: +msgctxt "#33087" +msgid "Bind a key to immediately go to the Home view. Cancel using BACK/PREVIOUS_MENU. Clear bound key using the STOP or CONTEXT_MENU (long press OK/Enter) button on the setting." +msgstr "" + +#: +msgctxt "#33088" +msgid "Only applies to video player UI" +msgstr "" + +#: +msgctxt "#33089" +msgid "Automatic seek-back" +msgstr "" + +#: +msgctxt "#33090" +msgid "If your audio doesn't resume as fast as the video, or you want to catch up after a longer pause, use this to seek back after resuming from pause to compensate for the delay. Default: Off" +msgstr "" + +#: +msgctxt "#33091" +msgid "{sec_or_ms} {unit_s_or_ms}" +msgstr "" + +#: +msgctxt "#33092" +msgid "Seek back on pause" +msgstr "" + +#: +msgctxt "#33093" +msgid "Seek back after" +msgstr "" + +#: +msgctxt "#33094" +msgid "Only seek back after having paused at least a certain amount of seconds" +msgstr "" + +#: +msgctxt "#33095" +msgid "Seek back on pause instead of on resume. When Transcoding you should enable this." +msgstr "Recule la position de lecture à la mise en pause, plutôt qu'en reprenant la lecture. Activez ceci en cas de transcodage." + +#: +msgctxt "#33096" +msgid "Only with Direct Play" +msgstr "Seulement en Direct Play" + +#: +msgctxt "#33097" +msgid "Enable seek back ony when we're Direct Playing, not Transcoding." +msgstr "Reculer la position de lecture seulement en Direct Play, pas en transcodage." + +#: +msgctxt "#33098" +msgid "Tickrate (Hz)" +msgstr "Fréquence (Hz)" + +#: +msgctxt "#33099" +msgid "Controls how often certain ticks are performed on GUI windows and the SeekDialog/VideoPlayer and how fast certain events are handled. Can be expensive when bigger than 1 Hz (once per second), can cause quirks when lower than 1 Hz (less than once per second). Depends on the hardware. Default: 1 Hz, Max: 10 Hz (every 100 ms), Sane highest and old default: 10 Hz (every 100 ms)" +msgstr "Contrôle la fréquence de mise à jour de certains éléments d'interface et du lecteur, et la réactivité de certains processus. Peut alourdir le système si supérieur à 1 Hz (une fois par seconde), peut réagir bizarrement si inférieur à 1 Hz (moins d'une fois par seconde). Ça dépend du matériel. Défaut : 1 Hz, Max : 10 Hz (toutes les 100 ms), Maximum raisonnable et ancien Défaut : 10 Hz (toutes les 100 ms)." + +#: +msgctxt "#33636" +msgid "Plex server read timeout" +msgstr "Délai maximum de lecture sur serveur Plex" + +#: +msgctxt "#33637" +msgid "Sets the maximum amount of time to read from a Plex Server in seconds. Default: 10" +msgstr "Définit l'attente maximum pour lire depuis un serveur Plex, en secondes. Défaut : 10" + +#: +msgctxt "#33638" +msgid "Plex.tv connect timeout" +msgstr "Délai maximum de connexion à Plex.tv" + +#: +msgctxt "#33640" +msgid "Plex.tv read timeout" +msgstr "Délai maximum de lecture sur Plex.tv" + +#: +msgctxt "#33642" +msgid "Dump config" +msgstr "Exporter la configuration" + +#: +msgctxt "#33643" +msgid "Dumps all user settings into the log on startup, when DEBUG logging is enabled. Masks private information." +msgstr "Exporte les réglages utilisateur dans le journal au démarrage, si la journalisation DEBUG est active. Masque les infos privées." + +#: +msgctxt "#33644" +msgid "Tracks" +msgstr "Pistes" + +#: +msgctxt "#33645" +msgid "plex.direct: Honor plex.tv's dnsRebindingProtection flag (DNS)" +msgstr "plex.direct : Respecte le marqueur dnsRebindingProtection (DNS) de plex.tv" + +#: +msgctxt "#33646" +msgid "Only handle plex.direct hosts when the server's attributes dnsRebindingProtection=1. Might not work in certain situations. Disable if you have connection issues. Default: On" +msgstr "Gérer les hôtes plex.direct seulement si l'attribut du serveur dnsRebindingProtection=1. Peut échouer dans certains cas. Désactivez si vous avez des problèmes de connexion. Défaut : Activé" + +#: +msgctxt "#33647" +msgid "plex.direct: Honor plex.tv's publicAddressMatches flag (DNS)" +msgstr "plex.direct : Respecte le marqueur publicAddressMatches (DNS) de plex.tv" + +#: +msgctxt "#33648" +msgid "Only handle plex.direct hosts when the server's attributes publicAddressMatches=1. Might not work in certain situations. Disable if you have connection issues. Default: On" +msgstr "Gérer les hôtes plex.direct seulement si l'attribut du serveur publicAddressMatches=1. Peut échouer dans certains cas. Désactivez si vous avez des problèmes de connexion. Défaut : Activé" + +#: +msgctxt "#32101" +msgid "If enabled, when playback ends and there is a 'Next Up' item available, it will be automatically be played after a {} second delay." +msgstr "Si activé, quand la lecture se termine et qu'un média 'Suivant' est disponible, celui-ci sera joué automatiquement après {} secondes." + +#: +msgctxt "#33651" +msgid "Startup delay" +msgstr "Délai au démarrage" + +#: +msgctxt "#33652" +msgid "Never show Post Play" +msgstr "Ne pas afficher Après Lecture" + +#: +msgctxt "#33506" +msgid "Show the intro skip button from the start of a video with an intro marker. The auto-skipping setting applies. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "Affiche un bouton pour sauter l'intro, au début d'une vidéo avec un marqueur d'intro. Le réglage de saut auto s'applique. N'annule pas le mode Binge si actif.\n" +"Réglage propre à chaque série TV." + +#: +msgctxt "#33619" +msgid "Automatically skips episode intros, credits and tries to skip episode recaps. Doesn't skip the intro of the first episode of a season and doesn't skip the final credits of a show.\n" +"\n" +"Can be disabled/enabled per TV show.\n" +"Overrides any setting below." +msgstr "Saute automatiquement les génériques de début et de fin d'épisode, et si possible le résumé des épisodes précédents. Ne saute pas l'intro du premier épisode d'une saison, ni le générique de fin d'une série.\n" +"\n" +"Réglage propre à chaque série TV.\n" +"Écrase les réglages ci-dessous." + +#: +msgctxt "#33052" +msgid "Which CA certificate bundle to use for request HTTPS verification. \"system\" uses the system-provided certificate bundle. \"ACME\" uses an extremely small bundle provided with the addon, which includes root certificates for Letsencrypt (plex.direct) and ZeroSSL. \"custom\" allows for a ca-certificate bundle in userdata/addon_data/script.plexmod/custom_bundle.crt" +msgstr "Quelle Autorité de Certification utiliser pour l'authentification HTTPS. \"système\" utilise les certificats fournis par le système. \"ACME\" utilise un paquet minimal fourni avec l'extension, incluant les certificats de base pour Letsencrypt (plex.direct) et ZeroSSL. \"personnalisé\" va chercher un paquet de certificats dans /addon_data/script.plexmod/custom_bundle.crt" + +#: +msgctxt "#33054" +msgid "ACME (addon-supplied)" +msgstr "ACME (fourni par l'extension)" + +#: +msgctxt "#33639" +msgid "Plex.tv: Sets the maximum amount of time to connect to Plex.tv in seconds. Default: 1" +msgstr "Plex.tv : Délai maximum pour se connecter à Plex.tv, en secondes. Défaut : 1" + +#: +msgctxt "#33641" +msgid "Plex.tv: Sets the maximum amount of time to read from Plex.tv in seconds. Default: 2" +msgstr "Plex.tv : Délai maximum pour lire depuis Plex.tv, en secondes. Défaut : 2" + +#: +msgctxt "#33653" +msgid "Background image resolution scaling %" +msgstr "Pourcentage de mise à l'échelle de l'image de fond" + +#: +msgctxt "#33654" +msgid "In percent, based on 1080p. Scales the resolution of all backgrounds for better image quality. May impact PMS/PM4K performance, will increase the cache usage accordingly. Can lead to crashes if your hardware is low on RAM. Needs addon restart. Use 400% for 4K. ATTENTION: In order for this to work you need to add 2160 and 2160 to your advancedsettings.xml." +msgstr "En pourcentage de 1080p. Change la résolution de tous les arrière-plans pour une meilleure qualité d'image. Peut impacter la performance PMS/PM4K, augmentera le cache si nécessaire. Plantage possible si votre matériel a peu de RAM. Redémarrage de l'extension nécessaire. Utiliser 400% pour du 4K. ATTENTION : Pour que ça fonctionne, il faut ajouter 2160 et 2160 dans advancedsettings.xml." + +#: +msgctxt "#33655" +msgid "Auto-Sync Subtitles" +msgstr "Synchro automatique des sous-titres" + +#: +msgctxt "#33656" +msgid "Only for External SRT subtitles. The PMS setting for voice activity detection has to be enabled for this to work." +msgstr "Seulement pour les sous-titres SRT externes. Le réglage PMS pour la détection d'activité vocale doit être actif pour que ça fonctionne." + +#: in player quick settings UI, needs to be short +msgctxt "#33657" +msgid "Enable Auto-Sync" +msgstr "Activer la synchronisation auto" + +#: in player quick settings UI, needs to be short +msgctxt "#33658" +msgid "Disable Auto-Sync" +msgstr "Désactiver la synchronisation auto" + +#: +msgctxt "#33659" +msgid "Hide hub: {}" +msgstr "Cacher le hub : {}" + +#: +msgctxt "#33660" +msgid "Disable HDR" +msgstr "Désactiver le HDR" + +#: +msgctxt "#33661" +msgid "If you don't want your client to handle HDR (or HDR-fallback), enable this to force transcoding. Doesn't apply to DV Profile 5." +msgstr "Si vous ne voulez pas que votre client gère le HDR (ou le remplacement du HDR), activez ceci pour forcer le transcodage. Ne s'applique pas au Profil DV 5." + +#: +msgctxt "#33662" +msgid "Remove from Continue Watching" +msgstr "Retirer des lectures en cours" + +#: +msgctxt "#33663" +msgid "Home: Confirm item actions" +msgstr "Accueil : Confirmer les actions" + +#: +msgctxt "#33664" +msgid "When acting on items in the Home view, such as mark played, hide from continue watching etc., show a confirmation dialog." +msgstr "En cas d'action sur des médias dans la page d'accueil, comme pour les marquer lus, retirer des lectures en cours etc., affiche un dialogue de confirmation." + +#: +msgctxt "#33665" +msgid "Disable audio codecs" +msgstr "Désactiver les codecs audio" + +#: +msgctxt "#33666" +msgid "Audio codecs you can't play back. Disables Direct Play for such media items, enables Direct Stream if possible, transcodes audio stream to compatible format." +msgstr "Codecs audio que vous ne pouvez pas lire. Désactive Direct Play pour ces médias, active Direct Stream si possible, transcode le flux audio vers un format compatible." + +#: +msgctxt "#32002" +msgid "20 Mbps" +msgstr "20 Mbps" + +#: +msgctxt "#32003" +msgid "12 Mbps" +msgstr "12 Mbps" + +#: +msgctxt "#32004" +msgid "10 Mbps" +msgstr "10 Mbps" + +#: +msgctxt "#32005" +msgid "8 Mbps" +msgstr "8 Mbps" + +#: +msgctxt "#32006" +msgid "4 Mbps" +msgstr "4 Mbps" + +#: +msgctxt "#32007" +msgid "3 Mbps" +msgstr "3 Mbps" + +#: +msgctxt "#32008" +msgid "2 Mbps" +msgstr "2 Mbps" + +#: +msgctxt "#32009" +msgid "1.5 Mbps" +msgstr "1.5 Mbps" + +#: +msgctxt "#33669" +msgid "Really sign out?" +msgstr "Confirmer la déconnexion ?" + +#: +msgctxt "#33670" +msgid "Update available" +msgstr "Mise à jour disponible" + +#: +msgctxt "#33672" +msgid "Check for updates" +msgstr "Vérifier les mises à jour" + +#: +msgctxt "#33673" +msgid "Automatically check for updates periodically. If installed from a Kodi repository and the Update Source setting is set to Repository, Kodi itself will handle the updating of this addon. Needs a Kodi restart when changed." +msgstr "Vérifie automatiquement les mises à jour de temps en temps. Si installé depuis un dépôt Kodi et si la source de mise à jour est réglée sur Dépôt, Kodi gérera seul la mise à jour de cette extension. Nécessite un redémarrage de Kodi." + +#: +msgctxt "#33674" +msgid "Check for updates on start" +msgstr "Vérifier les mises à jour au démarrage" + +#: +msgctxt "#33675" +msgid "Automatically check for updates on startup. Doesn't do much when Update source is Repository. Needs a Kodi restart when changed." +msgstr "Vérifie automatiquement les mises à jour au démarrage. Peu d'effet quand la source de mise à jour est Dépôt. Nécessite un redémarrage de Kodi." + +#: +msgctxt "#33676" +msgid "Update source" +msgstr "Source de mise à jour" + +#: +msgctxt "#33677" +msgid "Specifies the update mode. Will immediately check for a new version when changed and closing settings.\n" +"Default: Repository\n" +"\n" +"Beta: Bleeding edge (possibly unstable)\n" +"Stable: Stable branch (faster than Repository)\n" +"Repository: Kodi repository (official (slow) or Don't Panic)" +msgstr "Précise le mode de mise à jour. Cherchera immédiatement une nouvelle version après changement et fermeture des réglages.\n" +"Défaut : Dépôt\n" +"\n" +"Bêta : Super à jour (peut-être instable)\n" +"Stable : Branche stable (plus rapide que Dépôt)\n" +"Dépôt : Dépôt de Kodi (officiel (lent) ou Don't Panic)" + +#: +msgctxt "#33678" +msgid "Beta" +msgstr "Bêta" + +#: Update mode as in (beta, stable, repository) +msgctxt "#33679" +msgid "Stable" +msgstr "Stable" + +#: +msgctxt "#33680" +msgid "Repository" +msgstr "Dépôt" + +#: +msgctxt "#33683" +msgid "Exit, download and install" +msgstr "Quitter, télécharger et installer" + +#: +msgctxt "#33684" +msgid "Later" +msgstr "Plus tard" + +#: +msgctxt "#32015" +msgid "6 Mbps" +msgstr "6 Mbps" + +#: +msgctxt "#32016" +msgid "16 Mbps" +msgstr "16 Mbps" + +#: +msgctxt "#32017" +msgid "26 Mbps" +msgstr "26 Mbps" + +#: +msgctxt "#33681" +msgid "Service updated" +msgstr "Service mis à jour" + +#: +msgctxt "#33682" +msgid "The update {} has had changes to the updater itself. In order for the updated updater service to work, a Kodi restart is necessary. The addon will work normally, though. Do you still want to run the addon?" +msgstr "La mise à jour {} a aussi modifié le service de mises à jour. Pour appliquer ces changements, un redémarrage de Kodi est nécessaire. D'ici-là, le module fonctionnera normalement. Activer ce module quand même ?" + +#: +msgctxt "#33685" +msgid "Clamp video bitrate" +msgstr "Limiter le débit vidéo" + +#: +msgctxt "#33686" +msgid "Only show transcode bitrate targets lower than the current video's bitrate." +msgstr "Ne montrer que les débits de transcodage inférieurs au débit de la vidéo actuelle." + +#: +msgctxt "#33687" +msgid "Translation updated" +msgstr "Traduction mise à jour" + +#: +msgctxt "#33688" +msgid "The currently in-use translation has been updated. In order to load the new translation, a Kodi restart is necessary. The addon will still run properly, but you might see badly or untranslated strings. Do you still want to run the addon?" +msgstr "La traduction que vous utilisez a été mise à jour. Pour charger la nouvelle traduction, un redémarrage de Kodi est nécessaire. Sinon, le module fonctionnera mais vous verrez peut-être des éléments non traduits. Activer ce module quand même ?" + +#: +msgctxt "#33509" +msgid "Early intro skip threshold (default: < 120s/2m)" +msgstr "Seuil du saut d'intro (défaut : < 120s/2m)" + +#: +msgctxt "#33007" +msgid "Select in which cases to blur TV episode thumbnails, previews, redact summaries, hide episode titles etc." +msgstr "Dans quel cas flouter les miniatures et aperçus d'épisodes TV, censurer leurs résumés, cacher leur titre etc." + +#: +msgctxt "#33009" +msgid "Blur amount for unplayed/in-progress episodes" +msgstr "Niveau de flou pour les épisodes non lus / en cours" + +#: +msgctxt "#33010" +msgid "Unplayed" +msgstr "Non lus" + +#: +msgctxt "#33012" +msgid "No unplayed episode titles" +msgstr "Pas de titres d'épisodes non lus" + +#: +msgctxt "#33022" +msgid "Played indicators" +msgstr "Marques de lecture" + +#: +msgctxt "#33023" +msgid "Classic: Show orange triangle for unplayed items\n" +"Modern: Show green checkmark for played items\n" +"Modern (2024): Show white checkmark for played items\n" +"(default: Modern (2024))" +msgstr "Classique : Triangle orange pour les éléments non lus\n" +"Moderne : Coche verte pour les éléments lus\n" +"Moderne (2024) : Coche blanche pour les éléments lus\n" +"(défaut : Moderne (2024))" + +#: +msgctxt "#33025" +msgid "When the above is enabled, hide the black backdrop of the played state." +msgstr "Quand l'option ci-dessus est active, cacher le fond noir de l'état lu." + +#: +msgctxt "#33689" +msgid "Service running" +msgstr "Service en cours d'exécution" + +#: +msgctxt "#33690" +msgid "Last update check" +msgstr "Mise à jour vérifiée le" + +#: +msgctxt "#33691" +msgid "Native languages" +msgstr "Langues maternelles" + +#: +msgctxt "#33692" +msgid "When you usually watch things in a different language with subtitles, but are a native speaker of other languages, which you don't need subtitles for, prevent Plex from auto-selecting subtitles for those languages." +msgstr "Si vous regardez des médias en langue étrangère avec sous-titres, mais parlez nativement d'autres langues sans besoin de sous-titres, ceci empêche Plex d'activer automatiquement les sous-titres pour ces langues." + +#: +msgctxt "#33693" +msgid "Download subtitles using" +msgstr "Télécharger les sous-titres avec" + +#: +msgctxt "#33694" +msgid "Ask" +msgstr "Demander" + +#: +msgctxt "#33695" +msgid "Where do you want to download subtitles from? Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "Quelle doit être la source des sous-titres ? NB : Valable seulement pour les actions rapides de sous-titres du lecteur. Le téléchargement de sous-titres dans les options d'un média utilise toujours Plex comme source." + +#: +msgctxt "#33696" +msgid "No subtitles found." +msgstr "Pas de sous-titres." + +#: things in curly brackets are variables, don't translate them! +msgctxt "#33697" +msgid "{provider_title}, Score: {subtitle_score}{subtitle_info}" +msgstr "{provider_title}, Score : {subtitle_score}{subtitle_info}" + +#: hearing impaired subtitle flag +msgctxt "#33698" +msgid "HI" +msgstr "SME" + +#: forced subtitle flag +msgctxt "#33699" +msgid "forced" +msgstr "forcés" + +#: +msgctxt "#33700" +msgid "Download subtitles: {}" +msgstr "Télécharger des sous-titres : {}" + +#: +msgctxt "#33701" +msgid "Fallback to Kodi" +msgstr "Tenter avec Kodi" + +#: +msgctxt "#33702" +msgid "When no subtitles are found via the Plex subtitle search, fall back to Kodi subtitle search. Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "Quand la recherche Plex ne trouve pas de sous-titres, utiliser la recherche Kodi. NB : Valable seulement pour les actions rapides de sous-titres du lecteur. Le téléchargement de sous-titres dans les options d'un média utilise toujours Plex comme source." + +#: +msgctxt "#33703" +msgid "Download subtitles" +msgstr "Télécharger des sous-titres" + +#: +msgctxt "#33704" +msgid "Using which service?" +msgstr "Avec quel service ?" + +#: +msgctxt "#33705" +msgid "Hide ratings" +msgstr "Cacher les notes" + +#: +msgctxt "#33706" +msgid "Blur images" +msgstr "Flouter les images" + +#: +msgctxt "#33707" +msgid "Blur images on home (in progress)" +msgstr "Flouter les images sur l'accueil (en cours)" + +#: +msgctxt "#33708" +msgid "Hide summaries" +msgstr "Cacher les résumés" + +#: +msgctxt "#33709" +msgid "Show ratings for" +msgstr "Montrer les notes pour" + +#: +msgctxt "#33710" +msgid "Show reviews for" +msgstr "Montrer les avis pour" + +#: +msgctxt "#33711" +msgid "Always resume media" +msgstr "Toujours reprendre la lecture" + +#: +msgctxt "#33712" +msgid "When playback of an in-progress media is requested, resume it by default instead of asking whether to resume or start from the beginning." +msgstr "Au lancement d'un média partiellement lu, reprendre par défaut la lecture au lieu de demander s'il faut reprendre ou recommencer du début." + +#: +msgctxt "#33713" +msgid "Home: Resume in-progress items" +msgstr "Accueil : Reprendre les médias en cours" + +#: +msgctxt "#33714" +msgid "Resume in-progress items directly instead of visiting the media." +msgstr "Reprendre la lecture des médias en cours au lieu d'afficher leurs infos." + +#: +msgctxt "#33715" +msgid "OSD hide-delay" +msgstr "Délai OSD" + +#: +msgctxt "#33716" +msgid "How long to wait until hiding the OSD. Only works with Plextuary skin or others with configurable OSD timeout. Default: 4s (+/- 1s)" +msgstr "Délai avant de cacher l'OSD (affichage sur écran). Ne fonctionne qu'avec la skin Plextuary ou d'autres ayant ce réglage de durée de l'OSD. Défaut : 4s (+/- 1s)" + +#: +msgctxt "#33717" +msgid "Debug requests" +msgstr "Demandes de déboguage" + +#: +msgctxt "#33718" +msgid "Played" +msgstr "Joué" + +#: +msgctxt "#33719" +msgid "Refresh metadata" +msgstr "Actualiser les métadonnées" + +#: +msgctxt "#33038" +msgid "When encountering an intro marker with a start time offset greater than this, ignore it (default: 1400s/23m)" +msgstr "" + +#: +msgctxt "#33649" +msgid "\"Use alternate seek\" wait for seek" +msgstr "" + +#: +msgctxt "#33650" +msgid "This adjusts the delay between seek-tries on problematic platforms when \"Use alternate seek\" is enabled, which fixes resume not always working or double-seeking. When you have resume/double-seek issues, increase this. Default: 500ms" +msgstr "" + +#: +msgctxt "#33667" +msgid "Use alternate seek" +msgstr "" + +#: +msgctxt "#33668" +msgid "ATTENTION: Only enable this if you have reproducible audio issues after seeking/resuming.\n" +"\n" +"Use an alternative seek method in videos, which can help in problematic scenarios; brings its own issues/quirks. Disabled by default (enabled by default for CoreELEC and LG WebOS)" +msgstr "" + +#: +msgctxt "#33720" +msgid "Clear all caches" +msgstr "" + +#: +msgctxt "#33721" +msgid "Clear library cache (not items)" +msgstr "" + +#: +msgctxt "#33722" +msgid "Libraries" +msgstr "" + +#: +msgctxt "#33723" +msgid "Media Items" +msgstr "" + +#: +msgctxt "#33724" +msgid "Cache Plex data for" +msgstr "" + +#: +msgctxt "#33725" +msgid "Persist cached Plex data" +msgstr "" + +#: +msgctxt "#33726" +msgid "Instead of clearing the cache when exiting the addon, persist it instead. Warning: You'll most likely encounter missing items in libraries or outdated data. Use the corresponding menu functionalities to clear the cache for specific items or libraries." +msgstr "" + +#: +msgctxt "#33727" +msgid "Store Plex server responses for items and library views in a local SQLite database. Doesn't cache anything else (Home/Hubs are always up to date). Massively speeds up consecutive visits to items and libraries. Certain important events, such as watch state changes, automatically delete the item cache and its corresponding library cache. (Default: Off)" +msgstr "" + +#: +msgctxt "#33728" +msgid "Clear cache for item" +msgstr "" + +#: +msgctxt "#33729" +msgid "Expire cached Plex data after (hours)" +msgstr "" + +#: +msgctxt "#33730" +msgid "Randomly" +msgstr "" + +#: +msgctxt "#33731" +msgid "By Bitrate" +msgstr "" + +#: +msgctxt "#33732" +msgid "Bitrate" +msgstr "" + +#: +msgctxt "#33733" +msgid "Transcode target codec" +msgstr "" + +#: +msgctxt "#33734" +msgid "Sets the target codec when transcoding/direct streaming. Overridden when \"Transcode audio to AC3\" is set." +msgstr "" + +#: +msgctxt "#33735" +msgid "Always auto-start" +msgstr "" + +#: +msgctxt "#33736" +msgid "By default PM4K won't auto-start when the current window isn't a Home-derived window (e.g.: Settings). Enable this if you always want to auto-start PM4K regardless of the current window. Can also improve start-up time." +msgstr "" + +#: +msgctxt "#33737" +msgid "Loop theme music" +msgstr "" + +#: +msgctxt "#33671" +msgid "Current: {current_version}\n" +"New: {new_version}\n" +"\n" +"Changelog:\n" +"{changelog}" +msgstr "" + +#: +msgctxt "#33738" +msgid "Max playlist size" +msgstr "" + +#: +msgctxt "#33739" +msgid "How many items to load into a Kodi playlist before chunking them remotely using Plex PlayQueues (default: 500); Reduce on memory issues/crashes." +msgstr "" + +#: +msgctxt "#33740" +msgid "Home: Episodes season thumbnails" +msgstr "" + +#: +msgctxt "#33741" +msgid "Use season thumbnails/posters when displaying episodes in hubs instead of the TV show's." +msgstr "" + +#: +msgctxt "#34000" +msgid "Watchlist" +msgstr "" + +#: +msgctxt "#34001" +msgid "Released" +msgstr "" + +#: +msgctxt "#34002" +msgid "Movies & Shows" +msgstr "" + +#: e.g.: 8 seasons +msgctxt "#34003" +msgid "{} seasons" +msgstr "" + +#: +msgctxt "#34004" +msgid "Choose server" +msgstr "" + +#: +msgctxt "#34005" +msgid "Availability" +msgstr "" + +#: e.g.: 1 season +msgctxt "#34006" +msgid "{} season" +msgstr "" + +#: +msgctxt "#34007" +msgid "Use Watchlist" +msgstr "" + +#: +msgctxt "#34008" +msgid "Activates the current user's Plex watchlist as a section item. Adds watchlist functionality to certain media screens. Per-user setting. Default: On" +msgstr "" + +#: +msgctxt "#34009" +msgid "Watchlist auto-remove" +msgstr "" + +#: +msgctxt "#34010" +msgid "Automatically remove fully watched items from watchlist. Default: On" +msgstr "" + +#: +msgctxt "#34011" +msgid "Remove from watchlist" +msgstr "" + +#: +msgctxt "#34012" +msgid "Fast pause/resume" +msgstr "" + +#: +msgctxt "#34013" +msgid "when paused" +msgstr "" + +#: +msgctxt "#34014" +msgid "when playing" +msgstr "" + +#: +msgctxt "#34015" +msgid "User-specific. Use OK/ENTER button to pause instead of showing the OSD (which can then only be accessed using DOWN), or resume when paused. Only works with 'Behave like official Plex clients' enabled." +msgstr "" + +#: +msgctxt "#34016" +msgid "Max shutdown wait" +msgstr "" + +#: +msgctxt "#34017" +msgid "The plugin will try to hard exit once this time limit is reached (default: 5 seconds)" +msgstr "" + +#: +msgctxt "#34018" +msgid "Related Media" +msgstr "" + +#: watchlist discover hub title prefix +msgctxt "#34019" +msgid "Discover: {}" +msgstr "" + +#: +msgctxt "#34020" +msgid "Loading external hubs" +msgstr "" + +#: +msgctxt "#34021" +msgid "Please wait ..." +msgstr "" + +#: +msgctxt "#34022" +msgid "Video play completion behaviour" +msgstr "" + +#: +msgctxt "#34023" +msgid "Decide whether to use end credits markers to determine the 'watched' state of video items. When markers are not available the selected threshold percentage will be used." +msgstr "" + +#: +msgctxt "#34024" +msgid "at selected threshold percentage" +msgstr "" + +#: +msgctxt "#34025" +msgid "at final credits marker position" +msgstr "" + +#: +msgctxt "#34026" +msgid "at first credits marker position" +msgstr "" + +#: +msgctxt "#34027" +msgid "earliest between threshold percent and first credits marker" +msgstr "" + +#: +msgctxt "#32973" +msgid "Episodes: Continuous playback" +msgstr "" + +#: +msgctxt "#34028" +msgid "\"Use alternate seek\" valid seek window" +msgstr "" + +#: +msgctxt "#34029" +msgid "When \"Use alternate seek\" is enabled, any seek amount outside of this threshold window will be applied, otherwise the seek attempt will be ignored. If you encounter any seek issues, play with this value (ms, default: 2000)" +msgstr "" + +#: +msgctxt "#34030" +msgid "Unlock Direct Play for above 4K" +msgstr "" + +#: +msgctxt "#34031" +msgid "Producer" +msgstr "" + +#: +msgctxt "#34032" +msgid "Audio Language" +msgstr "" + +#: +msgctxt "#34033" +msgid "Subtitle Language" +msgstr "" + +#: +msgctxt "#34034" +msgid "Folder Location" +msgstr "" + +#: +msgctxt "#34035" +msgid "Editions" +msgstr "" + +#: +msgctxt "#34036" +msgid "DOVI" +msgstr "" + +#: +msgctxt "#34037" +msgid "HDR" +msgstr "" + +#: +msgctxt "#34038" +msgid "Force plex.direct mapping" +msgstr "" + +#: +msgctxt "#34039" +msgid "If you (still) don't see any posters: Under certain circumstances when Kodi still can't resolve hostnames when loading assets, even though the host resolves natively, enable this to force plex.direct mapping via advancedsettings.xml." +msgstr "" + +#: +msgctxt "#34040" +msgid "By Progress" +msgstr "" + +#: +msgctxt "#34041" +msgid "Progress" +msgstr "" + +#: +msgctxt "#34042" +msgid "By Album" +msgstr "" + +#: +msgctxt "#34043" +msgid "Album" msgstr "" diff --git a/script.plexmod/resources/language/resource.language.hu_hu/strings.po b/script.plexmod/resources/language/resource.language.hu_hu/strings.po index 18410177c1..7a76d72dc9 100644 --- a/script.plexmod/resources/language/resource.language.hu_hu/strings.po +++ b/script.plexmod/resources/language/resource.language.hu_hu/strings.po @@ -1,928 +1,3570 @@ -# XBMC Media Center language file msgid "" msgstr "" -"Project-Id-Version: XBMC-Addons\n" -"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n" -"POT-Creation-Date: 2013-12-12 22:56+0000\n" -"PO-Revision-Date: 2017-04-21 19:23+0100\n" -"Last-Translator: Norbert Suto \n" -"Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"X-Generator: POEditor.com\n" +"Project-Id-Version: PM4K / PlexMod for Kodi\n" "Language: hu\n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" +#: msgctxt "#32000" msgid "Main" msgstr "Főoldal" +#: msgctxt "#32001" msgid "Original" msgstr "Eredeti" -msgctxt "#32002" -msgid "20 Mbps 1080p" -msgstr "20 Mbps 1080p" - -msgctxt "#32003" -msgid "12 Mbps 1080p" -msgstr "12 Mbps 1080p" - -msgctxt "#32004" -msgid "10 Mbps 1080p" -msgstr "10 Mbps 1080p" - -msgctxt "#32005" -msgid "8 Mbps 1080p" -msgstr "8 Mbps 1080p" - -msgctxt "#32006" -msgid "4 Mbps 720p" -msgstr "4 Mbps 720p" - -msgctxt "#32007" -msgid "3 Mbps 720p" -msgstr "3 Mbps 720p" - -msgctxt "#32008" -msgid "2 Mbps 720p" -msgstr "2 Mbps 720p" - -msgctxt "#32009" -msgid "1.5 Mbps 480p" -msgstr "1.5 Mbps 480p" - +#: msgctxt "#32010" msgid "720 kbps" msgstr "720 kbps" +#: msgctxt "#32011" msgid "320 kbps" msgstr "320 kbps" +#: msgctxt "#32012" msgid "208 kbps" msgstr "208 kbps" +#: msgctxt "#32013" msgid "96 kbps" msgstr "96 kbps" +#: msgctxt "#32014" msgid "64 kbps" msgstr "64 kbps" +#: msgctxt "#32020" msgid "Local Quality" msgstr "Helyi minőség" +#: msgctxt "#32021" msgid "Remote Quality" msgstr "Távoli minőség" +#: msgctxt "#32022" msgid "Online Quality" msgstr "Online minőség" +#: msgctxt "#32023" msgid "Transcode Format" msgstr "Átkódolási formátum" +#: msgctxt "#32024" msgid "Debug Logging" msgstr "Hibakeresési naplózás" -msgctxt "#32025" -msgid "Allow Direct Play" -msgstr "Közvetlen lejátszás engedélyezése" - -msgctxt "#32026" -msgid "Allow Direct Stream" -msgstr "Közvetlen adatfolyam engedélyezése" - +#: msgctxt "#32027" msgid "Force" msgstr "Kényszerítés" +#: msgctxt "#32028" msgid "Always" msgstr "Mindig" +#: msgctxt "#32029" msgid "Only Image Formats" msgstr "Csak képformátumok esetében" +#: msgctxt "#32030" msgid "Auto" msgstr "Automatikus" -msgctxt "#32031" -msgid "Burn Subtitles (Direct Play Only)" -msgstr "Feliratok égetése (Csak közvetlen lejátszás esetén)" - +#: msgctxt "#32032" msgid "Allow Insecure Connections" msgstr "Nem biztonságos kapcsolatok engedélyezése" +#: msgctxt "#32033" msgid "Never" msgstr "Soha" +#: msgctxt "#32034" msgid "On Same network" msgstr "Ugyanazon a hálózaton" +#: msgctxt "#32035" msgid "Always" msgstr "Mindig" -msgctxt "#32036" -msgid "Allow 4K" -msgstr "4K engedélyezése" - -msgctxt "#32037" -msgid "Allow HEVC (h265)" -msgstr "HEVC (h265) engedélyezése" - +#: msgctxt "#32038" msgid "Automatically Sign In" msgstr "Automatikus bejelentkezés" +#: msgctxt "#32039" msgid "Post Play Auto Play" -msgstr "Post Play Auto Play" +msgstr "Folytatás automatikus lejátszással" +#: msgctxt "#32040" msgid "Enable Subtitle Downloading" -msgstr "Felirat letöltés engedélyezése" +msgstr "Felirat letöltésének engedélyezése" +#: msgctxt "#32041" msgid "Enable Subtitle Downloading" -msgstr "Felirat letöltés engedélyezése" +msgstr "Felirat letöltésének engedélyezése" +#: msgctxt "#32042" msgid "Server Discovery (GDM)" -msgstr "Szerver felderítés" +msgstr "Szerver felderítése (GDM)" +#: msgctxt "#32043" msgid "Start Plex On Kodi Startup" msgstr "Indítsa el a Plexet a Kodi indításakor" +#: msgctxt "#32044" msgid "Connection 1 IP" msgstr "1. Kapcsolat IP" +#: msgctxt "#32045" msgid "Connection 1 Port" msgstr "1. Kapcsolat Port" +#: msgctxt "#32046" msgid "Connection 2 IP" msgstr "2. Kapcsolat IP" +#: msgctxt "#32047" msgid "Connection 2 Port" msgstr "2. Kapcsolat Port" +#: msgctxt "#32048" msgid "Audio" -msgstr "Audió" +msgstr "Hang" +#: msgctxt "#32049" msgid "Advanced" -msgstr "Fejlett beállítások" +msgstr "Haladó beállítások" +#: msgctxt "#32050" msgid "Manual Servers" -msgstr "Kézi szerverek" +msgstr "Manuális szerverek" +#: msgctxt "#32051" msgid "Privacy" msgstr "Adatvédelem" +#: msgctxt "#32052" msgid "About" msgstr "Rólunk" +#: msgctxt "#32053" msgid "Video" msgstr "Videó" +#: msgctxt "#32054" msgid "Addon Version" -msgstr "Addon verzió" +msgstr "Kiegészítő verzió" +#: msgctxt "#32055" msgid "Kodi Version" msgstr "Kodi verzió" +#: msgctxt "#32056" msgid "Screen Resolution" -msgstr "Képernyőfelbontás" +msgstr "Képernyő felbontása" +#: msgctxt "#32057" msgid "Current Server Version" msgstr "Jelenlegi szerver verzió" +#: +msgctxt "#32058" +msgid "Never exceed original audio codec" +msgstr "Soha ne lépje túl az eredeti hangkodeket" +#: +msgctxt "#32059" +msgid "When transcoding audio, never exceed the original audio bitrate or channel count on the same codec." +msgstr "Hang átkódolásakor soha ne lépje túl az eredeti hang bitrátáját vagy a csatornaszámot ugyanazon a kodeken." + +#: +msgctxt "#32060" +msgid "Use Kodi audio channels" +msgstr "Kodi hangcsatornák használata" + +#: +msgctxt "#32064" +msgid "Treat DTS like AC3" +msgstr "Kezelje a DTS-t úgy, mint az AC3-t" + +#: msgctxt "#32100" msgid "Skip user selection and pin entry on startup." -msgstr "Felhasználó kiválasztás és a PIN kód megadás átugrása indításkor." - -msgctxt "#32101" -msgid "If enabled, when playback ends and there is a 'Next Up' item available, it will be automatically be played after a 15 second delay." -msgstr "Ha engedélyezi, akkor a lejátszás befejezése után, amennyiben rendelkezésre áll egy \"következő\" elem, az 15 másodperces késleltetés után automatikusan lejátszásra kerül." +msgstr "Felhasználó kiválasztása és a PIN-kód megadás átugrása indításkor." +#: msgctxt "#32102" msgid "Enable this if your hardware can handle 4K playback. Disable it to force transcoding." msgstr "Engedélyezze ezt, ha a hardver képes a 4K lejátszásra. Kapcsolja ki, amennyiben kényszeríteni szeretné az átkódolást." +#: msgctxt "#32103" msgid "Enable this if your hardware can handle HEVC/h265. Disable it to force transcoding." msgstr "Engedélyezze ezt, ha a hardver képes a HEVC/h265 lejátszásra. Kapcsolja ki, amennyiben kényszeríteni szeretné az átkódolást." +#: msgctxt "#32104" msgid "When to connect to servers with no secure connections.[CR][CR]* [B]Never[/B]: Never connect to a server insecurely[CR]* [B]On Same Network[/B]: Allow if on the same network[CR]* [B]Always[/B]: Allow same network and remote connections" msgstr "Mikor kapcsolódhat nem biztonságosan a szerverekhez.[CR][CR]* [B]Soha[/B]: Soha ne kapcsolódjon[CR]* [B]Azonos hálózat[/B]: Azonos hálózat esetén engedélyezze[CR]* [B]Mindig[/B]: Engedélyezze helyi és távoli kapcsolat esetén is" - +#: msgctxt "#32201" msgid "Trailer" msgstr "Előzetes" +#: msgctxt "#32202" msgid "Deleted Scene" msgstr "Törölt jelenet" +#: msgctxt "#32203" msgid "Interview" msgstr "Interjú" +#: msgctxt "#32204" msgid "Music Video" msgstr "Videóklip" +#: msgctxt "#32205" msgid "Behind the Scenes" msgstr "A kulisszák mögött" +#: msgctxt "#32206" msgid "Scene" msgstr "Jelenet" +#: msgctxt "#32207" msgid "Live Music Video" -msgstr "Élő zenei videó" +msgstr "Élő-zenei videó" +#: msgctxt "#32208" msgid "Lyric Music Video" msgstr "Zenei videó dalszöveggel" +#: msgctxt "#32209" msgid "Concert" msgstr "Koncert" +#: msgctxt "#32210" msgid "Featurette" msgstr "Kísérő film" +#: msgctxt "#32211" msgid "Short" msgstr "Rövidfilm" +#: msgctxt "#32212" msgid "Other" msgstr "Egyéb" +#: msgctxt "#32300" msgid "Go to Album" -msgstr "Ugrás az Albumra" +msgstr "Ugrás az albumra" +#: msgctxt "#32301" msgid "Go to Artist" msgstr "Ugrás az előadóra" +#: msgctxt "#32302" msgid "Go to {0}" msgstr "Ugrás ide {0}" -msgctxt "#32303" -msgid "Season" -msgstr "Évad" - -msgctxt "#32304" -msgid "Episode" -msgstr "Epizód" - +#: msgctxt "#32305" msgid "Extras" msgstr "Extrák" +#: msgctxt "#32306" msgid "Related Shows" -msgstr "Kapcsolódó műsorok" +msgstr "Kapcsolódó TV-műsorok" +#: msgctxt "#32307" msgid "More" msgstr "Egyéb" +#: msgctxt "#32308" msgid "Available" msgstr "Elérhető" +#: msgctxt "#32309" msgid "None" msgstr "Nincs" -msgctxt "#32310" -msgid "S" -msgstr "" - -msgctxt "#32311" -msgid "E" -msgstr "" - +#: msgctxt "#32312" msgid "Unavailable" msgstr "Nem elérhető" +#: msgctxt "#32313" msgid "This item is currently unavailable." msgstr "Ez az elem jelenleg nem érhető el." +#: msgctxt "#32314" msgid "In Progress" msgstr "Folyamatban" +#: msgctxt "#32315" msgid "Resume playback?" msgstr "Folytatja a lejátszást?" +#: msgctxt "#32316" msgid "Resume" msgstr "Folytat" +#: msgctxt "#32317" msgid "Play from beginning" msgstr "Lejátszás az elejétől" +#: msgctxt "#32318" msgid "Mark Unplayed" -msgstr "Nem nézettnek jelölés" +msgstr "Nem megnézettnek jelölés" +#: msgctxt "#32319" msgid "Mark Played" -msgstr "Nézettnek jelölés" +msgstr "Megnézettnek jelölés" +#: msgctxt "#32320" msgid "Mark Season Unplayed" -msgstr "Az évad nem nézettnek jelölése" +msgstr "Az évad nem megnézettnek jelölése" +#: msgctxt "#32321" msgid "Mark Season Played" -msgstr "Az évad nézettnek jelölése" +msgstr "Az évad megnézettnek jelölése" +#: msgctxt "#32322" msgid "Delete" msgstr "Törlés" +#: msgctxt "#32323" msgid "Go To Show" -msgstr "Ugrás a műsorra" +msgstr "Ugrás a TV-műsorra" +#: msgctxt "#32324" msgid "Go To {0}" msgstr "Ugrás {0}" +#: msgctxt "#32325" msgid "Play Next" msgstr "Következő lejátszása" +#: msgctxt "#32326" msgid "Really Delete?" msgstr "Tényleg törli?" +#: msgctxt "#32327" msgid "Are you sure you really want to delete this media?" msgstr "Biztos benne, hogy törli az alábbi tartalmat?" +#: msgctxt "#32328" msgid "Yes" msgstr "Igen" +#: msgctxt "#32329" msgid "No" msgstr "Nem" +#: msgctxt "#32330" msgid "Message" msgstr "Üzenet" +#: msgctxt "#32331" msgid "There was a problem while attempting to delete the media." msgstr "Probléma történt a tartalom törlése közben." +#: msgctxt "#32332" msgid "Home" -msgstr "Kezdőoldal" +msgstr "Kezdőképernyő" +#: msgctxt "#32333" msgid "Playlists" msgstr "Lejátszási listák" +#: msgctxt "#32334" msgid "Confirm Exit" msgstr "Kilépés megerősítése" +#: msgctxt "#32335" msgid "Are you ready to exit Plex?" msgstr "Készen áll arra, hogy kilépjen a Plexből?" +#: msgctxt "#32336" msgid "Exit" msgstr "Kilépés" +#: msgctxt "#32337" msgid "Cancel" msgstr "Mégse" +#: msgctxt "#32338" msgid "No Servers Found" msgstr "Nem található szerver" +#: msgctxt "#32339" msgid "Server is not accessible" msgstr "A szerver nem elérhető" +#: msgctxt "#32340" msgid "Connection tests are in progress. Please wait." msgstr "A kapcsolat tesztelése folyamatban. Kérem várjon." +#: msgctxt "#32341" msgid "Server is not accessible. Please sign into your server and check your connection." msgstr "A szerver nem elérhető. Kérem jelentkezzen be a szerverébe és ellenőrizze a hálózati kapcsolatot." +#: msgctxt "#32342" msgid "Switch User" -msgstr "Felhasználóváltás" +msgstr "Felhasználó váltása" +#: msgctxt "#32343" msgid "Settings" msgstr "Beállítások" +#: msgctxt "#32344" msgid "Sign Out" msgstr "Kijelentkezés" +#: msgctxt "#32345" msgid "All" msgstr "Összes" +#: msgctxt "#32346" msgid "By Name" msgstr "Név szerint" +#: msgctxt "#32347" -msgid "artists" -msgstr "előadók" +msgid "Artists" +msgstr "Előadók" +#: msgctxt "#32348" -msgid "movies" -msgstr "filmek" +msgid "Movies" +msgstr "Filmek" +#: msgctxt "#32349" msgid "photos" msgstr "fényképek" +#: msgctxt "#32350" -msgid "shows" -msgstr "műsorok" +msgid "Shows" +msgstr "TV-műsorok" +#: msgctxt "#32351" msgid "By Date Added" msgstr "Hozzáadás dátuma szerint" +#: msgctxt "#32352" msgid "Date Added" msgstr "Dátum szerint" +#: msgctxt "#32353" msgid "By Release Date" msgstr "Megjelenés dátuma szerint" +#: msgctxt "#32354" msgid "Release Date" msgstr "Megjelenés dátuma" +#: msgctxt "#32355" msgid "By Date Viewed" msgstr "Megtekintés dátuma szerint" +#: msgctxt "#32356" msgid "Date Viewed" msgstr "Megtekintés dátuma" -msgctxt "#32357" -msgid "By Name" -msgstr "Név szerint" - -msgctxt "#32358" -msgid "Name" -msgstr "Név" - +#: msgctxt "#32359" msgid "By Rating" msgstr "Értékelés szerint" +#: msgctxt "#32360" msgid "Rating" msgstr "Értékelés" +#: msgctxt "#32361" msgid "By Resolution" msgstr "Felbontás szerint" +#: msgctxt "#32362" msgid "Resolution" msgstr "Felbontás" +#: msgctxt "#32363" msgid "By Duration" msgstr "Időtartam szerint" +#: msgctxt "#32364" msgid "Duration" msgstr "Időtartam" +#: msgctxt "#32365" msgid "By First Aired" msgstr "Első sugárzás dátuma szerint" +#: msgctxt "#32366" msgid "First Aired" msgstr "Első sugárzás dátuma" +#: msgctxt "#32367" msgid "By Unplayed" -msgstr "Nem megtekintettek szerint" +msgstr "Nem megnézettek szerint" +#: msgctxt "#32368" msgid "Unplayed" -msgstr "Nem megtekintettek" +msgstr "Nem megnézett" +#: msgctxt "#32369" msgid "By Date Played" msgstr "Lejátszás dátuma szerint" +#: msgctxt "#32370" msgid "Date Played" msgstr "Lejátszás dátuma" +#: msgctxt "#32371" msgid "By Play Count" -msgstr "Lájátszások száma szerint" +msgstr "Lejátszások száma szerint" +#: msgctxt "#32372" msgid "Play Count" msgstr "Lejátszások száma" +#: msgctxt "#32373" msgid "By Date Taken" msgstr "Készítés dátuma szerint" +#: msgctxt "#32374" msgid "Date Taken" msgstr "Készítés dátuma" +#: msgctxt "#32375" msgid "No filters available" msgstr "Nincs elérhető szűrő" +#: msgctxt "#32376" msgid "Clear Filter" msgstr "Szűrők eltávolítása" +#: msgctxt "#32377" msgid "Year" msgstr "Év" +#: msgctxt "#32378" msgid "Decade" msgstr "Évtized" +#: msgctxt "#32379" msgid "Genre" msgstr "Műfaj" +#: msgctxt "#32380" msgid "Content Rating" msgstr "Értékelés" +#: msgctxt "#32381" msgid "Network" msgstr "Csatorna" +#: msgctxt "#32382" msgid "Collection" -msgstr "Nyűjtemény" +msgstr "Gyűjtemény" +#: msgctxt "#32383" msgid "Director" msgstr "Rendező" +#: msgctxt "#32384" msgid "Actor" msgstr "Színész" +#: msgctxt "#32385" msgid "Country" msgstr "Ország" +#: msgctxt "#32386" msgid "Studio" msgstr "Stúdió" +#: msgctxt "#32387" msgid "Labels" msgstr "Cimkék" +#: msgctxt "#32388" msgid "Camera Make" msgstr "Fényképezőgép gyártmánya" +#: msgctxt "#32389" msgid "Camera Model" -msgstr "Fényképezőgép Modell" +msgstr "Fényképezőgép típusa" +#: msgctxt "#32390" msgid "Aperture" msgstr "Rekesz" +#: msgctxt "#32391" msgid "Shutter Speed" -msgstr "Zár sebesség" +msgstr "Zársebesség" +#: msgctxt "#32392" msgid "Lens" msgstr "Lencsék" +#: msgctxt "#32393" msgid "TV Shows" -msgstr "Sorozatok" +msgstr "TV-műsorok" +#: msgctxt "#32394" msgid "Music" msgstr "Zene" +#: msgctxt "#32395" msgid "Audio" -msgstr "Audió" +msgstr "Hang" +#: msgctxt "#32396" msgid "Subtitles" msgstr "Feliratok" +#: msgctxt "#32397" msgid "Quality" msgstr "Minőség" +#: msgctxt "#32398" msgid "Kodi Video Settings" -msgstr "Kodi videó beállitások" +msgstr "Kodi videóbeállitások" +#: msgctxt "#32399" msgid "Kodi Audio Settings" -msgstr "Kodi audió beállitások" +msgstr "Kodi hangbeállitások" +#: msgctxt "#32400" msgid "Go To Season" msgstr "Ugrás az évadra" +#: msgctxt "#32401" msgid "Directors" msgstr "Rendezők" +#: msgctxt "#32402" msgid "Writer" msgstr "Szerző" +#: msgctxt "#32403" msgid "Writers" msgstr "Szerzők" +#: msgctxt "#32404" msgid "Related Movies" msgstr "Kapcsolódó filmek" +#: msgctxt "#32405" msgid "Download Subtitles" msgstr "Feliratok letöltése" +#: msgctxt "#32406" msgid "Subtitle Delay" -msgstr "Felirat késleltetés" +msgstr "Felirat késleltetése" +#: msgctxt "#32407" msgid "Next Subtitle" msgstr "Következő felirat" +#: msgctxt "#32408" msgid "Disable Subtitles" msgstr "Feliratok letiltása" +#: msgctxt "#32409" msgid "Enable Subtitles" msgstr "Feliratok engedélyezése" +#: msgctxt "#32410" msgid "Platform Version" msgstr "Platform verzió" +#: msgctxt "#32411" msgid "Unknown" msgstr "Ismeretlen" +#: msgctxt "#32412" msgid "Edit Or Clear" msgstr "Szerkesztés vagy törlés" +#: msgctxt "#32413" msgid "Edit IP address or clear the current setting?" msgstr "Módosíthatja az IP-címet, vagy törli az aktuális beállítást?" +#: msgctxt "#32414" msgid "Clear" msgstr "Törlés" +#: msgctxt "#32415" msgid "Edit" msgstr "Módositás" +#: msgctxt "#32416" msgid "Enter IP Address" msgstr "Adja meg az IP-címet" +#: msgctxt "#32417" msgid "Enter Port Number" msgstr "Adja meg a port számát" +#: msgctxt "#32418" msgid "Creator" msgstr "Alkotó" +#: msgctxt "#32419" msgid "Cast" msgstr "Szereplők" +#: msgctxt "#32420" msgid "Disc" msgstr "Lemez" +#: msgctxt "#32421" msgid "Sign Out" msgstr "Kijelentkezés" +#: msgctxt "#32422" msgid "Exit" msgstr "Kilépés" +#: msgctxt "#32423" msgid "Shutdown" msgstr "Leállítás" +#: msgctxt "#32424" msgid "Suspend" msgstr "Felfüggesztés" +#: msgctxt "#32425" msgid "Hibernate" msgstr "Hibernálás" +#: msgctxt "#32426" msgid "Reboot" msgstr "Újraindítás" +#: msgctxt "#32427" msgid "Failed" msgstr "Sikertelen" +#: msgctxt "#32428" msgid "Login failed!" msgstr "Bejelentkezés sikertelen" +#: msgctxt "#32429" msgid "Resume from {0}" msgstr "Folytatás innen {0}" +#: msgctxt "#32430" msgid "Discovery" msgstr "Felfedezés" +#: msgctxt "#32431" msgid "Search" msgstr "Keresés" +#: msgctxt "#32432" msgid "Space" -msgstr "Space" +msgstr "Szóköz" +#: msgctxt "#32433" msgid "Clear" msgstr "Törlés mind" +#: msgctxt "#32434" msgid "Searching..." msgstr "Keresés..." +#: msgctxt "#32435" msgid "No Results" msgstr "Nincs találat" +#: msgctxt "#32436" msgid "Paused" -msgstr "Szünetelve" +msgstr "Szüneteltetve" +#: msgctxt "#32437" msgid "Welcome" msgstr "Üdvözöljük" +#: msgctxt "#32438" msgid "Previous" msgstr "Előző" +#: msgctxt "#32439" msgid "Playing Next" msgstr "Következő lejátszása" +#: msgctxt "#32440" msgid "On Deck" -msgstr "Jelenlegi" +msgstr "Fedélzeten" +#: msgctxt "#32441" msgid "Unknown" msgstr "Ismeretlen" +#: msgctxt "#32442" msgid "Embedded" msgstr "Beágyazott" +#: msgctxt "#32443" msgid "Forced" msgstr "Kényszerített" +#: msgctxt "#32444" msgid "Lyrics" msgstr "Dalszöveg" +#: msgctxt "#32445" msgid "Mono" msgstr "Monó" +#: msgctxt "#32446" msgid "Stereo" msgstr "Sztereó" +#: msgctxt "#32447" msgid "None" msgstr "Nincs" +#: msgctxt "#32448" msgid "Playback Failed!" msgstr "A lejátszás sikertelen!" +#: msgctxt "#32449" msgid "Can't connect to plex.tv[CR]Check your internet connection and try again." msgstr "Nem lehet csatlakozni a plex.tv-hez[CR]Ellenőrizze az internetkapcsolatot, és próbálja újra." +#: msgctxt "#32450" msgid "Choose Version" -msgstr "Verzió választása" +msgstr "Verzió kiválasztása" +#: msgctxt "#32451" msgid "Play Version..." msgstr "Verzió lejátszása..." +#: msgctxt "#32452" msgid "No Content available in this library" msgstr "Nincs elérhető tartalom ebben a könyvtárban" +#: msgctxt "#32453" msgid "Please add content and/or check that 'Include in dashboard' is enabled." msgstr "Kérem adjon hozzá tartalmat és/vagy ellenőrizze, hogy a 'Hozzáadás az irányítópulthoz' opció be van kapcsolva" +#: msgctxt "#32454" msgid "No Content available for this filter" msgstr "Nincs elérhető tartalom ezzel a szűrővel" +#: msgctxt "#32455" msgid "Please change change or remove the current filter" msgstr "Kérem változtassa meg, vagy távolítsa el a jelenlegi szűrőt" +#: msgctxt "#32456" -msgid "Offline Mode" -msgstr "Offline mód" +msgid "Show" +msgstr "TV-műsor" +#: msgctxt "#32457" +msgid "By Show" +msgstr "TV-műsor szerint" + +#: +msgctxt "#32458" +msgid "Episodes" +msgstr "Epizódok" + +#: +msgctxt "#32459" +msgid "Offline Mode" +msgstr "Offline üzemmód" + +#: +msgctxt "#32460" msgid "Sign In" msgstr "Bejelentkezés" + +#: +msgctxt "#32461" +msgid "Albums" +msgstr "Albumok" + +#: +msgctxt "#32462" +msgid "Artist" +msgstr "Előadó" + +#: +msgctxt "#32463" +msgid "By Artist" +msgstr "Elóadó szerint" + +#: +msgctxt "#32464" +msgid "Player" +msgstr "Lejátszó" + +#: +msgctxt "#32465" +msgid "Use skip step settings from Kodi" +msgstr "A Kodi „lépés kihagyás” beállításainak használata" + +#: +msgctxt "#32466" +msgid "Automatically seek selected position after a delay" +msgstr "Késleltetés után automatikusan a kiválasztott pozícióra lép" + +#: +msgctxt "#32467" +msgid "User Interface" +msgstr "Felhasználói felület" + +#: +msgctxt "#32468" +msgid "Show dynamic background art" +msgstr "Dinamikus háttér megjelenítése" + +#: +msgctxt "#32469" +msgid "Background art blur amount" +msgstr "Háttérkép elmosódásának mértéke" + +#: +msgctxt "#32470" +msgid "Background art opacity" +msgstr "Háttérkép átlátszatlansága" + +#: +msgctxt "#32471" +msgid "Use Plex/Kodi steps for timeline" +msgstr "Plex/Kodi lépések használata az idővonalhoz" + +#: +msgctxt "#32480" +msgid "Theme music" +msgstr "Főcímzene" + +#: +msgctxt "#32481" +msgid "Off" +msgstr "Ki" + +#: +msgctxt "#32482" +msgid "%(percentage)s %%" +msgstr "%(percentage)s %%" + +#: +msgctxt "#32483" +msgid "Hide Stream Info" +msgstr "Folyaminformációk elrejtése" + +#: +msgctxt "#32484" +msgid "Show Stream Info" +msgstr "Folyaminformációk megjelenítése" + +#: +msgctxt "#32485" +msgid "Go back instantly with the previous menu action in scrolled views" +msgstr "Azonnali visszalépés az előző menü műveletével a görgetett nézetekben" + +#: +msgctxt "#32487" +msgid "Seek Delay" +msgstr "Léptetés késleltetése" + +#: +msgctxt "#32488" +msgid "Screensaver" +msgstr "Képernyővédő" + +#: +msgctxt "#32489" +msgid "Quiz Mode" +msgstr "Kvíz mód" + +#: +msgctxt "#32490" +msgid "Collections" +msgstr "Gyűjtemények" + +#: +msgctxt "#32491" +msgid "Folders" +msgstr "Mappák" + +#: +msgctxt "#32492" +msgid "Kodi Subtitle Settings" +msgstr "Kodi felirat beállításai" + +#: +msgctxt "#32495" +msgid "Skip intro" +msgstr "Főcím átugrása" + +#: +msgctxt "#32496" +msgid "Skip credits" +msgstr "Stáblista átugrása" + +#: +msgctxt "#32500" +msgid "Always show post-play screen (even for short videos)" +msgstr "Mindig jelenítse meg a folytatás képernyőt (még rövid videók esetén is)" + +#: +msgctxt "#32501" +msgid "Time-to-wait between videos on post-play" +msgstr "Lejátszandó videók közötti várakozási idő" + +#: +msgctxt "#32505" +msgid "Visit media in video playlist instead of playing it" +msgstr "Lejátszás helyett a média megtekintése a videó lejátszási listájában" + +#: +msgctxt "#32521" +msgid "Skip Intro Button Timeout" +msgstr "Főcím átugrása gomb időkorlátja" + +#: +msgctxt "#32522" +msgid "Automatically Skip Intro" +msgstr "Főcím automatikus átugrása" + +#: +msgctxt "#32524" +msgid "Set how long the skip intro button shows for." +msgstr "Beállíthatja, hogy a főcím átugrása gomb mennyi ideig jelenjen meg." + +#: +msgctxt "#32525" +msgid "Skip Credits Button Timeout" +msgstr "Stáblista átugrása gomb időkorlát" + +#: +msgctxt "#32526" +msgid "Automatically Skip Credits" +msgstr "Stáblista automatikus átugrása" + +#: +msgctxt "#32528" +msgid "Set how long the skip credits button shows for." +msgstr "Beállíthatja, hogy a stáblista átugrása gomb mennyi ideig jelenjen meg." + +#: +msgctxt "#32540" +msgid "Show when the current video will end in player" +msgstr "Megmutatja, mikor ér véget az aktuális videó a lejátszóban" + +#: +msgctxt "#32541" +msgid "Shows time left and at which time the media will end." +msgstr "Megmutatja a hátralévő időt és azt, hogy mikor ér véget a média." + +#: +msgctxt "#32542" +msgid "Show \"Ends at\" label for the end-time as well" +msgstr "„Befejezés” címke megjelenítése a végéhez is" + +#: +msgctxt "#32543" +msgid "Ends at" +msgstr "Befejezés" + +#: +msgctxt "#32602" +msgid "Enable this if your hardware can handle AV1. Disable it to force transcoding." +msgstr "Engedélyezze, ha a hardver képes kezelni az AV1-et. Ha kikapcsolja, akkor az átkódolást kényszeríti ki." + +#: +msgctxt "#33101" +msgid "By Audience Rating" +msgstr "Közönség értékelése szerint" + +#: +msgctxt "#33102" +msgid "Audience Rating" +msgstr "Közönség értékelése" + +#: +msgctxt "#33103" +msgid "By my Rating" +msgstr "Saját értékelésem szerint" + +#: +msgctxt "#33104" +msgid "My Rating" +msgstr "Saját értékelésem" + +#: +msgctxt "#33105" +msgid "By Content Rating" +msgstr "Tartalom értékelése szerint" + +#: +msgctxt "#33106" +msgid "Content Rating" +msgstr "Tartalom értékelése" + +#: +msgctxt "#33107" +msgid "By Critic Rating" +msgstr "Kritikusok értékelése szerint" + +#: +msgctxt "#33108" +msgid "Critic Rating" +msgstr "Kritikusok értékelése" + +#: +msgctxt "#33200" +msgid "Background Color" +msgstr "Háttér színe" + +#: +msgctxt "#33201" +msgid "Specify solid Background Color instead of using media images" +msgstr "Háttérszín megadása médiaképek használata helyett" + +#: +msgctxt "#33400" +msgid "Use old compatibility profile" +msgstr "Régi kompatibilitási profil használata" + +#: +msgctxt "#33401" +msgid "Uses the Chrome client profile instead of the custom one. Might fix rare issues with 3D playback." +msgstr "Chrome ügyfélprofil használata az egyéni helyett. Megoldhatja a 3D lejátszással kapcsolatos ritka problémákat." + +#: +msgctxt "#32031" +msgid "Burn-in Subtitles" +msgstr "Beégetett feliratok" + +#: +msgctxt "#32061" +msgid "When transcoding audio, target the audio channels set in Kodi." +msgstr "A hang átkódolásakor a Kodiban beállított hangcsatornákat célozza meg." + +#: +msgctxt "#32062" +msgid "Transcode audio to AC3" +msgstr "Hang átkódolása AC3-ba" + +#: +msgctxt "#32063" +msgid "Transcode audio to AC3 in certain conditions (useful for passthrough)." +msgstr "A hang átkódolása AC3-ra bizonyos feltételek mellett (hasznos az érintetlen átvitelhez)." + +#: +msgctxt "#32065" +msgid "When any of the force AC3 settings are enabled, treat DTS the same as AC3 (useful for Optical passthrough)" +msgstr "Ha az AC3 kényszerítése beállítások bármelyike engedélyezve van, a DTS-t ugyanúgy kezeli, mint az AC3-t (hasznos az optikai átvitelhez)." + +#: +msgctxt "#32066" +msgid "Force audio to AC3" +msgstr "Hang AC3-ra kényszerítése" + +#: +msgctxt "#32067" +msgid "Only force multichannel audio to AC3" +msgstr "Csak a többcsatornás hangot kényszeríti AC3-ra" + +#: +msgctxt "#32493" +msgid "When a media file has a forced/foreign subtitle for a subtitle-enabled language, the Plex Media Server preselects it. This behaviour is usually not necessary and not configurable. This setting fixes that by ignoring the PMSs decision and selecting the same language without a forced flag if possible." +msgstr "Ha egy médiafájl egy feliratozással engedélyezett nyelvhez kényszerített/idegen feliratot tartalmaz, a Plex Media Server előválasztja azt. Ez általában nem szükséges és nem konfigurálható. Ez a beállítás ezt úgy javítja, hogy figyelmen kívül hagyja a PMS döntését és lehetőség szerint ugyanazt a nyelvet választja ki kényszerített jelölés nélkül." + +#: +msgctxt "#32523" +msgid "Automatically skip intros if available. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "Főcím automatikus átugrása, ha elérhető. Nem írja felül a bekapcsolt folyamatosnézés-módot.\n" +"TV-műsoronként ki/bekapcsolható." + +#: +msgctxt "#32527" +msgid "Automatically skip credits if available. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "Stáblista automatikus átugrása, ha elérhető. Nem írja felül a bekapcsolt folyamatosnézés-módot.\n" +"TV-műsoronként ki/bekapcsolható." + +#: +msgctxt "#33501" +msgid "Video played threshold" +msgstr "Megnézett videó küszöbérték" + +#: +msgctxt "#33502" +msgid "Set this to the same value as your Plex server (Settings>Library>Video played threshold) to avoid certain pitfalls, Default: 90 %" +msgstr "Állítsa ezt a Plex szerver azonos értékére (Beállítások>Könyvtár>Lejátszott videó küszöbérték), hogy elkerüljön bizonyos buktatókat, Alapértelmezett: 90 %" + +#: +msgctxt "#33503" +msgid "Use alternative hubs refresh" +msgstr "Használja az alternatív csomópontok frissítést" + +#: +msgctxt "#33504" +msgid "Refreshes all hubs for all libraries after an item's watch-state has changed, instead of only those likely affected. Use this if you find a hub that doesn't update properly." +msgstr "Az összes könyvtár összes csomópontját frissíti, miután egy elem figyelési állapota megváltozott, ahelyett, hogy csak azokat frissítené, amelyek valószínűleg érintettek. Ezt akkor használja, ha olyan csomópontot talál, amely nem frissül megfelelően." + +#: +msgctxt "#33505" +msgid "Show intro skip button early" +msgstr "Főcím átugrása gomb korai megjelenítése" + +#: +msgctxt "#33507" +msgid "Enabled" +msgstr "Engedélyezve" + +#: +msgctxt "#33508" +msgid "Disabled" +msgstr "Letiltva" + +#: +msgctxt "#33510" +msgid "When showing the intro skip button early, only do so if the intro occurs within the first X seconds." +msgstr "Ha a főcím átugrása gombot korán mutatja, csak akkor tegye ezt, ha a bevezető az első X másodpercen belül jelenik meg." + +#: +msgctxt "#33600" +msgid "System" +msgstr "Rendszer" + +#: +msgctxt "#33601" +msgid "Show video chapters" +msgstr "Videófejezetek megjelenítése" + +#: +msgctxt "#33602" +msgid "If available, show video chapters from the video-file instead of the timeline-big-seek-steps." +msgstr "Ha rendelkezésre áll, az idővonal nagyléptékű ugrása helyett, mutassa a videófájlból származó fejezeteket." + +#: +msgctxt "#33603" +msgid "Use virtual chapters" +msgstr "Virtuális fejezetek használata" + +#: +msgctxt "#33604" +msgid "When the above is enabled and no video chapters are available, simulate them by using the markers identified by the Plex Server (Intro, Credits)." +msgstr "Ha a fentiek engedélyezve vannak, és nem állnak rendelkezésre videókapacitások, szimulálja azokat a Plex szerver által azonosított jelölésekkel (Főcím, Stáblista)." + +#: +msgctxt "#33605" +msgid "Video Chapters" +msgstr "Videófejezetek" + +#: +msgctxt "#33606" +msgid "Virtual Chapters" +msgstr "Virtuális fejezetek" + +#: +msgctxt "#33607" +msgid "Chapter {}" +msgstr "Fejezet {}" + +#: +msgctxt "#33608" +msgid "Intro" +msgstr "Főcím" + +#: +msgctxt "#33609" +msgid "Credits" +msgstr "Stáblista" + +#: +msgctxt "#33610" +msgid "Main" +msgstr "Főoldal" + +#: +msgctxt "#33611" +msgid "Chapters" +msgstr "Fejezetek" + +#: +msgctxt "#33612" +msgid "Markers" +msgstr "Jelölők" + +#: +msgctxt "#33613" +msgid "Kodi Buffer Size (MB)" +msgstr "Kodi puffer méret (MB)" + +#: +msgctxt "#33614" +msgid "Set the Kodi Cache/Buffer size. Free: {} MB, Recommended: ~50 MB, Recommended max: {} MB, Default: 20 MB." +msgstr "A Kodi gyorsítótár/puffer méretének beállítása. Szabad: {} MB, Ajánlott: ~MB, Ajánlott max: {} MB, Alapértelmezett: 20 MB." + +#: +msgctxt "#33615" +msgid "{time} left" +msgstr "{time} maradt" + +#: +msgctxt "#33616" +msgid "Addon Path" +msgstr "Kiegészítő útvonala" + +#: +msgctxt "#33617" +msgid "Userdata/Profile Path" +msgstr "Felhasználói adatok/Profil elérési útvonal" + +#: +msgctxt "#33618" +msgid "TV binge-viewing mode" +msgstr "TV folyamatosnézés-mód" + +#: +msgctxt "#33622" +msgid "LAN reachability timeout (ms)" +msgstr "LAN elérhetőségi időkorlát (ms)" + +#: +msgctxt "#33623" +msgid "When checking for LAN reachability, use this timeout. Default: 10ms" +msgstr "A LAN elérhetőségének ellenőrzésekor használja ezt az időkorlátot. Alapértelmezett: 10ms" + +#: +msgctxt "#33624" +msgid "Network" +msgstr "Hálózat" + +#: +msgctxt "#33625" +msgid "Smart LAN/local server discovery" +msgstr "Intelligens LAN/helyi kiszolgáló felderítése" + +#: +msgctxt "#33626" +msgid "Checks whether servers returned from Plex.tv are actually local/in your LAN. For specific setups (e.g. Docker) Plex.tv might not properly detect a local server.\n" +"\n" +"NOTE: Only works on Kodi 19 or above." +msgstr "A Plex.tv által visszaküldött kiszolgálók ellenőrzése, hogy valóban helyi/LAN-on belül vannak-e. Bizonyos beállítások (pl. Docker) esetén előfordulhat, hogy a Plex.tv nem megfelelően érzékeli a helyi szervert.\n" +"\n" +"MEGJEGYZÉS: Csak Kodi 19 vagy afelett működik." + +#: +msgctxt "#33627" +msgid "Prefer LAN/local servers over security" +msgstr "A LAN/helyi szerverek előnyben részesítése a biztonsággal szemben" + +#: +msgctxt "#33628" +msgid "Prioritizes local connections over secure ones. Needs the proper setting in \"Allow Insecure Connections\" and the Plex Server's \"Secure connections\" at \"Preferred\". Can be used to enforce manual servers." +msgstr "A helyi kapcsolatokat előnyben részesíti a biztonságosakkal szemben. Szükséges a megfelelő beállítás a „Nem biztonságos kapcsolatok engedélyezése” és a Plex szerver „Biztonságos kapcsolatok” „Előnyben részesített” beállítása. Használható kézi szerverek kikényszerítésére." + +#: +msgctxt "#33629" +msgid "Auto-skip intro/credits offset" +msgstr "Automatikus átugrás főcím/stáblista eltolás" + +#: +msgctxt "#33630" +msgid "Intro/credits markers might be a little early in Plex. When auto skipping add (or subtract) this many seconds from the marker. This avoids cutting off content, while possibly skipping the marker a little late." +msgstr "A főcím/stáblista jelölések talán egy kicsit koraiak a Plexben. Automatikus kihagyáskor adjon hozzá (vagy vonjon le) ennyi másodpercet a jelöléshez. Így elkerülhető a tartalom megszakítása, ugyanakkor a jelölés esetleg egy kicsit későn jelenik meg." + +#: +msgctxt "#32631" +msgid "Playback (user-specific)" +msgstr "Lejátszás (felhasználó specifikus)" + +#: +msgctxt "#33632" +msgid "Server connectivity check timeout (seconds)" +msgstr "Kiszolgálói kapcsolat ellenőrzésének időkorlátja (másodperc)" + +#: +msgctxt "#33633" +msgid "Set the maximum amount of time a server connection has to answer a connectivity request. Default: 2.5" +msgstr "Beállítja, hogy a kiszolgálói kapcsolatnak mennyi idő áll rendelkezésére a kapcsolódási kérelem megválaszolásához. Alapértelmezett: 2.5" + +#: +msgctxt "#33634" +msgid "Combined Chapters" +msgstr "Összevont fejezetek" + +#: +msgctxt "#33635" +msgid "Final Credits" +msgstr "Végső stáblista" + +#: +msgctxt "#32700" +msgid "Action on Sleep event" +msgstr "Cselekvés az elalvási funkcióval kapcsolatban" + +#: +msgctxt "#32701" +msgid "When Kodi receives a sleep event from the system, run the following action." +msgstr "Amikor a Kodi elalvás funkciót kap a rendszertől, futtassa a következő műveletet." + +#: +msgctxt "#32702" +msgid "Nothing" +msgstr "Semmi" + +#: +msgctxt "#32703" +msgid "Stop playback" +msgstr "Lejátszás leállítása" + +#: +msgctxt "#32704" +msgid "Quit Kodi" +msgstr "Kilépés a Kodiból" + +#: +msgctxt "#32705" +msgid "CEC Standby" +msgstr "CEC készenlét" + +#: +msgctxt "#32800" +msgid "Skipping intro" +msgstr "Főcím átugrása" + +#: +msgctxt "#32801" +msgid "Skipping credits" +msgstr "Stáblista átugrása" + +#: +msgctxt "#32900" +msgid "While playing back an item and seeking on the seekbar, automatically seek to the selected position after a delay instead of having to confirm the selection." +msgstr "Lejátszás, valamint a keresősávon történő léptetés közben, késleltetés után automatikusan a kiválasztott pozícióra ugrik a kiválasztás megerősítése helyett." + +#: +msgctxt "#32901" +msgid "Seek delay in seconds." +msgstr "Léptetés késleltetése másodpercben." + +#: +msgctxt "#32902" +msgid "Kodi has its own skip step settings. Try to use them if they're configured instead of the default ones." +msgstr "A Kodi rendelkezik lépések kihagyása beállítással. Próbálja meg ezeket használni, ha ezek vannak beállítva az alapértelmezettek helyett." + +#: +msgctxt "#32903" +msgid "Use the above for seeking on the timeline as well." +msgstr "Használja a fentieket az idővonalon való léptetéshez is." + +#: +msgctxt "#32904" +msgid "In seconds." +msgstr "Másodpercben." + +#: +msgctxt "#32905" +msgid "Cancel post-play timer by pressing OK/SELECT" +msgstr "A folytatásidőzítő törlése az OK/KIVÁLASZT gomb megnyomásával." + +#: +msgctxt "#32906" +msgid "Cancel skip marker timer with BACK" +msgstr "A kihagyásjelző időzítőjének törlése a VISSZA funkcióval" + +#: +msgctxt "#32907" +msgid "When auto-skipping a marker, allow cancelling the timer by pressing BACK." +msgstr "Amikor automatikusan kihagy egy jelölést, lehetővé teszi az időzítő leállítását a VISSZA gomb megnyomásával." + +#: +msgctxt "#32908" +msgid "Immediately skip marker with OK/SELECT" +msgstr "Azonnal kihagyja a jelölést az OK/KIVÁLASZT gombbal." + +#: +msgctxt "#32909" +msgid "When auto-skipping a marker with a timer, allow skipping immediately by pressing OK/SELECT." +msgstr "Egy időzítővel rendelkező jelölő automatikus kihagyásakor az OK/KIVÁLASZT gomb megnyomásával azonnal engedélyezze a kihagyást." + +#: +msgctxt "#32912" +msgid "Show buffer-state on timeline" +msgstr "Puffer állapotának megjelenítése az idővonalon" + +#: +msgctxt "#32913" +msgid "Shows the current Kodi buffer/cache state on the video player timeline." +msgstr "Megjeleníti a Kodi aktuális puffer/gyorsítótár állapotát a videólejátszó idővonalán." + +#: +msgctxt "#32914" +msgid "Loading" +msgstr "Betöltés" + +#: +msgctxt "#32915" +msgid "Slow connection" +msgstr "Lassú kapcsolat" + +#: +msgctxt "#32916" +msgid "Use with a wonky/slow connection, e.g. in a hotel room. Adjusts the UI to visually wait for item refreshes and waits for the buffer to fill when starting playback. Automatically sets readfactor=20, requires Kodi restart." +msgstr "Használja akadozó/lassú kapcsolat esetén, pl. szállodai szobában. Beállítja a felhasználói felületet, hogy vizuálisan várjon az elemfrissítésre, és a lejátszás indításakor megvárja, amíg a puffer megtelik. Automatikusan beállítja az olvasási faktor=20 értéket, a Kodi újraindítását igényli." + +#: +msgctxt "#32917" +msgid "Couldn't fill buffer in time ({}s)" +msgstr "Nem sikerült időben kitölteni a puffert ({}s)" + +#: +msgctxt "#32918" +msgid "Buffer wait timeout (seconds)" +msgstr "Puffer várakozási időkorlát (másodperc)" + +#: +msgctxt "#32919" +msgid "When slow connection is enabled in the addon, wait this long for the buffer to fill. Default: 120 s" +msgstr "Ha a lassú kapcsolat engedélyezve van a kiegészítőben, várjon ennyi ideig, amíg a puffer megtelik. Alapértelmezett: 120 s" + +#: +msgctxt "#32920" +msgid "Insufficient buffer wait (seconds)" +msgstr "Nem megfelelő puffer várakozás (másodperc)" + +#: +msgctxt "#32921" +msgid "When slow connection is enabled in the addon and the configured buffer isn't big enough for us to determine its fill state, wait this long when starting playback. Default: 10 s" +msgstr "Ha a lassú kapcsolat engedélyezve van a kiegészítőben és a konfigurált puffer nem elég nagy ahhoz, hogy meghatározzuk a töltöttségi állapotát, akkor a lejátszás indításakor várjon ennyit. Alapértelmezett: 10 s" + +#: +msgctxt "#32922" +msgid "Kodi Cache Readfactor" +msgstr "Kodi gyorsítótár olvasási faktor" + +#: +msgctxt "#32923" +msgid "Sets the Kodi cache readfactor value. Default: {0}, recommended: {1}. With \"Slow connection\" enabled this will be set to {2}, as otherwise the cache doesn't fill fast/aggressively enough." +msgstr "A Kodi gyorsítótár olvasási faktor értékének beállítása. Alapértelmezett: {0}, ajánlott: {1}. A „Lassú kapcsolat” engedélyezése esetén ez {2} értékre lesz állítva, különben a gyorsítótár nem töltődik elég gyorsan/agresszívan." + +#: +msgctxt "#32924" +msgid "Minimize" +msgstr "Minimalizálja" + +#: +msgctxt "#32925" +msgid "Playback Settings" +msgstr "Lejátszási beállítások" + +#: +msgctxt "#32926" +msgid "Wrong pin entered!" +msgstr "Rossz PIN-kód!" + +#: +msgctxt "#32927" +msgid "Use episode thumbnails in continue hub" +msgstr "Használja az epizódok miniatűrjeit a megtekintés folytatásában" + +#: +msgctxt "#32928" +msgid "Instead of using media artwork, use thumbnails for episodes in the continue hub on the home screen if available." +msgstr "A médiaképek használata helyett használja az epizódok miniatűrjeit a kezdőképernyőn található megtekintés folytatásában, ha rendelkezésre állnak." + +#: +msgctxt "#32929" +msgid "Use legacy background fallback image" +msgstr "Régi háttérkép használata" + +#: +msgctxt "#32930" +msgid "Previous Subtitle" +msgstr "Előző felirat" + +#: +msgctxt "#32931" +msgid "Audio/Subtitles" +msgstr "Hangok/Feliratok" + +#: +msgctxt "#32936" +msgid "Show playlist button" +msgstr "Lejátszási lista gomb megjelenítése" + +#: +msgctxt "#32937" +msgid "Show prev/next button" +msgstr "Előző/következő gomb megjelenítése" + +#: +msgctxt "#32941" +msgid "Forced subtitles fix" +msgstr "Kényszerített felirat javítása" + +#: +msgctxt "#32942" +msgid "Other seasons" +msgstr "Más évadok" + +#: +msgctxt "#32943" +msgid "Crossfade dynamic background art" +msgstr "Dinamikus háttér átfedése" + +#: +msgctxt "#32944" +msgid "Burn-in SSA subtitles (DirectStream)" +msgstr "Beégetett SSA feliratok (Közvetlen folyam)" + +#: +msgctxt "#32945" +msgid "When Direct Streaming instruct the Plex Server to burn in SSA/ASS subtitles (thus transcoding the video stream). If disabled it will not touch the video stream, but will convert the subtitle to unstyled text." +msgstr "Közvetlen folyam esetén utasítsa a Plex szervert, hogy égesse be az SSA/ASS feliratokat (így átkódolja a videófolyamot). Ha letiltja, akkor nem nyúl a videófolyamhoz, de a feliratot stilizálatlan szöveggé alakítja át." + +#: +msgctxt "#32946" +msgid "Stop video playback on idle after" +msgstr "Videolejátszás leállítása üresjáratban" + +#: +msgctxt "#32947" +msgid "Stop video playback on screensaver" +msgstr "Videolejátszás leállítása a képernyővédőn" + +#: +msgctxt "#32948" +msgid "Allow auto-skip when transcoding" +msgstr "Automatikus átugrás engedélyezése átkódoláskor" + +#: +msgctxt "#32949" +msgid "When transcoding/DirectStreaming, allow auto-skip functionality." +msgstr "Átkódolás/Közvetlen folyam esetén engedélyezze az automatikus átugrás funkciót." + +#: +msgctxt "#32950" +msgid "Use extended title for subtitles" +msgstr "Bővített cím használata a feliratokhoz" + +#: +msgctxt "#32951" +msgid "When displaying subtitles use the extendedDisplayTitle Plex exposes." +msgstr "A feliratok megjelenítésekor használja a Plex általi extendedDisplayTitle-t." + +#: +msgctxt "#32953" +msgid "Reviews" +msgstr "Vélemények" + +#: +msgctxt "#32954" +msgid "Needs Kodi restart. WARNING: This will overwrite advancedsettings.xml!\n" +"\n" +"To customize other cache/network-related values, copy \"script.plexmod/pm4k_cache_template.xml\" to profile folder and edit it to your liking. (See About section for the file paths)" +msgstr "Kodi újraindítása szükséges. FIGYELMEZTETÉS: Ez felülírja az advancedsettings.xml fájlt!\n" +"\n" +"Más, a gyorsítótárral/hálózattal kapcsolatos értékek testreszabásához másolja a „script.plexmod/pm4k_cache_template.xml” fájlt a profil mappába, és szerkessze tetszés szerint. (A fájl elérési útvonalait lásd a Leírás szakaszban)" + +#: +msgctxt "#32955" +msgid "Use Kodi keyboard for searching" +msgstr "Kodi billentyűzetének használata a kereséshez" + +#: +msgctxt "#32956" +msgid "Poster resolution scaling %" +msgstr "Plakát felbontásának méretezése %" + +#: +msgctxt "#32957" +msgid "In percent. Scales the resolution of all posters/thumbnails for better image quality. May impact PMS/PM4K performance, will increase the cache usage accordingly. Recommended: 200-300 % for for big screens if your hardware can handle it. Needs addon restart." +msgstr "Százalékban. A jobb képminőség érdekében méretezi az összes plakátok/miniatűrök felbontását. Befolyásolhatja a PMS/PM4K teljesítményét, ennek megfelelően növeli a gyorsítótár használatát. Ajánlott: 200-300 % a nagy képernyőkhöz, ha a hardver képes kezelni. Kiegészítő újraindítása szükséges." + +#: +msgctxt "#32958" +msgid "Calculate OpenSubtitles.com hash" +msgstr "OpenSubtitles.com eltérés kiszámítása" + +#: +msgctxt "#32959" +msgid "When opening the subtitle download feature, automatically calculate the OpenSubtitles.com hash for the given file. Can improve search results, downloads 2*64 KB of the video file to calculate the hash." +msgstr "A feliratletöltési funkció megnyitásakor automatikusan kiszámítja az OpenSubtitles.com eltérést az adott fájlhoz. Javíthatja a keresési eredményeket, az eltérés kiszámításához 2*64 KB-ot tölt le a videófájlból." + +#: +msgctxt "#32960" +msgid "Similar Artists" +msgstr "Hasonló előadók" + +#: +msgctxt "#32961" +msgid "Show hub bifurcation lines" +msgstr "Csomópont-bifurkációs vonalak megjelenítése" + +#: +msgctxt "#32962" +msgid "Visually separate hubs horizontally using a thin line." +msgstr "Vizuálisan és vízszintesen válassza el a csomópontokat egy vékony vonallal." + +#: +msgctxt "#32963" +msgid "Wait between videos (s)" +msgstr "Videók közötti várakozás (s)" + +#: +msgctxt "#32964" +msgid "When playing back consecutive videos (e.g. TV shows), wait this long before starting the next one in the queue. Might fix compatibility issues with certain configurations." +msgstr "Egymást követő videók (pl. TV-műsorok) lejátszásakor várjon ennyit, mielőtt a sorban következő videót elindítja. Bizonyos konfigurációk esetén kompatibilitási problémákat javíthat." + +#: +msgctxt "#32965" +msgid "Quit Kodi on exit by default" +msgstr "Alapértelmezés szerint kilépéskor kilép a Kodiból" + +#: +msgctxt "#32966" +msgid "When exiting the addon, use \"Quit Kodi\" as default option. Can be dynamically switched using CONTEXT_MENU (often longpress SELECT)" +msgstr "A kiegészítő elhagyásakor használja a „Kilépés a Kodiból” alapértelmezett opciót. A CONTEXT_MENU használatával dinamikusan átkapcsolható (gyakran hosszan nyomja a KIVÁLASZT gombot)." + +#: +msgctxt "#32967" +msgid "Kodi Colour Management" +msgstr "Kodi színkezelés" + +#: +msgctxt "#32968" +msgid "Kodi Resolution Settings" +msgstr "Kodi felbontás beállítások" + +#: +msgctxt "#32969" +msgid "Always request all library media items at once" +msgstr "Mindig egyszerre kérje az összes könyvtári médiaelemet" + +#: +msgctxt "#32970" +msgid "Retrieve all media in library up front instead of fetching it in chunks as the user navigates through the library" +msgstr "A könyvtárban lévő összes médiát előre lekérdezi, ahelyett, hogy a felhasználó a könyvtárban való navigálás során darabokban hívná le." + +#: +msgctxt "#32971" +msgid "Library item-request chunk size" +msgstr "Könyvtári elem-kérelem darabszáma" + +#: +msgctxt "#32972" +msgid "Request this amount of media items per chunk request in library view (+6-30 depending on view mode; less can be less straining for the UI at first, but puts more strain on the server)" +msgstr "Könyvtári nézetben darabonként kérje le ezt a médiaelem-mennyiséget (+6-30 a nézetmódtól függően; a kevesebb eleinte kevésbé megterhelő lehet a felhasználói felület számára, de nagyobb terhet ró a kiszolgálóra)." + +#: +msgctxt "#32973" +msgid "Episodes: Skip Post Play screen" +msgstr "Epizódok: Folytatás képernyő átugrása" + +#: +msgctxt "#32974" +msgid "When finishing an episode, don't show Post Play but go to the next one immediately.\n" +"Can be disabled/enabled per TV show. Doesn't override enabled binge mode. Overrides the Post Play setting." +msgstr "Ha befejez egy epizódot, ne jelenjen meg a folytatás, hanem azonnal lépjen a következő epizódra.\n" +"TV-műsoronként ki/bekapcsolható. Nem írja felül a bekapcsolt folyamatosnézés-módot. Felülírja a folytatás beállítását." + +#: +msgctxt "#32975" +msgid "Delete Season" +msgstr "Évad törlése" + +#: +msgctxt "#32976" +msgid "Adaptive" +msgstr "Adaptív" + +#: +msgctxt "#32978" +msgid "Enable this if your hardware can handle VC1. Disable it to force transcoding." +msgstr "Engedélyezze, ha a hardver képes kezelni a VC1-et. Ha kikapcsolja, akkor az átkódolást kényszeríti ki." + +#: +msgctxt "#32979" +msgid "Allows the server to only transcode streams of a video that need transcoding, while streaming the others unaltered. If disabled, force the server to transcode everything not direct playable." +msgstr "Lehetővé teszi a szerver számára, hogy a videónak csak az átkódolást igénylő folyamjait kódolja át, míg a többit változatlanul továbbítja. Ha letiltja, akkor a szervert arra kényszeríti, hogy minden olyan adatot átkódoljon, amely nem játszható le közvetlenül." + +#: +msgctxt "#32980" +msgid "Refresh Users" +msgstr "Felhasználók frissítése" + +#: +msgctxt "#32981" +msgid "Background worker count" +msgstr "Háttérfolyamatok száma" + +#: +msgctxt "#32982" +msgid "Depending on how many cores your CPU has and how much it can handle, increasing this might improve certain situations. If you experience crashes or other annormalities, leave this at its default (3). Needs an addon restart." +msgstr "Attól függően, hogy hány processzormaggal rendelkezik és mennyit tud kezelni, ennek növelése bizonyos helyzetekben javíthat. Ha összeomlásokat vagy egyéb rendellenességeket tapasztal, hagyja az alapértelmezett értéket (3). Szükség van a kiegészítő újraindítására." + +#: +msgctxt "#32985" +msgid "Modern" +msgstr "Modern" + +#: +msgctxt "#32986" +msgid "Modern (dotted)" +msgstr "Modern (pontozott)" + +#: +msgctxt "#32987" +msgid "Classic" +msgstr "Klasszikus" + +#: +msgctxt "#32988" +msgid "Custom" +msgstr "Egyéni" + +#: +msgctxt "#32989" +msgid "Modern (colored)" +msgstr "Modern (színes)" + +#: +msgctxt "#32990" +msgid "Handle plex.direct mapping" +msgstr "plex.direct hozzárendelés kezelése" + +#: +msgctxt "#32991" +msgid "Notify" +msgstr "Értesítés" + +#: +msgctxt "#32992" +msgid "When using servers with a plex.direct connection (most of them), should we automatically adjust advancedsettings.xml to cope with plex.direct domains? If not, you might want to add plex.direct to your router's DNS rebind exemption list." +msgstr "Ha „plex.direct” kapcsolattal rendelkező szervereket használunk (a legtöbbet), akkor az „advancedsettings.xml”-t automatikusan úgy kell beállítanunk, hogy megbirkózzon a „plex.direct” tartománynevekkel? Ha nem, akkor érdemes felvenni a „plex.direct”-et az útválasztó DNS-újrakötési mentességi listájára." + +#: +msgctxt "#32993" +msgid "{} unhandled plex.direct connections found" +msgstr "{} kezeletlen plex.direct kapcsolatokat találtunk" + +#: +msgctxt "#32994" +msgid "In order for PM4K to work properly, we need to add special handling for plex.direct connections. We've found {} new unhandled connections. Do you want us to write those to Kodi's advancedsettings.xml automatically? If not, you might want to add plex.direct to your router's DNS rebind exemption list. This can be changed in the settings as well." +msgstr "Ahhoz, hogy a PM4K megfelelően működjön, speciális kezelést kell adnunk a plex.direct kapcsolatokhoz. Találtunk {} új kezeletlen kapcsolato(ka)t. Szeretné, ha ezeket automatikusan beírnánk a Kodi advancedsettings.xml-be? Ha nem, akkor érdemes a plex.direct-et hozzáadni az útválasztó DNS-újrakötési mentességi listájához. Ezt is meg lehet változtatni a beállításokban." + +#: +msgctxt "#32995" +msgid "Advancedsettings.xml modified (plex.direct mappings)" +msgstr "advancedsettings.xml módosítva (plex.direct hozzárendelés)" + +#: +msgctxt "#32996" +msgid "The advancedsettings.xml file has been modified. Please restart Kodi for the changes to apply." +msgstr "Az advancedsettings.xml fájl módosult. Kérjük, indítsa újra a Kodit, hogy a módosítások érvényesüljenek." + +#: +msgctxt "#32997" +msgid "OK" +msgstr "OK" + +#: +msgctxt "#32998" +msgid "Use new Continue Watching hub on Home" +msgstr "Használja az új „Lejátszás folytatása” megjelenítést a kezdőképernyőn" + +#: +msgctxt "#32999" +msgid "Instead of separating Continue Watching and On Deck hubs, behave like the modern Plex clients, which combine those two types of hubs into one Continue Watching hub." +msgstr "Ahelyett, hogy szétválasztaná a „Lejátszás folytatása” és a kezdőlapot, viselkedjen úgy, mint a modern Plex kliensek, amelyek ezt a két típusú megjelenítést egyetlen „Lejátszás folytatása” ablakban egyesítik." + +#: +msgctxt "#33000" +msgid "Enable path mapping" +msgstr "Elérési útvonal engedélyezése" + +#: +msgctxt "#33002" +msgid "Verify mapped files exist" +msgstr "Ellenőrizze a hozzárendelt fájlok létezését" + +#: +msgctxt "#33003" +msgid "When path mapping is enabled and we've successfully mapped a file, verify its existence." +msgstr "Ha az elérési útvonal hozzárendelése engedélyezve van, és sikeresen hozzárendeltünk egy fájlt, ellenőrizzük annak létezését." + +#: +msgctxt "#33004" +msgid "No spoilers without OSD" +msgstr "Nincs ismertető OSD nélkül" + +#: +msgctxt "#33005" +msgid "When seeking without the OSD open, hide all time-related information from the user." +msgstr "OSD nélküli léptetés esetében minden idővel kapcsolatos információt el kell rejteni a felhasználó elől." + +#: +msgctxt "#33006" +msgid "No TV spoilers" +msgstr "Nincs TV-ismertető" + +#: +msgctxt "#33008" +msgid "[Spoilers removed]" +msgstr "[Ismertető eltávolítva]" + +#: +msgctxt "#33013" +msgid "When the above is anything but \"off\", hide episode titles as well." +msgstr "Ha a fentiek minden, csak nem „ki”, rejtse el az epizódcímeket is." + +#: +msgctxt "#33014" +msgid "Ignore plex.direct docker hosts" +msgstr "A plex.direct docker kiszolgálók figyelmen kívül hagyása" + +#: +msgctxt "#33015" +msgid "When checking for plex.direct host mapping, ignore local Docker IPv4 addresses (172.16.0.0/12)." +msgstr "A plex.direct kiszolgáló hozzárendelésének ellenőrzése során hagyja figyelmen kívül a helyi Docker IPv4-címeket (172.16.0.0/12)." + +#: +msgctxt "#32303" +msgid "Season {}" +msgstr "Évad {}" + +#: +msgctxt "#32304" +msgid "Episode {}" +msgstr "Epizód {}" + +#: +msgctxt "#32310" +msgid "S{}" +msgstr "É{}" + +#: +msgctxt "#32311" +msgid "E{}" +msgstr "E{}" + +#: +msgctxt "#32938" +msgid "Only for Episodes/Playlists" +msgstr "Csak epizódok/lejátszási listák esetén" + +#: +msgctxt "#33018" +msgid "Cache Plex Home users" +msgstr "Plex otthoni felhasználók gyorsítótára" + +#: +msgctxt "#33019" +msgid "Visit media item" +msgstr "Médiaelem megtekintése" + +#: +msgctxt "#33020" +msgid "Play" +msgstr "Lejátszás" + +#: +msgctxt "#33021" +msgid "Choose action" +msgstr "Művelet kiválasztása" + +#: +msgctxt "#33026" +msgid "Map path: {}" +msgstr "Térkép útvonal: {}" + +#: +msgctxt "#33027" +msgid "Remove mapping: {}" +msgstr "Hozzárendelés eltávolítása: {}" + +#: +msgctxt "#33028" +msgid "Hide library" +msgstr "Könyvtár elrejtése" + +#: +msgctxt "#33029" +msgid "Show library: {}" +msgstr "Könyvtár megjelenítése: {}" + +#: +msgctxt "#33030" +msgid "Choose action for: {}" +msgstr "Művelet kiválasztása a(z): {}" + +#: +msgctxt "#33031" +msgid "Select Kodi source for {}" +msgstr "Kodi forrás kiválasztása a(z) {}" + +#: +msgctxt "#33032" +msgid "Show path mapping indicators" +msgstr "Elérési útvonal hozzárendelés-jelzők megjelenítése" + +#: +msgctxt "#33033" +msgid "When path mapping is active for a library, display an indicator." +msgstr "Ha a könyvtárhoz aktív az elérési útvonal, jelzőt kell megjeleníteni." + +#: +msgctxt "#33035" +msgid "Delete {}: {}?" +msgstr "Töröljem {}: {}?" + +#: +msgctxt "#33036" +msgid "Delete episode S{0:02d}E{1:02d} from {2}?" +msgstr "Töröljem az epizódot az É{0:02d}E{1:02d} a(z) {2}?" + +#: +msgctxt "#33037" +msgid "Maximum intro offset to consider" +msgstr "Figyelembe veendő maximális főcím eltolása" + +#: +msgctxt "#33039" +msgid "Move" +msgstr "Mozgat" + +#: +msgctxt "#33040" +msgid "Reset library order" +msgstr "Könyvtár sorrendjének visszaállítása" + +#: +msgctxt "#33034" +msgid "Library settings" +msgstr "Könyvtár beállítások" + +#: +msgctxt "#32357" +msgid "By Title" +msgstr "Cím szerint" + +#: +msgctxt "#32358" +msgid "Title" +msgstr "Cím" + +#: +msgctxt "#33041" +msgid "Show hub: {}" +msgstr "TV-műsorok csomópont: {}" + +#: +msgctxt "#33042" +msgid "Episode Date Added" +msgstr "Epizód dátuma hozzáadva" + +#: +msgctxt "#33001" +msgid "Long-press (or context menu) on a library item or: Honor path_mapping.json in the addon_data/script.plexmod folder when DirectPlaying media. This can be used to stream using other techniques such as SMB/NFS/etc. instead of the default HTTP handler. path_mapping.example.json is included in the addon's main directory." +msgstr "Hosszan nyomjon (vagy kattintson a helyi menüre) egy könyvtári elemet, vagy: a path_mapping.json fájlt az addon_data/script.plexmod mappában, amikor közvetlen lejátszás használ. Ez használható más technikák, például SMB/NFS/stb. segítségével történő streamelésre az alapértelmezett HTTP kezelő helyett. path_mapping.example.json a kiegészítő főkönyvtárában található." + +#: +msgctxt "#33043" +msgid "Hubs round-robin" +msgstr "Körkörös csomópontok" + +#: +msgctxt "#33045" +msgid "Behave like official Plex clients" +msgstr "Viselkedjen úgy, mint a hivatalos Plex kliensek" + +#: +msgctxt "#32025" +msgid "Direct Play" +msgstr "Közvetlen lejátszás" + +#: +msgctxt "#32026" +msgid "Direct Stream" +msgstr "Közvetlen folyam" + +#: +msgctxt "#32036" +msgid "4K" +msgstr "4K" + +#: +msgctxt "#32037" +msgid "HEVC (h265)" +msgstr "HEVC (h265)" + +#: +msgctxt "#32601" +msgid "AV1" +msgstr "AV1" + +#: +msgctxt "#32932" +msgid "Subtitle quick-actions" +msgstr "Felirat gyorsbeavatkozások" + +#: +msgctxt "#32933" +msgid "FFWD/RWD" +msgstr "ELŐRE/HÁTRA" + +#: +msgctxt "#32934" +msgid "Repeat" +msgstr "Ismétlés" + +#: +msgctxt "#32935" +msgid "Shuffle" +msgstr "Keverés" + +#: +msgctxt "#32939" +msgid "User-specific.\n" +"Only applies to video player UI" +msgstr "Felhasználó specifikus.\n" +"Csak a videólejátszó felhasználói felületére vonatkozik" + +#: +msgctxt "#32977" +msgid "VC1" +msgstr "VC1" + +#: +msgctxt "#32983" +msgid "Theme" +msgstr "Téma" + +#: +msgctxt "#32984" +msgid "Sets the theme. Currently only customizes all control buttons. ATTENTION: [I]Might[/I] need an addon restart.\n" +"In order to customize this, copy one of the xml's in script.plexmod/resources/skins/Main/1080i/templates to addon_data/script.plexmod/templates/{templatename}_custom.xml and adjust it to your liking, then select \"Custom\" as your theme." +msgstr "Beállítja a témát. Jelenleg az összes vezérlőgombot testre szabja. FIGYELEM: [I]Lehet[/I], hogy szükség lesz egy kiegészítő újraindítására.\n" +"A testreszabáshoz másold az egyik xml-t a script.plexmod/resources/skins/Main/1080i/templates fájlból az addon_data/script.plexmod/templates/{templatename}_custom.xml fájlba, és állítsd be tetszésed szerint, majd válaszd a „Egyéni”-t témának." + +#: +msgctxt "#33011" +msgid "In progress" +msgstr "Folyamatban" + +#: +msgctxt "#33016" +msgid "Allow TV spoilers for" +msgstr "TV-ismertetők engedélyezése a(z)" + +#: +msgctxt "#33017" +msgid "Overrides the above for specific genres. Default: Reality, Game Show, Documentary, Sport" +msgstr "Felülírja a fentieket bizonyos műfajok esetében. Alapértelmezett: Reality, Játék Show, Dokumentumfilm, Sport." + +#: +msgctxt "#33024" +msgid "Hide background in modern indicators" +msgstr "Háttér elrejtése a modern jelölők esetében" + +#: +msgctxt "#33044" +msgid "Allow round-robining in hubs. Attention: Loads a lot of items. Depending on the hub size, this can lead to crashes. Current limit: {}; can be adjusted in addon settings." +msgstr "A körkörös-keresztezés engedélyezése a csomópontokban. Figyelem! Sok elemet tölt be. A csomópont méretétől függően ez összeomlásokhoz vezethet. Jelenlegi határérték: {}; az addon beállításaiban módosítható." + +#: +msgctxt "#33046" +msgid "When pressing up/down while the OSD isn't shown, show OSD instead of skipping chapters. Additionally, when pressing down while the OSD is shown, open the chapters (if available)." +msgstr "A fel/le gomb megnyomásával, miközben az OSD nincs megjelenítve, a fejezetek átugrása helyett az OSD jelenik meg. Továbbá, ha lenyomja a gombot, miközben az OSD megjelenik, megnyílnak a fejezetek (ha rendelkezésre állnak)." + +#: +msgctxt "#33047" +msgid "Hubs round-robin item limit" +msgstr "Csomópontok körkörös-keresztezés tételkorlátozás" + +#: +msgctxt "#33048" +msgid "When hubs round-robin is enabled, only round-robin until this item limit. If the hub size exceeds this limit, the remaining items will be lazy loaded as usual. Tested minimum safe value on NVIDIA SHIELD 2019 is 1000. Default: 250" +msgstr "Ha a csomópontok körkörös funkciója engedélyezve van, akkor csak addig a ponthatárig működik ez a funkció. Ha a csomópont mérete meghaladja ezt a határt, a fennmaradó elemek a szokásos módon lassan töltődnek be. A tesztelt minimális biztonságos érték NVIDIA SHIELD 2019-en 1000. Alapértelmezett: 250" + +#: +msgctxt "#33049" +msgid "Max retries" +msgstr "Maximális próbálkozás" + +#: +msgctxt "#33051" +msgid "Use CA certificate bundle" +msgstr "CA tanúsítványcsomag használata" + +#: +msgctxt "#33053" +msgid "System" +msgstr "Rendszer" + +#: +msgctxt "#33055" +msgid "Custom" +msgstr "Egyéni" + +#: +msgctxt "#33056" +msgid "None" +msgstr "Nincs" + +#: +msgctxt "#33057" +msgid "Show buttons" +msgstr "Gombok megjelenítése" + +#: +msgctxt "#33058" +msgid "Playback features" +msgstr "Lejátszási funkciók" + +#: +msgctxt "#33059" +msgid "Additional codecs" +msgstr "További kodekek" + +#: +msgctxt "#33060" +msgid "{feature_ds}: {desc_ds}\n" +"{feature_4k}: {desc_4k}" +msgstr "{feature_ds}: {desc_ds}\n" +"{feature_4k}: {desc_4k}" + +#: +msgctxt "#33061" +msgid "Enable certain codecs if your hardware supports them. Disable them to force transcoding." +msgstr "Engedélyezzen bizonyos kodekeket, ha a hardver támogatja őket. Az átkódolás kikapcsolásával kikényszerítheti az átkódolást." + +#: +msgctxt "#33062" +msgid "Compiling templates" +msgstr "Sablonok összeállítása" + +#: +msgctxt "#33063" +msgid "Looking for custom templates" +msgstr "Egyéni sablonok keresése" + +#: +msgctxt "#33064" +msgid "Rendering: {}" +msgstr "Renderelés: {}" + +#: +msgctxt "#33065" +msgid "Complete" +msgstr "Végrehajtva" + +#: +msgctxt "#33066" +msgid "Cache template files" +msgstr "Gyorsítótár sablonfájlok" + +#: +msgctxt "#33067" +msgid "Doesn't throw away the template source files after compiling them. Uses slightly more memory but increases the speed of theme-related changes." +msgstr "Összeállítás után nem dobja el a sablon forrásfájljait. Kicsit több memóriát használ, de növeli a témával kapcsolatos változtatások sebességét." + +#: +msgctxt "#33068" +msgid "Always compile templates" +msgstr "Mindig állítson össze sablonokat" + +#: +msgctxt "#33069" +msgid "Recompiles all templates on every startup. Useful for template/theme development." +msgstr "Minden indításkor összeállítja az összes sablont. Hasznos a sablon/téma fejlesztéséhez." + +#: +msgctxt "#33070" +msgid "Action on Wake event" +msgstr "Ébresztésre vonatkozó művelet" + +#: +msgctxt "#33071" +msgid "Restart PM4K" +msgstr "PM4K újraindítása" + +#: +msgctxt "#33072" +msgid "Wait for {}s" +msgstr "Várjon a(z) {}s" + +#: +msgctxt "#33073" +msgid "Wait after wakeup" +msgstr "Várakozás ébresztés után" + +#: +msgctxt "#33074" +msgid "Waiting {} second(s)" +msgstr "Várakozás {} másodperc" + +#: +msgctxt "#33076" +msgid "Modern (2024)" +msgstr "Modern (2024)" + +#: +msgctxt "#33077" +msgid "Scale modern indicators" +msgstr "Modern jelölők méretezése" + +#: +msgctxt "#33078" +msgid "Scale the modern indicators based on the poster size used. Default: On (tiny: 0.75, small: 1.0, medium: 1.175, big: 1.3)" +msgstr "A modern jelölőket a használt plakátméret alapján méretezze. Alapértelmezett: (apró: 0,75, kicsi: 1,0, közepes: 1,175, nagy: 1,3)" + +#: +msgctxt "#33079" +msgid "Hi-Res Music" +msgstr "Hi-Res zene" + +#: +msgctxt "#33080" +msgid "Allow DirectPlay of high resolution music (e.g. FLAC, >= 192 kHz)" +msgstr "A nagy felbontású zenék (pl. FLAC, >= 192 kHz) DirectPlay lejátszásának engedélyezése." + +#: +msgctxt "#33081" +msgid "Blur chapter images" +msgstr "Fejezetképek homályosítása" + +#: +msgctxt "#33082" +msgid "Scan Library Files" +msgstr "Könyvtári fájlok beolvasása" + +#: +msgctxt "#33083" +msgid "Empty Trash" +msgstr "Kuka ürítése" + +#: +msgctxt "#33084" +msgid "Analyze" +msgstr "Elemezze" + +#: +msgctxt "#33085" +msgid "Map key to home" +msgstr "Gomb hozzárendelése a kezdőképernyőhöz" + +#: +msgctxt "#33086" +msgid "Press the key you want to map to go to home within {} seconds" +msgstr "Nyomja meg azt a gombot, amelyet le akar képezni, hogy {} másodpercen belül a kezdőképernyőre váltson." + +#: +msgctxt "#33620" +msgid "Plex server connect timeout" +msgstr "Plex szerver kapcsolódási időkorlát" + +#: +msgctxt "#33621" +msgid "Sets the maximum amount of time to connect to a Plex Server in seconds. Default: 5" +msgstr "Beállítja a Plex szerverhez való csatlakozás maximális időtartamát másodpercben. Alapértelmezett: 5" + +#: +msgctxt "#32940" +msgid "Video Player" +msgstr "Videólejátszó" + +#: +msgctxt "#33050" +msgid "The maximum number of retries each connection should attempt. The default (3) helps with hibernation/wakeup issues. Set higher for bad connections, setting this to 0 was the old default before 0.7.9" +msgstr "Az egyes kapcsolatok maximálisan megkísérelhető újbóli próbálkozásainak száma. Az alapértelmezett (3) segít a hibernációs/ébredési problémák esetén. Rossz kapcsolatok esetén magasabb értéket kell beállítani, a 0 volt a régi alapértelmezett érték a 0.7.9-es verzió előtt." + +#: +msgctxt "#33075" +msgid "Do something after waking up. Waiting for a time until resuming normal execution helps with networks coming back up after sleep events. Default: 1 second (5 seconds, CoreELEC)." +msgstr "Ébredés után történjen valami. A normális működés folytatásához szükséges idő megvárása segít az alvó mód után újra működésbe lépő hálózatoknál. Alapértelmezett: 1 másodperc (5 másodperc, CoreELEC)." + +#: +msgctxt "#33087" +msgid "Bind a key to immediately go to the Home view. Cancel using BACK/PREVIOUS_MENU. Clear bound key using the STOP or CONTEXT_MENU (long press OK/Enter) button on the setting." +msgstr "Egy gomb programozásával azonnal a kezdőképernyőre léphet. Megszakítás a VISSZA/PREVIOUS_MENU használatával. Törölje a programozott gombot a STOP vagy a CONTEXT_MENU (OK/Enter hosszú nyomása) gomb használatával a beállításon." + +#: +msgctxt "#33088" +msgid "Only applies to video player UI" +msgstr "Csak a videolejátszó felhasználói felületére vonatkozik" + +#: +msgctxt "#33089" +msgid "Automatic seek-back" +msgstr "Automatikus visszaugrás" + +#: +msgctxt "#33090" +msgid "If your audio doesn't resume as fast as the video, or you want to catch up after a longer pause, use this to seek back after resuming from pause to compensate for the delay. Default: Off" +msgstr "Ha a hang nem folytatódik olyan gyorsan, mint a videó, vagy ha hosszabb szünet után szeretné bepótolni a lemaradást, használja a visszaugrás funkciót a szünetről való folytatás után a késleltetés kompenzálására. Alapértelmezett: Ki" + +#: +msgctxt "#33091" +msgid "{sec_or_ms} {unit_s_or_ms}" +msgstr "{sec_or_ms} {unit_s_or_ms}" + +#: +msgctxt "#33092" +msgid "Seek back on pause" +msgstr "Visszaugrás szüneteltetéskor" + +#: +msgctxt "#33093" +msgid "Seek back after" +msgstr "Visszaugrás" + +#: +msgctxt "#33094" +msgid "Only seek back after having paused at least a certain amount of seconds" +msgstr "Csak akkor ugrik vissza, ha legalább egy bizonyos számú másodpercnyi szünetet tartott" + +#: +msgctxt "#33095" +msgid "Seek back on pause instead of on resume. When Transcoding you should enable this." +msgstr "Szüneteltetés esetén visszaugrás a folytatás helyett. Átkódoláskor ezt engedélyeznie kell." + +#: +msgctxt "#33096" +msgid "Only with Direct Play" +msgstr "Csak közvetlen lejátszással" + +#: +msgctxt "#33097" +msgid "Enable seek back ony when we're Direct Playing, not Transcoding." +msgstr "Csak akkor engedélyezze a visszaugrást, ha közvetlen lejátszásról van szó, nem pedig átkódolásról." + +#: +msgctxt "#33098" +msgid "Tickrate (Hz)" +msgstr "Ketyegésmérték (Hz)" + +#: +msgctxt "#33099" +msgid "Controls how often certain ticks are performed on GUI windows and the SeekDialog/VideoPlayer and how fast certain events are handled. Can be expensive when bigger than 1 Hz (once per second), can cause quirks when lower than 1 Hz (less than once per second). Depends on the hardware. Default: 1 Hz, Max: 10 Hz (every 100 ms), Sane highest and old default: 10 Hz (every 100 ms)" +msgstr "Szabályozza, hogy bizonyos kattintások milyen gyakran történjenek a GUI és a SeekDialog/VideoPlayer ablakokon, és milyen gyorsan kezeljen bizonyos eseményeket. Gond lehet, ha nagyobb, mint 1 Hz (másodpercenként egyszer), furcsaságokat okozhat, ha kisebb, mint 1 Hz (kevesebb, mint másodpercenként egyszer). A hardvertől függ. Alapértelmezett: 1 Hz, Max: 10 Hz (100 ms-ként), legmagasabb és régi alapértelmezett: 10 Hz (100 ms-ként)." + +#: +msgctxt "#33636" +msgid "Plex server read timeout" +msgstr "Plex szerver olvasási időkorlát" + +#: +msgctxt "#33637" +msgid "Sets the maximum amount of time to read from a Plex Server in seconds. Default: 10" +msgstr "Beállítja a Plex szerverről történő olvasás maximális időtartamát másodpercben. Alapértelmezett: 10" + +#: +msgctxt "#33638" +msgid "Plex.tv connect timeout" +msgstr "plex.tv kapcsolódási időkorlát" + +#: +msgctxt "#33640" +msgid "Plex.tv read timeout" +msgstr "plex.tv olvasási időkorlát" + +#: +msgctxt "#33642" +msgid "Dump config" +msgstr "Konfiguráció betöltése" + +#: +msgctxt "#33643" +msgid "Dumps all user settings into the log on startup, when DEBUG logging is enabled. Masks private information." +msgstr "Minden felhasználói beállítást a naplóba tölt indításkor, ha a DEBUG naplózás engedélyezve van. Magánjellegű információk elfedése." + +#: +msgctxt "#33644" +msgid "Tracks" +msgstr "Zeneszámok" + +#: +msgctxt "#33645" +msgid "plex.direct: Honor plex.tv's dnsRebindingProtection flag (DNS)" +msgstr "plex.direct: a plex.tv dnsRebindingProtection jelölésnek (DNS)" + +#: +msgctxt "#33646" +msgid "Only handle plex.direct hosts when the server's attributes dnsRebindingProtection=1. Might not work in certain situations. Disable if you have connection issues. Default: On" +msgstr "Csak akkor kezeli a plex.direct kiszolgálókat, ha a kiszolgáló dnsRebindingProtection=1 attribútuma. Bizonyos helyzetekben nem biztos, hogy működik. Kapcsolati problémák esetén tiltsa le. Alapértelmezett: On" + +#: +msgctxt "#33647" +msgid "plex.direct: Honor plex.tv's publicAddressMatches flag (DNS)" +msgstr "plex.direct: a plex.tv publicAddressMatches jelölásnek (DNS)" + +#: +msgctxt "#33648" +msgid "Only handle plex.direct hosts when the server's attributes publicAddressMatches=1. Might not work in certain situations. Disable if you have connection issues. Default: On" +msgstr "Csak akkor kezeli a plex.direct kiszolgálókat, ha a kiszolgáló publicAddressMatches=1 attribútuma. Bizonyos helyzetekben nem biztos, hogy működik. Kapcsolati problémák esetén tiltsa le. Alapértelmezett: On" + +#: +msgctxt "#32101" +msgid "If enabled, when playback ends and there is a 'Next Up' item available, it will be automatically be played after a {} second delay." +msgstr "Engedélyezése esetén a lejátszás végeztével, ha van egy következő elem, az automatikusan lejátszásra kerül {} másodperces késleltetés után." + +#: +msgctxt "#33651" +msgid "Startup delay" +msgstr "Indítás késleltetése" + +#: +msgctxt "#33652" +msgid "Never show Post Play" +msgstr "Ne jelenjen meg a folytatás" + +#: +msgctxt "#33506" +msgid "Show the intro skip button from the start of a video with an intro marker. The auto-skipping setting applies. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "A főcím átugrása gomb megjelenítése a videó elejétől kezdve egy főcím jelölővel. Az automatikus átugrás beállítása érvényes. Nem írja felül a bekapcsolt folyamatosnézés-módot.\n" +"Tévéműsoronként ki/bekapcsolható." + +#: +msgctxt "#33619" +msgid "Automatically skips episode intros, credits and tries to skip episode recaps. Doesn't skip the intro of the first episode of a season and doesn't skip the final credits of a show.\n" +"\n" +"Can be disabled/enabled per TV show.\n" +"Overrides any setting below." +msgstr "Automatikusan kihagyja az epizódok főcímeit, a stáblistát és megpróbálja kihagyni az epizódok összefoglalóit. Nem hagyja ki az évad első epizódjának főcímét, és nem hagyja ki a sorozat utolsó stáblistáját.\n" +"\n" +"TV-műsoronként ki/bekapcsolható.\n" +"Felülírja az alábbi beállításokat." + +#: +msgctxt "#33052" +msgid "Which CA certificate bundle to use for request HTTPS verification. \"system\" uses the system-provided certificate bundle. \"ACME\" uses an extremely small bundle provided with the addon, which includes root certificates for Letsencrypt (plex.direct) and ZeroSSL. \"custom\" allows for a ca-certificate bundle in userdata/addon_data/script.plexmod/custom_bundle.crt" +msgstr "Melyik CA tanúsítványcsomagot használja a HTTPS-ellenőrzéshez. A „rendszer” az általa biztosított tanúsítványcsomagot használja. Az „ACME” a kiegészítővel biztosított rendkívül kis csomagot használja, amely a Letsencrypt (plex.direct) és a ZeroSSL gyökértanúsítványait tartalmazza. Az „egyéni” lehetővé teszi a CA-tanúsítványköteg használatát a userdata/addon_data/script.plexmod/custom_bundle.crt fájlban." + +#: +msgctxt "#33054" +msgid "ACME (addon-supplied)" +msgstr "ACME (kiegészítő által biztosított)" + +#: +msgctxt "#33639" +msgid "Plex.tv: Sets the maximum amount of time to connect to Plex.tv in seconds. Default: 1" +msgstr "Plex.tv: Plex.tv-hez való kapcsolódás maximális időtartamának beállítása másodpercben. Alapértelmezett: 1" + +#: +msgctxt "#33641" +msgid "Plex.tv: Sets the maximum amount of time to read from Plex.tv in seconds. Default: 2" +msgstr "Plex.tv: Plex.tv-ről történő olvasás maximális időtartamának beállítása másodpercben. Alapértelmezett: 2" + +#: +msgctxt "#33653" +msgid "Background image resolution scaling %" +msgstr "Háttérkép felbontásának méretezése %" + +#: +msgctxt "#33654" +msgid "In percent, based on 1080p. Scales the resolution of all backgrounds for better image quality. May impact PMS/PM4K performance, will increase the cache usage accordingly. Can lead to crashes if your hardware is low on RAM. Needs addon restart. Use 400% for 4K. ATTENTION: In order for this to work you need to add 2160 and 2160 to your advancedsettings.xml." +msgstr "Százalékban, 1080p alapján. A jobb képminőség érdekében az összes háttér felbontását átméretezi. Befolyásolhatja a PMS/PM4K teljesítményét, ennek megfelelően növeli a gyorsítótár használatát. Összeomlásokhoz vezethet, ha kevés a RAM. Kiegészítő újraindítása szükséges. 4K esetén 400%-ot használjon. FIGYELEM: Ahhoz, hogy ez működjön, hozzá kell adni a 2160 és 2160 elemeket az advancedsettings.xml fájlhoz." + +#: +msgctxt "#33655" +msgid "Auto-Sync Subtitles" +msgstr "Feliratok automatikus szinkronizálása" + +#: +msgctxt "#33656" +msgid "Only for External SRT subtitles. The PMS setting for voice activity detection has to be enabled for this to work." +msgstr "Csak külső SRT feliratok esetében. Ahhoz, hogy ez működjön, engedélyezni kell a PMS hangaktivitás-érzékelési beállítást." + +#: in player quick settings UI, needs to be short +msgctxt "#33657" +msgid "Enable Auto-Sync" +msgstr "Automatikus szinkronizálás engedélyezése" + +#: in player quick settings UI, needs to be short +msgctxt "#33658" +msgid "Disable Auto-Sync" +msgstr "Automatikus szinkronizálás letiltása" + +#: +msgctxt "#33659" +msgid "Hide hub: {}" +msgstr "Csomópont elrejtése: {}" + +#: +msgctxt "#33660" +msgid "Disable HDR" +msgstr "HDR letiltása" + +#: +msgctxt "#33661" +msgid "If you don't want your client to handle HDR (or HDR-fallback), enable this to force transcoding. Doesn't apply to DV Profile 5." +msgstr "Ha nem szeretné, hogy a kliens kezelje a HDR-t (vagy a HDR-átalakítást), engedélyezze ezt az opciót az átkódolás kikényszerítéséhez. Nem vonatkozik a DV Profile 5-re." + +#: +msgctxt "#33662" +msgid "Remove from Continue Watching" +msgstr "Lejátszás folytatásából való törlés" + +#: +msgctxt "#33663" +msgid "Home: Confirm item actions" +msgstr "Kezdőképernyő: Megerősítem a műveleteket" + +#: +msgctxt "#33664" +msgid "When acting on items in the Home view, such as mark played, hide from continue watching etc., show a confirmation dialog." +msgstr "Kezdőképernyőn lévő elemekkel kapcsolatos műveletek, például lejátszott elem jelölése, lejátszás folytatása elrejtése stb. esetén megjelenik egy megerősítő párbeszédpanel." + +#: +msgctxt "#33665" +msgid "Disable audio codecs" +msgstr "Hangkodekek letiltása" + +#: +msgctxt "#33666" +msgid "Audio codecs you can't play back. Disables Direct Play for such media items, enables Direct Stream if possible, transcodes audio stream to compatible format." +msgstr "Nem lejátszható hangkodekek. Letiltja a közvetlen lejátszást az ilyen médiaelemek esetében, ha lehetséges, engedélyezi a közvetlen folyamot, átkódolja a hangfolyamot kompatibilis formátumba." + +#: +msgctxt "#32002" +msgid "20 Mbps" +msgstr "20 Mbps" + +#: +msgctxt "#32003" +msgid "12 Mbps" +msgstr "12 Mbps" + +#: +msgctxt "#32004" +msgid "10 Mbps" +msgstr "10 Mbps" + +#: +msgctxt "#32005" +msgid "8 Mbps" +msgstr "8 Mbps" + +#: +msgctxt "#32006" +msgid "4 Mbps" +msgstr "4 Mbps" + +#: +msgctxt "#32007" +msgid "3 Mbps" +msgstr "3 Mbps" + +#: +msgctxt "#32008" +msgid "2 Mbps" +msgstr "2 Mbps" + +#: +msgctxt "#32009" +msgid "1.5 Mbps" +msgstr "1,5 Mbps" + +#: +msgctxt "#33669" +msgid "Really sign out?" +msgstr "Valóban kilép?" + +#: +msgctxt "#33670" +msgid "Update available" +msgstr "Frissítés elérhető" + +#: +msgctxt "#33672" +msgid "Check for updates" +msgstr "Frissítések keresése" + +#: +msgctxt "#33673" +msgid "Automatically check for updates periodically. If installed from a Kodi repository and the Update Source setting is set to Repository, Kodi itself will handle the updating of this addon. Needs a Kodi restart when changed." +msgstr "Időnként automatikusan ellenőrizze a frissítéseket. Ha a Kodi-tárolóból telepíti, és a frissítés forrása a tároló, akkor a Kodi maga fogja kezelni a kiegészítő frissítését. Módosításkor a Kodi újraindítása szükséges." + +#: +msgctxt "#33674" +msgid "Check for updates on start" +msgstr "Frissítések keresése indításkor" + +#: +msgctxt "#33675" +msgid "Automatically check for updates on startup. Doesn't do much when Update source is Repository. Needs a Kodi restart when changed." +msgstr "Indításkor automatikusan ellenőrizze a frissítéseket. Tároló esetében ritkábbak a frissítések. Módosításkor a Kodi újraindítása szükséges." + +#: +msgctxt "#33676" +msgid "Update source" +msgstr "Frissítés forrása" + +#: +msgctxt "#33677" +msgid "Specifies the update mode. Will immediately check for a new version when changed and closing settings.\n" +"Default: Repository\n" +"\n" +"Beta: Bleeding edge (possibly unstable)\n" +"Stable: Stable branch (faster than Repository)\n" +"Repository: Kodi repository (official (slow) or Don't Panic)" +msgstr "Megadja a frissítési módot. A beállítások módosításakor és bezárásakor azonnal ellenőrzi az új verziót.\n" +"Alapértelmezett: Tároló\n" +"\n" +"Béta: több sebből vérezhet (esetleg instabil)\n" +"Stabil: Stabil vonal (gyorsabb, mint a Tároló)\n" +"Tároló: Kodi tároló (hivatalos (lassú) vagy Don't Panic)" + +#: +msgctxt "#33678" +msgid "Beta" +msgstr "Béta" + +#: Update mode as in (beta, stable, repository) +msgctxt "#33679" +msgid "Stable" +msgstr "Stabil" + +#: +msgctxt "#33680" +msgid "Repository" +msgstr "Tároló" + +#: +msgctxt "#33683" +msgid "Exit, download and install" +msgstr "Kilépés, letöltés és telepítés" + +#: +msgctxt "#33684" +msgid "Later" +msgstr "Később" + +#: +msgctxt "#32015" +msgid "6 Mbps" +msgstr "6 Mbps" + +#: +msgctxt "#32016" +msgid "16 Mbps" +msgstr "16 Mbps" + +#: +msgctxt "#32017" +msgid "26 Mbps" +msgstr "26 Mbps" + +#: +msgctxt "#33681" +msgid "Service updated" +msgstr "Szolgáltatás frissítve" + +#: +msgctxt "#33682" +msgid "The update {} has had changes to the updater itself. In order for the updated updater service to work, a Kodi restart is necessary. The addon will work normally, though. Do you still want to run the addon?" +msgstr "A frissítés {} magának a frissítőnek a módosítása. A szolgáltatás működéséhez, a Kodi újraindítása szükséges. Ennek ellenére a kiegészítő normálisan fog működni. Még mindig szeretné futtatni a kiegészítőt?" + +#: +msgctxt "#33685" +msgid "Clamp video bitrate" +msgstr "Videó bitrátájának rögzítése" + +#: +msgctxt "#33686" +msgid "Only show transcode bitrate targets lower than the current video's bitrate." +msgstr "Csak az aktuális videó bitrátájánál alacsonyabb átkódolási bitráta-célokat jeleníti meg." + +#: +msgctxt "#33687" +msgid "Translation updated" +msgstr "Fordítás frissítve" + +#: +msgctxt "#33688" +msgid "The currently in-use translation has been updated. In order to load the new translation, a Kodi restart is necessary. The addon will still run properly, but you might see badly or untranslated strings. Do you still want to run the addon?" +msgstr "A jelenleg használatban lévő fordítás frissítésre került. Az új fordítás betöltéséhez a Kodi újraindítása szükséges. A kiegészítő továbbra is megfelelően fog futni, de előfordulhat, hogy rosszul vagy le nem fordított karakterláncokat fog látni. Továbbra is szeretné futtatni a kiegészítőt?" + +#: +msgctxt "#33509" +msgid "Early intro skip threshold (default: < 120s/2m)" +msgstr "Korai főcím átugrása küszöb (alapértelmezett: < 120s/2m)" + +#: +msgctxt "#33007" +msgid "Select in which cases to blur TV episode thumbnails, previews, redact summaries, hide episode titles etc." +msgstr "Válassza ki, hogy mely esetekben szeretné elmosni a TV-epizódok miniatűrjeit, előnézeteiket, szerkeszteni az összefoglalókat, elrejteni az epizódcímeket stb." + +#: +msgctxt "#33009" +msgid "Blur amount for unplayed/in-progress episodes" +msgstr "Elmosódás mértéke a le nem játszott/folyamatban lévő epizódoknál" + +#: +msgctxt "#33010" +msgid "Unplayed" +msgstr "Nem megnézett" + +#: +msgctxt "#33012" +msgid "No unplayed episode titles" +msgstr "Nincsenek nem megnézett epizódok" + +#: +msgctxt "#33022" +msgid "Played indicators" +msgstr "Megnézett jelölők" + +#: +msgctxt "#33023" +msgid "Classic: Show orange triangle for unplayed items\n" +"Modern: Show green checkmark for played items\n" +"Modern (2024): Show white checkmark for played items\n" +"(default: Modern (2024))" +msgstr "Klasszikus: Narancssárga háromszög megjelenítése a nem lejátszott elemeknél\n" +"Modern: Zöld pipa megjelenítése a lejátszott elemeknél\n" +"Modern (2024): Fehér pipa megjelenítése a lejátszott elemeknél\n" +"(alapértelmezett: Modern (2024))" + +#: +msgctxt "#33025" +msgid "When the above is enabled, hide the black backdrop of the played state." +msgstr "Ha a fentiek engedélyezve vannak, elrejti a lejátszott állapot fekete hátterét." + +#: +msgctxt "#33689" +msgid "Service running" +msgstr "Folyamatban lévő szolgáltatás" + +#: +msgctxt "#33690" +msgid "Last update check" +msgstr "Utolsó frissítés ellenőrzése" + +#: +msgctxt "#33691" +msgid "Native languages" +msgstr "Anyanyelvek" + +#: +msgctxt "#33692" +msgid "When you usually watch things in a different language with subtitles, but are a native speaker of other languages, which you don't need subtitles for, prevent Plex from auto-selecting subtitles for those languages." +msgstr "Ha általában más nyelven nézi a feliratos dolgokat, de anyanyelvi szinten beszél más nyelveket, amelyekhez nincs szüksége feliratra, akadályozza meg, hogy a Plex automatikusan válassza ki a feliratokat ezekhez a nyelvekhez." + +#: +msgctxt "#33693" +msgid "Download subtitles using" +msgstr "Feliratok letöltése a(z)" + +#: +msgctxt "#33694" +msgid "Ask" +msgstr "Kérdezz" + +#: +msgctxt "#33695" +msgid "Where do you want to download subtitles from? Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "Honnan szeretné letölteni a feliratokat? Megjegyzés: Ez jelenleg csak a lejátszó feliratozási gyorsműveleteire vonatkozik. A felirat letöltése a folyam beállításaiban mindig a Plexet használja forrásként." + +#: +msgctxt "#33696" +msgid "No subtitles found." +msgstr "Nem található felirat." + +#: things in curly brackets are variables, don't translate them! +msgctxt "#33697" +msgid "{provider_title}, Score: {subtitle_score}{subtitle_info}" +msgstr "{provider_title}, Eredmény: {subtitle_score}{subtitle_info}" + +#: hearing impaired subtitle flag +msgctxt "#33698" +msgid "HI" +msgstr "ÜDV" + +#: forced subtitle flag +msgctxt "#33699" +msgid "forced" +msgstr "kényszerített" + +#: +msgctxt "#33700" +msgid "Download subtitles: {}" +msgstr "Feliratok letöltése: {}" + +#: +msgctxt "#33701" +msgid "Fallback to Kodi" +msgstr "Visszacsatolás a Kodiba" + +#: +msgctxt "#33702" +msgid "When no subtitles are found via the Plex subtitle search, fall back to Kodi subtitle search. Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "Ha a Plex feliratkeresője nem talál semmit, akkor térjen vissza a Kodi feliratkeresőjére. Megjegyzés: Ez jelenleg csak a lejátszó feliratozási gyorsbeavatkozásaira vonatkozik. A folyam beállításaiban a felirat letöltése mindig a Plexet használja forrásként." + +#: +msgctxt "#33703" +msgid "Download subtitles" +msgstr "Feliratok letöltése" + +#: +msgctxt "#33704" +msgid "Using which service?" +msgstr "Melyik szolgáltatást használja?" + +#: +msgctxt "#33705" +msgid "Hide ratings" +msgstr "Értékelések elrejtése" + +#: +msgctxt "#33706" +msgid "Blur images" +msgstr "Képek homályosítása" + +#: +msgctxt "#33707" +msgid "Blur images on home (in progress)" +msgstr "Képek homályosítása a kezdőképernyőn (folyamatban)" + +#: +msgctxt "#33708" +msgid "Hide summaries" +msgstr "Összefoglalók elrejtése" + +#: +msgctxt "#33709" +msgid "Show ratings for" +msgstr "Értékelések megjelenítése a(z)" + +#: +msgctxt "#33710" +msgid "Show reviews for" +msgstr "Vélemények megjelenítése a következőhöz" + +#: +msgctxt "#33711" +msgid "Always resume media" +msgstr "Mindig folytassa a médiát" + +#: +msgctxt "#33712" +msgid "When playback of an in-progress media is requested, resume it by default instead of asking whether to resume or start from the beginning." +msgstr "Ha kérés érkezik a folyamatban lévő média lejátszása iránt, alapértelmezés szerint folytassa azt, ahelyett, hogy megkérdezné, hogy folytassa-e vagy kezdje elölről." + +#: +msgctxt "#33713" +msgid "Home: Resume in-progress items" +msgstr "Kezdőképernyő: Folyamatban lévő elemek folytatása" + +#: +msgctxt "#33714" +msgid "Resume in-progress items directly instead of visiting the media." +msgstr "A folyamatban lévő elemek folytatása közvetlenül a média meglátogatása helyett." + +#: +msgctxt "#33715" +msgid "OSD hide-delay" +msgstr "OSD elrejtés-késleltetés" + +#: +msgctxt "#33716" +msgid "How long to wait until hiding the OSD. Only works with Plextuary skin or others with configurable OSD timeout. Default: 4s (+/- 1s)" +msgstr "Mennyi ideig kell várni az OSD elrejtéséig. Csak a Plextuary felszín vagy más, konfigurálható OSD időkorlátozással rendelkező felszínekkel működik. Alapértelmezett: 4s (+/- 1s)" + +#: +msgctxt "#33717" +msgid "Debug requests" +msgstr "Hibakeresések" + +#: +msgctxt "#33718" +msgid "Played" +msgstr "Megnézett" + +#: +msgctxt "#33719" +msgid "Refresh metadata" +msgstr "Metaadat frissítése" + +#: +msgctxt "#33038" +msgid "When encountering an intro marker with a start time offset greater than this, ignore it (default: 1400s/23m)" +msgstr "Ha olyan főcím jelölővel találkozik, amelynek kezdőidő eltolódása ennél nagyobb, akkor figyelmen kívül hagyja (alapértelmezett: 1400s/23m)." + +#: +msgctxt "#33649" +msgid "\"Use alternate seek\" wait for seek" +msgstr "„Alternatív léptetés használata” várjon az léptetésre" + +#: +msgctxt "#33650" +msgid "This adjusts the delay between seek-tries on problematic platforms when \"Use alternate seek\" is enabled, which fixes resume not always working or double-seeking. When you have resume/double-seek issues, increase this. Default: 500ms" +msgstr "Ez beállítja a keresési próbálkozások közötti késleltetést a problémás platformokon, amikor az „Alternatív léptetés használata” engedélyezve van, ami javítja a nem mindig működő folytatást vagy a kettős keresést. Ha folytatással/kettős kereséssel kapcsolatos problémái vannak, növelje ezt az értéket. Alapértelmezett: 500ms" + +#: +msgctxt "#33667" +msgid "Use alternate seek" +msgstr "Alternatív léptetés használata" + +#: +msgctxt "#33668" +msgid "ATTENTION: Only enable this if you have reproducible audio issues after seeking/resuming.\n" +"\n" +"Use an alternative seek method in videos, which can help in problematic scenarios; brings its own issues/quirks. Disabled by default (enabled by default for CoreELEC and LG WebOS)" +msgstr "FIGYELEM: Csak akkor engedélyezze ezt, ha a léptetés/folytatás után reprodukálható hangproblémák jelentkeznek.\n" +"\n" +"Használjon alternatív léptetési módszert a videókban, ami segíthet a problémás helyzetekben; saját problémákat/hibákat hoz. Alapértelmezés szerint letiltva (CoreELEC és LG WebOS esetén alapértelmezés szerint engedélyezve)." + +#: +msgctxt "#33720" +msgid "Clear all caches" +msgstr "Minden gyorsítótár törlése" + +#: +msgctxt "#33721" +msgid "Clear library cache (not items)" +msgstr "Könyvtári gyorsítótár törlése (nem elemek)" + +#: +msgctxt "#33722" +msgid "Libraries" +msgstr "Könyvtárak" + +#: +msgctxt "#33723" +msgid "Media Items" +msgstr "Médiaelemek" + +#: +msgctxt "#33724" +msgid "Cache Plex data for" +msgstr "Plex adatok gyorsítótárba helyezése a(z)" + +#: +msgctxt "#33725" +msgid "Persist cached Plex data" +msgstr "A gyorsítótárazott Plex adatok megőrzése" + +#: +msgctxt "#33726" +msgid "Instead of clearing the cache when exiting the addon, persist it instead. Warning: You'll most likely encounter missing items in libraries or outdated data. Use the corresponding menu functionalities to clear the cache for specific items or libraries." +msgstr "Ahelyett, hogy törli a gyorsítótárat a kiegészítőből való kilépéskor, tartsa meg azt. Figyelmeztetés: A könyvtárakban valószínűleg hiányzó elemekkel vagy elavult adatokkal fog találkozni. Használja a megfelelő menüfunkciókat a gyorsítótár törléséhez bizonyos elemek vagy könyvtárak esetében." + +#: +msgctxt "#33727" +msgid "Store Plex server responses for items and library views in a local SQLite database. Doesn't cache anything else (Home/Hubs are always up to date). Massively speeds up consecutive visits to items and libraries. Certain important events, such as watch state changes, automatically delete the item cache and its corresponding library cache. (Default: Off)" +msgstr "A Plex szerver válaszainak tárolása az elemekre és a könyvtári nézetekre egy helyi SQLite-adatbázisban. Semmi mást nem gyorsítótáraz (a Kezdőképernyő/Csomópontok mindig naprakész). Jelentősen felgyorsítja az elemek és könyvtárak egymást követő látogatását. Bizonyos fontos események, mint például az óra állapotának változása, automatikusan törli az elem- és a hozzá tartozó könyvtár-gyorsítótárat. (Alapértelmezett: Ki)" + +#: +msgctxt "#33728" +msgid "Clear cache for item" +msgstr "Törölje a gyorsítótárat az elemhez" + +#: +msgctxt "#33729" +msgid "Expire cached Plex data after (hours)" +msgstr "A gyorsítótárazott Plex-adatok lejárata (órák) után" + +#: +msgctxt "#33730" +msgid "Randomly" +msgstr "Véletlenszerűen" + +#: +msgctxt "#33731" +msgid "By Bitrate" +msgstr "Bitráta szerint" + +#: +msgctxt "#33732" +msgid "Bitrate" +msgstr "Bitráta" + +#: +msgctxt "#33733" +msgid "Transcode target codec" +msgstr "Átkódolás cél-kodek" + +#: +msgctxt "#33734" +msgid "Sets the target codec when transcoding/direct streaming. Overridden when \"Transcode audio to AC3\" is set." +msgstr "Beállítja a cél-kodeket átkódolás/közvetlen folyam esetén. Felülírva, ha a „Hang átkódolása AC3-ra” van beállítva." + +#: +msgctxt "#33735" +msgid "Always auto-start" +msgstr "Mindig automatikus indítás" + +#: +msgctxt "#33736" +msgid "By default PM4K won't auto-start when the current window isn't a Home-derived window (e.g.: Settings). Enable this if you always want to auto-start PM4K regardless of the current window. Can also improve start-up time." +msgstr "Alapértelmezés szerint a PM4K nem indul el automatikusan, ha az aktuális ablak nem a kezdőképernyőről származó ablak (pl.: Beállítások). Engedélyezze ezt, ha azt szeretné, hogy a PM4K mindig automatikusan induljon, függetlenül az aktuális ablaktól. Az indítási időt is javíthatja." + +#: +msgctxt "#33737" +msgid "Loop theme music" +msgstr "Főcímzene hozzárendelése" + +#: +msgctxt "#33671" +msgid "Current: {current_version}\n" +"New: {new_version}\n" +"\n" +"Changelog:\n" +"{changelog}" +msgstr "Jelenlegi: {current_version}\n" +"Új: {new_version}\n" +"\n" +"Változások:\n" +"{changelog}" + +#: +msgctxt "#33738" +msgid "Max playlist size" +msgstr "Lejátszási lista maximális mérete" + +#: +msgctxt "#33739" +msgid "How many items to load into a Kodi playlist before chunking them remotely using Plex PlayQueues (default: 500); Reduce on memory issues/crashes." +msgstr "Hány elemet töltsön be a Kodi lejátszási listájába, mielőtt a Plex PlayQueues segítségével távolról összevágja őket (alapértelmezett: 500); memóriaproblémák/összeomlások csökkentése." + +#: +msgctxt "#33740" +msgid "Home: Episodes season thumbnails" +msgstr "Kezdőképernyő: Évadok epizódjainak miniatűrjei" + +#: +msgctxt "#33741" +msgid "Use season thumbnails/posters when displaying episodes in hubs instead of the TV show's." +msgstr "A TV-műsorok helyett használjon évadminiatűröket/plakátokat az epizódok megjelenítéséhez a csomópontokban." + +#: +msgctxt "#34000" +msgid "Watchlist" +msgstr "Figyelőlista" + +#: +msgctxt "#34001" +msgid "Released" +msgstr "Megjelent" + +#: +msgctxt "#34002" +msgid "Movies & Shows" +msgstr "Filmek & TV-műsorok" + +#: e.g.: 8 seasons +msgctxt "#34003" +msgid "{} seasons" +msgstr "{} évadok" + +#: +msgctxt "#34004" +msgid "Choose server" +msgstr "Szerver kiválasztása" + +#: +msgctxt "#34005" +msgid "Availability" +msgstr "Rendelkezésre állás" + +#: e.g.: 1 season +msgctxt "#34006" +msgid "{} season" +msgstr "{} évad" + +#: +msgctxt "#34007" +msgid "Use Watchlist" +msgstr "Figyelőlista használata" + +#: +msgctxt "#34008" +msgid "Activates the current user's Plex watchlist as a section item. Adds watchlist functionality to certain media screens. Per-user setting. Default: On" +msgstr "Aktiválja az aktuális felhasználó Plex-figyelőlistáját, mint szakasz elemet. Figyelőlista funkciót ad bizonyos média képernyőkhöz. Felhasználónkénti beállítás. Alapértelmezett: Be" + +#: +msgctxt "#34009" +msgid "Watchlist auto-remove" +msgstr "Figyelőlista automatikus eltávolítása" + +#: +msgctxt "#34010" +msgid "Automatically remove fully watched items from watchlist. Default: On" +msgstr "A megtekintett elemeket automatikusan eltávolítja a figyelőlistáról. Alapértelmezett: Be" + +#: +msgctxt "#34011" +msgid "Remove from watchlist" +msgstr "Eltávolítás a figyelőlistáról" + +#: +msgctxt "#34012" +msgid "Fast pause/resume" +msgstr "Gyors szüneteltetés/folytatás" + +#: +msgctxt "#34013" +msgid "when paused" +msgstr "szüneteltetéskor" + +#: +msgctxt "#34014" +msgid "when playing" +msgstr "lejátszás közben" + +#: +msgctxt "#34015" +msgid "User-specific. Use OK/ENTER button to pause instead of showing the OSD (which can then only be accessed using DOWN), or resume when paused. Only works with 'Behave like official Plex clients' enabled." +msgstr "Felhasználó specifikus. Az OK/ENTER gombbal szüneteltetheti a lejátszást az OSD megjelenítése helyett (amelyet ezután csak a LE gombbal érhet el), vagy folytathatja a lejátszást szüneteltetés után. Csak akkor működik, ha a „Hivatalos Plex kliensekhez hasonló viselkedés” beállítás engedélyezve van." + +#: +msgctxt "#34016" +msgid "Max shutdown wait" +msgstr "Leállás előtti várakozási idő" + +#: +msgctxt "#34017" +msgid "The plugin will try to hard exit once this time limit is reached (default: 5 seconds)" +msgstr "A kiegészítő megpróbál kilépni, ha elérte az időkorlátot (alapértelmezett: 5 másodperc)." + +#: +msgctxt "#34018" +msgid "Related Media" +msgstr "Kapcsolódó média" + +#: watchlist discover hub title prefix +msgctxt "#34019" +msgid "Discover: {}" +msgstr "Felfedezés: {}" + +#: +msgctxt "#34020" +msgid "Loading external hubs" +msgstr "Külső csomópontok betöltése" + +#: +msgctxt "#34021" +msgid "Please wait ..." +msgstr "Kérem várjon …" + +#: +msgctxt "#34022" +msgid "Video play completion behaviour" +msgstr "Videólejátszás befejezésviselkedés" + +#: +msgctxt "#34023" +msgid "Decide whether to use end credits markers to determine the 'watched' state of video items. When markers are not available the selected threshold percentage will be used." +msgstr "Döntse el, hogy a videók megtekintett állapotának meghatározásához végső stáblista-jelölőket kíván-e használni. Ha jelölők nem állnak rendelkezésre, akkor a kiválasztott küszöbérték százalékos aránya kerül alkalmazásra." + +#: +msgctxt "#34024" +msgid "at selected threshold percentage" +msgstr "a kiválasztott küszöbérték százalékánál" + +#: +msgctxt "#34025" +msgid "at final credits marker position" +msgstr "a végső stáblista-jelölő pozíciójánál" + +#: +msgctxt "#34026" +msgid "at first credits marker position" +msgstr "az első stáblista-jelölő pozíciója" + +#: +msgctxt "#34027" +msgid "earliest between threshold percent and first credits marker" +msgstr "legkorábban a küszöbérték és az első stáblista-jelölő között" + +#: +msgctxt "#32973" +msgid "Episodes: Continuous playback" +msgstr "Epizódok: Folyamatos lejátszás" + +#: +msgctxt "#34028" +msgid "\"Use alternate seek\" valid seek window" +msgstr "„Alternatív léptetés használata” érvényes keresési ablak" + +#: +msgctxt "#34029" +msgid "When \"Use alternate seek\" is enabled, any seek amount outside of this threshold window will be applied, otherwise the seek attempt will be ignored. If you encounter any seek issues, play with this value (ms, default: 2000)" +msgstr "Ha az „Alternatív léptetés használata” be van kapcsolva, akkor minden, ezen küszöbérték-ablakon kívüli ugrási érték alkalmazásra kerül, ellenkező esetben az léptetési kísérlet figyelmen kívül marad. Ha bármilyen léptetési problémát tapasztal, próbálkozzon ezzel az értékkel (ms, alapértelmezett: 2000)." + +#: +msgctxt "#34030" +msgid "Unlock Direct Play for above 4K" +msgstr "Közvetlen lejátszás feloldása 4K felett" + +#: +msgctxt "#34031" +msgid "Producer" +msgstr "Producer" + +#: +msgctxt "#34032" +msgid "Audio Language" +msgstr "Hangsáv nyelve" + +#: +msgctxt "#34033" +msgid "Subtitle Language" +msgstr "Felirat nyelve" + +#: +msgctxt "#34034" +msgid "Folder Location" +msgstr "Mappa helye" + +#: +msgctxt "#34035" +msgid "Editions" +msgstr "Kiadások" + +#: +msgctxt "#34036" +msgid "DOVI" +msgstr "DOVI" + +#: +msgctxt "#34037" +msgid "HDR" +msgstr "HDR" + +#: +msgctxt "#34038" +msgid "Force plex.direct mapping" +msgstr "Kényszerített plex.direct leképezés" + +#: +msgctxt "#34039" +msgid "If you (still) don't see any posters: Under certain circumstances when Kodi still can't resolve hostnames when loading assets, even though the host resolves natively, enable this to force plex.direct mapping via advancedsettings.xml." +msgstr "Ha (még mindig) nem lát semmilyen plakátot: Bizonyos körülmények között, amikor a Kodi továbbra sem tudja feloldani a gazdagép nevét az eszközök betöltésekor, annak ellenére, hogy a gazdagép natívan feloldódik, engedélyezze ezt a beállítást, hogy kényszerítse a plex.direct leképezést az advancedsettings.xml fájlon keresztül." + +#: +msgctxt "#34040" +msgid "By Progress" +msgstr "Folyamat szerint" + +#: +msgctxt "#34041" +msgid "Progress" +msgstr "Folyamat" + +#: +msgctxt "#34042" +msgid "By Album" +msgstr "Album szerint" + +#: +msgctxt "#34043" +msgid "Album" +msgstr "Album" + diff --git a/script.plexmod/resources/language/resource.language.it_it/strings.po b/script.plexmod/resources/language/resource.language.it_it/strings.po index 2142657d7d..05f6fe02da 100644 --- a/script.plexmod/resources/language/resource.language.it_it/strings.po +++ b/script.plexmod/resources/language/resource.language.it_it/strings.po @@ -17,46 +17,6 @@ msgctxt "#32001" msgid "Original" msgstr "Originale" -#: -msgctxt "#32002" -msgid "20 Mbps 1080p" -msgstr "20 Mbps 1080p" - -#: -msgctxt "#32003" -msgid "12 Mbps 1080p" -msgstr "12 Mbps 1080p" - -#: -msgctxt "#32004" -msgid "10 Mbps 1080p" -msgstr "10 Mbps 1080p" - -#: -msgctxt "#32005" -msgid "8 Mbps 1080p" -msgstr "8 Mbps 1080p" - -#: -msgctxt "#32006" -msgid "4 Mbps 720p" -msgstr "4 Mbps 720p" - -#: -msgctxt "#32007" -msgid "3 Mbps 720p" -msgstr "3 Mbps 720p" - -#: -msgctxt "#32008" -msgid "2 Mbps 720p" -msgstr "2 Mbps 720p" - -#: -msgctxt "#32009" -msgid "1.5 Mbps 480p" -msgstr "1.5 Mbps 480p" - #: msgctxt "#32010" msgid "720 kbps" @@ -107,16 +67,6 @@ msgctxt "#32024" msgid "Debug Logging" msgstr "Log di Debug" -#: -msgctxt "#32025" -msgid "Allow Direct Play" -msgstr "Permetti Play Diretto" - -#: -msgctxt "#32026" -msgid "Allow Direct Stream" -msgstr "Permetti Stream Diretto" - #: msgctxt "#32027" msgid "Force" @@ -157,16 +107,6 @@ msgctxt "#32035" msgid "Always" msgstr "Sempre" -#: -msgctxt "#32036" -msgid "Allow 4K" -msgstr "Permetti 4K" - -#: -msgctxt "#32037" -msgid "Allow HEVC (h265)" -msgstr "Permetti HEVC (h265)" - #: msgctxt "#32038" msgid "Automatically Sign In" @@ -292,11 +232,6 @@ msgctxt "#32100" msgid "Skip user selection and pin entry on startup." msgstr "All'avvio salta l'immissione del PIN e la selezione utente." -#: -msgctxt "#32101" -msgid "If enabled, when playback ends and there is a 'Next Up' item available, it will be automatically be played after a 15 second delay." -msgstr "Se abilitato, quando la riproduzione finisce e un 'Titolo Successivo' è disponibile, sarà automaticamente riprodotto dopo un ritardo di 15 secondi." - #: msgctxt "#32102" msgid "Enable this if your hardware can handle 4K playback. Disable it to force transcoding." @@ -637,16 +572,6 @@ msgctxt "#32356" msgid "Date Viewed" msgstr "Data di già visto" -#: -msgctxt "#32357" -msgid "By Name" -msgstr "Per nome" - -#: -msgctxt "#32358" -msgid "Name" -msgstr "Nome" - #: msgctxt "#32359" msgid "By Rating" @@ -1347,11 +1272,6 @@ msgctxt "#32543" msgid "Ends at" msgstr "Termina alle" -#: -msgctxt "#32601" -msgid "Allow AV1" -msgstr "Abilita AV1" - #: msgctxt "#32602" msgid "Enable this if your hardware can handle AV1. Disable it to force transcoding." @@ -1496,13 +1416,6 @@ msgctxt "#33505" msgid "Show intro skip button early" msgstr "Mostra il pulsante di salto intro in anticipo" -#: -msgctxt "#33506" -msgid "Show the intro skip button from the start of a video with an intro marker. The auto-skipping setting applies. Doesn't override enabled binge mode.\n" -"Can be disabled/enabled per TV show." -msgstr "Mostra il pulsante di salto intro dall'inizio di un video con un indicatore di intro. L'impostazione di auto-salto si applica. Non annulla la modalità maratona abilitata. \n" -"Può essere disattivato/attivato per ogni serie TV." - #: msgctxt "#33507" msgid "Enabled" @@ -1513,11 +1426,6 @@ msgctxt "#33508" msgid "Disabled" msgstr "Disattivato" -#: -msgctxt "#33509" -msgid "Early intro skip threshold (default: < 60s/1m)" -msgstr "Soglia di salto anticipato delle Intro (predefinito: < 60s/1m)" - #: msgctxt "#33510" msgid "When showing the intro skip button early, only do so if the intro occurs within the first X seconds." @@ -1618,27 +1526,6 @@ msgctxt "#33618" msgid "TV binge-viewing mode" msgstr "Modalità maratona TV" -#: -msgctxt "#33619" -msgid "Automatically skips episode intros, credits and tries to skip episode recaps. Doesn't skip the intro of the first episode of a season and doesn't skip the final credits of a show.\n" -"\n" -"Can be disabled/enabled per TV show.\n" -"Overrides any setting below." -msgstr "Modalità maratona TV: Salta automaticamente le intro degli episodi, i crediti e cerca di saltare i riassunti degli episodi. Non salta l'introduzione del primo episodio di una stagione e non salta i crediti finali di uno spettacolo. \n" -"\n" -"\n" -"Può essere disattivata/attivata per singoli spettacoli TV. Sovrascrive qualsiasi impostazione seguente." - -#: -msgctxt "#33620" -msgid "Plex requests timeout (seconds)" -msgstr "Timeout delle richieste Plex (secondi)" - -#: -msgctxt "#33621" -msgid "Set the (async and connection) timeout value of the Python requests library in seconds. Default: 5" -msgstr "Imposta il valore di timeout (asincrono e di connessione) della libreria delle richieste Python in secondi. Predefinito: 5" - #: msgctxt "#33622" msgid "LAN reachability timeout (ms)" @@ -1903,26 +1790,6 @@ msgctxt "#32931" msgid "Audio/Subtitles" msgstr "Audio/Sottotitoli" -#: -msgctxt "#32932" -msgid "Show subtitle quick-actions button" -msgstr "Mostra il pulsante delle azioni rapide dei sottotitoli" - -#: -msgctxt "#32933" -msgid "Show FFWD/RWD buttons" -msgstr "Mostra i pulsanti FFWD/RWD" - -#: -msgctxt "#32934" -msgid "Show repeat button" -msgstr "Mostra il pulsante Ripeti" - -#: -msgctxt "#32935" -msgid "Show shuffle button" -msgstr "Mostra il pulsante Casuale" - #: msgctxt "#32936" msgid "Show playlist button" @@ -1933,16 +1800,6 @@ msgctxt "#32937" msgid "Show prev/next button" msgstr "Mostra il pulsante prev/next" -#: -msgctxt "#32939" -msgid "Only applies to video player UI" -msgstr "Si applica solo all'interfaccia del Player" - -#: -msgctxt "#32940" -msgid "Player UI" -msgstr "Interfaccia del Player" - #: msgctxt "#32941" msgid "Forced subtitles fix" @@ -2124,11 +1981,6 @@ msgctxt "#32976" msgid "Adaptive" msgstr "Adattivo" -#: -msgctxt "#32977" -msgid "Allow VC1" -msgstr "Abilita VC1" - #: msgctxt "#32978" msgid "Enable this if your hardware can handle VC1. Disable it to force transcoding." @@ -2154,18 +2006,6 @@ msgctxt "#32982" msgid "Depending on how many cores your CPU has and how much it can handle, increasing this might improve certain situations. If you experience crashes or other annormalities, leave this at its default (3). Needs an addon restart." msgstr "A seconda di quanti core ha la tua CPU e di quanto può gestire, aumentare questo valore potrebbe migliorare certe situazioni. Se riscontri crash o altre anomalie, lascia questo valore a default(3). Richiede un riavvio dell'addon." -#: -msgctxt "#32983" -msgid "Player Theme" -msgstr "Tema del Player" - -#: -msgctxt "#32984" -msgid "Sets the player theme. Currently only customizes the playback control buttons. ATTENTION: [I]Might[/I] need an addon restart.\n" -"In order to customize this, copy one of the xml's in script.plexmod/resources/skins/Main/1080i/templates to addon_data/script.plexmod/templates/seek_dialog_buttons_custom.xml and adjust it to your liking, then select \"Custom\" as your theme." -msgstr "Imposta il tema del lettore. Attualmente personalizza solo i pulsanti di controllo della riproduzione. ATTENZIONE: [I]Potrebbe[/I] essere necessario riavviare l'addon. \n" -"Per personalizzarlo, copia uno degli xml in script.plexmod/resources/skins/Main/1080i/templates in addon_data/script.plexmod/templates/seek_dialog_buttons_custom.xml e modificalo secondo le tue preferenze, quindi seleziona \"Personalizzato\" come tema." - #: msgctxt "#32985" msgid "Modern" @@ -2246,11 +2086,6 @@ msgctxt "#33000" msgid "Enable path mapping" msgstr "Abilita path mapping" -#: -msgctxt "#33001" -msgid "Honor path_mapping.json in the addon_data/script.plexmod folder when DirectPlaying media. This can be used to stream using other techniques such as SMB/NFS/etc. instead of the default HTTP handler. path_mapping.example.json is included in the addon's main directory." -msgstr "Rispetta path_mapping.json nella cartella addon_data/script.plexmod quando riproduci direttamente i media. Questo può essere utilizzato per lo streaming utilizzando altre tecniche come SMB/NFS/ecc. invece dell'handler HTTP predefinito. È incluso un'esempio di path_mapping.json nella cartella principale dell'addon come path_mapping.example.json." - #: msgctxt "#33002" msgid "Verify mapped files exist" @@ -2276,36 +2111,11 @@ msgctxt "#33006" msgid "No TV spoilers" msgstr "No TV spoilers" -#: -msgctxt "#33007" -msgid "When visiting an episode/season view, blur unwatched/unwatched+in-progress episode thumbnails, previews and redact summaries. When the Addon Setting \"Use episode thumbnails in continue hub\" is enabled, blur them as well." -msgstr "Quando si accede alla vista episodio/stagione, sfoca le miniature degli episodi non visti/o + in corso, anteprime e redige i riepiloghi. Quando l'impostazione dell'Addon \"Usa le miniature degli episodi nel riquadro continua a guardare\" è abilitata, sfoca anche loro." - #: msgctxt "#33008" msgid "[Spoilers removed]" msgstr "[Spoileri rimossi]" -#: -msgctxt "#33009" -msgid "Blur amount for unwatched/in-progress episodes" -msgstr "Livello di sfocatura per gli episodi non visti/in corso" - -#: -msgctxt "#33010" -msgid "Unwatched" -msgstr "Non visti" - -#: -msgctxt "#33011" -msgid "Unwatched/in progress" -msgstr "Non visti/in corso" - -#: -msgctxt "#33012" -msgid "No unwatched episode titles" -msgstr "Nessun titolo di episodi non visti" - #: msgctxt "#33013" msgid "When the above is anything but \"off\", hide episode titles as well." @@ -2321,16 +2131,6 @@ msgctxt "#33015" msgid "When checking for plex.direct host mapping, ignore local Docker IPv4 addresses (172.16.0.0/12)." msgstr "Nel controllo della mappatura dell'host plex.direct, ignorare gli indirizzi IPv4 locali di Docker (172.16.0.0/12)." -#: -msgctxt "#33016" -msgid "Allow TV spoilers for specific genres" -msgstr "Consenti spoiler TV per generi specifici" - -#: -msgctxt "#33017" -msgid "Overrides the above for: {}" -msgstr "Sovrascrive quanto sopra per: {}" - #: msgctxt "#32303" msgid "Season {}" @@ -2376,26 +2176,6 @@ msgctxt "#33021" msgid "Choose action" msgstr "" -#: -msgctxt "#33022" -msgid "Use modern inverted watched states" -msgstr "" - -#: -msgctxt "#33023" -msgid "Instead of marking unwatched items, mark watched items with a checkmark (modern clients; default: off)" -msgstr "" - -#: -msgctxt "#33024" -msgid "Hide black backdrop in inverted watched states" -msgstr "" - -#: -msgctxt "#33025" -msgid "When the above is enabled, hide the black backdrop of the watched state." -msgstr "" - #: msgctxt "#33026" msgid "Map path: {}" @@ -2451,11 +2231,6 @@ msgctxt "#33037" msgid "Maximum intro offset to consider" msgstr "" -#: -msgctxt "#33038" -msgid "When encountering an intro marker with a start time offset greater than this, ignore it (default: 600s/10m)" -msgstr "" - #: msgctxt "#33039" msgid "Move" @@ -2501,26 +2276,11 @@ msgctxt "#33043" msgid "Hubs round-robin" msgstr "" -#: -msgctxt "#33044" -msgid "Allow round-robining in hubs" -msgstr "" - #: msgctxt "#33045" msgid "Behave like official Plex clients" msgstr "" -#: -msgctxt "#33046" -msgid "Show OSD when pressing up/down without OSD instead of showing chapters." -msgstr "" - -#: -msgctxt "#33046" -msgid "When pressing up/down while the OSD isn't shown, show OSD instead of skipping chapters." -msgstr "" - #: msgctxt "#32025" msgid "Direct Play" @@ -2588,11 +2348,6 @@ msgid "Sets the theme. Currently only customizes all control buttons. ATTENTION: "In order to customize this, copy one of the xml's in script.plexmod/resources/skins/Main/1080i/templates to addon_data/script.plexmod/templates/{templatename}_custom.xml and adjust it to your liking, then select \"Custom\" as your theme." msgstr "" -#: -msgctxt "#33007" -msgid "Select in which cases to blur TV episode thumbnails, previews, redact summaries, hide episode titles and whether to blur chapter images. When the Addon Setting \"Use episode thumbnails in continue hub\" is enabled, blur them as well." -msgstr "" - #: msgctxt "#33011" msgid "In progress" @@ -2608,19 +2363,6 @@ msgctxt "#33017" msgid "Overrides the above for specific genres. Default: Reality, Game Show, Documentary, Sport" msgstr "" -#: -msgctxt "#33022" -msgid "Watched indicators" -msgstr "" - -#: -msgctxt "#33023" -msgid "Classic: Show orange triangle for unwatched items\n" -"Modern: Show green checkmark for watched items\n" -"Modern (2024): Show white checkmark for watched items\n" -"(default: Modern (2024))" -msgstr "" - #: msgctxt "#33024" msgid "Hide background in modern indicators" @@ -2651,31 +2393,16 @@ msgctxt "#33049" msgid "Max retries" msgstr "" -#: -msgctxt "#33050" -msgid "The maximum number of retries each connection should attempt. The default (1) helps with hibernation/wakeup issues. Set higher for bad connections, setting this to 0 was the old default before 0.7.9" -msgstr "" - #: msgctxt "#33051" msgid "Use CA certificate bundle" msgstr "" -#: -msgctxt "#33052" -msgid "Which CA certificate bundle to use for request HTTPS verification. Default is \"system\", which uses the system-provided certificate bundle. \"plex.direct\" uses an extremely small bundle provided with the addon, which only applies to plex.direct connections (any other connections will use the system bundle) and might slightly improve performance. \"custom\" allows for a certificate bundle in userdata/addon_data/script.plexmod/custom_bundle.crt" -msgstr "" - #: msgctxt "#33053" msgid "System" msgstr "" -#: -msgctxt "#33054" -msgid "plex.direct (addon-supplied)" -msgstr "" - #: msgctxt "#33055" msgid "Custom" @@ -2777,11 +2504,6 @@ msgctxt "#33074" msgid "Waiting {} second(s)" msgstr "" -#: -msgctxt "#33075" -msgid "Do something after waking up. Waiting for a time until resuming normal execution helps with networks coming back up after sleep events. Default: 1 second (5 seconds, CoreELEC)" -msgstr "" - #: msgctxt "#33076" msgid "Modern (2024)" @@ -2837,11 +2559,6 @@ msgctxt "#33086" msgid "Press the key you want to map to go to home within {} seconds" msgstr "" -#: -msgctxt "#33087" -msgid "Bind a key to immediately go to the Home view. Cancel using BACK/PREVIOUS_MENU. Clear bound key using the STOP button on the setting." -msgstr "" - #: msgctxt "#33620" msgid "Plex server connect timeout" @@ -2947,21 +2664,11 @@ msgctxt "#33638" msgid "Plex.tv connect timeout" msgstr "" -#: -msgctxt "#33639" -msgid "Plex.tv: Sets the maximum amount of time to connect to Plex.tv in seconds. Default: 5" -msgstr "" - #: msgctxt "#33640" msgid "Plex.tv read timeout" msgstr "" -#: -msgctxt "#33641" -msgid "Plex.tv: Sets the maximum amount of time to read from Plex.tv in seconds. Default: 20" -msgstr "" - #: msgctxt "#33642" msgid "Dump config" @@ -2977,16 +2684,6 @@ msgctxt "#33644" msgid "Tracks" msgstr "" -#: -msgctxt "#33645" -msgid "Ignore plex.direct DNS handling for remote hosts" -msgstr "" - -#: -msgctxt "#33646" -msgid "Only handle plex.direct hosts when the server's attributes dnsRebindingProtection=1 and publicAddressMatches=1. Might not work in certain situations. Disable if you have connection issues." -msgstr "" - #: msgctxt "#33645" msgid "plex.direct: Honor plex.tv's dnsRebindingProtection flag (DNS)" @@ -3008,12 +2705,849 @@ msgid "Only handle plex.direct hosts when the server's attributes publicAddressM msgstr "" #: -msgctxt "#33649" -msgid "CoreELEC: Resume-fix wait for seek" +msgctxt "#32101" +msgid "If enabled, when playback ends and there is a 'Next Up' item available, it will be automatically be played after a {} second delay." msgstr "" #: -msgctxt "#33650" -msgid "This adjusts the delay between seek-tries on CoreELEC, which fixes resume not always working or double-seeking. When you have resume/double-seek issues, increase this. 100ms should be stable as well. Default: 350ms" +msgctxt "#33651" +msgid "Startup delay" +msgstr "" + +#: +msgctxt "#33652" +msgid "Never show Post Play" +msgstr "" + +#: +msgctxt "#33506" +msgid "Show the intro skip button from the start of a video with an intro marker. The auto-skipping setting applies. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "Mostra il pulsante di salto intro dall'inizio di un video con un indicatore di intro. L'impostazione di auto-salto si applica. Non annulla la modalità maratona abilitata. \n" +"Può essere disattivato/attivato per ogni serie TV." + +#: +msgctxt "#33619" +msgid "Automatically skips episode intros, credits and tries to skip episode recaps. Doesn't skip the intro of the first episode of a season and doesn't skip the final credits of a show.\n" +"\n" +"Can be disabled/enabled per TV show.\n" +"Overrides any setting below." +msgstr "Modalità maratona TV: Salta automaticamente le intro degli episodi, i crediti e cerca di saltare i riassunti degli episodi. Non salta l'introduzione del primo episodio di una stagione e non salta i crediti finali di uno spettacolo. \n" +"\n" +"\n" +"Può essere disattivata/attivata per singoli spettacoli TV. Sovrascrive qualsiasi impostazione seguente." + +#: +msgctxt "#33052" +msgid "Which CA certificate bundle to use for request HTTPS verification. \"system\" uses the system-provided certificate bundle. \"ACME\" uses an extremely small bundle provided with the addon, which includes root certificates for Letsencrypt (plex.direct) and ZeroSSL. \"custom\" allows for a ca-certificate bundle in userdata/addon_data/script.plexmod/custom_bundle.crt" +msgstr "" + +#: +msgctxt "#33054" +msgid "ACME (addon-supplied)" +msgstr "" + +#: +msgctxt "#33639" +msgid "Plex.tv: Sets the maximum amount of time to connect to Plex.tv in seconds. Default: 1" +msgstr "" + +#: +msgctxt "#33641" +msgid "Plex.tv: Sets the maximum amount of time to read from Plex.tv in seconds. Default: 2" +msgstr "" + +#: +msgctxt "#33653" +msgid "Background image resolution scaling %" +msgstr "" + +#: +msgctxt "#33654" +msgid "In percent, based on 1080p. Scales the resolution of all backgrounds for better image quality. May impact PMS/PM4K performance, will increase the cache usage accordingly. Can lead to crashes if your hardware is low on RAM. Needs addon restart. Use 400% for 4K. ATTENTION: In order for this to work you need to add 2160 and 2160 to your advancedsettings.xml." +msgstr "" + +#: +msgctxt "#33655" +msgid "Auto-Sync Subtitles" +msgstr "" + +#: +msgctxt "#33656" +msgid "Only for External SRT subtitles. The PMS setting for voice activity detection has to be enabled for this to work." +msgstr "" + +#: in player quick settings UI, needs to be short +msgctxt "#33657" +msgid "Enable Auto-Sync" +msgstr "" + +#: in player quick settings UI, needs to be short +msgctxt "#33658" +msgid "Disable Auto-Sync" +msgstr "" + +#: +msgctxt "#33659" +msgid "Hide hub: {}" +msgstr "" + +#: +msgctxt "#33660" +msgid "Disable HDR" +msgstr "" + +#: +msgctxt "#33661" +msgid "If you don't want your client to handle HDR (or HDR-fallback), enable this to force transcoding. Doesn't apply to DV Profile 5." +msgstr "" + +#: +msgctxt "#33662" +msgid "Remove from Continue Watching" +msgstr "" + +#: +msgctxt "#33663" +msgid "Home: Confirm item actions" +msgstr "" + +#: +msgctxt "#33664" +msgid "When acting on items in the Home view, such as mark played, hide from continue watching etc., show a confirmation dialog." +msgstr "" + +#: +msgctxt "#33665" +msgid "Disable audio codecs" +msgstr "" + +#: +msgctxt "#33666" +msgid "Audio codecs you can't play back. Disables Direct Play for such media items, enables Direct Stream if possible, transcodes audio stream to compatible format." +msgstr "" + +#: +msgctxt "#32002" +msgid "20 Mbps" +msgstr "20 Mbps" + +#: +msgctxt "#32003" +msgid "12 Mbps" +msgstr "12 Mbps" + +#: +msgctxt "#32004" +msgid "10 Mbps" +msgstr "10 Mbps" + +#: +msgctxt "#32005" +msgid "8 Mbps" +msgstr "8 Mbps" + +#: +msgctxt "#32006" +msgid "4 Mbps" +msgstr "4 Mbps" + +#: +msgctxt "#32007" +msgid "3 Mbps" +msgstr "3 Mbps" + +#: +msgctxt "#32008" +msgid "2 Mbps" +msgstr "2 Mbps" + +#: +msgctxt "#32009" +msgid "1.5 Mbps" +msgstr "1.5 Mbps" + +#: +msgctxt "#33669" +msgid "Really sign out?" +msgstr "" + +#: +msgctxt "#33670" +msgid "Update available" +msgstr "" + +#: +msgctxt "#33672" +msgid "Check for updates" +msgstr "" + +#: +msgctxt "#33673" +msgid "Automatically check for updates periodically. If installed from a Kodi repository and the Update Source setting is set to Repository, Kodi itself will handle the updating of this addon. Needs a Kodi restart when changed." +msgstr "" + +#: +msgctxt "#33674" +msgid "Check for updates on start" +msgstr "" + +#: +msgctxt "#33675" +msgid "Automatically check for updates on startup. Doesn't do much when Update source is Repository. Needs a Kodi restart when changed." +msgstr "" + +#: +msgctxt "#33676" +msgid "Update source" +msgstr "" + +#: +msgctxt "#33677" +msgid "Specifies the update mode. Will immediately check for a new version when changed and closing settings.\n" +"Default: Repository\n" +"\n" +"Beta: Bleeding edge (possibly unstable)\n" +"Stable: Stable branch (faster than Repository)\n" +"Repository: Kodi repository (official (slow) or Don't Panic)" +msgstr "" + +#: +msgctxt "#33678" +msgid "Beta" +msgstr "" + +#: Update mode as in (beta, stable, repository) +msgctxt "#33679" +msgid "Stable" +msgstr "" + +#: +msgctxt "#33680" +msgid "Repository" +msgstr "" + +#: +msgctxt "#33683" +msgid "Exit, download and install" +msgstr "" + +#: +msgctxt "#33684" +msgid "Later" +msgstr "" + +#: +msgctxt "#32015" +msgid "6 Mbps" +msgstr "" + +#: +msgctxt "#32016" +msgid "16 Mbps" +msgstr "" + +#: +msgctxt "#32017" +msgid "26 Mbps" +msgstr "" + +#: +msgctxt "#33681" +msgid "Service updated" +msgstr "" + +#: +msgctxt "#33682" +msgid "The update {} has had changes to the updater itself. In order for the updated updater service to work, a Kodi restart is necessary. The addon will work normally, though. Do you still want to run the addon?" +msgstr "" + +#: +msgctxt "#33685" +msgid "Clamp video bitrate" +msgstr "" + +#: +msgctxt "#33686" +msgid "Only show transcode bitrate targets lower than the current video's bitrate." +msgstr "" + +#: +msgctxt "#33687" +msgid "Translation updated" +msgstr "" + +#: +msgctxt "#33688" +msgid "The currently in-use translation has been updated. In order to load the new translation, a Kodi restart is necessary. The addon will still run properly, but you might see badly or untranslated strings. Do you still want to run the addon?" +msgstr "" + +#: +msgctxt "#33509" +msgid "Early intro skip threshold (default: < 120s/2m)" +msgstr "" + +#: +msgctxt "#33007" +msgid "Select in which cases to blur TV episode thumbnails, previews, redact summaries, hide episode titles etc." +msgstr "" + +#: +msgctxt "#33009" +msgid "Blur amount for unplayed/in-progress episodes" +msgstr "" + +#: +msgctxt "#33010" +msgid "Unplayed" +msgstr "" + +#: +msgctxt "#33012" +msgid "No unplayed episode titles" +msgstr "" + +#: +msgctxt "#33022" +msgid "Played indicators" +msgstr "" + +#: +msgctxt "#33023" +msgid "Classic: Show orange triangle for unplayed items\n" +"Modern: Show green checkmark for played items\n" +"Modern (2024): Show white checkmark for played items\n" +"(default: Modern (2024))" +msgstr "" + +#: +msgctxt "#33025" +msgid "When the above is enabled, hide the black backdrop of the played state." +msgstr "" + +#: +msgctxt "#33689" +msgid "Service running" +msgstr "" + +#: +msgctxt "#33690" +msgid "Last update check" +msgstr "" + +#: +msgctxt "#33691" +msgid "Native languages" +msgstr "" + +#: +msgctxt "#33692" +msgid "When you usually watch things in a different language with subtitles, but are a native speaker of other languages, which you don't need subtitles for, prevent Plex from auto-selecting subtitles for those languages." +msgstr "" + +#: +msgctxt "#33693" +msgid "Download subtitles using" +msgstr "" + +#: +msgctxt "#33694" +msgid "Ask" +msgstr "" + +#: +msgctxt "#33695" +msgid "Where do you want to download subtitles from? Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "" + +#: +msgctxt "#33696" +msgid "No subtitles found." +msgstr "" + +#: things in curly brackets are variables, don't translate them! +msgctxt "#33697" +msgid "{provider_title}, Score: {subtitle_score}{subtitle_info}" +msgstr "" + +#: hearing impaired subtitle flag +msgctxt "#33698" +msgid "HI" +msgstr "" + +#: forced subtitle flag +msgctxt "#33699" +msgid "forced" +msgstr "" + +#: +msgctxt "#33700" +msgid "Download subtitles: {}" +msgstr "" + +#: +msgctxt "#33701" +msgid "Fallback to Kodi" +msgstr "" + +#: +msgctxt "#33702" +msgid "When no subtitles are found via the Plex subtitle search, fall back to Kodi subtitle search. Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "" + +#: +msgctxt "#33703" +msgid "Download subtitles" +msgstr "" + +#: +msgctxt "#33704" +msgid "Using which service?" +msgstr "" + +#: +msgctxt "#33705" +msgid "Hide ratings" +msgstr "" + +#: +msgctxt "#33706" +msgid "Blur images" +msgstr "" + +#: +msgctxt "#33707" +msgid "Blur images on home (in progress)" +msgstr "" + +#: +msgctxt "#33708" +msgid "Hide summaries" +msgstr "" + +#: +msgctxt "#33709" +msgid "Show ratings for" +msgstr "" + +#: +msgctxt "#33710" +msgid "Show reviews for" +msgstr "" + +#: +msgctxt "#33711" +msgid "Always resume media" +msgstr "" + +#: +msgctxt "#33712" +msgid "When playback of an in-progress media is requested, resume it by default instead of asking whether to resume or start from the beginning." +msgstr "" + +#: +msgctxt "#33713" +msgid "Home: Resume in-progress items" +msgstr "" + +#: +msgctxt "#33714" +msgid "Resume in-progress items directly instead of visiting the media." +msgstr "" + +#: +msgctxt "#33715" +msgid "OSD hide-delay" +msgstr "" + +#: +msgctxt "#33716" +msgid "How long to wait until hiding the OSD. Only works with Plextuary skin or others with configurable OSD timeout. Default: 4s (+/- 1s)" +msgstr "" + +#: +msgctxt "#33717" +msgid "Debug requests" +msgstr "" + +#: +msgctxt "#33718" +msgid "Played" +msgstr "" + +#: +msgctxt "#33719" +msgid "Refresh metadata" +msgstr "" + +#: +msgctxt "#33038" +msgid "When encountering an intro marker with a start time offset greater than this, ignore it (default: 1400s/23m)" +msgstr "" + +#: +msgctxt "#33649" +msgid "\"Use alternate seek\" wait for seek" +msgstr "" + +#: +msgctxt "#33650" +msgid "This adjusts the delay between seek-tries on problematic platforms when \"Use alternate seek\" is enabled, which fixes resume not always working or double-seeking. When you have resume/double-seek issues, increase this. Default: 500ms" +msgstr "" + +#: +msgctxt "#33667" +msgid "Use alternate seek" +msgstr "" + +#: +msgctxt "#33668" +msgid "ATTENTION: Only enable this if you have reproducible audio issues after seeking/resuming.\n" +"\n" +"Use an alternative seek method in videos, which can help in problematic scenarios; brings its own issues/quirks. Disabled by default (enabled by default for CoreELEC and LG WebOS)" +msgstr "" + +#: +msgctxt "#33720" +msgid "Clear all caches" +msgstr "" + +#: +msgctxt "#33721" +msgid "Clear library cache (not items)" +msgstr "" + +#: +msgctxt "#33722" +msgid "Libraries" +msgstr "" + +#: +msgctxt "#33723" +msgid "Media Items" +msgstr "" + +#: +msgctxt "#33724" +msgid "Cache Plex data for" +msgstr "" + +#: +msgctxt "#33725" +msgid "Persist cached Plex data" +msgstr "" + +#: +msgctxt "#33726" +msgid "Instead of clearing the cache when exiting the addon, persist it instead. Warning: You'll most likely encounter missing items in libraries or outdated data. Use the corresponding menu functionalities to clear the cache for specific items or libraries." +msgstr "" + +#: +msgctxt "#33727" +msgid "Store Plex server responses for items and library views in a local SQLite database. Doesn't cache anything else (Home/Hubs are always up to date). Massively speeds up consecutive visits to items and libraries. Certain important events, such as watch state changes, automatically delete the item cache and its corresponding library cache. (Default: Off)" +msgstr "" + +#: +msgctxt "#33728" +msgid "Clear cache for item" +msgstr "" + +#: +msgctxt "#33729" +msgid "Expire cached Plex data after (hours)" +msgstr "" + +#: +msgctxt "#33730" +msgid "Randomly" +msgstr "" + +#: +msgctxt "#33731" +msgid "By Bitrate" +msgstr "" + +#: +msgctxt "#33732" +msgid "Bitrate" +msgstr "" + +#: +msgctxt "#33733" +msgid "Transcode target codec" +msgstr "" + +#: +msgctxt "#33734" +msgid "Sets the target codec when transcoding/direct streaming. Overridden when \"Transcode audio to AC3\" is set." +msgstr "" + +#: +msgctxt "#33735" +msgid "Always auto-start" +msgstr "" + +#: +msgctxt "#33736" +msgid "By default PM4K won't auto-start when the current window isn't a Home-derived window (e.g.: Settings). Enable this if you always want to auto-start PM4K regardless of the current window. Can also improve start-up time." +msgstr "" + +#: +msgctxt "#33737" +msgid "Loop theme music" +msgstr "" + +#: +msgctxt "#33671" +msgid "Current: {current_version}\n" +"New: {new_version}\n" +"\n" +"Changelog:\n" +"{changelog}" +msgstr "" + +#: +msgctxt "#33738" +msgid "Max playlist size" +msgstr "" + +#: +msgctxt "#33739" +msgid "How many items to load into a Kodi playlist before chunking them remotely using Plex PlayQueues (default: 500); Reduce on memory issues/crashes." +msgstr "" + +#: +msgctxt "#33740" +msgid "Home: Episodes season thumbnails" +msgstr "" + +#: +msgctxt "#33741" +msgid "Use season thumbnails/posters when displaying episodes in hubs instead of the TV show's." +msgstr "" + +#: +msgctxt "#34000" +msgid "Watchlist" +msgstr "" + +#: +msgctxt "#34001" +msgid "Released" +msgstr "" + +#: +msgctxt "#34002" +msgid "Movies & Shows" +msgstr "" + +#: e.g.: 8 seasons +msgctxt "#34003" +msgid "{} seasons" +msgstr "" + +#: +msgctxt "#34004" +msgid "Choose server" +msgstr "" + +#: +msgctxt "#34005" +msgid "Availability" +msgstr "" + +#: e.g.: 1 season +msgctxt "#34006" +msgid "{} season" +msgstr "" + +#: +msgctxt "#34007" +msgid "Use Watchlist" +msgstr "" + +#: +msgctxt "#34008" +msgid "Activates the current user's Plex watchlist as a section item. Adds watchlist functionality to certain media screens. Per-user setting. Default: On" +msgstr "" + +#: +msgctxt "#34009" +msgid "Watchlist auto-remove" +msgstr "" + +#: +msgctxt "#34010" +msgid "Automatically remove fully watched items from watchlist. Default: On" +msgstr "" + +#: +msgctxt "#34011" +msgid "Remove from watchlist" +msgstr "" + +#: +msgctxt "#34012" +msgid "Fast pause/resume" +msgstr "" + +#: +msgctxt "#34013" +msgid "when paused" +msgstr "" + +#: +msgctxt "#34014" +msgid "when playing" +msgstr "" + +#: +msgctxt "#34015" +msgid "User-specific. Use OK/ENTER button to pause instead of showing the OSD (which can then only be accessed using DOWN), or resume when paused. Only works with 'Behave like official Plex clients' enabled." +msgstr "" + +#: +msgctxt "#34016" +msgid "Max shutdown wait" +msgstr "" + +#: +msgctxt "#34017" +msgid "The plugin will try to hard exit once this time limit is reached (default: 5 seconds)" +msgstr "" + +#: +msgctxt "#34018" +msgid "Related Media" +msgstr "" + +#: watchlist discover hub title prefix +msgctxt "#34019" +msgid "Discover: {}" +msgstr "" + +#: +msgctxt "#34020" +msgid "Loading external hubs" +msgstr "" + +#: +msgctxt "#34021" +msgid "Please wait ..." +msgstr "" + +#: +msgctxt "#34022" +msgid "Video play completion behaviour" +msgstr "" + +#: +msgctxt "#34023" +msgid "Decide whether to use end credits markers to determine the 'watched' state of video items. When markers are not available the selected threshold percentage will be used." +msgstr "" + +#: +msgctxt "#34024" +msgid "at selected threshold percentage" +msgstr "" + +#: +msgctxt "#34025" +msgid "at final credits marker position" +msgstr "" + +#: +msgctxt "#34026" +msgid "at first credits marker position" +msgstr "" + +#: +msgctxt "#34027" +msgid "earliest between threshold percent and first credits marker" +msgstr "" + +#: +msgctxt "#32973" +msgid "Episodes: Continuous playback" +msgstr "" + +#: +msgctxt "#34028" +msgid "\"Use alternate seek\" valid seek window" +msgstr "" + +#: +msgctxt "#34029" +msgid "When \"Use alternate seek\" is enabled, any seek amount outside of this threshold window will be applied, otherwise the seek attempt will be ignored. If you encounter any seek issues, play with this value (ms, default: 2000)" +msgstr "" + +#: +msgctxt "#34030" +msgid "Unlock Direct Play for above 4K" +msgstr "" + +#: +msgctxt "#34031" +msgid "Producer" +msgstr "" + +#: +msgctxt "#34032" +msgid "Audio Language" +msgstr "" + +#: +msgctxt "#34033" +msgid "Subtitle Language" +msgstr "" + +#: +msgctxt "#34034" +msgid "Folder Location" +msgstr "" + +#: +msgctxt "#34035" +msgid "Editions" +msgstr "" + +#: +msgctxt "#34036" +msgid "DOVI" +msgstr "" + +#: +msgctxt "#34037" +msgid "HDR" +msgstr "" + +#: +msgctxt "#34038" +msgid "Force plex.direct mapping" +msgstr "" + +#: +msgctxt "#34039" +msgid "If you (still) don't see any posters: Under certain circumstances when Kodi still can't resolve hostnames when loading assets, even though the host resolves natively, enable this to force plex.direct mapping via advancedsettings.xml." +msgstr "" + +#: +msgctxt "#34040" +msgid "By Progress" +msgstr "" + +#: +msgctxt "#34041" +msgid "Progress" +msgstr "" + +#: +msgctxt "#34042" +msgid "By Album" +msgstr "" + +#: +msgctxt "#34043" +msgid "Album" msgstr "" diff --git a/script.plexmod/resources/language/resource.language.pl_pl/strings.po b/script.plexmod/resources/language/resource.language.pl_pl/strings.po index d8b263ae0c..ba4e035348 100644 --- a/script.plexmod/resources/language/resource.language.pl_pl/strings.po +++ b/script.plexmod/resources/language/resource.language.pl_pl/strings.po @@ -1,984 +1,3542 @@ -# XBMC Media Center language file msgid "" msgstr "" -"Project-Id-Version: XBMC-Addons\n" -"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n" -"POT-Creation-Date: 2013-12-12 22:56+0000\n" -"PO-Revision-Date: 2020-06-25 12:04+0200\n" -"Last-Translator: Lukasz Zacharski \n" -"Language-Team: LANGUAGE\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"X-Generator: POEditor.com\n" +"Project-Id-Version: PM4K / PlexMod for Kodi\n" "Language: pl\n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" +#: msgctxt "#32000" msgid "Main" msgstr "Główne" +#: msgctxt "#32001" msgid "Original" msgstr "Oryginalna" -msgctxt "#32002" -msgid "20 Mbps 1080p" -msgstr "20 Mb/s 1080p" - -msgctxt "#32003" -msgid "12 Mbps 1080p" -msgstr "12 Mb/s 1080p" - -msgctxt "#32004" -msgid "10 Mbps 1080p" -msgstr "10 Mb/s 1080p" - -msgctxt "#32005" -msgid "8 Mbps 1080p" -msgstr "8 Mb/s 1080p" - -msgctxt "#32006" -msgid "4 Mbps 720p" -msgstr "4 Mb/s 720p" - -msgctxt "#32007" -msgid "3 Mbps 720p" -msgstr "3 Mb/s 720p" - -msgctxt "#32008" -msgid "2 Mbps 720p" -msgstr "2 Mb/s 720p" - -msgctxt "#32009" -msgid "1.5 Mbps 480p" -msgstr "1.5 Mb/s 480p" - +#: msgctxt "#32010" msgid "720 kbps" msgstr "720 kb/s" +#: msgctxt "#32011" msgid "320 kbps" msgstr "320 kb/s" +#: msgctxt "#32012" msgid "208 kbps" msgstr "208 kb/s" +#: msgctxt "#32013" msgid "96 kbps" msgstr "96 kb/s" +#: msgctxt "#32014" msgid "64 kbps" msgstr "64 kb/s" +#: msgctxt "#32020" msgid "Local Quality" msgstr "Jakość lokalna" +#: msgctxt "#32021" msgid "Remote Quality" msgstr "Jakość zdalna" +#: msgctxt "#32022" msgid "Online Quality" msgstr "Jakość online" +#: msgctxt "#32023" msgid "Transcode Format" msgstr "Format transkodowania" +#: msgctxt "#32024" msgid "Debug Logging" msgstr "Dziennik debugowania" -msgctxt "#32025" -msgid "Allow Direct Play" -msgstr "Zezwól na bezpośrednie odtwarzanie" - -msgctxt "#32026" -msgid "Allow Direct Stream" -msgstr "Zezwól na bezpośrednie strumieniowanie" - +#: msgctxt "#32027" msgid "Force" msgstr "Wymuszaj" +#: msgctxt "#32028" msgid "Always" msgstr "Zawsze" +#: msgctxt "#32029" msgid "Only Image Formats" msgstr "Tylko formaty graficzne" +#: msgctxt "#32030" msgid "Auto" msgstr "Automatyczne" -msgctxt "#32031" -msgid "Burn Subtitles (Direct Play Only)" -msgstr "Wypalaj napisy (tylko bezpośrednie odtwarzanie)" - +#: msgctxt "#32032" msgid "Allow Insecure Connections" msgstr "Zezwól na niezabezpieczone połączenia" +#: msgctxt "#32033" msgid "Never" msgstr "Nigdy" +#: msgctxt "#32034" msgid "On Same network" msgstr "W tej samej sieci" +#: msgctxt "#32035" msgid "Always" msgstr "Zawsze" -msgctxt "#32036" -msgid "Allow 4K" -msgstr "Zezwól na 4K" - -msgctxt "#32037" -msgid "Allow HEVC (h265)" -msgstr "Zezwól na HEVC (H.265)" - +#: msgctxt "#32038" msgid "Automatically Sign In" msgstr "Zaloguj się automatycznie" +#: msgctxt "#32039" msgid "Post Play Auto Play" msgstr "Odtwarzaj „Następne” automatycznie" +#: msgctxt "#32040" msgid "Enable Subtitle Downloading" msgstr "Włącz pobieranie napisów" +#: msgctxt "#32041" msgid "Enable Subtitle Downloading" msgstr "Włącz pobieranie napisów" +#: msgctxt "#32042" msgid "Server Discovery (GDM)" msgstr "Odnajdowanie serwera (GDM)" +#: msgctxt "#32043" msgid "Start Plex On Kodi Startup" msgstr "Uruchom Plex przy starcie Kodi" +#: msgctxt "#32044" msgid "Connection 1 IP" msgstr "IP połączenia 1" +#: msgctxt "#32045" msgid "Connection 1 Port" msgstr "Port połączenia 1" +#: msgctxt "#32046" msgid "Connection 2 IP" msgstr "IP połączenia 2" +#: msgctxt "#32047" msgid "Connection 2 Port" msgstr "Port połączenia 2" +#: msgctxt "#32048" msgid "Audio" msgstr "Audio" +#: msgctxt "#32049" msgid "Advanced" msgstr "Zaawansowane" +#: msgctxt "#32050" msgid "Manual Servers" msgstr "Ręcznie dodane serwery" +#: msgctxt "#32051" msgid "Privacy" msgstr "Prywatność" +#: msgctxt "#32052" msgid "About" msgstr "O dodatku" +#: msgctxt "#32053" msgid "Video" msgstr "Wideo" +#: msgctxt "#32054" msgid "Addon Version" msgstr "Wersja dodatku" +#: msgctxt "#32055" msgid "Kodi Version" msgstr "Wersja Kodi" +#: msgctxt "#32056" msgid "Screen Resolution" msgstr "Rozdzielczość ekranu" +#: msgctxt "#32057" msgid "Current Server Version" msgstr "Obecna wersja serwera" +#: +msgctxt "#32058" +msgid "Never exceed original audio codec" +msgstr "" + +#: +msgctxt "#32059" +msgid "When transcoding audio, never exceed the original audio bitrate or channel count on the same codec." +msgstr "" + +#: +msgctxt "#32060" +msgid "Use Kodi audio channels" +msgstr "" + +#: +msgctxt "#32064" +msgid "Treat DTS like AC3" +msgstr "" +#: msgctxt "#32100" msgid "Skip user selection and pin entry on startup." msgstr "Pomiń wybór użytkownika i kod PIN przy starcie." -msgctxt "#32101" -msgid "If enabled, when playback ends and there is a 'Next Up' item available, it will be automatically be played after a 15 second delay." -msgstr "Jeśli włączone, po zakończeniu odtwarzania i dostępnej pozycji w „Następne”, zostanie ona automatycznie odtworzona po 15 sekundach." - +#: msgctxt "#32102" msgid "Enable this if your hardware can handle 4K playback. Disable it to force transcoding." msgstr "Włącz, jeśli Twój sprzęt obsługuje odtwarzanie 4K. Wyłącz, aby wymusić transkodowanie." +#: msgctxt "#32103" msgid "Enable this if your hardware can handle HEVC/h265. Disable it to force transcoding." msgstr "Włącz, jeśli Twój sprzęt obsługuje HEVC/H.265. Wyłącz, aby wymusić transkodowanie." +#: msgctxt "#32104" msgid "When to connect to servers with no secure connections.[CR][CR]* [B]Never[/B]: Never connect to a server insecurely[CR]* [B]On Same Network[/B]: Allow if on the same network[CR]* [B]Always[/B]: Allow same network and remote connections" msgstr "Kiedy łączyć z serwerami bez zabezpieczonych połączeń.[CR][CR]* [B]Nigdy[/B]: Nigdy nie łącz w sposób niezabezpieczony[CR]* [B]W tej samej sieci[/B]: Zezwól, jeśli w tej samej sieci[CR]* [B]Zawsze[/B]: Zezwól w tej samej sieci i zdalnie" - +#: msgctxt "#32201" msgid "Trailer" msgstr "Zwiastun" +#: msgctxt "#32202" msgid "Deleted Scene" msgstr "Usunięta scena" +#: msgctxt "#32203" msgid "Interview" msgstr "Wywiad" +#: msgctxt "#32204" msgid "Music Video" msgstr "Teledysk" +#: msgctxt "#32205" msgid "Behind the Scenes" msgstr "Za kulisami" +#: msgctxt "#32206" msgid "Scene" msgstr "Scena" +#: msgctxt "#32207" msgid "Live Music Video" msgstr "Teledysk na żywo" +#: msgctxt "#32208" msgid "Lyric Music Video" msgstr "Teledysk z tekstem" +#: msgctxt "#32209" msgid "Concert" msgstr "Koncert" +#: msgctxt "#32210" msgid "Featurette" msgstr "Średniometrażowy" +#: msgctxt "#32211" msgid "Short" msgstr "Krótkometrażowy" +#: msgctxt "#32212" msgid "Other" msgstr "Inne" +#: msgctxt "#32300" msgid "Go to Album" msgstr "Przejdź do albumu" +#: msgctxt "#32301" msgid "Go to Artist" msgstr "Przejdź do wykonawcy" +#: msgctxt "#32302" msgid "Go to {0}" msgstr "Przejdź do {0}" -msgctxt "#32303" -msgid "Season" -msgstr "Sezon" - -msgctxt "#32304" -msgid "Episode" -msgstr "Odcinek" - +#: msgctxt "#32305" msgid "Extras" msgstr "Dodatki" +#: msgctxt "#32306" msgid "Related Shows" msgstr "Powiązane seriale" +#: msgctxt "#32307" msgid "More" msgstr "Więcej" +#: msgctxt "#32308" msgid "Available" msgstr "Dostępne" +#: msgctxt "#32309" msgid "None" msgstr "Brak" -msgctxt "#32310" -msgid "S" -msgstr "S" - -msgctxt "#32311" -msgid "E" -msgstr "O" - +#: msgctxt "#32312" msgid "Unavailable" msgstr "Niedostępne" +#: msgctxt "#32313" msgid "This item is currently unavailable." msgstr "Ten element jest obecnie niedostępny." +#: msgctxt "#32314" msgid "In Progress" msgstr "W toku" +#: msgctxt "#32315" msgid "Resume playback?" msgstr "Wznowić odtwarzanie" +#: msgctxt "#32316" msgid "Resume" msgstr "Wznów" +#: msgctxt "#32317" msgid "Play from beginning" msgstr "Odtwórz od początku" +#: msgctxt "#32318" msgid "Mark Unplayed" msgstr "Oznacz jako nieodtworzone" +#: msgctxt "#32319" msgid "Mark Played" msgstr "Oznacz jako odtworzone" +#: msgctxt "#32320" msgid "Mark Season Unplayed" msgstr "Oznacz sezon jako nieodtworzony" +#: msgctxt "#32321" msgid "Mark Season Played" msgstr "Oznacz sezon jako odtworzony" +#: msgctxt "#32322" msgid "Delete" msgstr "Usuń" +#: msgctxt "#32323" msgid "Go To Show" msgstr "Przejdź do serialu" +#: msgctxt "#32324" msgid "Go To {0}" msgstr "Przejdź do {0}" +#: msgctxt "#32325" msgid "Play Next" msgstr "Odtwórz następne" +#: msgctxt "#32326" msgid "Really Delete?" msgstr "Na pewno usunąć?" +#: msgctxt "#32327" msgid "Are you sure you really want to delete this media?" msgstr "Czy na pewno chcesz usunąć te multimedia?" +#: msgctxt "#32328" msgid "Yes" msgstr "Tak" +#: msgctxt "#32329" msgid "No" msgstr "Nie" +#: msgctxt "#32330" msgid "Message" msgstr "Wiadomość" +#: msgctxt "#32331" msgid "There was a problem while attempting to delete the media." msgstr "Wystąpił problem z usunięciem multimediów." +#: msgctxt "#32332" msgid "Home" msgstr "Strona główna" +#: msgctxt "#32333" msgid "Playlists" msgstr "Playlisty" +#: msgctxt "#32334" msgid "Confirm Exit" msgstr "Potwierdź wyjście" +#: msgctxt "#32335" msgid "Are you ready to exit Plex?" msgstr "Czy chcesz wyjść z Plex?" +#: msgctxt "#32336" msgid "Exit" msgstr "Wyjdź" +#: msgctxt "#32337" msgid "Cancel" msgstr "Anuluj" +#: msgctxt "#32338" msgid "No Servers Found" msgstr "Nie znaleziono serwerów" +#: msgctxt "#32339" msgid "Server is not accessible" msgstr "Serwer jest niedostępny" +#: msgctxt "#32340" msgid "Connection tests are in progress. Please wait." msgstr "Trwa testowanie połączeń. Proszę czekać." +#: msgctxt "#32341" msgid "Server is not accessible. Please sign into your server and check your connection." msgstr "Serwer jest niedostępny. Zaloguj się na swój serwer i sprawdź połączenie." +#: msgctxt "#32342" msgid "Switch User" msgstr "Przełącz użytkownika" +#: msgctxt "#32343" msgid "Settings" msgstr "Ustawienia" +#: msgctxt "#32344" msgid "Sign Out" msgstr "Wyloguj się" +#: msgctxt "#32345" msgid "All" msgstr "Wszystkie" +#: msgctxt "#32346" msgid "By Name" msgstr "Wg nazwy" +#: msgctxt "#32347" msgid "Artists" msgstr "Wykonawcy" +#: msgctxt "#32348" -msgid "movies" -msgstr "Filmy" +msgid "Movies" +msgstr "" +#: msgctxt "#32349" msgid "photos" msgstr "Zdjęcia" +#: msgctxt "#32350" msgid "Shows" msgstr "Seriale" +#: msgctxt "#32351" msgid "By Date Added" msgstr "Wg daty dodania" +#: msgctxt "#32352" msgid "Date Added" msgstr "Data dodania" +#: msgctxt "#32353" msgid "By Release Date" msgstr "Wg daty wydania" +#: msgctxt "#32354" msgid "Release Date" msgstr "Data wydania" +#: msgctxt "#32355" msgid "By Date Viewed" msgstr "Wg daty wyświetlenia" +#: msgctxt "#32356" msgid "Date Viewed" msgstr "Data wyświetlenia" -msgctxt "#32357" -msgid "By Name" -msgstr "Wg nazwy" - -msgctxt "#32358" -msgid "Name" -msgstr "Nazwa" - +#: msgctxt "#32359" msgid "By Rating" msgstr "Wg oceny" +#: msgctxt "#32360" msgid "Rating" msgstr "Ocena" +#: msgctxt "#32361" msgid "By Resolution" msgstr "Wg rozdzielczości" +#: msgctxt "#32362" msgid "Resolution" msgstr "Rozdzielczość" +#: msgctxt "#32363" msgid "By Duration" msgstr "Wg czasu trwania" +#: msgctxt "#32364" msgid "Duration" msgstr "Czas trwania" +#: msgctxt "#32365" msgid "By First Aired" msgstr "Wg pierwszej emisji" +#: msgctxt "#32366" msgid "First Aired" msgstr "Pierwsza emisja" +#: msgctxt "#32367" msgid "By Unplayed" msgstr "Wg nieodtworzonych" +#: msgctxt "#32368" msgid "Unplayed" msgstr "Nieodtworzone" +#: msgctxt "#32369" msgid "By Date Played" msgstr "Wg daty odtworzenia" +#: msgctxt "#32370" msgid "Date Played" msgstr "Data odtworzenia" +#: msgctxt "#32371" msgid "By Play Count" msgstr "Wg ilości odtworzeń" +#: msgctxt "#32372" msgid "Play Count" msgstr "Ilość odtworzeń" +#: msgctxt "#32373" msgid "By Date Taken" msgstr "Wg daty wykonania" +#: msgctxt "#32374" msgid "Date Taken" msgstr "Data wykonania" +#: msgctxt "#32375" msgid "No filters available" msgstr "Brak dostępnych filtrów" +#: msgctxt "#32376" msgid "Clear Filter" msgstr "Wyczyść filtr" +#: msgctxt "#32377" msgid "Year" msgstr "Rok" +#: msgctxt "#32378" msgid "Decade" msgstr "Dekada" +#: msgctxt "#32379" msgid "Genre" msgstr "Gatunek" +#: msgctxt "#32380" msgid "Content Rating" msgstr "Ocena treści" +#: msgctxt "#32381" msgid "Network" msgstr "Stacja" +#: msgctxt "#32382" msgid "Collection" msgstr "Kolekcja" +#: msgctxt "#32383" msgid "Director" msgstr "Reżyser" +#: msgctxt "#32384" msgid "Actor" msgstr "Aktor" +#: msgctxt "#32385" msgid "Country" msgstr "Kraj" +#: msgctxt "#32386" msgid "Studio" msgstr "Studio" +#: msgctxt "#32387" msgid "Labels" msgstr "Etykiety" +#: msgctxt "#32388" msgid "Camera Make" msgstr "Producent aparatu" +#: msgctxt "#32389" msgid "Camera Model" msgstr "Model aparatu" +#: msgctxt "#32390" msgid "Aperture" msgstr "Przysłona" +#: msgctxt "#32391" msgid "Shutter Speed" msgstr "Czas migawki" +#: msgctxt "#32392" msgid "Lens" msgstr "Obiektyw" +#: msgctxt "#32393" msgid "TV Shows" msgstr "Seriale" +#: msgctxt "#32394" msgid "Music" msgstr "Muzyka" +#: msgctxt "#32395" msgid "Audio" msgstr "Audio" +#: msgctxt "#32396" msgid "Subtitles" msgstr "Napisy" +#: msgctxt "#32397" msgid "Quality" msgstr "Jakość" +#: msgctxt "#32398" msgid "Kodi Video Settings" msgstr "Ustawienia wideo Kodi" +#: msgctxt "#32399" msgid "Kodi Audio Settings" msgstr "Ustawienia audio Kodi" +#: msgctxt "#32400" msgid "Go To Season" msgstr "Przejdź do sezonu" +#: msgctxt "#32401" msgid "Directors" msgstr "Reżyserzy" +#: msgctxt "#32402" msgid "Writer" msgstr "Scenarzysta" +#: msgctxt "#32403" msgid "Writers" msgstr "Scenarzyści" +#: msgctxt "#32404" msgid "Related Movies" msgstr "Powiązane filmy" +#: msgctxt "#32405" msgid "Download Subtitles" msgstr "Pobierz napisy" +#: msgctxt "#32406" msgid "Subtitle Delay" msgstr "Opóźnienie napisów" +#: msgctxt "#32407" msgid "Next Subtitle" msgstr "Następne napisy" +#: msgctxt "#32408" msgid "Disable Subtitles" msgstr "Wyłącz napisy" +#: msgctxt "#32409" msgid "Enable Subtitles" msgstr "Włącz napisy" +#: msgctxt "#32410" msgid "Platform Version" msgstr "Wersja platformy" +#: msgctxt "#32411" msgid "Unknown" msgstr "Nieznana" +#: msgctxt "#32412" msgid "Edit Or Clear" msgstr "Edytuj lub wyczyść" +#: msgctxt "#32413" msgid "Edit IP address or clear the current setting?" msgstr "Edytować adres IP lub wyczyścić obecne ustawienie?" +#: msgctxt "#32414" msgid "Clear" msgstr "Wyczyść" +#: msgctxt "#32415" msgid "Edit" msgstr "Edytuj" +#: msgctxt "#32416" msgid "Enter IP Address" msgstr "Wprowadź adres IP" +#: msgctxt "#32417" msgid "Enter Port Number" msgstr "Wprowadź numer portu" +#: msgctxt "#32418" msgid "Creator" msgstr "Twórca" +#: msgctxt "#32419" msgid "Cast" msgstr "Obsada" +#: msgctxt "#32420" msgid "Disc" msgstr "Dysk" +#: msgctxt "#32421" msgid "Sign Out" msgstr "Wyloguj się" +#: msgctxt "#32422" msgid "Exit" msgstr "Wyjdź" +#: msgctxt "#32423" msgid "Shutdown" msgstr "Zamknij" +#: msgctxt "#32424" msgid "Suspend" msgstr "Uśpij" +#: msgctxt "#32425" msgid "Hibernate" msgstr "Hibernuj" +#: msgctxt "#32426" msgid "Reboot" msgstr "Uruchom ponownie" +#: msgctxt "#32427" msgid "Failed" msgstr "Nieudane" +#: msgctxt "#32428" msgid "Login failed!" msgstr "Logowanie nieudane!" +#: msgctxt "#32429" msgid "Resume from {0}" msgstr "Wznów od {0}" +#: msgctxt "#32430" msgid "Discovery" msgstr "Odkrywanie" +#: msgctxt "#32431" msgid "Search" msgstr "Wyszukiwanie" +#: msgctxt "#32432" msgid "Space" msgstr "Spacja" +#: msgctxt "#32433" msgid "Clear" msgstr "Wyczyść" +#: msgctxt "#32434" msgid "Searching..." msgstr "Wyszukiwanie..." +#: msgctxt "#32435" msgid "No Results" msgstr "Brak wyników" +#: msgctxt "#32436" msgid "Paused" msgstr "Wstrzymano" +#: msgctxt "#32437" msgid "Welcome" msgstr "Witaj" +#: msgctxt "#32438" msgid "Previous" msgstr "Poprzednie" +#: msgctxt "#32439" msgid "Playing Next" msgstr "Odtwarzanie następnego" +#: msgctxt "#32440" msgid "On Deck" msgstr "Na tapecie" +#: msgctxt "#32441" msgid "Unknown" msgstr "Nieznany" +#: msgctxt "#32442" msgid "Embedded" msgstr "Osadzone" +#: msgctxt "#32443" msgid "Forced" msgstr "Wymuszone" +#: msgctxt "#32444" msgid "Lyrics" msgstr "Tekst" +#: msgctxt "#32445" msgid "Mono" msgstr "Mono" +#: msgctxt "#32446" msgid "Stereo" msgstr "Stereo" +#: msgctxt "#32447" msgid "None" msgstr "Brak" +#: msgctxt "#32448" msgid "Playback Failed!" msgstr "Odtwarzanie nieudane!" +#: msgctxt "#32449" msgid "Can't connect to plex.tv[CR]Check your internet connection and try again." msgstr "Nie można połączyć z plex.tv[CR]Sprawdź połączenie internetowe i spróbuj ponownie." +#: msgctxt "#32450" msgid "Choose Version" msgstr "Wybierz wersję" +#: msgctxt "#32451" msgid "Play Version..." msgstr "Odtwórz wersję..." +#: msgctxt "#32452" msgid "No Content available in this library" msgstr "Brak zawartości w tej bibliotece" +#: msgctxt "#32453" msgid "Please add content and/or check that 'Include in dashboard' is enabled." msgstr "Dodaj zawartość i/lub sprawdź, czy opcja „Załącz na pulpicie” jest włączona." +#: msgctxt "#32454" msgid "No Content available for this filter" msgstr "Brak zawartości dla tego filtra" +#: msgctxt "#32455" msgid "Please change change or remove the current filter" msgstr "Zmień lub usuń bieżący filtr" +#: msgctxt "#32456" msgid "Show" msgstr "Serial" +#: msgctxt "#32457" msgid "By Show" msgstr "Wg serialu" +#: msgctxt "#32458" msgid "Episodes" msgstr "Odcinki(ów)" +#: msgctxt "#32459" msgid "Offline Mode" msgstr "Tryb offline" +#: msgctxt "#32460" msgid "Sign In" msgstr "Zaloguj się" +#: msgctxt "#32461" msgid "Albums" msgstr "Albumy" +#: msgctxt "#32462" msgid "Artist" msgstr "Wykonawca" +#: msgctxt "#32463" msgid "By Artist" msgstr "Wg wykonawcy" +#: msgctxt "#32464" msgid "Player" msgstr "Odtwarzacz" +#: msgctxt "#32465" msgid "Use skip step settings from Kodi" msgstr "Użyj ustawień kroków przeskoku z Kodi" +#: msgctxt "#32466" msgid "Automatically seek selected position after a delay" msgstr "Automatycznie szukaj wybranej pozycji po opóźnieniu" +#: msgctxt "#32467" msgid "User Interface" msgstr "Interfejs użytkownika" +#: +msgctxt "#32468" +msgid "Show dynamic background art" +msgstr "" + +#: +msgctxt "#32469" +msgid "Background art blur amount" +msgstr "" + +#: +msgctxt "#32470" +msgid "Background art opacity" +msgstr "" + +#: msgctxt "#32471" msgid "Use Plex/Kodi steps for timeline" msgstr "Użyj kroków Plex/Kodi dla osi czasu" +#: +msgctxt "#32480" +msgid "Theme music" +msgstr "" + +#: +msgctxt "#32481" +msgid "Off" +msgstr "" + +#: +msgctxt "#32482" +msgid "%(percentage)s %%" +msgstr "" + +#: +msgctxt "#32483" +msgid "Hide Stream Info" +msgstr "" + +#: +msgctxt "#32484" +msgid "Show Stream Info" +msgstr "" + +#: msgctxt "#32485" msgid "Go back instantly with the previous menu action in scrolled views" msgstr "Wróć natychmiast akcją menu „poprzednie” w przewijanych widokach" +#: +msgctxt "#32487" +msgid "Seek Delay" +msgstr "" + +#: +msgctxt "#32488" +msgid "Screensaver" +msgstr "" + +#: +msgctxt "#32489" +msgid "Quiz Mode" +msgstr "" + +#: +msgctxt "#32490" +msgid "Collections" +msgstr "" + +#: +msgctxt "#32491" +msgid "Folders" +msgstr "" + +#: msgctxt "#32492" msgid "Kodi Subtitle Settings" msgstr "Ustawienia napisów Kodi" +#: msgctxt "#32495" msgid "Skip intro" msgstr "Pomiń wstęp" + +#: +msgctxt "#32496" +msgid "Skip credits" +msgstr "" + +#: +msgctxt "#32500" +msgid "Always show post-play screen (even for short videos)" +msgstr "" + +#: +msgctxt "#32501" +msgid "Time-to-wait between videos on post-play" +msgstr "" + +#: +msgctxt "#32505" +msgid "Visit media in video playlist instead of playing it" +msgstr "" + +#: +msgctxt "#32521" +msgid "Skip Intro Button Timeout" +msgstr "" + +#: +msgctxt "#32522" +msgid "Automatically Skip Intro" +msgstr "" + +#: +msgctxt "#32524" +msgid "Set how long the skip intro button shows for." +msgstr "" + +#: +msgctxt "#32525" +msgid "Skip Credits Button Timeout" +msgstr "" + +#: +msgctxt "#32526" +msgid "Automatically Skip Credits" +msgstr "" + +#: +msgctxt "#32528" +msgid "Set how long the skip credits button shows for." +msgstr "" + +#: +msgctxt "#32540" +msgid "Show when the current video will end in player" +msgstr "" + +#: +msgctxt "#32541" +msgid "Shows time left and at which time the media will end." +msgstr "" + +#: +msgctxt "#32542" +msgid "Show \"Ends at\" label for the end-time as well" +msgstr "" + +#: +msgctxt "#32543" +msgid "Ends at" +msgstr "" + +#: +msgctxt "#32602" +msgid "Enable this if your hardware can handle AV1. Disable it to force transcoding." +msgstr "" + +#: +msgctxt "#33101" +msgid "By Audience Rating" +msgstr "" + +#: +msgctxt "#33102" +msgid "Audience Rating" +msgstr "" + +#: +msgctxt "#33103" +msgid "By my Rating" +msgstr "" + +#: +msgctxt "#33104" +msgid "My Rating" +msgstr "" + +#: +msgctxt "#33105" +msgid "By Content Rating" +msgstr "" + +#: +msgctxt "#33106" +msgid "Content Rating" +msgstr "" + +#: +msgctxt "#33107" +msgid "By Critic Rating" +msgstr "" + +#: +msgctxt "#33108" +msgid "Critic Rating" +msgstr "" + +#: +msgctxt "#33200" +msgid "Background Color" +msgstr "" + +#: +msgctxt "#33201" +msgid "Specify solid Background Color instead of using media images" +msgstr "" + +#: +msgctxt "#33400" +msgid "Use old compatibility profile" +msgstr "" + +#: +msgctxt "#33401" +msgid "Uses the Chrome client profile instead of the custom one. Might fix rare issues with 3D playback." +msgstr "" + +#: +msgctxt "#32031" +msgid "Burn-in Subtitles" +msgstr "" + +#: +msgctxt "#32061" +msgid "When transcoding audio, target the audio channels set in Kodi." +msgstr "" + +#: +msgctxt "#32062" +msgid "Transcode audio to AC3" +msgstr "" + +#: +msgctxt "#32063" +msgid "Transcode audio to AC3 in certain conditions (useful for passthrough)." +msgstr "" + +#: +msgctxt "#32065" +msgid "When any of the force AC3 settings are enabled, treat DTS the same as AC3 (useful for Optical passthrough)" +msgstr "" + +#: +msgctxt "#32066" +msgid "Force audio to AC3" +msgstr "" + +#: +msgctxt "#32067" +msgid "Only force multichannel audio to AC3" +msgstr "" + +#: +msgctxt "#32493" +msgid "When a media file has a forced/foreign subtitle for a subtitle-enabled language, the Plex Media Server preselects it. This behaviour is usually not necessary and not configurable. This setting fixes that by ignoring the PMSs decision and selecting the same language without a forced flag if possible." +msgstr "" + +#: +msgctxt "#32523" +msgid "Automatically skip intros if available. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "" + +#: +msgctxt "#32527" +msgid "Automatically skip credits if available. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "" + +#: +msgctxt "#33501" +msgid "Video played threshold" +msgstr "" + +#: +msgctxt "#33502" +msgid "Set this to the same value as your Plex server (Settings>Library>Video played threshold) to avoid certain pitfalls, Default: 90 %" +msgstr "" + +#: +msgctxt "#33503" +msgid "Use alternative hubs refresh" +msgstr "" + +#: +msgctxt "#33504" +msgid "Refreshes all hubs for all libraries after an item's watch-state has changed, instead of only those likely affected. Use this if you find a hub that doesn't update properly." +msgstr "" + +#: +msgctxt "#33505" +msgid "Show intro skip button early" +msgstr "" + +#: +msgctxt "#33507" +msgid "Enabled" +msgstr "" + +#: +msgctxt "#33508" +msgid "Disabled" +msgstr "" + +#: +msgctxt "#33510" +msgid "When showing the intro skip button early, only do so if the intro occurs within the first X seconds." +msgstr "" + +#: +msgctxt "#33600" +msgid "System" +msgstr "" + +#: +msgctxt "#33601" +msgid "Show video chapters" +msgstr "" + +#: +msgctxt "#33602" +msgid "If available, show video chapters from the video-file instead of the timeline-big-seek-steps." +msgstr "" + +#: +msgctxt "#33603" +msgid "Use virtual chapters" +msgstr "" + +#: +msgctxt "#33604" +msgid "When the above is enabled and no video chapters are available, simulate them by using the markers identified by the Plex Server (Intro, Credits)." +msgstr "" + +#: +msgctxt "#33605" +msgid "Video Chapters" +msgstr "" + +#: +msgctxt "#33606" +msgid "Virtual Chapters" +msgstr "" + +#: +msgctxt "#33607" +msgid "Chapter {}" +msgstr "" + +#: +msgctxt "#33608" +msgid "Intro" +msgstr "" + +#: +msgctxt "#33609" +msgid "Credits" +msgstr "" + +#: +msgctxt "#33610" +msgid "Main" +msgstr "" + +#: +msgctxt "#33611" +msgid "Chapters" +msgstr "" + +#: +msgctxt "#33612" +msgid "Markers" +msgstr "" + +#: +msgctxt "#33613" +msgid "Kodi Buffer Size (MB)" +msgstr "" + +#: +msgctxt "#33614" +msgid "Set the Kodi Cache/Buffer size. Free: {} MB, Recommended: ~50 MB, Recommended max: {} MB, Default: 20 MB." +msgstr "" + +#: +msgctxt "#33615" +msgid "{time} left" +msgstr "" + +#: +msgctxt "#33616" +msgid "Addon Path" +msgstr "" + +#: +msgctxt "#33617" +msgid "Userdata/Profile Path" +msgstr "" + +#: +msgctxt "#33618" +msgid "TV binge-viewing mode" +msgstr "" + +#: +msgctxt "#33622" +msgid "LAN reachability timeout (ms)" +msgstr "" + +#: +msgctxt "#33623" +msgid "When checking for LAN reachability, use this timeout. Default: 10ms" +msgstr "" + +#: +msgctxt "#33624" +msgid "Network" +msgstr "" + +#: +msgctxt "#33625" +msgid "Smart LAN/local server discovery" +msgstr "" + +#: +msgctxt "#33626" +msgid "Checks whether servers returned from Plex.tv are actually local/in your LAN. For specific setups (e.g. Docker) Plex.tv might not properly detect a local server.\n" +"\n" +"NOTE: Only works on Kodi 19 or above." +msgstr "" + +#: +msgctxt "#33627" +msgid "Prefer LAN/local servers over security" +msgstr "" + +#: +msgctxt "#33628" +msgid "Prioritizes local connections over secure ones. Needs the proper setting in \"Allow Insecure Connections\" and the Plex Server's \"Secure connections\" at \"Preferred\". Can be used to enforce manual servers." +msgstr "" + +#: +msgctxt "#33629" +msgid "Auto-skip intro/credits offset" +msgstr "" + +#: +msgctxt "#33630" +msgid "Intro/credits markers might be a little early in Plex. When auto skipping add (or subtract) this many seconds from the marker. This avoids cutting off content, while possibly skipping the marker a little late." +msgstr "" + +#: +msgctxt "#32631" +msgid "Playback (user-specific)" +msgstr "" + +#: +msgctxt "#33632" +msgid "Server connectivity check timeout (seconds)" +msgstr "" + +#: +msgctxt "#33633" +msgid "Set the maximum amount of time a server connection has to answer a connectivity request. Default: 2.5" +msgstr "" + +#: +msgctxt "#33634" +msgid "Combined Chapters" +msgstr "" + +#: +msgctxt "#33635" +msgid "Final Credits" +msgstr "" + +#: +msgctxt "#32700" +msgid "Action on Sleep event" +msgstr "" + +#: +msgctxt "#32701" +msgid "When Kodi receives a sleep event from the system, run the following action." +msgstr "" + +#: +msgctxt "#32702" +msgid "Nothing" +msgstr "" + +#: +msgctxt "#32703" +msgid "Stop playback" +msgstr "" + +#: +msgctxt "#32704" +msgid "Quit Kodi" +msgstr "" + +#: +msgctxt "#32705" +msgid "CEC Standby" +msgstr "" + +#: +msgctxt "#32800" +msgid "Skipping intro" +msgstr "" + +#: +msgctxt "#32801" +msgid "Skipping credits" +msgstr "" + +#: +msgctxt "#32900" +msgid "While playing back an item and seeking on the seekbar, automatically seek to the selected position after a delay instead of having to confirm the selection." +msgstr "" + +#: +msgctxt "#32901" +msgid "Seek delay in seconds." +msgstr "" + +#: +msgctxt "#32902" +msgid "Kodi has its own skip step settings. Try to use them if they're configured instead of the default ones." +msgstr "" + +#: +msgctxt "#32903" +msgid "Use the above for seeking on the timeline as well." +msgstr "" + +#: +msgctxt "#32904" +msgid "In seconds." +msgstr "" + +#: +msgctxt "#32905" +msgid "Cancel post-play timer by pressing OK/SELECT" +msgstr "" + +#: +msgctxt "#32906" +msgid "Cancel skip marker timer with BACK" +msgstr "" + +#: +msgctxt "#32907" +msgid "When auto-skipping a marker, allow cancelling the timer by pressing BACK." +msgstr "" + +#: +msgctxt "#32908" +msgid "Immediately skip marker with OK/SELECT" +msgstr "" + +#: +msgctxt "#32909" +msgid "When auto-skipping a marker with a timer, allow skipping immediately by pressing OK/SELECT." +msgstr "" + +#: +msgctxt "#32912" +msgid "Show buffer-state on timeline" +msgstr "" + +#: +msgctxt "#32913" +msgid "Shows the current Kodi buffer/cache state on the video player timeline." +msgstr "" + +#: +msgctxt "#32914" +msgid "Loading" +msgstr "" + +#: +msgctxt "#32915" +msgid "Slow connection" +msgstr "" + +#: +msgctxt "#32916" +msgid "Use with a wonky/slow connection, e.g. in a hotel room. Adjusts the UI to visually wait for item refreshes and waits for the buffer to fill when starting playback. Automatically sets readfactor=20, requires Kodi restart." +msgstr "" + +#: +msgctxt "#32917" +msgid "Couldn't fill buffer in time ({}s)" +msgstr "" + +#: +msgctxt "#32918" +msgid "Buffer wait timeout (seconds)" +msgstr "" + +#: +msgctxt "#32919" +msgid "When slow connection is enabled in the addon, wait this long for the buffer to fill. Default: 120 s" +msgstr "" + +#: +msgctxt "#32920" +msgid "Insufficient buffer wait (seconds)" +msgstr "" + +#: +msgctxt "#32921" +msgid "When slow connection is enabled in the addon and the configured buffer isn't big enough for us to determine its fill state, wait this long when starting playback. Default: 10 s" +msgstr "" + +#: +msgctxt "#32922" +msgid "Kodi Cache Readfactor" +msgstr "" + +#: +msgctxt "#32923" +msgid "Sets the Kodi cache readfactor value. Default: {0}, recommended: {1}. With \"Slow connection\" enabled this will be set to {2}, as otherwise the cache doesn't fill fast/aggressively enough." +msgstr "" + +#: +msgctxt "#32924" +msgid "Minimize" +msgstr "" + +#: +msgctxt "#32925" +msgid "Playback Settings" +msgstr "" + +#: +msgctxt "#32926" +msgid "Wrong pin entered!" +msgstr "" + +#: +msgctxt "#32927" +msgid "Use episode thumbnails in continue hub" +msgstr "" + +#: +msgctxt "#32928" +msgid "Instead of using media artwork, use thumbnails for episodes in the continue hub on the home screen if available." +msgstr "" + +#: +msgctxt "#32929" +msgid "Use legacy background fallback image" +msgstr "" + +#: +msgctxt "#32930" +msgid "Previous Subtitle" +msgstr "" + +#: +msgctxt "#32931" +msgid "Audio/Subtitles" +msgstr "" + +#: +msgctxt "#32936" +msgid "Show playlist button" +msgstr "" + +#: +msgctxt "#32937" +msgid "Show prev/next button" +msgstr "" + +#: +msgctxt "#32941" +msgid "Forced subtitles fix" +msgstr "" + +#: +msgctxt "#32942" +msgid "Other seasons" +msgstr "" + +#: +msgctxt "#32943" +msgid "Crossfade dynamic background art" +msgstr "" + +#: +msgctxt "#32944" +msgid "Burn-in SSA subtitles (DirectStream)" +msgstr "" + +#: +msgctxt "#32945" +msgid "When Direct Streaming instruct the Plex Server to burn in SSA/ASS subtitles (thus transcoding the video stream). If disabled it will not touch the video stream, but will convert the subtitle to unstyled text." +msgstr "" + +#: +msgctxt "#32946" +msgid "Stop video playback on idle after" +msgstr "" + +#: +msgctxt "#32947" +msgid "Stop video playback on screensaver" +msgstr "" + +#: +msgctxt "#32948" +msgid "Allow auto-skip when transcoding" +msgstr "" + +#: +msgctxt "#32949" +msgid "When transcoding/DirectStreaming, allow auto-skip functionality." +msgstr "" + +#: +msgctxt "#32950" +msgid "Use extended title for subtitles" +msgstr "" + +#: +msgctxt "#32951" +msgid "When displaying subtitles use the extendedDisplayTitle Plex exposes." +msgstr "" + +#: +msgctxt "#32953" +msgid "Reviews" +msgstr "" + +#: +msgctxt "#32954" +msgid "Needs Kodi restart. WARNING: This will overwrite advancedsettings.xml!\n" +"\n" +"To customize other cache/network-related values, copy \"script.plexmod/pm4k_cache_template.xml\" to profile folder and edit it to your liking. (See About section for the file paths)" +msgstr "" + +#: +msgctxt "#32955" +msgid "Use Kodi keyboard for searching" +msgstr "" + +#: +msgctxt "#32956" +msgid "Poster resolution scaling %" +msgstr "" + +#: +msgctxt "#32957" +msgid "In percent. Scales the resolution of all posters/thumbnails for better image quality. May impact PMS/PM4K performance, will increase the cache usage accordingly. Recommended: 200-300 % for for big screens if your hardware can handle it. Needs addon restart." +msgstr "" + +#: +msgctxt "#32958" +msgid "Calculate OpenSubtitles.com hash" +msgstr "" + +#: +msgctxt "#32959" +msgid "When opening the subtitle download feature, automatically calculate the OpenSubtitles.com hash for the given file. Can improve search results, downloads 2*64 KB of the video file to calculate the hash." +msgstr "" + +#: +msgctxt "#32960" +msgid "Similar Artists" +msgstr "" + +#: +msgctxt "#32961" +msgid "Show hub bifurcation lines" +msgstr "" + +#: +msgctxt "#32962" +msgid "Visually separate hubs horizontally using a thin line." +msgstr "" + +#: +msgctxt "#32963" +msgid "Wait between videos (s)" +msgstr "" + +#: +msgctxt "#32964" +msgid "When playing back consecutive videos (e.g. TV shows), wait this long before starting the next one in the queue. Might fix compatibility issues with certain configurations." +msgstr "" + +#: +msgctxt "#32965" +msgid "Quit Kodi on exit by default" +msgstr "" + +#: +msgctxt "#32966" +msgid "When exiting the addon, use \"Quit Kodi\" as default option. Can be dynamically switched using CONTEXT_MENU (often longpress SELECT)" +msgstr "" + +#: +msgctxt "#32967" +msgid "Kodi Colour Management" +msgstr "" + +#: +msgctxt "#32968" +msgid "Kodi Resolution Settings" +msgstr "" + +#: +msgctxt "#32969" +msgid "Always request all library media items at once" +msgstr "" + +#: +msgctxt "#32970" +msgid "Retrieve all media in library up front instead of fetching it in chunks as the user navigates through the library" +msgstr "" + +#: +msgctxt "#32971" +msgid "Library item-request chunk size" +msgstr "" + +#: +msgctxt "#32972" +msgid "Request this amount of media items per chunk request in library view (+6-30 depending on view mode; less can be less straining for the UI at first, but puts more strain on the server)" +msgstr "" + +#: +msgctxt "#32973" +msgid "Episodes: Skip Post Play screen" +msgstr "" + +#: +msgctxt "#32974" +msgid "When finishing an episode, don't show Post Play but go to the next one immediately.\n" +"Can be disabled/enabled per TV show. Doesn't override enabled binge mode. Overrides the Post Play setting." +msgstr "" + +#: +msgctxt "#32975" +msgid "Delete Season" +msgstr "" + +#: +msgctxt "#32976" +msgid "Adaptive" +msgstr "" + +#: +msgctxt "#32978" +msgid "Enable this if your hardware can handle VC1. Disable it to force transcoding." +msgstr "" + +#: +msgctxt "#32979" +msgid "Allows the server to only transcode streams of a video that need transcoding, while streaming the others unaltered. If disabled, force the server to transcode everything not direct playable." +msgstr "" + +#: +msgctxt "#32980" +msgid "Refresh Users" +msgstr "" + +#: +msgctxt "#32981" +msgid "Background worker count" +msgstr "" + +#: +msgctxt "#32982" +msgid "Depending on how many cores your CPU has and how much it can handle, increasing this might improve certain situations. If you experience crashes or other annormalities, leave this at its default (3). Needs an addon restart." +msgstr "" + +#: +msgctxt "#32985" +msgid "Modern" +msgstr "" + +#: +msgctxt "#32986" +msgid "Modern (dotted)" +msgstr "" + +#: +msgctxt "#32987" +msgid "Classic" +msgstr "" + +#: +msgctxt "#32988" +msgid "Custom" +msgstr "" + +#: +msgctxt "#32989" +msgid "Modern (colored)" +msgstr "" + +#: +msgctxt "#32990" +msgid "Handle plex.direct mapping" +msgstr "" + +#: +msgctxt "#32991" +msgid "Notify" +msgstr "" + +#: +msgctxt "#32992" +msgid "When using servers with a plex.direct connection (most of them), should we automatically adjust advancedsettings.xml to cope with plex.direct domains? If not, you might want to add plex.direct to your router's DNS rebind exemption list." +msgstr "" + +#: +msgctxt "#32993" +msgid "{} unhandled plex.direct connections found" +msgstr "" + +#: +msgctxt "#32994" +msgid "In order for PM4K to work properly, we need to add special handling for plex.direct connections. We've found {} new unhandled connections. Do you want us to write those to Kodi's advancedsettings.xml automatically? If not, you might want to add plex.direct to your router's DNS rebind exemption list. This can be changed in the settings as well." +msgstr "" + +#: +msgctxt "#32995" +msgid "Advancedsettings.xml modified (plex.direct mappings)" +msgstr "" + +#: +msgctxt "#32996" +msgid "The advancedsettings.xml file has been modified. Please restart Kodi for the changes to apply." +msgstr "" + +#: +msgctxt "#32997" +msgid "OK" +msgstr "" + +#: +msgctxt "#32998" +msgid "Use new Continue Watching hub on Home" +msgstr "" + +#: +msgctxt "#32999" +msgid "Instead of separating Continue Watching and On Deck hubs, behave like the modern Plex clients, which combine those two types of hubs into one Continue Watching hub." +msgstr "" + +#: +msgctxt "#33000" +msgid "Enable path mapping" +msgstr "" + +#: +msgctxt "#33002" +msgid "Verify mapped files exist" +msgstr "" + +#: +msgctxt "#33003" +msgid "When path mapping is enabled and we've successfully mapped a file, verify its existence." +msgstr "" + +#: +msgctxt "#33004" +msgid "No spoilers without OSD" +msgstr "" + +#: +msgctxt "#33005" +msgid "When seeking without the OSD open, hide all time-related information from the user." +msgstr "" + +#: +msgctxt "#33006" +msgid "No TV spoilers" +msgstr "" + +#: +msgctxt "#33008" +msgid "[Spoilers removed]" +msgstr "" + +#: +msgctxt "#33013" +msgid "When the above is anything but \"off\", hide episode titles as well." +msgstr "" + +#: +msgctxt "#33014" +msgid "Ignore plex.direct docker hosts" +msgstr "" + +#: +msgctxt "#33015" +msgid "When checking for plex.direct host mapping, ignore local Docker IPv4 addresses (172.16.0.0/12)." +msgstr "" + +#: +msgctxt "#32303" +msgid "Season {}" +msgstr "" + +#: +msgctxt "#32304" +msgid "Episode {}" +msgstr "" + +#: +msgctxt "#32310" +msgid "S{}" +msgstr "" + +#: +msgctxt "#32311" +msgid "E{}" +msgstr "" + +#: +msgctxt "#32938" +msgid "Only for Episodes/Playlists" +msgstr "" + +#: +msgctxt "#33018" +msgid "Cache Plex Home users" +msgstr "" + +#: +msgctxt "#33019" +msgid "Visit media item" +msgstr "" + +#: +msgctxt "#33020" +msgid "Play" +msgstr "" + +#: +msgctxt "#33021" +msgid "Choose action" +msgstr "" + +#: +msgctxt "#33026" +msgid "Map path: {}" +msgstr "" + +#: +msgctxt "#33027" +msgid "Remove mapping: {}" +msgstr "" + +#: +msgctxt "#33028" +msgid "Hide library" +msgstr "" + +#: +msgctxt "#33029" +msgid "Show library: {}" +msgstr "" + +#: +msgctxt "#33030" +msgid "Choose action for: {}" +msgstr "" + +#: +msgctxt "#33031" +msgid "Select Kodi source for {}" +msgstr "" + +#: +msgctxt "#33032" +msgid "Show path mapping indicators" +msgstr "" + +#: +msgctxt "#33033" +msgid "When path mapping is active for a library, display an indicator." +msgstr "" + +#: +msgctxt "#33035" +msgid "Delete {}: {}?" +msgstr "" + +#: +msgctxt "#33036" +msgid "Delete episode S{0:02d}E{1:02d} from {2}?" +msgstr "" + +#: +msgctxt "#33037" +msgid "Maximum intro offset to consider" +msgstr "" + +#: +msgctxt "#33039" +msgid "Move" +msgstr "" + +#: +msgctxt "#33040" +msgid "Reset library order" +msgstr "" + +#: +msgctxt "#33034" +msgid "Library settings" +msgstr "" + +#: +msgctxt "#32357" +msgid "By Title" +msgstr "" + +#: +msgctxt "#32358" +msgid "Title" +msgstr "" + +#: +msgctxt "#33041" +msgid "Show hub: {}" +msgstr "" + +#: +msgctxt "#33042" +msgid "Episode Date Added" +msgstr "" + +#: +msgctxt "#33001" +msgid "Long-press (or context menu) on a library item or: Honor path_mapping.json in the addon_data/script.plexmod folder when DirectPlaying media. This can be used to stream using other techniques such as SMB/NFS/etc. instead of the default HTTP handler. path_mapping.example.json is included in the addon's main directory." +msgstr "" + +#: +msgctxt "#33043" +msgid "Hubs round-robin" +msgstr "" + +#: +msgctxt "#33045" +msgid "Behave like official Plex clients" +msgstr "" + +#: +msgctxt "#32025" +msgid "Direct Play" +msgstr "" + +#: +msgctxt "#32026" +msgid "Direct Stream" +msgstr "" + +#: +msgctxt "#32036" +msgid "4K" +msgstr "" + +#: +msgctxt "#32037" +msgid "HEVC (h265)" +msgstr "" + +#: +msgctxt "#32601" +msgid "AV1" +msgstr "" + +#: +msgctxt "#32932" +msgid "Subtitle quick-actions" +msgstr "" + +#: +msgctxt "#32933" +msgid "FFWD/RWD" +msgstr "" + +#: +msgctxt "#32934" +msgid "Repeat" +msgstr "" + +#: +msgctxt "#32935" +msgid "Shuffle" +msgstr "" + +#: +msgctxt "#32939" +msgid "User-specific.\n" +"Only applies to video player UI" +msgstr "" + +#: +msgctxt "#32977" +msgid "VC1" +msgstr "" + +#: +msgctxt "#32983" +msgid "Theme" +msgstr "" + +#: +msgctxt "#32984" +msgid "Sets the theme. Currently only customizes all control buttons. ATTENTION: [I]Might[/I] need an addon restart.\n" +"In order to customize this, copy one of the xml's in script.plexmod/resources/skins/Main/1080i/templates to addon_data/script.plexmod/templates/{templatename}_custom.xml and adjust it to your liking, then select \"Custom\" as your theme." +msgstr "" + +#: +msgctxt "#33011" +msgid "In progress" +msgstr "" + +#: +msgctxt "#33016" +msgid "Allow TV spoilers for" +msgstr "" + +#: +msgctxt "#33017" +msgid "Overrides the above for specific genres. Default: Reality, Game Show, Documentary, Sport" +msgstr "" + +#: +msgctxt "#33024" +msgid "Hide background in modern indicators" +msgstr "" + +#: +msgctxt "#33044" +msgid "Allow round-robining in hubs. Attention: Loads a lot of items. Depending on the hub size, this can lead to crashes. Current limit: {}; can be adjusted in addon settings." +msgstr "" + +#: +msgctxt "#33046" +msgid "When pressing up/down while the OSD isn't shown, show OSD instead of skipping chapters. Additionally, when pressing down while the OSD is shown, open the chapters (if available)." +msgstr "" + +#: +msgctxt "#33047" +msgid "Hubs round-robin item limit" +msgstr "" + +#: +msgctxt "#33048" +msgid "When hubs round-robin is enabled, only round-robin until this item limit. If the hub size exceeds this limit, the remaining items will be lazy loaded as usual. Tested minimum safe value on NVIDIA SHIELD 2019 is 1000. Default: 250" +msgstr "" + +#: +msgctxt "#33049" +msgid "Max retries" +msgstr "" + +#: +msgctxt "#33051" +msgid "Use CA certificate bundle" +msgstr "" + +#: +msgctxt "#33053" +msgid "System" +msgstr "" + +#: +msgctxt "#33055" +msgid "Custom" +msgstr "" + +#: +msgctxt "#33056" +msgid "None" +msgstr "" + +#: +msgctxt "#33057" +msgid "Show buttons" +msgstr "" + +#: +msgctxt "#33058" +msgid "Playback features" +msgstr "" + +#: +msgctxt "#33059" +msgid "Additional codecs" +msgstr "" + +#: +msgctxt "#33060" +msgid "{feature_ds}: {desc_ds}\n" +"{feature_4k}: {desc_4k}" +msgstr "" + +#: +msgctxt "#33061" +msgid "Enable certain codecs if your hardware supports them. Disable them to force transcoding." +msgstr "" + +#: +msgctxt "#33062" +msgid "Compiling templates" +msgstr "" + +#: +msgctxt "#33063" +msgid "Looking for custom templates" +msgstr "" + +#: +msgctxt "#33064" +msgid "Rendering: {}" +msgstr "" + +#: +msgctxt "#33065" +msgid "Complete" +msgstr "" + +#: +msgctxt "#33066" +msgid "Cache template files" +msgstr "" + +#: +msgctxt "#33067" +msgid "Doesn't throw away the template source files after compiling them. Uses slightly more memory but increases the speed of theme-related changes." +msgstr "" + +#: +msgctxt "#33068" +msgid "Always compile templates" +msgstr "" + +#: +msgctxt "#33069" +msgid "Recompiles all templates on every startup. Useful for template/theme development." +msgstr "" + +#: +msgctxt "#33070" +msgid "Action on Wake event" +msgstr "" + +#: +msgctxt "#33071" +msgid "Restart PM4K" +msgstr "" + +#: +msgctxt "#33072" +msgid "Wait for {}s" +msgstr "" + +#: +msgctxt "#33073" +msgid "Wait after wakeup" +msgstr "" + +#: +msgctxt "#33074" +msgid "Waiting {} second(s)" +msgstr "" + +#: +msgctxt "#33076" +msgid "Modern (2024)" +msgstr "" + +#: +msgctxt "#33077" +msgid "Scale modern indicators" +msgstr "" + +#: +msgctxt "#33078" +msgid "Scale the modern indicators based on the poster size used. Default: On (tiny: 0.75, small: 1.0, medium: 1.175, big: 1.3)" +msgstr "" + +#: +msgctxt "#33079" +msgid "Hi-Res Music" +msgstr "" + +#: +msgctxt "#33080" +msgid "Allow DirectPlay of high resolution music (e.g. FLAC, >= 192 kHz)" +msgstr "" + +#: +msgctxt "#33081" +msgid "Blur chapter images" +msgstr "" + +#: +msgctxt "#33082" +msgid "Scan Library Files" +msgstr "" + +#: +msgctxt "#33083" +msgid "Empty Trash" +msgstr "" + +#: +msgctxt "#33084" +msgid "Analyze" +msgstr "" + +#: +msgctxt "#33085" +msgid "Map key to home" +msgstr "" + +#: +msgctxt "#33086" +msgid "Press the key you want to map to go to home within {} seconds" +msgstr "" + +#: +msgctxt "#33620" +msgid "Plex server connect timeout" +msgstr "" + +#: +msgctxt "#33621" +msgid "Sets the maximum amount of time to connect to a Plex Server in seconds. Default: 5" +msgstr "" + +#: +msgctxt "#32940" +msgid "Video Player" +msgstr "" + +#: +msgctxt "#33050" +msgid "The maximum number of retries each connection should attempt. The default (3) helps with hibernation/wakeup issues. Set higher for bad connections, setting this to 0 was the old default before 0.7.9" +msgstr "" + +#: +msgctxt "#33075" +msgid "Do something after waking up. Waiting for a time until resuming normal execution helps with networks coming back up after sleep events. Default: 1 second (5 seconds, CoreELEC)." +msgstr "" + +#: +msgctxt "#33087" +msgid "Bind a key to immediately go to the Home view. Cancel using BACK/PREVIOUS_MENU. Clear bound key using the STOP or CONTEXT_MENU (long press OK/Enter) button on the setting." +msgstr "" + +#: +msgctxt "#33088" +msgid "Only applies to video player UI" +msgstr "" + +#: +msgctxt "#33089" +msgid "Automatic seek-back" +msgstr "" + +#: +msgctxt "#33090" +msgid "If your audio doesn't resume as fast as the video, or you want to catch up after a longer pause, use this to seek back after resuming from pause to compensate for the delay. Default: Off" +msgstr "" + +#: +msgctxt "#33091" +msgid "{sec_or_ms} {unit_s_or_ms}" +msgstr "" + +#: +msgctxt "#33092" +msgid "Seek back on pause" +msgstr "" + +#: +msgctxt "#33093" +msgid "Seek back after" +msgstr "" + +#: +msgctxt "#33094" +msgid "Only seek back after having paused at least a certain amount of seconds" +msgstr "" + +#: +msgctxt "#33095" +msgid "Seek back on pause instead of on resume. When Transcoding you should enable this." +msgstr "" + +#: +msgctxt "#33096" +msgid "Only with Direct Play" +msgstr "" + +#: +msgctxt "#33097" +msgid "Enable seek back ony when we're Direct Playing, not Transcoding." +msgstr "" + +#: +msgctxt "#33098" +msgid "Tickrate (Hz)" +msgstr "" + +#: +msgctxt "#33099" +msgid "Controls how often certain ticks are performed on GUI windows and the SeekDialog/VideoPlayer and how fast certain events are handled. Can be expensive when bigger than 1 Hz (once per second), can cause quirks when lower than 1 Hz (less than once per second). Depends on the hardware. Default: 1 Hz, Max: 10 Hz (every 100 ms), Sane highest and old default: 10 Hz (every 100 ms)" +msgstr "" + +#: +msgctxt "#33636" +msgid "Plex server read timeout" +msgstr "" + +#: +msgctxt "#33637" +msgid "Sets the maximum amount of time to read from a Plex Server in seconds. Default: 10" +msgstr "" + +#: +msgctxt "#33638" +msgid "Plex.tv connect timeout" +msgstr "" + +#: +msgctxt "#33640" +msgid "Plex.tv read timeout" +msgstr "" + +#: +msgctxt "#33642" +msgid "Dump config" +msgstr "" + +#: +msgctxt "#33643" +msgid "Dumps all user settings into the log on startup, when DEBUG logging is enabled. Masks private information." +msgstr "" + +#: +msgctxt "#33644" +msgid "Tracks" +msgstr "" + +#: +msgctxt "#33645" +msgid "plex.direct: Honor plex.tv's dnsRebindingProtection flag (DNS)" +msgstr "" + +#: +msgctxt "#33646" +msgid "Only handle plex.direct hosts when the server's attributes dnsRebindingProtection=1. Might not work in certain situations. Disable if you have connection issues. Default: On" +msgstr "" + +#: +msgctxt "#33647" +msgid "plex.direct: Honor plex.tv's publicAddressMatches flag (DNS)" +msgstr "" + +#: +msgctxt "#33648" +msgid "Only handle plex.direct hosts when the server's attributes publicAddressMatches=1. Might not work in certain situations. Disable if you have connection issues. Default: On" +msgstr "" + +#: +msgctxt "#32101" +msgid "If enabled, when playback ends and there is a 'Next Up' item available, it will be automatically be played after a {} second delay." +msgstr "" + +#: +msgctxt "#33651" +msgid "Startup delay" +msgstr "" + +#: +msgctxt "#33652" +msgid "Never show Post Play" +msgstr "" + +#: +msgctxt "#33506" +msgid "Show the intro skip button from the start of a video with an intro marker. The auto-skipping setting applies. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "" + +#: +msgctxt "#33619" +msgid "Automatically skips episode intros, credits and tries to skip episode recaps. Doesn't skip the intro of the first episode of a season and doesn't skip the final credits of a show.\n" +"\n" +"Can be disabled/enabled per TV show.\n" +"Overrides any setting below." +msgstr "" + +#: +msgctxt "#33052" +msgid "Which CA certificate bundle to use for request HTTPS verification. \"system\" uses the system-provided certificate bundle. \"ACME\" uses an extremely small bundle provided with the addon, which includes root certificates for Letsencrypt (plex.direct) and ZeroSSL. \"custom\" allows for a ca-certificate bundle in userdata/addon_data/script.plexmod/custom_bundle.crt" +msgstr "" + +#: +msgctxt "#33054" +msgid "ACME (addon-supplied)" +msgstr "" + +#: +msgctxt "#33639" +msgid "Plex.tv: Sets the maximum amount of time to connect to Plex.tv in seconds. Default: 1" +msgstr "" + +#: +msgctxt "#33641" +msgid "Plex.tv: Sets the maximum amount of time to read from Plex.tv in seconds. Default: 2" +msgstr "" + +#: +msgctxt "#33653" +msgid "Background image resolution scaling %" +msgstr "" + +#: +msgctxt "#33654" +msgid "In percent, based on 1080p. Scales the resolution of all backgrounds for better image quality. May impact PMS/PM4K performance, will increase the cache usage accordingly. Can lead to crashes if your hardware is low on RAM. Needs addon restart. Use 400% for 4K. ATTENTION: In order for this to work you need to add 2160 and 2160 to your advancedsettings.xml." +msgstr "" + +#: +msgctxt "#33655" +msgid "Auto-Sync Subtitles" +msgstr "" + +#: +msgctxt "#33656" +msgid "Only for External SRT subtitles. The PMS setting for voice activity detection has to be enabled for this to work." +msgstr "" + +#: in player quick settings UI, needs to be short +msgctxt "#33657" +msgid "Enable Auto-Sync" +msgstr "" + +#: in player quick settings UI, needs to be short +msgctxt "#33658" +msgid "Disable Auto-Sync" +msgstr "" + +#: +msgctxt "#33659" +msgid "Hide hub: {}" +msgstr "" + +#: +msgctxt "#33660" +msgid "Disable HDR" +msgstr "" + +#: +msgctxt "#33661" +msgid "If you don't want your client to handle HDR (or HDR-fallback), enable this to force transcoding. Doesn't apply to DV Profile 5." +msgstr "" + +#: +msgctxt "#33662" +msgid "Remove from Continue Watching" +msgstr "" + +#: +msgctxt "#33663" +msgid "Home: Confirm item actions" +msgstr "" + +#: +msgctxt "#33664" +msgid "When acting on items in the Home view, such as mark played, hide from continue watching etc., show a confirmation dialog." +msgstr "" + +#: +msgctxt "#33665" +msgid "Disable audio codecs" +msgstr "" + +#: +msgctxt "#33666" +msgid "Audio codecs you can't play back. Disables Direct Play for such media items, enables Direct Stream if possible, transcodes audio stream to compatible format." +msgstr "" + +#: +msgctxt "#32002" +msgid "20 Mbps" +msgstr "20 Mb/s" + +#: +msgctxt "#32003" +msgid "12 Mbps" +msgstr "12 Mb/s" + +#: +msgctxt "#32004" +msgid "10 Mbps" +msgstr "10 Mb/s" + +#: +msgctxt "#32005" +msgid "8 Mbps" +msgstr "8 Mb/s" + +#: +msgctxt "#32006" +msgid "4 Mbps" +msgstr "4 Mb/s" + +#: +msgctxt "#32007" +msgid "3 Mbps" +msgstr "3 Mb/s" + +#: +msgctxt "#32008" +msgid "2 Mbps" +msgstr "2 Mb/s" + +#: +msgctxt "#32009" +msgid "1.5 Mbps" +msgstr "1.5 Mb/s" + +#: +msgctxt "#33669" +msgid "Really sign out?" +msgstr "" + +#: +msgctxt "#33670" +msgid "Update available" +msgstr "" + +#: +msgctxt "#33672" +msgid "Check for updates" +msgstr "" + +#: +msgctxt "#33673" +msgid "Automatically check for updates periodically. If installed from a Kodi repository and the Update Source setting is set to Repository, Kodi itself will handle the updating of this addon. Needs a Kodi restart when changed." +msgstr "" + +#: +msgctxt "#33674" +msgid "Check for updates on start" +msgstr "" + +#: +msgctxt "#33675" +msgid "Automatically check for updates on startup. Doesn't do much when Update source is Repository. Needs a Kodi restart when changed." +msgstr "" + +#: +msgctxt "#33676" +msgid "Update source" +msgstr "" + +#: +msgctxt "#33677" +msgid "Specifies the update mode. Will immediately check for a new version when changed and closing settings.\n" +"Default: Repository\n" +"\n" +"Beta: Bleeding edge (possibly unstable)\n" +"Stable: Stable branch (faster than Repository)\n" +"Repository: Kodi repository (official (slow) or Don't Panic)" +msgstr "" + +#: +msgctxt "#33678" +msgid "Beta" +msgstr "" + +#: Update mode as in (beta, stable, repository) +msgctxt "#33679" +msgid "Stable" +msgstr "" + +#: +msgctxt "#33680" +msgid "Repository" +msgstr "" + +#: +msgctxt "#33683" +msgid "Exit, download and install" +msgstr "" + +#: +msgctxt "#33684" +msgid "Later" +msgstr "" + +#: +msgctxt "#32015" +msgid "6 Mbps" +msgstr "" + +#: +msgctxt "#32016" +msgid "16 Mbps" +msgstr "" + +#: +msgctxt "#32017" +msgid "26 Mbps" +msgstr "" + +#: +msgctxt "#33681" +msgid "Service updated" +msgstr "" + +#: +msgctxt "#33682" +msgid "The update {} has had changes to the updater itself. In order for the updated updater service to work, a Kodi restart is necessary. The addon will work normally, though. Do you still want to run the addon?" +msgstr "" + +#: +msgctxt "#33685" +msgid "Clamp video bitrate" +msgstr "" + +#: +msgctxt "#33686" +msgid "Only show transcode bitrate targets lower than the current video's bitrate." +msgstr "" + +#: +msgctxt "#33687" +msgid "Translation updated" +msgstr "" + +#: +msgctxt "#33688" +msgid "The currently in-use translation has been updated. In order to load the new translation, a Kodi restart is necessary. The addon will still run properly, but you might see badly or untranslated strings. Do you still want to run the addon?" +msgstr "" + +#: +msgctxt "#33509" +msgid "Early intro skip threshold (default: < 120s/2m)" +msgstr "" + +#: +msgctxt "#33007" +msgid "Select in which cases to blur TV episode thumbnails, previews, redact summaries, hide episode titles etc." +msgstr "" + +#: +msgctxt "#33009" +msgid "Blur amount for unplayed/in-progress episodes" +msgstr "" + +#: +msgctxt "#33010" +msgid "Unplayed" +msgstr "" + +#: +msgctxt "#33012" +msgid "No unplayed episode titles" +msgstr "" + +#: +msgctxt "#33022" +msgid "Played indicators" +msgstr "" + +#: +msgctxt "#33023" +msgid "Classic: Show orange triangle for unplayed items\n" +"Modern: Show green checkmark for played items\n" +"Modern (2024): Show white checkmark for played items\n" +"(default: Modern (2024))" +msgstr "" + +#: +msgctxt "#33025" +msgid "When the above is enabled, hide the black backdrop of the played state." +msgstr "" + +#: +msgctxt "#33689" +msgid "Service running" +msgstr "" + +#: +msgctxt "#33690" +msgid "Last update check" +msgstr "" + +#: +msgctxt "#33691" +msgid "Native languages" +msgstr "" + +#: +msgctxt "#33692" +msgid "When you usually watch things in a different language with subtitles, but are a native speaker of other languages, which you don't need subtitles for, prevent Plex from auto-selecting subtitles for those languages." +msgstr "" + +#: +msgctxt "#33693" +msgid "Download subtitles using" +msgstr "" + +#: +msgctxt "#33694" +msgid "Ask" +msgstr "" + +#: +msgctxt "#33695" +msgid "Where do you want to download subtitles from? Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "" + +#: +msgctxt "#33696" +msgid "No subtitles found." +msgstr "" + +#: things in curly brackets are variables, don't translate them! +msgctxt "#33697" +msgid "{provider_title}, Score: {subtitle_score}{subtitle_info}" +msgstr "" + +#: hearing impaired subtitle flag +msgctxt "#33698" +msgid "HI" +msgstr "" + +#: forced subtitle flag +msgctxt "#33699" +msgid "forced" +msgstr "" + +#: +msgctxt "#33700" +msgid "Download subtitles: {}" +msgstr "" + +#: +msgctxt "#33701" +msgid "Fallback to Kodi" +msgstr "" + +#: +msgctxt "#33702" +msgid "When no subtitles are found via the Plex subtitle search, fall back to Kodi subtitle search. Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "" + +#: +msgctxt "#33703" +msgid "Download subtitles" +msgstr "" + +#: +msgctxt "#33704" +msgid "Using which service?" +msgstr "" + +#: +msgctxt "#33705" +msgid "Hide ratings" +msgstr "" + +#: +msgctxt "#33706" +msgid "Blur images" +msgstr "" + +#: +msgctxt "#33707" +msgid "Blur images on home (in progress)" +msgstr "" + +#: +msgctxt "#33708" +msgid "Hide summaries" +msgstr "" + +#: +msgctxt "#33709" +msgid "Show ratings for" +msgstr "" + +#: +msgctxt "#33710" +msgid "Show reviews for" +msgstr "" + +#: +msgctxt "#33711" +msgid "Always resume media" +msgstr "" + +#: +msgctxt "#33712" +msgid "When playback of an in-progress media is requested, resume it by default instead of asking whether to resume or start from the beginning." +msgstr "" + +#: +msgctxt "#33713" +msgid "Home: Resume in-progress items" +msgstr "" + +#: +msgctxt "#33714" +msgid "Resume in-progress items directly instead of visiting the media." +msgstr "" + +#: +msgctxt "#33715" +msgid "OSD hide-delay" +msgstr "" + +#: +msgctxt "#33716" +msgid "How long to wait until hiding the OSD. Only works with Plextuary skin or others with configurable OSD timeout. Default: 4s (+/- 1s)" +msgstr "" + +#: +msgctxt "#33717" +msgid "Debug requests" +msgstr "" + +#: +msgctxt "#33718" +msgid "Played" +msgstr "" + +#: +msgctxt "#33719" +msgid "Refresh metadata" +msgstr "" + +#: +msgctxt "#33038" +msgid "When encountering an intro marker with a start time offset greater than this, ignore it (default: 1400s/23m)" +msgstr "" + +#: +msgctxt "#33649" +msgid "\"Use alternate seek\" wait for seek" +msgstr "" + +#: +msgctxt "#33650" +msgid "This adjusts the delay between seek-tries on problematic platforms when \"Use alternate seek\" is enabled, which fixes resume not always working or double-seeking. When you have resume/double-seek issues, increase this. Default: 500ms" +msgstr "" + +#: +msgctxt "#33667" +msgid "Use alternate seek" +msgstr "" + +#: +msgctxt "#33668" +msgid "ATTENTION: Only enable this if you have reproducible audio issues after seeking/resuming.\n" +"\n" +"Use an alternative seek method in videos, which can help in problematic scenarios; brings its own issues/quirks. Disabled by default (enabled by default for CoreELEC and LG WebOS)" +msgstr "" + +#: +msgctxt "#33720" +msgid "Clear all caches" +msgstr "" + +#: +msgctxt "#33721" +msgid "Clear library cache (not items)" +msgstr "" + +#: +msgctxt "#33722" +msgid "Libraries" +msgstr "" + +#: +msgctxt "#33723" +msgid "Media Items" +msgstr "" + +#: +msgctxt "#33724" +msgid "Cache Plex data for" +msgstr "" + +#: +msgctxt "#33725" +msgid "Persist cached Plex data" +msgstr "" + +#: +msgctxt "#33726" +msgid "Instead of clearing the cache when exiting the addon, persist it instead. Warning: You'll most likely encounter missing items in libraries or outdated data. Use the corresponding menu functionalities to clear the cache for specific items or libraries." +msgstr "" + +#: +msgctxt "#33727" +msgid "Store Plex server responses for items and library views in a local SQLite database. Doesn't cache anything else (Home/Hubs are always up to date). Massively speeds up consecutive visits to items and libraries. Certain important events, such as watch state changes, automatically delete the item cache and its corresponding library cache. (Default: Off)" +msgstr "" + +#: +msgctxt "#33728" +msgid "Clear cache for item" +msgstr "" + +#: +msgctxt "#33729" +msgid "Expire cached Plex data after (hours)" +msgstr "" + +#: +msgctxt "#33730" +msgid "Randomly" +msgstr "" + +#: +msgctxt "#33731" +msgid "By Bitrate" +msgstr "" + +#: +msgctxt "#33732" +msgid "Bitrate" +msgstr "" + +#: +msgctxt "#33733" +msgid "Transcode target codec" +msgstr "" + +#: +msgctxt "#33734" +msgid "Sets the target codec when transcoding/direct streaming. Overridden when \"Transcode audio to AC3\" is set." +msgstr "" + +#: +msgctxt "#33735" +msgid "Always auto-start" +msgstr "" + +#: +msgctxt "#33736" +msgid "By default PM4K won't auto-start when the current window isn't a Home-derived window (e.g.: Settings). Enable this if you always want to auto-start PM4K regardless of the current window. Can also improve start-up time." +msgstr "" + +#: +msgctxt "#33737" +msgid "Loop theme music" +msgstr "" + +#: +msgctxt "#33671" +msgid "Current: {current_version}\n" +"New: {new_version}\n" +"\n" +"Changelog:\n" +"{changelog}" +msgstr "" + +#: +msgctxt "#33738" +msgid "Max playlist size" +msgstr "" + +#: +msgctxt "#33739" +msgid "How many items to load into a Kodi playlist before chunking them remotely using Plex PlayQueues (default: 500); Reduce on memory issues/crashes." +msgstr "" + +#: +msgctxt "#33740" +msgid "Home: Episodes season thumbnails" +msgstr "" + +#: +msgctxt "#33741" +msgid "Use season thumbnails/posters when displaying episodes in hubs instead of the TV show's." +msgstr "" + +#: +msgctxt "#34000" +msgid "Watchlist" +msgstr "" + +#: +msgctxt "#34001" +msgid "Released" +msgstr "" + +#: +msgctxt "#34002" +msgid "Movies & Shows" +msgstr "" + +#: e.g.: 8 seasons +msgctxt "#34003" +msgid "{} seasons" +msgstr "" + +#: +msgctxt "#34004" +msgid "Choose server" +msgstr "" + +#: +msgctxt "#34005" +msgid "Availability" +msgstr "" + +#: e.g.: 1 season +msgctxt "#34006" +msgid "{} season" +msgstr "" + +#: +msgctxt "#34007" +msgid "Use Watchlist" +msgstr "" + +#: +msgctxt "#34008" +msgid "Activates the current user's Plex watchlist as a section item. Adds watchlist functionality to certain media screens. Per-user setting. Default: On" +msgstr "" + +#: +msgctxt "#34009" +msgid "Watchlist auto-remove" +msgstr "" + +#: +msgctxt "#34010" +msgid "Automatically remove fully watched items from watchlist. Default: On" +msgstr "" + +#: +msgctxt "#34011" +msgid "Remove from watchlist" +msgstr "" + +#: +msgctxt "#34012" +msgid "Fast pause/resume" +msgstr "" + +#: +msgctxt "#34013" +msgid "when paused" +msgstr "" + +#: +msgctxt "#34014" +msgid "when playing" +msgstr "" + +#: +msgctxt "#34015" +msgid "User-specific. Use OK/ENTER button to pause instead of showing the OSD (which can then only be accessed using DOWN), or resume when paused. Only works with 'Behave like official Plex clients' enabled." +msgstr "" + +#: +msgctxt "#34016" +msgid "Max shutdown wait" +msgstr "" + +#: +msgctxt "#34017" +msgid "The plugin will try to hard exit once this time limit is reached (default: 5 seconds)" +msgstr "" + +#: +msgctxt "#34018" +msgid "Related Media" +msgstr "" + +#: watchlist discover hub title prefix +msgctxt "#34019" +msgid "Discover: {}" +msgstr "" + +#: +msgctxt "#34020" +msgid "Loading external hubs" +msgstr "" + +#: +msgctxt "#34021" +msgid "Please wait ..." +msgstr "" + +#: +msgctxt "#34022" +msgid "Video play completion behaviour" +msgstr "" + +#: +msgctxt "#34023" +msgid "Decide whether to use end credits markers to determine the 'watched' state of video items. When markers are not available the selected threshold percentage will be used." +msgstr "" + +#: +msgctxt "#34024" +msgid "at selected threshold percentage" +msgstr "" + +#: +msgctxt "#34025" +msgid "at final credits marker position" +msgstr "" + +#: +msgctxt "#34026" +msgid "at first credits marker position" +msgstr "" + +#: +msgctxt "#34027" +msgid "earliest between threshold percent and first credits marker" +msgstr "" + +#: +msgctxt "#32973" +msgid "Episodes: Continuous playback" +msgstr "" + +#: +msgctxt "#34028" +msgid "\"Use alternate seek\" valid seek window" +msgstr "" + +#: +msgctxt "#34029" +msgid "When \"Use alternate seek\" is enabled, any seek amount outside of this threshold window will be applied, otherwise the seek attempt will be ignored. If you encounter any seek issues, play with this value (ms, default: 2000)" +msgstr "" + +#: +msgctxt "#34030" +msgid "Unlock Direct Play for above 4K" +msgstr "" + +#: +msgctxt "#34031" +msgid "Producer" +msgstr "" + +#: +msgctxt "#34032" +msgid "Audio Language" +msgstr "" + +#: +msgctxt "#34033" +msgid "Subtitle Language" +msgstr "" + +#: +msgctxt "#34034" +msgid "Folder Location" +msgstr "" + +#: +msgctxt "#34035" +msgid "Editions" +msgstr "" + +#: +msgctxt "#34036" +msgid "DOVI" +msgstr "" + +#: +msgctxt "#34037" +msgid "HDR" +msgstr "" + +#: +msgctxt "#34038" +msgid "Force plex.direct mapping" +msgstr "" + +#: +msgctxt "#34039" +msgid "If you (still) don't see any posters: Under certain circumstances when Kodi still can't resolve hostnames when loading assets, even though the host resolves natively, enable this to force plex.direct mapping via advancedsettings.xml." +msgstr "" + +#: +msgctxt "#34040" +msgid "By Progress" +msgstr "" + +#: +msgctxt "#34041" +msgid "Progress" +msgstr "" + +#: +msgctxt "#34042" +msgid "By Album" +msgstr "" + +#: +msgctxt "#34043" +msgid "Album" +msgstr "" + diff --git a/script.plexmod/resources/language/resource.language.pt_br/strings.po b/script.plexmod/resources/language/resource.language.pt_br/strings.po index 98bd80061d..7848a119b1 100644 --- a/script.plexmod/resources/language/resource.language.pt_br/strings.po +++ b/script.plexmod/resources/language/resource.language.pt_br/strings.po @@ -1,951 +1,3544 @@ -# XBMC Media Center language file msgid "" msgstr "" -"Project-Id-Version: XBMC-Addons\n" -"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n" -"POT-Creation-Date: 2013-12-12 22:56+0000\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: pt-BR\n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"X-Generator: POEditor.com\n" +"Project-Id-Version: PM4K / PlexMod for Kodi\n" +"Language: pt-br\n" +#: msgctxt "#32000" msgid "Main" msgstr "Principal" +#: msgctxt "#32001" msgid "Original" msgstr "Original" -msgctxt "#32002" -msgid "20 Mbps 1080p" -msgstr "20 Mbps 1080p" - -msgctxt "#32003" -msgid "12 Mbps 1080p" -msgstr "12 Mbps 1080p" - -msgctxt "#32004" -msgid "10 Mbps 1080p" -msgstr "10 Mbps 1080p" - -msgctxt "#32005" -msgid "8 Mbps 1080p" -msgstr "8 Mbps 1080p" - -msgctxt "#32006" -msgid "4 Mbps 720p" -msgstr "4 Mbps 720p" - -msgctxt "#32007" -msgid "3 Mbps 720p" -msgstr "3 Mbps 720p" - -msgctxt "#32008" -msgid "2 Mbps 720p" -msgstr "2 Mbps 720p" - -msgctxt "#32009" -msgid "1.5 Mbps 480p" -msgstr "1.5 Mbps 480p" - +#: msgctxt "#32010" msgid "720 kbps" msgstr "720 kbps" +#: msgctxt "#32011" msgid "320 kbps" msgstr "320 kbps" +#: msgctxt "#32012" msgid "208 kbps" msgstr "208 kbps" +#: msgctxt "#32013" msgid "96 kbps" msgstr "96 kbps" +#: msgctxt "#32014" msgid "64 kbps" msgstr "64 kbps" +#: msgctxt "#32020" msgid "Local Quality" msgstr "Qualidade Local" +#: msgctxt "#32021" msgid "Remote Quality" msgstr "Qualidade Remota" +#: msgctxt "#32022" msgid "Online Quality" msgstr "Qualidade Online" +#: msgctxt "#32023" msgid "Transcode Format" msgstr "Formato de Transcodificação" +#: msgctxt "#32024" msgid "Debug Logging" msgstr "Registro de Depuração" -msgctxt "#32025" -msgid "Allow Direct Play" -msgstr "Permitir Reprodução Direta" - -msgctxt "#32026" -msgid "Allow Direct Stream" -msgstr "Permitir Transmissão Direta" - +#: msgctxt "#32027" msgid "Force" msgstr "Forçar" +#: msgctxt "#32028" msgid "Always" msgstr "Sempre" +#: msgctxt "#32029" msgid "Only Image Formats" msgstr "Apenas Formatos de Imagem" +#: msgctxt "#32030" msgid "Auto" msgstr "Auto" -msgctxt "#32031" -msgid "Burn Subtitles (Direct Play Only)" -msgstr "Gravar Legendas (Somente Reprodução Direta)" - +#: msgctxt "#32032" msgid "Allow Insecure Connections" msgstr "Permitir Conexões Inseguras" +#: msgctxt "#32033" msgid "Never" msgstr "Nunca" +#: msgctxt "#32034" msgid "On Same network" msgstr "Na Mesma Rede" +#: msgctxt "#32035" msgid "Always" msgstr "Sempre" -msgctxt "#32036" -msgid "Allow 4K" -msgstr "Permitir 4K" - -msgctxt "#32037" -msgid "Allow HEVC (h265)" -msgstr "Permitir HEVC (h265)" - +#: msgctxt "#32038" msgid "Automatically Sign In" msgstr "Conectar Automaticamente" +#: msgctxt "#32039" msgid "Post Play Auto Play" msgstr "Reprodução Automática Após Reprodução" +#: msgctxt "#32040" msgid "Enable Subtitle Downloading" msgstr "Habilitar Download de Legendas" +#: msgctxt "#32041" msgid "Enable Subtitle Downloading" msgstr "Habilitar Download de Legendas" +#: msgctxt "#32042" msgid "Server Discovery (GDM)" msgstr "Descoberta de Servidor (GDM)" +#: msgctxt "#32043" msgid "Start Plex On Kodi Startup" msgstr "Iniciar Plex quando Kodi Iniciar" +#: msgctxt "#32044" msgid "Connection 1 IP" msgstr "Conexão de IP 1" +#: msgctxt "#32045" msgid "Connection 1 Port" msgstr "Conexão de Porta 1" +#: msgctxt "#32046" msgid "Connection 2 IP" msgstr "Conexão de IP 2" +#: msgctxt "#32047" msgid "Connection 2 Port" msgstr "Conexão de Porta 2" +#: msgctxt "#32048" msgid "Audio" msgstr "Áudio" +#: msgctxt "#32049" msgid "Advanced" msgstr "Avançado" +#: msgctxt "#32050" msgid "Manual Servers" msgstr "Servidores Manuais" +#: msgctxt "#32051" msgid "Privacy" msgstr "Privacidade" +#: msgctxt "#32052" msgid "About" msgstr "Sobre" +#: msgctxt "#32053" msgid "Video" msgstr "Vídeo" +#: msgctxt "#32054" msgid "Addon Version" msgstr "Versão do Addon" +#: msgctxt "#32055" msgid "Kodi Version" msgstr "Versão do Kodi" +#: msgctxt "#32056" msgid "Screen Resolution" msgstr "Resolução da Tela" +#: msgctxt "#32057" msgid "Current Server Version" msgstr "Versão Atual do Servidor" +#: +msgctxt "#32058" +msgid "Never exceed original audio codec" +msgstr "Nunca exceder o codec de áudio original" + +#: +msgctxt "#32059" +msgid "When transcoding audio, never exceed the original audio bitrate or channel count on the same codec." +msgstr "Quando fizer transcoding do áudio, nunca exceder o bitrate do áudio original ou número de canais no mesmo codec." + +#: +msgctxt "#32060" +msgid "Use Kodi audio channels" +msgstr "Usar os canais de áudio do Kodi" + +#: +msgctxt "#32064" +msgid "Treat DTS like AC3" +msgstr "Tratar DTS como AC3" + +#: msgctxt "#32100" msgid "Skip user selection and pin entry on startup." msgstr "Pular seleção de usuário e inserção do PIN ao iniciar." -msgctxt "#32101" -msgid "If enabled, when playback ends and there is a 'Next Up' item available, it will be automatically be played after a 15 second delay." -msgstr "Se habilitado, quando a reprodução acabar e existir algum item 'A Seguir', será automáticamente reproduzido depois de 15 segudos." - +#: msgctxt "#32102" msgid "Enable this if your hardware can handle 4K playback. Disable it to force transcoding." msgstr "Habilite isto se seu equipamento suporta reprodução 4K. Desabilite para forçar transcodificação." +#: msgctxt "#32103" msgid "Enable this if your hardware can handle HEVC/h265. Disable it to force transcoding." msgstr "Habilite isto se seu equipamento suporta HEVC/h265. Desabilite para forçar transcodificação." +#: msgctxt "#32104" msgid "When to connect to servers with no secure connections.[CR][CR]* [B]Never[/B]: Never connect to a server insecurely[CR]* [B]On Same Network[/B]: Allow if on the same network[CR]* [B]Always[/B]: Allow same network and remote connections" msgstr "Quando conectar à servidores sem conexão segura.[CR][CR]* [B]Nunca[/B]: Nunca conectar à um servidor inseguramente[CR]* [B]Na Mesma Rede[/B]: Permitir se na mesma reder[CR]* [B]Sempre[/B]: Permitir conexão na mesma rede e remotamente" +#: msgctxt "#32201" msgid "Trailer" msgstr "Trailer" +#: msgctxt "#32202" msgid "Deleted Scene" msgstr "Cena apagada" +#: msgctxt "#32203" msgid "Interview" msgstr "Entrevista" +#: msgctxt "#32204" msgid "Music Video" msgstr "Videoclipe" +#: msgctxt "#32205" msgid "Behind the Scenes" msgstr "Por trás das cenas" +#: msgctxt "#32206" msgid "Scene" msgstr "Cena" +#: msgctxt "#32207" msgid "Live Music Video" msgstr "Videoclipe ao vivo" +#: msgctxt "#32208" msgid "Lyric Music Video" msgstr "Videclipe com Letra" +#: msgctxt "#32209" msgid "Concert" msgstr "Show" +#: msgctxt "#32210" msgid "Featurette" msgstr "Featurette" +#: msgctxt "#32211" msgid "Short" msgstr "Curta" +#: msgctxt "#32212" msgid "Other" msgstr "Outro" +#: msgctxt "#32300" msgid "Go to Album" msgstr "Ir para Álbum" +#: msgctxt "#32301" msgid "Go to Artist" msgstr "Ir para Artista" +#: msgctxt "#32302" msgid "Go to {0}" msgstr "Ir para {0}" -msgctxt "#32303" -msgid "Season" -msgstr "Temporada" - -msgctxt "#32304" -msgid "Episode" -msgstr "Episódio" - +#: msgctxt "#32305" msgid "Extras" msgstr "Extras" +#: msgctxt "#32306" msgid "Related Shows" msgstr "Séries relacionadas" +#: msgctxt "#32307" msgid "More" msgstr "Mais" +#: msgctxt "#32308" msgid "Available" msgstr "Disponível" +#: msgctxt "#32309" msgid "None" msgstr "Nenhum" -msgctxt "#32310" -msgid "S" -msgstr "S" - -msgctxt "#32311" -msgid "E" -msgstr "E" - +#: msgctxt "#32312" msgid "Unavailable" msgstr "Indisponível" +#: msgctxt "#32313" msgid "This item is currently unavailable." msgstr "Este item está atualmente indisponível." +#: msgctxt "#32314" msgid "In Progress" msgstr "Em Progresso" +#: msgctxt "#32315" msgid "Resume playback?" msgstr "Resumir reprodução" +#: msgctxt "#32316" msgid "Resume" msgstr "Resumir" +#: msgctxt "#32317" msgid "Play from beginning" msgstr "Reproduzir do começo" +#: msgctxt "#32318" msgid "Mark Unplayed" msgstr "Marcar como Não-Visto" +#: msgctxt "#32319" msgid "Mark Played" msgstr "Marcar como Visto" +#: msgctxt "#32320" msgid "Mark Season Unplayed" msgstr "Marcar Temporada como Não-Vista" +#: msgctxt "#32321" msgid "Mark Season Played" msgstr "Marcar Temporada como Vista" +#: msgctxt "#32322" msgid "Delete" msgstr "Apagar" +#: msgctxt "#32323" msgid "Go To Show" msgstr "Ir para Série" +#: msgctxt "#32324" msgid "Go To {0}" msgstr "Ir para {0}" +#: msgctxt "#32325" msgid "Play Next" msgstr "Reproduzir Próximo" +#: msgctxt "#32326" msgid "Really Delete?" msgstr "Realmente apagar?" +#: msgctxt "#32327" msgid "Are you sure you really want to delete this media?" msgstr "Tem certeza que quer apagar esta mídia?" +#: msgctxt "#32328" msgid "Yes" msgstr "Sim" +#: msgctxt "#32329" msgid "No" msgstr "Não" +#: msgctxt "#32330" msgid "Message" msgstr "Mensagem" +#: msgctxt "#32331" msgid "There was a problem while attempting to delete the media." msgstr "Ocorreu um problema ao apagar esta mídia." +#: msgctxt "#32332" msgid "Home" msgstr "Início" +#: msgctxt "#32333" msgid "Playlists" msgstr "Listas de Reprodução" +#: msgctxt "#32334" msgid "Confirm Exit" msgstr "Confirmar Saída" +#: msgctxt "#32335" msgid "Are you ready to exit Plex?" msgstr "Está pronto para sair do Plex?" +#: msgctxt "#32336" msgid "Exit" msgstr "Sair" +#: msgctxt "#32337" msgid "Cancel" msgstr "Cancelar" +#: msgctxt "#32338" msgid "No Servers Found" msgstr "Nenhum Servidor Encontrado" +#: msgctxt "#32339" msgid "Server is not accessible" msgstr "Servidor inacessível" +#: msgctxt "#32340" msgid "Connection tests are in progress. Please wait." msgstr "Testes de conexão estão em progresso. Por favor, aguarde." +#: msgctxt "#32341" msgid "Server is not accessible. Please sign into your server and check your connection." msgstr "Servidor inacessível. Conecte em seu servidor e teste sua conexão." +#: msgctxt "#32342" msgid "Switch User" msgstr "Trocar Usuário" +#: msgctxt "#32343" msgid "Settings" msgstr "Configurações" +#: msgctxt "#32344" msgid "Sign Out" msgstr "Desconectar" +#: msgctxt "#32345" msgid "All" msgstr "Tudo" +#: msgctxt "#32346" msgid "By Name" msgstr "Por Nome" +#: msgctxt "#32347" msgid "Artists" msgstr "Artistas" +#: msgctxt "#32348" -msgid "movies" -msgstr "filmes" +msgid "Movies" +msgstr "Filmes" +#: msgctxt "#32349" msgid "photos" msgstr "fotos" +#: msgctxt "#32350" msgid "Shows" msgstr "Séries" +#: msgctxt "#32351" msgid "By Date Added" msgstr "Por Data Adicionado" +#: msgctxt "#32352" msgid "Date Added" msgstr "Data Adicionado" +#: msgctxt "#32353" msgid "By Release Date" msgstr "Por Data de Lançamento" +#: msgctxt "#32354" msgid "Release Date" msgstr "Data de Lançamento" +#: msgctxt "#32355" msgid "By Date Viewed" msgstr "Por Data de Visualização" +#: msgctxt "#32356" msgid "Date Viewed" msgstr "Data de Visualização" -msgctxt "#32357" -msgid "By Name" -msgstr "Por Nome" - -msgctxt "#32358" -msgid "Name" -msgstr "Nome" - +#: msgctxt "#32359" msgid "By Rating" msgstr "Por Avaliação" +#: msgctxt "#32360" msgid "Rating" msgstr "Avaliação" +#: msgctxt "#32361" msgid "By Resolution" msgstr "Por Resolução" +#: msgctxt "#32362" msgid "Resolution" msgstr "Resolução" +#: msgctxt "#32363" msgid "By Duration" msgstr "Por Duração" +#: msgctxt "#32364" msgid "Duration" msgstr "Duração" +#: msgctxt "#32365" msgid "By First Aired" msgstr "Por Data de Estreia" +#: msgctxt "#32366" msgid "First Aired" msgstr "Data de Estreia" +#: msgctxt "#32367" msgid "By Unplayed" msgstr "Por Não-Vistos" +#: msgctxt "#32368" msgid "Unplayed" msgstr "Não-Vistos" +#: msgctxt "#32369" msgid "By Date Played" msgstr "Por Data de Reprodução" +#: msgctxt "#32370" msgid "Date Played" msgstr "Data de Reprodução" +#: msgctxt "#32371" msgid "By Play Count" msgstr "Por Número de Reproduções" +#: msgctxt "#32372" msgid "Play Count" msgstr "Número de Reproduções" +#: msgctxt "#32373" msgid "By Date Taken" msgstr "Por Data Tirada" +#: msgctxt "#32374" msgid "Date Taken" msgstr "Data Tirada" +#: msgctxt "#32375" msgid "No filters available" msgstr "Nenhum filtro disponível" +#: msgctxt "#32376" msgid "Clear Filter" msgstr "Limpar filtro" +#: msgctxt "#32377" msgid "Year" msgstr "Ano" +#: msgctxt "#32378" msgid "Decade" msgstr "Década" +#: msgctxt "#32379" msgid "Genre" msgstr "Gênero" +#: msgctxt "#32380" msgid "Content Rating" msgstr "Classificação do Conteúdo" +#: msgctxt "#32381" msgid "Network" msgstr "Rede" +#: msgctxt "#32382" msgid "Collection" msgstr "Coleção" +#: msgctxt "#32383" msgid "Director" msgstr "Diretor" +#: msgctxt "#32384" msgid "Actor" msgstr "Ator" +#: msgctxt "#32385" msgid "Country" msgstr "País" +#: msgctxt "#32386" msgid "Studio" msgstr "Estúdio" +#: msgctxt "#32387" msgid "Labels" msgstr "Rótulos" +#: msgctxt "#32388" msgid "Camera Make" msgstr "Fabricante da câmera" +#: msgctxt "#32389" msgid "Camera Model" msgstr "Modelo da câmera" +#: msgctxt "#32390" msgid "Aperture" msgstr "Abertura" +#: msgctxt "#32391" msgid "Shutter Speed" msgstr "Velocidade do Obturador" +#: msgctxt "#32392" msgid "Lens" msgstr "Lente" +#: msgctxt "#32393" msgid "TV Shows" msgstr "Séries" +#: msgctxt "#32394" msgid "Music" msgstr "Música" +#: msgctxt "#32395" msgid "Audio" msgstr "Áudio" +#: msgctxt "#32396" msgid "Subtitles" msgstr "Legendas" +#: msgctxt "#32397" msgid "Quality" msgstr "Qualidade" +#: msgctxt "#32398" msgid "Kodi Video Settings" msgstr "Configurações de Vídeo do Kodi" +#: msgctxt "#32399" msgid "Kodi Audio Settings" msgstr "Configurações de Áudio do Kodi" +#: msgctxt "#32400" msgid "Go To Season" msgstr "Ir para Temporada" +#: msgctxt "#32401" msgid "Directors" msgstr "Diretores" +#: msgctxt "#32402" msgid "Writer" msgstr "Escritor" +#: msgctxt "#32403" msgid "Writers" msgstr "Escritores" +#: msgctxt "#32404" msgid "Related Movies" msgstr "Filmes relacionados" +#: msgctxt "#32405" msgid "Download Subtitles" msgstr "Baixar Legendas" +#: msgctxt "#32406" msgid "Subtitle Delay" msgstr "Atraso da Legenda" +#: msgctxt "#32407" msgid "Next Subtitle" msgstr "Próxima Legenda" +#: msgctxt "#32408" msgid "Disable Subtitles" msgstr "Desabilitar Legendas" +#: msgctxt "#32409" msgid "Enable Subtitles" msgstr "Habilitar Legendas" +#: msgctxt "#32410" msgid "Platform Version" msgstr "Versão da Plataforma" +#: msgctxt "#32411" msgid "Unknown" msgstr "Desconhecido" +#: msgctxt "#32412" msgid "Edit Or Clear" msgstr "Editar ou Limpar" +#: msgctxt "#32413" msgid "Edit IP address or clear the current setting?" msgstr "Editar endereço de IP ou limpar configurações atuais?" +#: msgctxt "#32414" msgid "Clear" msgstr "Limpar" +#: msgctxt "#32415" msgid "Edit" msgstr "Editar" +#: msgctxt "#32416" msgid "Enter IP Address" msgstr "Insira o endereço de IP" +#: msgctxt "#32417" msgid "Enter Port Number" msgstr "Insira o número da Porta" +#: msgctxt "#32418" msgid "Creator" msgstr "Criador" +#: msgctxt "#32419" msgid "Cast" msgstr "Elenco" +#: msgctxt "#32420" msgid "Disc" msgstr "Disco" +#: msgctxt "#32421" msgid "Sign Out" msgstr "Desconectar" +#: msgctxt "#32422" msgid "Exit" msgstr "Sair" +#: msgctxt "#32423" msgid "Shutdown" msgstr "Desligar" +#: msgctxt "#32424" msgid "Suspend" msgstr "Suspender" +#: msgctxt "#32425" msgid "Hibernate" msgstr "Hibernar" +#: msgctxt "#32426" msgid "Reboot" msgstr "Reiniciar" +#: msgctxt "#32427" msgid "Failed" msgstr "Falhou" +#: msgctxt "#32428" msgid "Login failed!" msgstr "Login falhou!" +#: msgctxt "#32429" msgid "Resume from {0}" msgstr "Resumir de {0}" +#: msgctxt "#32430" msgid "Discovery" msgstr "Descobrir" +#: msgctxt "#32431" msgid "Search" msgstr "Procurar" +#: msgctxt "#32432" msgid "Space" msgstr "Espaço" +#: msgctxt "#32433" msgid "Clear" msgstr "Limpar" +#: msgctxt "#32434" msgid "Searching..." msgstr "Procurando..." +#: msgctxt "#32435" msgid "No Results" msgstr "Sem Resultados" +#: msgctxt "#32436" msgid "Paused" msgstr "Pausado" +#: msgctxt "#32437" msgid "Welcome" msgstr "Bem-Vindo" +#: msgctxt "#32438" msgid "Previous" msgstr "Anterior" +#: msgctxt "#32439" msgid "Playing Next" msgstr "Reproduzindo a seguir" +#: msgctxt "#32440" msgid "On Deck" msgstr "No Deck" +#: msgctxt "#32441" msgid "Unknown" msgstr "Desconhecido" +#: msgctxt "#32442" msgid "Embedded" msgstr "Embutido" +#: msgctxt "#32443" msgid "Forced" msgstr "Forçado" +#: msgctxt "#32444" msgid "Lyrics" msgstr "Letra da música" +#: msgctxt "#32445" msgid "Mono" msgstr "Mono" +#: msgctxt "#32446" msgid "Stereo" msgstr "Estéreo" +#: msgctxt "#32447" msgid "None" msgstr "Nenhum" +#: msgctxt "#32448" msgid "Playback Failed!" msgstr "Reprodução falhou!" +#: msgctxt "#32449" msgid "Can't connect to plex.tv[CR]Check your internet connection and try again." msgstr "Nao pôde conectar com o plex.tv[CR]Teste sua conexão com a internet e tente novamente." +#: msgctxt "#32450" msgid "Choose Version" msgstr "Escolha a Versão" +#: msgctxt "#32451" msgid "Play Version..." msgstr "Reproduzir Versão..." +#: msgctxt "#32452" msgid "No Content available in this library" msgstr "Nenhum conteúdo disponível nesta biblioteca" +#: msgctxt "#32453" msgid "Please add content and/or check that 'Include in dashboard' is enabled." msgstr "Por favor adicione e/ou confirme que 'Incluir no Painel' está habilitado." +#: msgctxt "#32454" msgid "No Content available for this filter" msgstr "Nenhum conteúdo disponível para este filtro" +#: msgctxt "#32455" msgid "Please change change or remove the current filter" msgstr "Por favor, troque ou remova o filtro atual" +#: msgctxt "#32456" msgid "Show" msgstr "Série" +#: msgctxt "#32457" msgid "By Show" msgstr "Por Série" +#: msgctxt "#32458" msgid "Episodes" msgstr "Episódios" +#: msgctxt "#32459" msgid "Offline Mode" msgstr "Modo Offline" +#: msgctxt "#32460" msgid "Sign In" msgstr "Conectar" +#: msgctxt "#32461" msgid "Albums" msgstr "Álbuns" +#: msgctxt "#32462" msgid "Artist" msgstr "Artista" +#: msgctxt "#32463" msgid "By Artist" msgstr "Por Artista" +#: +msgctxt "#32464" +msgid "Player" +msgstr "Player" + +#: +msgctxt "#32465" +msgid "Use skip step settings from Kodi" +msgstr "Usar o pular etapas das configurações do Kodi" + +#: +msgctxt "#32466" +msgid "Automatically seek selected position after a delay" +msgstr "Buscar automaticamente a posição selecionada após um atraso" + +#: +msgctxt "#32467" +msgid "User Interface" +msgstr "Interface de usuário" + +#: +msgctxt "#32468" +msgid "Show dynamic background art" +msgstr "Exibir arte de fundo dinâmica" + +#: +msgctxt "#32469" +msgid "Background art blur amount" +msgstr "Nível de desfoque da arte de fundo" + +#: +msgctxt "#32470" +msgid "Background art opacity" +msgstr "Opacidade da arte de fundo" + +#: +msgctxt "#32471" +msgid "Use Plex/Kodi steps for timeline" +msgstr "Usar etapas do Plex/Kodi para a linha do tempo" + +#: +msgctxt "#32480" +msgid "Theme music" +msgstr "Trilha sonora" + +#: +msgctxt "#32481" +msgid "Off" +msgstr "Desativado" + +#: +msgctxt "#32482" +msgid "%(percentage)s %%" +msgstr "" + +#: +msgctxt "#32483" +msgid "Hide Stream Info" +msgstr "Ocultar Informações do Stream" + +#: +msgctxt "#32484" +msgid "Show Stream Info" +msgstr "Exibir Informações do Stream" + +#: +msgctxt "#32485" +msgid "Go back instantly with the previous menu action in scrolled views" +msgstr "Voltar instantaneamente com a ação de menu anterior em visualizações com rolagem" + +#: +msgctxt "#32487" +msgid "Seek Delay" +msgstr "Atraso da busca" + +#: +msgctxt "#32488" +msgid "Screensaver" +msgstr "Protetor de tela" + +#: +msgctxt "#32489" +msgid "Quiz Mode" +msgstr "Modo Quiz" + +#: +msgctxt "#32490" +msgid "Collections" +msgstr "Coleções" + +#: +msgctxt "#32491" +msgid "Folders" +msgstr "Pastas" + +#: +msgctxt "#32492" +msgid "Kodi Subtitle Settings" +msgstr "Configurações de Legenda do Kodi" + +#: +msgctxt "#32495" +msgid "Skip intro" +msgstr "Pular introdução" + +#: +msgctxt "#32496" +msgid "Skip credits" +msgstr "Pular créditos" + +#: +msgctxt "#32500" +msgid "Always show post-play screen (even for short videos)" +msgstr "Sempre mostrar a tela pós-reprodução (mesmo para vídeos curtos)" + +#: +msgctxt "#32501" +msgid "Time-to-wait between videos on post-play" +msgstr "Tempo de espera entre vídeos na tela pós-reprodução" + +#: +msgctxt "#32505" +msgid "Visit media in video playlist instead of playing it" +msgstr "Visitar o item da playlist de vídeo em vez de reproduzi-lo" + +#: +msgctxt "#32521" +msgid "Skip Intro Button Timeout" +msgstr "Tempo limite do botão Pular Abertura" + +#: +msgctxt "#32522" +msgid "Automatically Skip Intro" +msgstr "Pular Abertura automaticamente" + +#: +msgctxt "#32524" +msgid "Set how long the skip intro button shows for." +msgstr "Defina por quanto tempo o botão de pular abertura será exibido" + +#: +msgctxt "#32525" +msgid "Skip Credits Button Timeout" +msgstr "Tempo limite do botão Pular Créditos" + +#: +msgctxt "#32526" +msgid "Automatically Skip Credits" +msgstr "Pular Créditos automaticamente" + +#: +msgctxt "#32528" +msgid "Set how long the skip credits button shows for." +msgstr "Defina por quanto tempo o botão de pular créditos será exibido." + +#: +msgctxt "#32540" +msgid "Show when the current video will end in player" +msgstr "Mostrar quando o vídeo atual terminará no player" + +#: +msgctxt "#32541" +msgid "Shows time left and at which time the media will end." +msgstr "Mostra o tempo restante e a hora exata em que o vídeo terminará." + +#: +msgctxt "#32542" +msgid "Show \"Ends at\" label for the end-time as well" +msgstr "Mostrar também o rótulo \"Termina às\" para o horário de término" + +#: +msgctxt "#32543" +msgid "Ends at" +msgstr "Termina às" + +#: +msgctxt "#32602" +msgid "Enable this if your hardware can handle AV1. Disable it to force transcoding." +msgstr "Ative isto se seu hardware for compatível com AV1. Desative para forçar a transcodificação." + +#: +msgctxt "#33101" +msgid "By Audience Rating" +msgstr "Por Classificação do Público" + +#: +msgctxt "#33102" +msgid "Audience Rating" +msgstr "Classificação do Público" + +#: +msgctxt "#33103" +msgid "By my Rating" +msgstr "Pela Minha Avaliação" + +#: +msgctxt "#33104" +msgid "My Rating" +msgstr "Minha Avaliação" + +#: +msgctxt "#33105" +msgid "By Content Rating" +msgstr "Por Classificação Indicativa" + +#: +msgctxt "#33106" +msgid "Content Rating" +msgstr "Classificação Indicativa" + +#: +msgctxt "#33107" +msgid "By Critic Rating" +msgstr "Por Avaliação da Crítica" + +#: +msgctxt "#33108" +msgid "Critic Rating" +msgstr "Avaliação da Crítica" + +#: +msgctxt "#33200" +msgid "Background Color" +msgstr "Cor de Fundo" + +#: +msgctxt "#33201" +msgid "Specify solid Background Color instead of using media images" +msgstr "Especifique uma cor de fundo sólida em vez de usar imagens da mídia" + +#: +msgctxt "#33400" +msgid "Use old compatibility profile" +msgstr "Usar perfil de compatibilidade antigo" + +#: +msgctxt "#33401" +msgid "Uses the Chrome client profile instead of the custom one. Might fix rare issues with 3D playback." +msgstr "Usa o perfil de cliente do Chrome em vez do personalizado. Pode corrigir problemas raros com reprodução 3D." + +#: +msgctxt "#32031" +msgid "Burn-in Subtitles" +msgstr "Legendas incorporadas (burn-in)" + +#: +msgctxt "#32061" +msgid "When transcoding audio, target the audio channels set in Kodi." +msgstr "Ao transcodificar áudio, usar os canais de áudio definidos no Kodi." + +#: +msgctxt "#32062" +msgid "Transcode audio to AC3" +msgstr "Transcodificar áudio para AC3" + +#: +msgctxt "#32063" +msgid "Transcode audio to AC3 in certain conditions (useful for passthrough)." +msgstr "Transcodificar áudio para AC3 em certas condições (útil para passthrough)." + +#: +msgctxt "#32065" +msgid "When any of the force AC3 settings are enabled, treat DTS the same as AC3 (useful for Optical passthrough)" +msgstr "Quando qualquer opção de forçar AC3 estiver ativada, tratar DTS como AC3 (útil para passthrough óptico)" + +#: +msgctxt "#32066" +msgid "Force audio to AC3" +msgstr "Forçar áudio para AC3" + +#: +msgctxt "#32067" +msgid "Only force multichannel audio to AC3" +msgstr "Forçar apenas áudio multicanal para AC3" + +#: +msgctxt "#32493" +msgid "When a media file has a forced/foreign subtitle for a subtitle-enabled language, the Plex Media Server preselects it. This behaviour is usually not necessary and not configurable. This setting fixes that by ignoring the PMSs decision and selecting the same language without a forced flag if possible." +msgstr "Quando um arquivo de mídia tem uma legenda forçada/estrangeira para um idioma com legendas habilitadas, o Plex Media Server a pré-seleciona. Esse comportamento geralmente não é necessário e não pode ser configurado. Esta opção corrige isso, ignorando a decisão do PMS e selecionando o mesmo idioma sem a flag de forçada, se possível." + +#: +msgctxt "#32523" +msgid "Automatically skip intros if available. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "Pular aberturas automaticamente, se disponível. Não substitui o modo maratona ativado.\n" +"Pode ser ativado/desativado por série." + +#: +msgctxt "#32527" +msgid "Automatically skip credits if available. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "Pular créditos automaticamente, se disponível. Não substitui o modo maratona ativado.\n" +"Pode ser ativado/desativado por série." + +#: +msgctxt "#33501" +msgid "Video played threshold" +msgstr "Limite para considerar vídeo como assistido" + +#: +msgctxt "#33502" +msgid "Set this to the same value as your Plex server (Settings>Library>Video played threshold) to avoid certain pitfalls, Default: 90 %" +msgstr "Defina o mesmo valor usado no seu servidor Plex (Configurações > Biblioteca > Limite de vídeo assistido) para evitar inconsistências. Padrão: 90%" + +#: +msgctxt "#33503" +msgid "Use alternative hubs refresh" +msgstr "Usar atualização alternativa para hubs" + +#: +msgctxt "#33504" +msgid "Refreshes all hubs for all libraries after an item's watch-state has changed, instead of only those likely affected. Use this if you find a hub that doesn't update properly." +msgstr "Atualiza todos os hubs de todas as bibliotecas após uma mudança no status de reprodução, em vez de apenas os mais prováveis. Use esta opção se notar hubs que não atualizam corretamente." + +#: +msgctxt "#33505" +msgid "Show intro skip button early" +msgstr "Exibir botão de pular abertura mais cedo" + +#: +msgctxt "#33507" +msgid "Enabled" +msgstr "Ativado" + +#: +msgctxt "#33508" +msgid "Disabled" +msgstr "Desativado" + +#: +msgctxt "#33510" +msgid "When showing the intro skip button early, only do so if the intro occurs within the first X seconds." +msgstr "Ao mostrar o botão de pular abertura mais cedo, só exibir se a abertura ocorrer dentro dos primeiros X segundos." + +#: +msgctxt "#33600" +msgid "System" +msgstr "Sistema" + +#: +msgctxt "#33601" +msgid "Show video chapters" +msgstr "Mostrar capítulos do vídeo" + +#: +msgctxt "#33602" +msgid "If available, show video chapters from the video-file instead of the timeline-big-seek-steps." +msgstr "Se disponível, mostrar capítulos do vídeo diretamente do arquivo em vez dos saltos grandes na linha do tempo." + +#: +msgctxt "#33603" +msgid "Use virtual chapters" +msgstr "Usar capítulos virtuais" + +#: +msgctxt "#33604" +msgid "When the above is enabled and no video chapters are available, simulate them by using the markers identified by the Plex Server (Intro, Credits)." +msgstr "Quando a opção acima estiver ativada e não houver capítulos no vídeo, simular capítulos usando marcadores identificados pelo servidor Plex (Abertura, Créditos)." + +#: +msgctxt "#33605" +msgid "Video Chapters" +msgstr "" + +#: +msgctxt "#33606" +msgid "Virtual Chapters" +msgstr "" + +#: +msgctxt "#33607" +msgid "Chapter {}" +msgstr "" + +#: +msgctxt "#33608" +msgid "Intro" +msgstr "" + +#: +msgctxt "#33609" +msgid "Credits" +msgstr "" + +#: +msgctxt "#33610" +msgid "Main" +msgstr "" + +#: +msgctxt "#33611" +msgid "Chapters" +msgstr "" + +#: +msgctxt "#33612" +msgid "Markers" +msgstr "" + +#: +msgctxt "#33613" +msgid "Kodi Buffer Size (MB)" +msgstr "" + +#: +msgctxt "#33614" +msgid "Set the Kodi Cache/Buffer size. Free: {} MB, Recommended: ~50 MB, Recommended max: {} MB, Default: 20 MB." +msgstr "" + +#: +msgctxt "#33615" +msgid "{time} left" +msgstr "" + +#: +msgctxt "#33616" +msgid "Addon Path" +msgstr "" + +#: +msgctxt "#33617" +msgid "Userdata/Profile Path" +msgstr "" + +#: +msgctxt "#33618" +msgid "TV binge-viewing mode" +msgstr "" + +#: +msgctxt "#33622" +msgid "LAN reachability timeout (ms)" +msgstr "" + +#: +msgctxt "#33623" +msgid "When checking for LAN reachability, use this timeout. Default: 10ms" +msgstr "" + +#: +msgctxt "#33624" +msgid "Network" +msgstr "Rede" + +#: +msgctxt "#33625" +msgid "Smart LAN/local server discovery" +msgstr "" + +#: +msgctxt "#33626" +msgid "Checks whether servers returned from Plex.tv are actually local/in your LAN. For specific setups (e.g. Docker) Plex.tv might not properly detect a local server.\n" +"\n" +"NOTE: Only works on Kodi 19 or above." +msgstr "" + +#: +msgctxt "#33627" +msgid "Prefer LAN/local servers over security" +msgstr "" + +#: +msgctxt "#33628" +msgid "Prioritizes local connections over secure ones. Needs the proper setting in \"Allow Insecure Connections\" and the Plex Server's \"Secure connections\" at \"Preferred\". Can be used to enforce manual servers." +msgstr "" + +#: +msgctxt "#33629" +msgid "Auto-skip intro/credits offset" +msgstr "" + +#: +msgctxt "#33630" +msgid "Intro/credits markers might be a little early in Plex. When auto skipping add (or subtract) this many seconds from the marker. This avoids cutting off content, while possibly skipping the marker a little late." +msgstr "" + +#: +msgctxt "#32631" +msgid "Playback (user-specific)" +msgstr "" + +#: +msgctxt "#33632" +msgid "Server connectivity check timeout (seconds)" +msgstr "" + +#: +msgctxt "#33633" +msgid "Set the maximum amount of time a server connection has to answer a connectivity request. Default: 2.5" +msgstr "" + +#: +msgctxt "#33634" +msgid "Combined Chapters" +msgstr "" + +#: +msgctxt "#33635" +msgid "Final Credits" +msgstr "" + +#: +msgctxt "#32700" +msgid "Action on Sleep event" +msgstr "" + +#: +msgctxt "#32701" +msgid "When Kodi receives a sleep event from the system, run the following action." +msgstr "" + +#: +msgctxt "#32702" +msgid "Nothing" +msgstr "" + +#: +msgctxt "#32703" +msgid "Stop playback" +msgstr "" + +#: +msgctxt "#32704" +msgid "Quit Kodi" +msgstr "" + +#: +msgctxt "#32705" +msgid "CEC Standby" +msgstr "" + +#: +msgctxt "#32800" +msgid "Skipping intro" +msgstr "" + +#: +msgctxt "#32801" +msgid "Skipping credits" +msgstr "" + +#: +msgctxt "#32900" +msgid "While playing back an item and seeking on the seekbar, automatically seek to the selected position after a delay instead of having to confirm the selection." +msgstr "" + +#: +msgctxt "#32901" +msgid "Seek delay in seconds." +msgstr "" + +#: +msgctxt "#32902" +msgid "Kodi has its own skip step settings. Try to use them if they're configured instead of the default ones." +msgstr "" + +#: +msgctxt "#32903" +msgid "Use the above for seeking on the timeline as well." +msgstr "" + +#: +msgctxt "#32904" +msgid "In seconds." +msgstr "" + +#: +msgctxt "#32905" +msgid "Cancel post-play timer by pressing OK/SELECT" +msgstr "" + +#: +msgctxt "#32906" +msgid "Cancel skip marker timer with BACK" +msgstr "" + +#: +msgctxt "#32907" +msgid "When auto-skipping a marker, allow cancelling the timer by pressing BACK." +msgstr "" + +#: +msgctxt "#32908" +msgid "Immediately skip marker with OK/SELECT" +msgstr "" + +#: +msgctxt "#32909" +msgid "When auto-skipping a marker with a timer, allow skipping immediately by pressing OK/SELECT." +msgstr "" + +#: +msgctxt "#32912" +msgid "Show buffer-state on timeline" +msgstr "" + +#: +msgctxt "#32913" +msgid "Shows the current Kodi buffer/cache state on the video player timeline." +msgstr "" + +#: +msgctxt "#32914" +msgid "Loading" +msgstr "" + +#: +msgctxt "#32915" +msgid "Slow connection" +msgstr "" + +#: +msgctxt "#32916" +msgid "Use with a wonky/slow connection, e.g. in a hotel room. Adjusts the UI to visually wait for item refreshes and waits for the buffer to fill when starting playback. Automatically sets readfactor=20, requires Kodi restart." +msgstr "" + +#: +msgctxt "#32917" +msgid "Couldn't fill buffer in time ({}s)" +msgstr "" + +#: +msgctxt "#32918" +msgid "Buffer wait timeout (seconds)" +msgstr "" + +#: +msgctxt "#32919" +msgid "When slow connection is enabled in the addon, wait this long for the buffer to fill. Default: 120 s" +msgstr "" + +#: +msgctxt "#32920" +msgid "Insufficient buffer wait (seconds)" +msgstr "" + +#: +msgctxt "#32921" +msgid "When slow connection is enabled in the addon and the configured buffer isn't big enough for us to determine its fill state, wait this long when starting playback. Default: 10 s" +msgstr "" + +#: +msgctxt "#32922" +msgid "Kodi Cache Readfactor" +msgstr "" + +#: +msgctxt "#32923" +msgid "Sets the Kodi cache readfactor value. Default: {0}, recommended: {1}. With \"Slow connection\" enabled this will be set to {2}, as otherwise the cache doesn't fill fast/aggressively enough." +msgstr "" + +#: +msgctxt "#32924" +msgid "Minimize" +msgstr "" + +#: +msgctxt "#32925" +msgid "Playback Settings" +msgstr "" + +#: +msgctxt "#32926" +msgid "Wrong pin entered!" +msgstr "" + +#: +msgctxt "#32927" +msgid "Use episode thumbnails in continue hub" +msgstr "" + +#: +msgctxt "#32928" +msgid "Instead of using media artwork, use thumbnails for episodes in the continue hub on the home screen if available." +msgstr "" + +#: +msgctxt "#32929" +msgid "Use legacy background fallback image" +msgstr "" + +#: +msgctxt "#32930" +msgid "Previous Subtitle" +msgstr "" + +#: +msgctxt "#32931" +msgid "Audio/Subtitles" +msgstr "" + +#: +msgctxt "#32936" +msgid "Show playlist button" +msgstr "" + +#: +msgctxt "#32937" +msgid "Show prev/next button" +msgstr "" + +#: +msgctxt "#32941" +msgid "Forced subtitles fix" +msgstr "" + +#: +msgctxt "#32942" +msgid "Other seasons" +msgstr "" + +#: +msgctxt "#32943" +msgid "Crossfade dynamic background art" +msgstr "" + +#: +msgctxt "#32944" +msgid "Burn-in SSA subtitles (DirectStream)" +msgstr "" + +#: +msgctxt "#32945" +msgid "When Direct Streaming instruct the Plex Server to burn in SSA/ASS subtitles (thus transcoding the video stream). If disabled it will not touch the video stream, but will convert the subtitle to unstyled text." +msgstr "" + +#: +msgctxt "#32946" +msgid "Stop video playback on idle after" +msgstr "" + +#: +msgctxt "#32947" +msgid "Stop video playback on screensaver" +msgstr "" + +#: +msgctxt "#32948" +msgid "Allow auto-skip when transcoding" +msgstr "" + +#: +msgctxt "#32949" +msgid "When transcoding/DirectStreaming, allow auto-skip functionality." +msgstr "" + +#: +msgctxt "#32950" +msgid "Use extended title for subtitles" +msgstr "" + +#: +msgctxt "#32951" +msgid "When displaying subtitles use the extendedDisplayTitle Plex exposes." +msgstr "" + +#: +msgctxt "#32953" +msgid "Reviews" +msgstr "" + +#: +msgctxt "#32954" +msgid "Needs Kodi restart. WARNING: This will overwrite advancedsettings.xml!\n" +"\n" +"To customize other cache/network-related values, copy \"script.plexmod/pm4k_cache_template.xml\" to profile folder and edit it to your liking. (See About section for the file paths)" +msgstr "" + +#: +msgctxt "#32955" +msgid "Use Kodi keyboard for searching" +msgstr "" + +#: +msgctxt "#32956" +msgid "Poster resolution scaling %" +msgstr "" + +#: +msgctxt "#32957" +msgid "In percent. Scales the resolution of all posters/thumbnails for better image quality. May impact PMS/PM4K performance, will increase the cache usage accordingly. Recommended: 200-300 % for for big screens if your hardware can handle it. Needs addon restart." +msgstr "" + +#: +msgctxt "#32958" +msgid "Calculate OpenSubtitles.com hash" +msgstr "" + +#: +msgctxt "#32959" +msgid "When opening the subtitle download feature, automatically calculate the OpenSubtitles.com hash for the given file. Can improve search results, downloads 2*64 KB of the video file to calculate the hash." +msgstr "" + +#: +msgctxt "#32960" +msgid "Similar Artists" +msgstr "" + +#: +msgctxt "#32961" +msgid "Show hub bifurcation lines" +msgstr "" + +#: +msgctxt "#32962" +msgid "Visually separate hubs horizontally using a thin line." +msgstr "" + +#: +msgctxt "#32963" +msgid "Wait between videos (s)" +msgstr "" + +#: +msgctxt "#32964" +msgid "When playing back consecutive videos (e.g. TV shows), wait this long before starting the next one in the queue. Might fix compatibility issues with certain configurations." +msgstr "" + +#: +msgctxt "#32965" +msgid "Quit Kodi on exit by default" +msgstr "" + +#: +msgctxt "#32966" +msgid "When exiting the addon, use \"Quit Kodi\" as default option. Can be dynamically switched using CONTEXT_MENU (often longpress SELECT)" +msgstr "" + +#: +msgctxt "#32967" +msgid "Kodi Colour Management" +msgstr "" + +#: +msgctxt "#32968" +msgid "Kodi Resolution Settings" +msgstr "" + +#: +msgctxt "#32969" +msgid "Always request all library media items at once" +msgstr "" + +#: +msgctxt "#32970" +msgid "Retrieve all media in library up front instead of fetching it in chunks as the user navigates through the library" +msgstr "" + +#: +msgctxt "#32971" +msgid "Library item-request chunk size" +msgstr "" + +#: +msgctxt "#32972" +msgid "Request this amount of media items per chunk request in library view (+6-30 depending on view mode; less can be less straining for the UI at first, but puts more strain on the server)" +msgstr "" + +#: +msgctxt "#32973" +msgid "Episodes: Skip Post Play screen" +msgstr "" + +#: +msgctxt "#32974" +msgid "When finishing an episode, don't show Post Play but go to the next one immediately.\n" +"Can be disabled/enabled per TV show. Doesn't override enabled binge mode. Overrides the Post Play setting." +msgstr "" + +#: +msgctxt "#32975" +msgid "Delete Season" +msgstr "" + +#: +msgctxt "#32976" +msgid "Adaptive" +msgstr "" + +#: +msgctxt "#32978" +msgid "Enable this if your hardware can handle VC1. Disable it to force transcoding." +msgstr "" + +#: +msgctxt "#32979" +msgid "Allows the server to only transcode streams of a video that need transcoding, while streaming the others unaltered. If disabled, force the server to transcode everything not direct playable." +msgstr "" + +#: +msgctxt "#32980" +msgid "Refresh Users" +msgstr "" + +#: +msgctxt "#32981" +msgid "Background worker count" +msgstr "" + +#: +msgctxt "#32982" +msgid "Depending on how many cores your CPU has and how much it can handle, increasing this might improve certain situations. If you experience crashes or other annormalities, leave this at its default (3). Needs an addon restart." +msgstr "" + +#: +msgctxt "#32985" +msgid "Modern" +msgstr "" + +#: +msgctxt "#32986" +msgid "Modern (dotted)" +msgstr "" + +#: +msgctxt "#32987" +msgid "Classic" +msgstr "" + +#: +msgctxt "#32988" +msgid "Custom" +msgstr "" + +#: +msgctxt "#32989" +msgid "Modern (colored)" +msgstr "" + +#: +msgctxt "#32990" +msgid "Handle plex.direct mapping" +msgstr "" + +#: +msgctxt "#32991" +msgid "Notify" +msgstr "" + +#: +msgctxt "#32992" +msgid "When using servers with a plex.direct connection (most of them), should we automatically adjust advancedsettings.xml to cope with plex.direct domains? If not, you might want to add plex.direct to your router's DNS rebind exemption list." +msgstr "" + +#: +msgctxt "#32993" +msgid "{} unhandled plex.direct connections found" +msgstr "" + +#: +msgctxt "#32994" +msgid "In order for PM4K to work properly, we need to add special handling for plex.direct connections. We've found {} new unhandled connections. Do you want us to write those to Kodi's advancedsettings.xml automatically? If not, you might want to add plex.direct to your router's DNS rebind exemption list. This can be changed in the settings as well." +msgstr "" + +#: +msgctxt "#32995" +msgid "Advancedsettings.xml modified (plex.direct mappings)" +msgstr "" + +#: +msgctxt "#32996" +msgid "The advancedsettings.xml file has been modified. Please restart Kodi for the changes to apply." +msgstr "" + +#: +msgctxt "#32997" +msgid "OK" +msgstr "" + +#: +msgctxt "#32998" +msgid "Use new Continue Watching hub on Home" +msgstr "" + +#: +msgctxt "#32999" +msgid "Instead of separating Continue Watching and On Deck hubs, behave like the modern Plex clients, which combine those two types of hubs into one Continue Watching hub." +msgstr "" + +#: +msgctxt "#33000" +msgid "Enable path mapping" +msgstr "" + +#: +msgctxt "#33002" +msgid "Verify mapped files exist" +msgstr "" + +#: +msgctxt "#33003" +msgid "When path mapping is enabled and we've successfully mapped a file, verify its existence." +msgstr "" + +#: +msgctxt "#33004" +msgid "No spoilers without OSD" +msgstr "" + +#: +msgctxt "#33005" +msgid "When seeking without the OSD open, hide all time-related information from the user." +msgstr "" + +#: +msgctxt "#33006" +msgid "No TV spoilers" +msgstr "" + +#: +msgctxt "#33008" +msgid "[Spoilers removed]" +msgstr "" + +#: +msgctxt "#33013" +msgid "When the above is anything but \"off\", hide episode titles as well." +msgstr "" + +#: +msgctxt "#33014" +msgid "Ignore plex.direct docker hosts" +msgstr "" + +#: +msgctxt "#33015" +msgid "When checking for plex.direct host mapping, ignore local Docker IPv4 addresses (172.16.0.0/12)." +msgstr "" + +#: +msgctxt "#32303" +msgid "Season {}" +msgstr "" + +#: +msgctxt "#32304" +msgid "Episode {}" +msgstr "" + +#: +msgctxt "#32310" +msgid "S{}" +msgstr "" + +#: +msgctxt "#32311" +msgid "E{}" +msgstr "" + +#: +msgctxt "#32938" +msgid "Only for Episodes/Playlists" +msgstr "" + +#: +msgctxt "#33018" +msgid "Cache Plex Home users" +msgstr "" + +#: +msgctxt "#33019" +msgid "Visit media item" +msgstr "" + +#: +msgctxt "#33020" +msgid "Play" +msgstr "" + +#: +msgctxt "#33021" +msgid "Choose action" +msgstr "" + +#: +msgctxt "#33026" +msgid "Map path: {}" +msgstr "" + +#: +msgctxt "#33027" +msgid "Remove mapping: {}" +msgstr "" + +#: +msgctxt "#33028" +msgid "Hide library" +msgstr "" + +#: +msgctxt "#33029" +msgid "Show library: {}" +msgstr "" + +#: +msgctxt "#33030" +msgid "Choose action for: {}" +msgstr "" + +#: +msgctxt "#33031" +msgid "Select Kodi source for {}" +msgstr "" + +#: +msgctxt "#33032" +msgid "Show path mapping indicators" +msgstr "" + +#: +msgctxt "#33033" +msgid "When path mapping is active for a library, display an indicator." +msgstr "" + +#: +msgctxt "#33035" +msgid "Delete {}: {}?" +msgstr "" + +#: +msgctxt "#33036" +msgid "Delete episode S{0:02d}E{1:02d} from {2}?" +msgstr "" + +#: +msgctxt "#33037" +msgid "Maximum intro offset to consider" +msgstr "" + +#: +msgctxt "#33039" +msgid "Move" +msgstr "" + +#: +msgctxt "#33040" +msgid "Reset library order" +msgstr "" + +#: +msgctxt "#33034" +msgid "Library settings" +msgstr "" + +#: +msgctxt "#32357" +msgid "By Title" +msgstr "" + +#: +msgctxt "#32358" +msgid "Title" +msgstr "" + +#: +msgctxt "#33041" +msgid "Show hub: {}" +msgstr "" + +#: +msgctxt "#33042" +msgid "Episode Date Added" +msgstr "" + +#: +msgctxt "#33001" +msgid "Long-press (or context menu) on a library item or: Honor path_mapping.json in the addon_data/script.plexmod folder when DirectPlaying media. This can be used to stream using other techniques such as SMB/NFS/etc. instead of the default HTTP handler. path_mapping.example.json is included in the addon's main directory." +msgstr "" + +#: +msgctxt "#33043" +msgid "Hubs round-robin" +msgstr "" + +#: +msgctxt "#33045" +msgid "Behave like official Plex clients" +msgstr "" + +#: +msgctxt "#32025" +msgid "Direct Play" +msgstr "" + +#: +msgctxt "#32026" +msgid "Direct Stream" +msgstr "" + +#: +msgctxt "#32036" +msgid "4K" +msgstr "" + +#: +msgctxt "#32037" +msgid "HEVC (h265)" +msgstr "" + +#: +msgctxt "#32601" +msgid "AV1" +msgstr "" + +#: +msgctxt "#32932" +msgid "Subtitle quick-actions" +msgstr "" + +#: +msgctxt "#32933" +msgid "FFWD/RWD" +msgstr "" + +#: +msgctxt "#32934" +msgid "Repeat" +msgstr "" + +#: +msgctxt "#32935" +msgid "Shuffle" +msgstr "" + +#: +msgctxt "#32939" +msgid "User-specific.\n" +"Only applies to video player UI" +msgstr "" + +#: +msgctxt "#32977" +msgid "VC1" +msgstr "" + +#: +msgctxt "#32983" +msgid "Theme" +msgstr "" + +#: +msgctxt "#32984" +msgid "Sets the theme. Currently only customizes all control buttons. ATTENTION: [I]Might[/I] need an addon restart.\n" +"In order to customize this, copy one of the xml's in script.plexmod/resources/skins/Main/1080i/templates to addon_data/script.plexmod/templates/{templatename}_custom.xml and adjust it to your liking, then select \"Custom\" as your theme." +msgstr "" + +#: +msgctxt "#33011" +msgid "In progress" +msgstr "" + +#: +msgctxt "#33016" +msgid "Allow TV spoilers for" +msgstr "" + +#: +msgctxt "#33017" +msgid "Overrides the above for specific genres. Default: Reality, Game Show, Documentary, Sport" +msgstr "" + +#: +msgctxt "#33024" +msgid "Hide background in modern indicators" +msgstr "" + +#: +msgctxt "#33044" +msgid "Allow round-robining in hubs. Attention: Loads a lot of items. Depending on the hub size, this can lead to crashes. Current limit: {}; can be adjusted in addon settings." +msgstr "" + +#: +msgctxt "#33046" +msgid "When pressing up/down while the OSD isn't shown, show OSD instead of skipping chapters. Additionally, when pressing down while the OSD is shown, open the chapters (if available)." +msgstr "" + +#: +msgctxt "#33047" +msgid "Hubs round-robin item limit" +msgstr "" + +#: +msgctxt "#33048" +msgid "When hubs round-robin is enabled, only round-robin until this item limit. If the hub size exceeds this limit, the remaining items will be lazy loaded as usual. Tested minimum safe value on NVIDIA SHIELD 2019 is 1000. Default: 250" +msgstr "" + +#: +msgctxt "#33049" +msgid "Max retries" +msgstr "" + +#: +msgctxt "#33051" +msgid "Use CA certificate bundle" +msgstr "" + +#: +msgctxt "#33053" +msgid "System" +msgstr "" + +#: +msgctxt "#33055" +msgid "Custom" +msgstr "" + +#: +msgctxt "#33056" +msgid "None" +msgstr "" + +#: +msgctxt "#33057" +msgid "Show buttons" +msgstr "" + +#: +msgctxt "#33058" +msgid "Playback features" +msgstr "" + +#: +msgctxt "#33059" +msgid "Additional codecs" +msgstr "" + +#: +msgctxt "#33060" +msgid "{feature_ds}: {desc_ds}\n" +"{feature_4k}: {desc_4k}" +msgstr "" + +#: +msgctxt "#33061" +msgid "Enable certain codecs if your hardware supports them. Disable them to force transcoding." +msgstr "" + +#: +msgctxt "#33062" +msgid "Compiling templates" +msgstr "" + +#: +msgctxt "#33063" +msgid "Looking for custom templates" +msgstr "" + +#: +msgctxt "#33064" +msgid "Rendering: {}" +msgstr "" + +#: +msgctxt "#33065" +msgid "Complete" +msgstr "" + +#: +msgctxt "#33066" +msgid "Cache template files" +msgstr "" + +#: +msgctxt "#33067" +msgid "Doesn't throw away the template source files after compiling them. Uses slightly more memory but increases the speed of theme-related changes." +msgstr "" + +#: +msgctxt "#33068" +msgid "Always compile templates" +msgstr "" + +#: +msgctxt "#33069" +msgid "Recompiles all templates on every startup. Useful for template/theme development." +msgstr "" + +#: +msgctxt "#33070" +msgid "Action on Wake event" +msgstr "" + +#: +msgctxt "#33071" +msgid "Restart PM4K" +msgstr "" + +#: +msgctxt "#33072" +msgid "Wait for {}s" +msgstr "" + +#: +msgctxt "#33073" +msgid "Wait after wakeup" +msgstr "" + +#: +msgctxt "#33074" +msgid "Waiting {} second(s)" +msgstr "" + +#: +msgctxt "#33076" +msgid "Modern (2024)" +msgstr "" + +#: +msgctxt "#33077" +msgid "Scale modern indicators" +msgstr "" + +#: +msgctxt "#33078" +msgid "Scale the modern indicators based on the poster size used. Default: On (tiny: 0.75, small: 1.0, medium: 1.175, big: 1.3)" +msgstr "" + +#: +msgctxt "#33079" +msgid "Hi-Res Music" +msgstr "" + +#: +msgctxt "#33080" +msgid "Allow DirectPlay of high resolution music (e.g. FLAC, >= 192 kHz)" +msgstr "" + +#: +msgctxt "#33081" +msgid "Blur chapter images" +msgstr "" + +#: +msgctxt "#33082" +msgid "Scan Library Files" +msgstr "" + +#: +msgctxt "#33083" +msgid "Empty Trash" +msgstr "" + +#: +msgctxt "#33084" +msgid "Analyze" +msgstr "" + +#: +msgctxt "#33085" +msgid "Map key to home" +msgstr "" + +#: +msgctxt "#33086" +msgid "Press the key you want to map to go to home within {} seconds" +msgstr "" + +#: +msgctxt "#33620" +msgid "Plex server connect timeout" +msgstr "" + +#: +msgctxt "#33621" +msgid "Sets the maximum amount of time to connect to a Plex Server in seconds. Default: 5" +msgstr "" + +#: +msgctxt "#32940" +msgid "Video Player" +msgstr "" + +#: +msgctxt "#33050" +msgid "The maximum number of retries each connection should attempt. The default (3) helps with hibernation/wakeup issues. Set higher for bad connections, setting this to 0 was the old default before 0.7.9" +msgstr "" + +#: +msgctxt "#33075" +msgid "Do something after waking up. Waiting for a time until resuming normal execution helps with networks coming back up after sleep events. Default: 1 second (5 seconds, CoreELEC)." +msgstr "" + +#: +msgctxt "#33087" +msgid "Bind a key to immediately go to the Home view. Cancel using BACK/PREVIOUS_MENU. Clear bound key using the STOP or CONTEXT_MENU (long press OK/Enter) button on the setting." +msgstr "" + +#: +msgctxt "#33088" +msgid "Only applies to video player UI" +msgstr "" + +#: +msgctxt "#33089" +msgid "Automatic seek-back" +msgstr "" + +#: +msgctxt "#33090" +msgid "If your audio doesn't resume as fast as the video, or you want to catch up after a longer pause, use this to seek back after resuming from pause to compensate for the delay. Default: Off" +msgstr "" + +#: +msgctxt "#33091" +msgid "{sec_or_ms} {unit_s_or_ms}" +msgstr "" + +#: +msgctxt "#33092" +msgid "Seek back on pause" +msgstr "" + +#: +msgctxt "#33093" +msgid "Seek back after" +msgstr "" + +#: +msgctxt "#33094" +msgid "Only seek back after having paused at least a certain amount of seconds" +msgstr "" + +#: +msgctxt "#33095" +msgid "Seek back on pause instead of on resume. When Transcoding you should enable this." +msgstr "" + +#: +msgctxt "#33096" +msgid "Only with Direct Play" +msgstr "" + +#: +msgctxt "#33097" +msgid "Enable seek back ony when we're Direct Playing, not Transcoding." +msgstr "" + +#: +msgctxt "#33098" +msgid "Tickrate (Hz)" +msgstr "" + +#: +msgctxt "#33099" +msgid "Controls how often certain ticks are performed on GUI windows and the SeekDialog/VideoPlayer and how fast certain events are handled. Can be expensive when bigger than 1 Hz (once per second), can cause quirks when lower than 1 Hz (less than once per second). Depends on the hardware. Default: 1 Hz, Max: 10 Hz (every 100 ms), Sane highest and old default: 10 Hz (every 100 ms)" +msgstr "" + +#: +msgctxt "#33636" +msgid "Plex server read timeout" +msgstr "" + +#: +msgctxt "#33637" +msgid "Sets the maximum amount of time to read from a Plex Server in seconds. Default: 10" +msgstr "" + +#: +msgctxt "#33638" +msgid "Plex.tv connect timeout" +msgstr "" + +#: +msgctxt "#33640" +msgid "Plex.tv read timeout" +msgstr "" + +#: +msgctxt "#33642" +msgid "Dump config" +msgstr "" + +#: +msgctxt "#33643" +msgid "Dumps all user settings into the log on startup, when DEBUG logging is enabled. Masks private information." +msgstr "" + +#: +msgctxt "#33644" +msgid "Tracks" +msgstr "" + +#: +msgctxt "#33645" +msgid "plex.direct: Honor plex.tv's dnsRebindingProtection flag (DNS)" +msgstr "" + +#: +msgctxt "#33646" +msgid "Only handle plex.direct hosts when the server's attributes dnsRebindingProtection=1. Might not work in certain situations. Disable if you have connection issues. Default: On" +msgstr "" + +#: +msgctxt "#33647" +msgid "plex.direct: Honor plex.tv's publicAddressMatches flag (DNS)" +msgstr "" + +#: +msgctxt "#33648" +msgid "Only handle plex.direct hosts when the server's attributes publicAddressMatches=1. Might not work in certain situations. Disable if you have connection issues. Default: On" +msgstr "" + +#: +msgctxt "#32101" +msgid "If enabled, when playback ends and there is a 'Next Up' item available, it will be automatically be played after a {} second delay." +msgstr "" + +#: +msgctxt "#33651" +msgid "Startup delay" +msgstr "" + +#: +msgctxt "#33652" +msgid "Never show Post Play" +msgstr "" + +#: +msgctxt "#33506" +msgid "Show the intro skip button from the start of a video with an intro marker. The auto-skipping setting applies. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "" + +#: +msgctxt "#33619" +msgid "Automatically skips episode intros, credits and tries to skip episode recaps. Doesn't skip the intro of the first episode of a season and doesn't skip the final credits of a show.\n" +"\n" +"Can be disabled/enabled per TV show.\n" +"Overrides any setting below." +msgstr "" + +#: +msgctxt "#33052" +msgid "Which CA certificate bundle to use for request HTTPS verification. \"system\" uses the system-provided certificate bundle. \"ACME\" uses an extremely small bundle provided with the addon, which includes root certificates for Letsencrypt (plex.direct) and ZeroSSL. \"custom\" allows for a ca-certificate bundle in userdata/addon_data/script.plexmod/custom_bundle.crt" +msgstr "" + +#: +msgctxt "#33054" +msgid "ACME (addon-supplied)" +msgstr "" + +#: +msgctxt "#33639" +msgid "Plex.tv: Sets the maximum amount of time to connect to Plex.tv in seconds. Default: 1" +msgstr "" + +#: +msgctxt "#33641" +msgid "Plex.tv: Sets the maximum amount of time to read from Plex.tv in seconds. Default: 2" +msgstr "" + +#: +msgctxt "#33653" +msgid "Background image resolution scaling %" +msgstr "" + +#: +msgctxt "#33654" +msgid "In percent, based on 1080p. Scales the resolution of all backgrounds for better image quality. May impact PMS/PM4K performance, will increase the cache usage accordingly. Can lead to crashes if your hardware is low on RAM. Needs addon restart. Use 400% for 4K. ATTENTION: In order for this to work you need to add 2160 and 2160 to your advancedsettings.xml." +msgstr "" + +#: +msgctxt "#33655" +msgid "Auto-Sync Subtitles" +msgstr "" + +#: +msgctxt "#33656" +msgid "Only for External SRT subtitles. The PMS setting for voice activity detection has to be enabled for this to work." +msgstr "" + +#: in player quick settings UI, needs to be short +msgctxt "#33657" +msgid "Enable Auto-Sync" +msgstr "" + +#: in player quick settings UI, needs to be short +msgctxt "#33658" +msgid "Disable Auto-Sync" +msgstr "" + +#: +msgctxt "#33659" +msgid "Hide hub: {}" +msgstr "" + +#: +msgctxt "#33660" +msgid "Disable HDR" +msgstr "" + +#: +msgctxt "#33661" +msgid "If you don't want your client to handle HDR (or HDR-fallback), enable this to force transcoding. Doesn't apply to DV Profile 5." +msgstr "" + +#: +msgctxt "#33662" +msgid "Remove from Continue Watching" +msgstr "" + +#: +msgctxt "#33663" +msgid "Home: Confirm item actions" +msgstr "" + +#: +msgctxt "#33664" +msgid "When acting on items in the Home view, such as mark played, hide from continue watching etc., show a confirmation dialog." +msgstr "" + +#: +msgctxt "#33665" +msgid "Disable audio codecs" +msgstr "" + +#: +msgctxt "#33666" +msgid "Audio codecs you can't play back. Disables Direct Play for such media items, enables Direct Stream if possible, transcodes audio stream to compatible format." +msgstr "" + +#: +msgctxt "#32002" +msgid "20 Mbps" +msgstr "" + +#: +msgctxt "#32003" +msgid "12 Mbps" +msgstr "" + +#: +msgctxt "#32004" +msgid "10 Mbps" +msgstr "" + +#: +msgctxt "#32005" +msgid "8 Mbps" +msgstr "" + +#: +msgctxt "#32006" +msgid "4 Mbps" +msgstr "" + +#: +msgctxt "#32007" +msgid "3 Mbps" +msgstr "" + +#: +msgctxt "#32008" +msgid "2 Mbps" +msgstr "" + +#: +msgctxt "#32009" +msgid "1.5 Mbps" +msgstr "" + +#: +msgctxt "#33669" +msgid "Really sign out?" +msgstr "" + +#: +msgctxt "#33670" +msgid "Update available" +msgstr "" + +#: +msgctxt "#33672" +msgid "Check for updates" +msgstr "" + +#: +msgctxt "#33673" +msgid "Automatically check for updates periodically. If installed from a Kodi repository and the Update Source setting is set to Repository, Kodi itself will handle the updating of this addon. Needs a Kodi restart when changed." +msgstr "" + +#: +msgctxt "#33674" +msgid "Check for updates on start" +msgstr "" + +#: +msgctxt "#33675" +msgid "Automatically check for updates on startup. Doesn't do much when Update source is Repository. Needs a Kodi restart when changed." +msgstr "" + +#: +msgctxt "#33676" +msgid "Update source" +msgstr "" + +#: +msgctxt "#33677" +msgid "Specifies the update mode. Will immediately check for a new version when changed and closing settings.\n" +"Default: Repository\n" +"\n" +"Beta: Bleeding edge (possibly unstable)\n" +"Stable: Stable branch (faster than Repository)\n" +"Repository: Kodi repository (official (slow) or Don't Panic)" +msgstr "" + +#: +msgctxt "#33678" +msgid "Beta" +msgstr "" + +#: Update mode as in (beta, stable, repository) +msgctxt "#33679" +msgid "Stable" +msgstr "" + +#: +msgctxt "#33680" +msgid "Repository" +msgstr "" + +#: +msgctxt "#33683" +msgid "Exit, download and install" +msgstr "" + +#: +msgctxt "#33684" +msgid "Later" +msgstr "" + +#: +msgctxt "#32015" +msgid "6 Mbps" +msgstr "" + +#: +msgctxt "#32016" +msgid "16 Mbps" +msgstr "" + +#: +msgctxt "#32017" +msgid "26 Mbps" +msgstr "" + +#: +msgctxt "#33681" +msgid "Service updated" +msgstr "" + +#: +msgctxt "#33682" +msgid "The update {} has had changes to the updater itself. In order for the updated updater service to work, a Kodi restart is necessary. The addon will work normally, though. Do you still want to run the addon?" +msgstr "" + +#: +msgctxt "#33685" +msgid "Clamp video bitrate" +msgstr "" + +#: +msgctxt "#33686" +msgid "Only show transcode bitrate targets lower than the current video's bitrate." +msgstr "" + +#: +msgctxt "#33687" +msgid "Translation updated" +msgstr "" + +#: +msgctxt "#33688" +msgid "The currently in-use translation has been updated. In order to load the new translation, a Kodi restart is necessary. The addon will still run properly, but you might see badly or untranslated strings. Do you still want to run the addon?" +msgstr "" + +#: +msgctxt "#33509" +msgid "Early intro skip threshold (default: < 120s/2m)" +msgstr "" + +#: +msgctxt "#33007" +msgid "Select in which cases to blur TV episode thumbnails, previews, redact summaries, hide episode titles etc." +msgstr "" + +#: +msgctxt "#33009" +msgid "Blur amount for unplayed/in-progress episodes" +msgstr "" + +#: +msgctxt "#33010" +msgid "Unplayed" +msgstr "" + +#: +msgctxt "#33012" +msgid "No unplayed episode titles" +msgstr "" + +#: +msgctxt "#33022" +msgid "Played indicators" +msgstr "" + +#: +msgctxt "#33023" +msgid "Classic: Show orange triangle for unplayed items\n" +"Modern: Show green checkmark for played items\n" +"Modern (2024): Show white checkmark for played items\n" +"(default: Modern (2024))" +msgstr "" + +#: +msgctxt "#33025" +msgid "When the above is enabled, hide the black backdrop of the played state." +msgstr "" + +#: +msgctxt "#33689" +msgid "Service running" +msgstr "" + +#: +msgctxt "#33690" +msgid "Last update check" +msgstr "" + +#: +msgctxt "#33691" +msgid "Native languages" +msgstr "" + +#: +msgctxt "#33692" +msgid "When you usually watch things in a different language with subtitles, but are a native speaker of other languages, which you don't need subtitles for, prevent Plex from auto-selecting subtitles for those languages." +msgstr "" + +#: +msgctxt "#33693" +msgid "Download subtitles using" +msgstr "" + +#: +msgctxt "#33694" +msgid "Ask" +msgstr "" + +#: +msgctxt "#33695" +msgid "Where do you want to download subtitles from? Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "" + +#: +msgctxt "#33696" +msgid "No subtitles found." +msgstr "" + +#: things in curly brackets are variables, don't translate them! +msgctxt "#33697" +msgid "{provider_title}, Score: {subtitle_score}{subtitle_info}" +msgstr "" + +#: hearing impaired subtitle flag +msgctxt "#33698" +msgid "HI" +msgstr "" + +#: forced subtitle flag +msgctxt "#33699" +msgid "forced" +msgstr "" + +#: +msgctxt "#33700" +msgid "Download subtitles: {}" +msgstr "" + +#: +msgctxt "#33701" +msgid "Fallback to Kodi" +msgstr "" + +#: +msgctxt "#33702" +msgid "When no subtitles are found via the Plex subtitle search, fall back to Kodi subtitle search. Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "" + +#: +msgctxt "#33703" +msgid "Download subtitles" +msgstr "" + +#: +msgctxt "#33704" +msgid "Using which service?" +msgstr "" + +#: +msgctxt "#33705" +msgid "Hide ratings" +msgstr "" + +#: +msgctxt "#33706" +msgid "Blur images" +msgstr "" + +#: +msgctxt "#33707" +msgid "Blur images on home (in progress)" +msgstr "" + +#: +msgctxt "#33708" +msgid "Hide summaries" +msgstr "" + +#: +msgctxt "#33709" +msgid "Show ratings for" +msgstr "" + +#: +msgctxt "#33710" +msgid "Show reviews for" +msgstr "" + +#: +msgctxt "#33711" +msgid "Always resume media" +msgstr "" + +#: +msgctxt "#33712" +msgid "When playback of an in-progress media is requested, resume it by default instead of asking whether to resume or start from the beginning." +msgstr "" + +#: +msgctxt "#33713" +msgid "Home: Resume in-progress items" +msgstr "" + +#: +msgctxt "#33714" +msgid "Resume in-progress items directly instead of visiting the media." +msgstr "" + +#: +msgctxt "#33715" +msgid "OSD hide-delay" +msgstr "" + +#: +msgctxt "#33716" +msgid "How long to wait until hiding the OSD. Only works with Plextuary skin or others with configurable OSD timeout. Default: 4s (+/- 1s)" +msgstr "" + +#: +msgctxt "#33717" +msgid "Debug requests" +msgstr "" + +#: +msgctxt "#33718" +msgid "Played" +msgstr "" + +#: +msgctxt "#33719" +msgid "Refresh metadata" +msgstr "" + +#: +msgctxt "#33038" +msgid "When encountering an intro marker with a start time offset greater than this, ignore it (default: 1400s/23m)" +msgstr "" + +#: +msgctxt "#33649" +msgid "\"Use alternate seek\" wait for seek" +msgstr "" + +#: +msgctxt "#33650" +msgid "This adjusts the delay between seek-tries on problematic platforms when \"Use alternate seek\" is enabled, which fixes resume not always working or double-seeking. When you have resume/double-seek issues, increase this. Default: 500ms" +msgstr "" + +#: +msgctxt "#33667" +msgid "Use alternate seek" +msgstr "" + +#: +msgctxt "#33668" +msgid "ATTENTION: Only enable this if you have reproducible audio issues after seeking/resuming.\n" +"\n" +"Use an alternative seek method in videos, which can help in problematic scenarios; brings its own issues/quirks. Disabled by default (enabled by default for CoreELEC and LG WebOS)" +msgstr "" + +#: +msgctxt "#33720" +msgid "Clear all caches" +msgstr "" + +#: +msgctxt "#33721" +msgid "Clear library cache (not items)" +msgstr "" + +#: +msgctxt "#33722" +msgid "Libraries" +msgstr "" + +#: +msgctxt "#33723" +msgid "Media Items" +msgstr "" + +#: +msgctxt "#33724" +msgid "Cache Plex data for" +msgstr "" + +#: +msgctxt "#33725" +msgid "Persist cached Plex data" +msgstr "" + +#: +msgctxt "#33726" +msgid "Instead of clearing the cache when exiting the addon, persist it instead. Warning: You'll most likely encounter missing items in libraries or outdated data. Use the corresponding menu functionalities to clear the cache for specific items or libraries." +msgstr "" + +#: +msgctxt "#33727" +msgid "Store Plex server responses for items and library views in a local SQLite database. Doesn't cache anything else (Home/Hubs are always up to date). Massively speeds up consecutive visits to items and libraries. Certain important events, such as watch state changes, automatically delete the item cache and its corresponding library cache. (Default: Off)" +msgstr "" + +#: +msgctxt "#33728" +msgid "Clear cache for item" +msgstr "" + +#: +msgctxt "#33729" +msgid "Expire cached Plex data after (hours)" +msgstr "" + +#: +msgctxt "#33730" +msgid "Randomly" +msgstr "" + +#: +msgctxt "#33731" +msgid "By Bitrate" +msgstr "" + +#: +msgctxt "#33732" +msgid "Bitrate" +msgstr "" + +#: +msgctxt "#33733" +msgid "Transcode target codec" +msgstr "" + +#: +msgctxt "#33734" +msgid "Sets the target codec when transcoding/direct streaming. Overridden when \"Transcode audio to AC3\" is set." +msgstr "" + +#: +msgctxt "#33735" +msgid "Always auto-start" +msgstr "" + +#: +msgctxt "#33736" +msgid "By default PM4K won't auto-start when the current window isn't a Home-derived window (e.g.: Settings). Enable this if you always want to auto-start PM4K regardless of the current window. Can also improve start-up time." +msgstr "" + +#: +msgctxt "#33737" +msgid "Loop theme music" +msgstr "" + +#: +msgctxt "#33671" +msgid "Current: {current_version}\n" +"New: {new_version}\n" +"\n" +"Changelog:\n" +"{changelog}" +msgstr "" + +#: +msgctxt "#33738" +msgid "Max playlist size" +msgstr "" + +#: +msgctxt "#33739" +msgid "How many items to load into a Kodi playlist before chunking them remotely using Plex PlayQueues (default: 500); Reduce on memory issues/crashes." +msgstr "" + +#: +msgctxt "#33740" +msgid "Home: Episodes season thumbnails" +msgstr "" + +#: +msgctxt "#33741" +msgid "Use season thumbnails/posters when displaying episodes in hubs instead of the TV show's." +msgstr "" + +#: +msgctxt "#34000" +msgid "Watchlist" +msgstr "" + +#: +msgctxt "#34001" +msgid "Released" +msgstr "" + +#: +msgctxt "#34002" +msgid "Movies & Shows" +msgstr "" + +#: e.g.: 8 seasons +msgctxt "#34003" +msgid "{} seasons" +msgstr "" + +#: +msgctxt "#34004" +msgid "Choose server" +msgstr "" + +#: +msgctxt "#34005" +msgid "Availability" +msgstr "" + +#: e.g.: 1 season +msgctxt "#34006" +msgid "{} season" +msgstr "" + +#: +msgctxt "#34007" +msgid "Use Watchlist" +msgstr "" + +#: +msgctxt "#34008" +msgid "Activates the current user's Plex watchlist as a section item. Adds watchlist functionality to certain media screens. Per-user setting. Default: On" +msgstr "" + +#: +msgctxt "#34009" +msgid "Watchlist auto-remove" +msgstr "" + +#: +msgctxt "#34010" +msgid "Automatically remove fully watched items from watchlist. Default: On" +msgstr "" + +#: +msgctxt "#34011" +msgid "Remove from watchlist" +msgstr "" + +#: +msgctxt "#34012" +msgid "Fast pause/resume" +msgstr "" + +#: +msgctxt "#34013" +msgid "when paused" +msgstr "" + +#: +msgctxt "#34014" +msgid "when playing" +msgstr "" + +#: +msgctxt "#34015" +msgid "User-specific. Use OK/ENTER button to pause instead of showing the OSD (which can then only be accessed using DOWN), or resume when paused. Only works with 'Behave like official Plex clients' enabled." +msgstr "" + +#: +msgctxt "#34016" +msgid "Max shutdown wait" +msgstr "" + +#: +msgctxt "#34017" +msgid "The plugin will try to hard exit once this time limit is reached (default: 5 seconds)" +msgstr "" + +#: +msgctxt "#34018" +msgid "Related Media" +msgstr "" + +#: watchlist discover hub title prefix +msgctxt "#34019" +msgid "Discover: {}" +msgstr "" + +#: +msgctxt "#34020" +msgid "Loading external hubs" +msgstr "" + +#: +msgctxt "#34021" +msgid "Please wait ..." +msgstr "" + +#: +msgctxt "#34022" +msgid "Video play completion behaviour" +msgstr "" + +#: +msgctxt "#34023" +msgid "Decide whether to use end credits markers to determine the 'watched' state of video items. When markers are not available the selected threshold percentage will be used." +msgstr "" + +#: +msgctxt "#34024" +msgid "at selected threshold percentage" +msgstr "" + +#: +msgctxt "#34025" +msgid "at final credits marker position" +msgstr "" + +#: +msgctxt "#34026" +msgid "at first credits marker position" +msgstr "" + +#: +msgctxt "#34027" +msgid "earliest between threshold percent and first credits marker" +msgstr "" + +#: +msgctxt "#32973" +msgid "Episodes: Continuous playback" +msgstr "" + +#: +msgctxt "#34028" +msgid "\"Use alternate seek\" valid seek window" +msgstr "" + +#: +msgctxt "#34029" +msgid "When \"Use alternate seek\" is enabled, any seek amount outside of this threshold window will be applied, otherwise the seek attempt will be ignored. If you encounter any seek issues, play with this value (ms, default: 2000)" +msgstr "" + +#: +msgctxt "#34030" +msgid "Unlock Direct Play for above 4K" +msgstr "" + +#: +msgctxt "#34031" +msgid "Producer" +msgstr "" + +#: +msgctxt "#34032" +msgid "Audio Language" +msgstr "" + +#: +msgctxt "#34033" +msgid "Subtitle Language" +msgstr "" + +#: +msgctxt "#34034" +msgid "Folder Location" +msgstr "" + +#: +msgctxt "#34035" +msgid "Editions" +msgstr "" + +#: +msgctxt "#34036" +msgid "DOVI" +msgstr "" + +#: +msgctxt "#34037" +msgid "HDR" +msgstr "" + +#: +msgctxt "#34038" +msgid "Force plex.direct mapping" +msgstr "" + +#: +msgctxt "#34039" +msgid "If you (still) don't see any posters: Under certain circumstances when Kodi still can't resolve hostnames when loading assets, even though the host resolves natively, enable this to force plex.direct mapping via advancedsettings.xml." +msgstr "" + +#: +msgctxt "#34040" +msgid "By Progress" +msgstr "" + +#: +msgctxt "#34041" +msgid "Progress" +msgstr "" + +#: +msgctxt "#34042" +msgid "By Album" +msgstr "" + +#: +msgctxt "#34043" +msgid "Album" +msgstr "" + diff --git a/script.plexmod/resources/language/resource.language.pt_pt/strings.po b/script.plexmod/resources/language/resource.language.pt_pt/strings.po index 7c265ff3c6..ba42956339 100644 --- a/script.plexmod/resources/language/resource.language.pt_pt/strings.po +++ b/script.plexmod/resources/language/resource.language.pt_pt/strings.po @@ -1,980 +1,3544 @@ -# XBMC Media Center language file -# Guerreiro , 2019. -# msgid "" msgstr "" -"Project-Id-Version: XBMC-Addons\n" -"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n" -"POT-Creation-Date: 2013-12-12 22:56+0000\n" -"PO-Revision-Date: 2019-09-10 18:44+0100\n" -"Last-Translator: Guerreiro \n" -"Language-Team: Português <>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"X-Generator: POEditor.com\n" +"Project-Id-Version: PM4K / PlexMod for Kodi\n" "Language: pt\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +#: msgctxt "#32000" msgid "Main" msgstr "Principal" +#: msgctxt "#32001" msgid "Original" msgstr "Original" -msgctxt "#32002" -msgid "20 Mbps 1080p" -msgstr "20 Mbps 1080p" - -msgctxt "#32003" -msgid "12 Mbps 1080p" -msgstr "12 Mbps 1080p" - -msgctxt "#32004" -msgid "10 Mbps 1080p" -msgstr "10 Mbps 1080p" - -msgctxt "#32005" -msgid "8 Mbps 1080p" -msgstr "8 Mbps 1080p" - -msgctxt "#32006" -msgid "4 Mbps 720p" -msgstr "4 Mbps 720p" - -msgctxt "#32007" -msgid "3 Mbps 720p" -msgstr "3 Mbps 720p" - -msgctxt "#32008" -msgid "2 Mbps 720p" -msgstr "2 Mbps 720p" - -msgctxt "#32009" -msgid "1.5 Mbps 480p" -msgstr "1.5 Mbps 480p" - +#: msgctxt "#32010" msgid "720 kbps" msgstr "720 kbps" +#: msgctxt "#32011" msgid "320 kbps" msgstr "320 kbps" +#: msgctxt "#32012" msgid "208 kbps" msgstr "208 kbps" +#: msgctxt "#32013" msgid "96 kbps" msgstr "96 kbps" +#: msgctxt "#32014" msgid "64 kbps" msgstr "64 kbps" +#: msgctxt "#32020" msgid "Local Quality" msgstr "Qualidade Local" +#: msgctxt "#32021" msgid "Remote Quality" msgstr "Qualidade Remota" +#: msgctxt "#32022" msgid "Online Quality" msgstr "Qualidade Online" +#: msgctxt "#32023" msgid "Transcode Format" msgstr "Formato de Transcodificação" +#: msgctxt "#32024" msgid "Debug Logging" msgstr "Registo de Depuração" -msgctxt "#32025" -msgid "Allow Direct Play" -msgstr "Permitir Reprodução Direta" - -msgctxt "#32026" -msgid "Allow Direct Stream" -msgstr "Permitir Transmissão Direta" - +#: msgctxt "#32027" msgid "Force" msgstr "Forçar" +#: msgctxt "#32028" msgid "Always" msgstr "Sempre" +#: msgctxt "#32029" msgid "Only Image Formats" msgstr "Apenas Formatos de Imagem" +#: msgctxt "#32030" msgid "Auto" msgstr "Auto" -msgctxt "#32031" -msgid "Burn Subtitles (Direct Play Only)" -msgstr "Gravar Legendas (Somente Reprodução Direta)" - +#: msgctxt "#32032" msgid "Allow Insecure Connections" msgstr "Permitir Ligações Inseguras" +#: msgctxt "#32033" msgid "Never" msgstr "Nunca" +#: msgctxt "#32034" msgid "On Same network" msgstr "Na Mesma Rede" +#: msgctxt "#32035" msgid "Always" msgstr "Sempre" -msgctxt "#32036" -msgid "Allow 4K" -msgstr "Permitir 4K" - -msgctxt "#32037" -msgid "Allow HEVC (h265)" -msgstr "Permitir HEVC (h265)" - +#: msgctxt "#32038" msgid "Automatically Sign In" msgstr "Entrar Automaticamente" +#: msgctxt "#32039" msgid "Post Play Auto Play" msgstr "Reprodução Automática" +#: msgctxt "#32040" msgid "Enable Subtitle Downloading" msgstr "Ativar a Transferência de Legendas" +#: msgctxt "#32041" msgid "Enable Subtitle Downloading" msgstr "Ativar a Transferência de Legendas" +#: msgctxt "#32042" msgid "Server Discovery (GDM)" msgstr "Descoberta de Servidor (GDM)" +#: msgctxt "#32043" msgid "Start Plex On Kodi Startup" msgstr "Iniciar Plex Automaticamente" +#: msgctxt "#32044" msgid "Connection 1 IP" msgstr "IP de Ligação 1" +#: msgctxt "#32045" msgid "Connection 1 Port" msgstr "Porta de Ligação 1" +#: msgctxt "#32046" msgid "Connection 2 IP" msgstr "IP de Ligação 2" +#: msgctxt "#32047" msgid "Connection 2 Port" msgstr "Porta de Ligação 2" +#: msgctxt "#32048" msgid "Audio" msgstr "Áudio" +#: msgctxt "#32049" msgid "Advanced" msgstr "Avançado" +#: msgctxt "#32050" msgid "Manual Servers" msgstr "Servidores Manuais" +#: msgctxt "#32051" msgid "Privacy" msgstr "Privacidade" +#: msgctxt "#32052" msgid "About" msgstr "Sobre" +#: msgctxt "#32053" msgid "Video" msgstr "Vídeo" +#: msgctxt "#32054" msgid "Addon Version" msgstr "Versão do Addon" +#: msgctxt "#32055" msgid "Kodi Version" msgstr "Versão do Kodi" +#: msgctxt "#32056" msgid "Screen Resolution" msgstr "Resolução de Ecrã" +#: msgctxt "#32057" msgid "Current Server Version" msgstr "Versão Atual do Servidor" +#: +msgctxt "#32058" +msgid "Never exceed original audio codec" +msgstr "Nunca exceder o codec de audio original" + +#: +msgctxt "#32059" +msgid "When transcoding audio, never exceed the original audio bitrate or channel count on the same codec." +msgstr "Ao transcodificar áudio, nunca exceder a taxa de bits de áudio original ou a contagem de canais no mesmo codec." + +#: +msgctxt "#32060" +msgid "Use Kodi audio channels" +msgstr "Usar canais de audio do Kodi" + +#: +msgctxt "#32064" +msgid "Treat DTS like AC3" +msgstr "Tratar DTS como AC3" + +#: msgctxt "#32100" msgid "Skip user selection and pin entry on startup." msgstr "Saltar seleção de utilizador e inserção do PIN ao iniciar." -msgctxt "#32101" -msgid "If enabled, when playback ends and there is a 'Next Up' item available, it will be automatically be played after a 15 second delay." -msgstr "Se ativado, quando a reprodução acabar e existir algum item 'A Seguir', será automáticamente reproduzido depois de 15 segundos." - +#: msgctxt "#32102" msgid "Enable this if your hardware can handle 4K playback. Disable it to force transcoding." msgstr "Ative isto se seu equipamento suportar reprodução 4K. Desative para forçar conversão." +#: msgctxt "#32103" msgid "Enable this if your hardware can handle HEVC/h265. Disable it to force transcoding." msgstr "Ative isto se seu equipamento suportar HEVC/h265. Desative para forçar conversão." +#: msgctxt "#32104" msgid "When to connect to servers with no secure connections.[CR][CR]* [B]Never[/B]: Never connect to a server insecurely[CR]* [B]On Same Network[/B]: Allow if on the same network[CR]* [B]Always[/B]: Allow same network and remote connections" msgstr "Quando ligar à servidores sem ligação segura.[CR][CR]* [B]Nunca[/B]: Nunca ligar à um servidor de forma insegura[CR]* [B]Na Mesma Rede[/B]: Permitir se na mesma rede[CR]* [B]Sempre[/B]: Permitir ligação na mesma rede e remotamente" +#: msgctxt "#32201" msgid "Trailer" msgstr "Trailer" +#: msgctxt "#32202" msgid "Deleted Scene" msgstr "Cena apagada" +#: msgctxt "#32203" msgid "Interview" msgstr "Entrevista" +#: msgctxt "#32204" msgid "Music Video" msgstr "Vídeo Clipe" +#: msgctxt "#32205" msgid "Behind the Scenes" msgstr "Nos Bastidores" +#: msgctxt "#32206" msgid "Scene" msgstr "Cena" +#: msgctxt "#32207" msgid "Live Music Video" msgstr "Vídeo Clipe ao Vivo" +#: msgctxt "#32208" msgid "Lyric Music Video" msgstr "Vide Clipe com Letra" +#: msgctxt "#32209" msgid "Concert" msgstr "Concerto" +#: msgctxt "#32210" msgid "Featurette" msgstr "Destaques" +#: msgctxt "#32211" msgid "Short" msgstr "Curta" +#: msgctxt "#32212" msgid "Other" msgstr "Outro" +#: msgctxt "#32300" msgid "Go to Album" msgstr "Ir para Álbum" +#: msgctxt "#32301" msgid "Go to Artist" msgstr "Ir para Artista" +#: msgctxt "#32302" msgid "Go to {0}" msgstr "Ir para {0}" -msgctxt "#32303" -msgid "Season" -msgstr "Temporada" - -msgctxt "#32304" -msgid "Episode" -msgstr "Episódio" - +#: msgctxt "#32305" msgid "Extras" msgstr "Extras" +#: msgctxt "#32306" msgid "Related Shows" msgstr "Séries relacionadas" +#: msgctxt "#32307" msgid "More" msgstr "Mais" +#: msgctxt "#32308" msgid "Available" msgstr "Disponível" +#: msgctxt "#32309" msgid "None" msgstr "Nenhum" -msgctxt "#32310" -msgid "S" -msgstr "T" - -msgctxt "#32311" -msgid "E" -msgstr "E" - +#: msgctxt "#32312" msgid "Unavailable" msgstr "Indisponível" +#: msgctxt "#32313" msgid "This item is currently unavailable." msgstr "Este item está atualmente indisponível." +#: msgctxt "#32314" msgid "In Progress" msgstr "Em Progresso" +#: msgctxt "#32315" msgid "Resume playback?" msgstr "Resumir reprodução" +#: msgctxt "#32316" msgid "Resume" msgstr "Resumir" +#: msgctxt "#32317" msgid "Play from beginning" msgstr "Reproduzir do inicio" +#: msgctxt "#32318" msgid "Mark Unplayed" msgstr "Marcar como Não-Visto" +#: msgctxt "#32319" msgid "Mark Played" msgstr "Marcar como Visto" +#: msgctxt "#32320" msgid "Mark Season Unplayed" msgstr "Marcar Temporada como Não-Vista" +#: msgctxt "#32321" msgid "Mark Season Played" msgstr "Marcar Temporada como Vista" +#: msgctxt "#32322" msgid "Delete" msgstr "Apagar" +#: msgctxt "#32323" msgid "Go To Show" msgstr "Ir para Série" +#: msgctxt "#32324" msgid "Go To {0}" msgstr "Ir para {0}" +#: msgctxt "#32325" msgid "Play Next" msgstr "Reproduzir Próximo" +#: msgctxt "#32326" msgid "Really Delete?" msgstr "Realmente Apagar?" +#: msgctxt "#32327" msgid "Are you sure you really want to delete this media?" msgstr "Tem certeza que quer apagar este item?" +#: msgctxt "#32328" msgid "Yes" msgstr "Sim" +#: msgctxt "#32329" msgid "No" msgstr "Não" +#: msgctxt "#32330" msgid "Message" msgstr "Mensagem" +#: msgctxt "#32331" msgid "There was a problem while attempting to delete the media." msgstr "Ocorreu um problema ao apagar este item." +#: msgctxt "#32332" msgid "Home" msgstr "Início" +#: msgctxt "#32333" msgid "Playlists" msgstr "Listas de Reprodução" +#: msgctxt "#32334" msgid "Confirm Exit" msgstr "Confirmar Saída" +#: msgctxt "#32335" msgid "Are you ready to exit Plex?" msgstr "Está pronto para sair do Plex?" +#: msgctxt "#32336" msgid "Exit" msgstr "Sair" +#: msgctxt "#32337" msgid "Cancel" msgstr "Cancelar" +#: msgctxt "#32338" msgid "No Servers Found" msgstr "Nenhum Servidor Encontrado" +#: msgctxt "#32339" msgid "Server is not accessible" msgstr "Servidor inacessível" +#: msgctxt "#32340" msgid "Connection tests are in progress. Please wait." msgstr "Testes de ligação estão em progresso. Por favor, aguarde." +#: msgctxt "#32341" msgid "Server is not accessible. Please sign into your server and check your connection." msgstr "Servidor inacessível. Ligue-se ao servidor e teste a ligação." +#: msgctxt "#32342" msgid "Switch User" msgstr "Mudar de Utilizador" +#: msgctxt "#32343" msgid "Settings" msgstr "Definições" +#: msgctxt "#32344" msgid "Sign Out" msgstr "Terminar Sessão" +#: msgctxt "#32345" msgid "All" msgstr "Tudo" +#: msgctxt "#32346" msgid "By Name" msgstr "Por Nome" +#: msgctxt "#32347" msgid "Artists" msgstr "Artistas" +#: msgctxt "#32348" msgid "Movies" msgstr "Filmes" +#: msgctxt "#32349" msgid "photos" msgstr "fotos" +#: msgctxt "#32350" msgid "Shows" msgstr "Séries" +#: msgctxt "#32351" msgid "By Date Added" msgstr "Por Data Adicionado" +#: msgctxt "#32352" msgid "Date Added" msgstr "Data Adicionado" +#: msgctxt "#32353" msgid "By Release Date" msgstr "Por Data de Lançamento" +#: msgctxt "#32354" msgid "Release Date" msgstr "Data de Lançamento" +#: msgctxt "#32355" msgid "By Date Viewed" msgstr "Por Data de Visualização" +#: msgctxt "#32356" msgid "Date Viewed" msgstr "Data de Visualização" -msgctxt "#32357" -msgid "By Name" -msgstr "Por Nome" - -msgctxt "#32358" -msgid "Name" -msgstr "Nome" - +#: msgctxt "#32359" msgid "By Rating" msgstr "Por Avaliação" +#: msgctxt "#32360" msgid "Rating" msgstr "Avaliação" +#: msgctxt "#32361" msgid "By Resolution" msgstr "Por Resolução" +#: msgctxt "#32362" msgid "Resolution" msgstr "Resolução" +#: msgctxt "#32363" msgid "By Duration" msgstr "Por Duração" +#: msgctxt "#32364" msgid "Duration" msgstr "Duração" +#: msgctxt "#32365" msgid "By First Aired" msgstr "Por Data de Estreia" +#: msgctxt "#32366" msgid "First Aired" msgstr "Data de Estreia" +#: msgctxt "#32367" msgid "By Unplayed" msgstr "Por Não-Vistos" +#: msgctxt "#32368" msgid "Unplayed" msgstr "Não-Vistos" +#: msgctxt "#32369" msgid "By Date Played" msgstr "Por Data de Reprodução" +#: msgctxt "#32370" msgid "Date Played" msgstr "Data de Reprodução" +#: msgctxt "#32371" msgid "By Play Count" msgstr "Por Número de Reproduções" +#: msgctxt "#32372" msgid "Play Count" msgstr "Número de Reproduções" +#: msgctxt "#32373" msgid "By Date Taken" msgstr "Por Data Tirada" +#: msgctxt "#32374" msgid "Date Taken" msgstr "Data Tirada" +#: msgctxt "#32375" msgid "No filters available" msgstr "Nenhum filtro disponível" +#: msgctxt "#32376" msgid "Clear Filter" msgstr "Limpar filtro" +#: msgctxt "#32377" msgid "Year" msgstr "Ano" +#: msgctxt "#32378" msgid "Decade" msgstr "Década" +#: msgctxt "#32379" msgid "Genre" msgstr "Género" +#: msgctxt "#32380" msgid "Content Rating" msgstr "Classificação do Conteúdo" +#: msgctxt "#32381" msgid "Network" msgstr "Rede" +#: msgctxt "#32382" msgid "Collection" msgstr "Coleção" +#: msgctxt "#32383" msgid "Director" msgstr "Diretor" +#: msgctxt "#32384" msgid "Actor" msgstr "Ator" +#: msgctxt "#32385" msgid "Country" msgstr "País" +#: msgctxt "#32386" msgid "Studio" msgstr "Estúdio" +#: msgctxt "#32387" msgid "Labels" msgstr "Rótulos" +#: msgctxt "#32388" msgid "Camera Make" msgstr "Fabricante da câmara" +#: msgctxt "#32389" msgid "Camera Model" msgstr "Modelo da câmara" +#: msgctxt "#32390" msgid "Aperture" msgstr "Abertura" +#: msgctxt "#32391" msgid "Shutter Speed" msgstr "Velocidade do Obturador" +#: msgctxt "#32392" msgid "Lens" msgstr "Lente" +#: msgctxt "#32393" msgid "TV Shows" msgstr "Séries TV" +#: msgctxt "#32394" msgid "Music" msgstr "Música" +#: msgctxt "#32395" msgid "Audio" msgstr "Áudio" +#: msgctxt "#32396" msgid "Subtitles" msgstr "Legendas" +#: msgctxt "#32397" msgid "Quality" msgstr "Qualidade" +#: msgctxt "#32398" msgid "Kodi Video Settings" msgstr "Configurações de Vídeo do Kodi" +#: msgctxt "#32399" msgid "Kodi Audio Settings" msgstr "Configurações de Áudio do Kodi" +#: msgctxt "#32400" msgid "Go To Season" msgstr "Ir para Temporada" +#: msgctxt "#32401" msgid "Directors" msgstr "Diretores" +#: msgctxt "#32402" msgid "Writer" msgstr "Escritor" +#: msgctxt "#32403" msgid "Writers" msgstr "Escritores" +#: msgctxt "#32404" msgid "Related Movies" msgstr "Filmes relacionados" +#: msgctxt "#32405" msgid "Download Subtitles" msgstr "Transferir Legendas" +#: msgctxt "#32406" msgid "Subtitle Delay" msgstr "Atraso da Legenda" +#: msgctxt "#32407" msgid "Next Subtitle" msgstr "Próxima Legenda" +#: msgctxt "#32408" msgid "Disable Subtitles" msgstr "Desativar Legendas" +#: msgctxt "#32409" msgid "Enable Subtitles" msgstr "Ativar Legendas" +#: msgctxt "#32410" msgid "Platform Version" msgstr "Versão da Plataforma" +#: msgctxt "#32411" msgid "Unknown" msgstr "Desconhecido" +#: msgctxt "#32412" msgid "Edit Or Clear" msgstr "Editar ou Limpar" +#: msgctxt "#32413" msgid "Edit IP address or clear the current setting?" msgstr "Editar endereço de IP ou limpar configurações atuais?" +#: msgctxt "#32414" msgid "Clear" msgstr "Limpar" +#: msgctxt "#32415" msgid "Edit" msgstr "Editar" +#: msgctxt "#32416" msgid "Enter IP Address" msgstr "Insira o endereço de IP" +#: msgctxt "#32417" msgid "Enter Port Number" msgstr "Insira o número da Porta" +#: msgctxt "#32418" msgid "Creator" msgstr "Criador" +#: msgctxt "#32419" msgid "Cast" msgstr "Elenco" +#: msgctxt "#32420" msgid "Disc" msgstr "Disco" +#: msgctxt "#32421" msgid "Sign Out" msgstr "Terminar Sessão" +#: msgctxt "#32422" msgid "Exit" msgstr "Sair" +#: msgctxt "#32423" msgid "Shutdown" msgstr "Desligar" +#: msgctxt "#32424" msgid "Suspend" msgstr "Suspender" +#: msgctxt "#32425" msgid "Hibernate" msgstr "Hibernar" +#: msgctxt "#32426" msgid "Reboot" msgstr "Reiniciar" +#: msgctxt "#32427" msgid "Failed" msgstr "Falhou" +#: msgctxt "#32428" msgid "Login failed!" msgstr "Falha ao iniciar sessão!" +#: msgctxt "#32429" msgid "Resume from {0}" msgstr "Resumir de {0}" +#: msgctxt "#32430" msgid "Discovery" msgstr "Descobrir" +#: msgctxt "#32431" msgid "Search" msgstr "Procurar" +#: msgctxt "#32432" msgid "Space" msgstr "Espaço" +#: msgctxt "#32433" msgid "Clear" msgstr "Limpar" +#: msgctxt "#32434" msgid "Searching..." msgstr "A Procurar..." +#: msgctxt "#32435" msgid "No Results" msgstr "Sem Resultados" +#: msgctxt "#32436" msgid "Paused" msgstr "Em Pausa" +#: msgctxt "#32437" msgid "Welcome" msgstr "Bem-Vindo" +#: msgctxt "#32438" msgid "Previous" msgstr "Anterior" +#: msgctxt "#32439" msgid "Playing Next" msgstr "Reproduzindo a seguir" +#: msgctxt "#32440" msgid "On Deck" msgstr "Próximos a Ver" +#: msgctxt "#32441" msgid "Unknown" msgstr "Desconhecido" +#: msgctxt "#32442" msgid "Embedded" msgstr "Embutido" +#: msgctxt "#32443" msgid "Forced" msgstr "Forçado" +#: msgctxt "#32444" msgid "Lyrics" msgstr "Letra da música" +#: msgctxt "#32445" msgid "Mono" msgstr "Mono" +#: msgctxt "#32446" msgid "Stereo" msgstr "Estéreo" +#: msgctxt "#32447" msgid "None" msgstr "Nenhum" +#: msgctxt "#32448" msgid "Playback Failed!" msgstr "Falha ao Reproduzir!" +#: msgctxt "#32449" msgid "Can't connect to plex.tv[CR]Check your internet connection and try again." msgstr "Não foi possivel ligar ao plex.tv[CR]Teste a ligação de internet e tente novamente." +#: msgctxt "#32450" msgid "Choose Version" msgstr "Escolher Versão" +#: msgctxt "#32451" msgid "Play Version..." msgstr "Reproduzir Versão..." +#: msgctxt "#32452" msgid "No Content available in this library" msgstr "Nenhum conteúdo disponível nesta biblioteca" +#: msgctxt "#32453" msgid "Please add content and/or check that 'Include in dashboard' is enabled." msgstr "Por favor adicione e/ou confirme que 'Incluir no Painel' está desativado." +#: msgctxt "#32454" msgid "No Content available for this filter" msgstr "Nenhum conteúdo disponível para este filtro" +#: msgctxt "#32455" msgid "Please change change or remove the current filter" msgstr "Por favor, troque ou remova o filtro atual" +#: msgctxt "#32456" msgid "Show" msgstr "Série" +#: msgctxt "#32457" msgid "By Show" msgstr "Por Série" +#: msgctxt "#32458" msgid "Episodes" msgstr "Episódios" +#: msgctxt "#32459" msgid "Offline Mode" msgstr "Modo Offline" +#: msgctxt "#32460" msgid "Sign In" msgstr "Iniciar Sessão" +#: msgctxt "#32461" msgid "Albums" msgstr "Álbuns" +#: msgctxt "#32462" msgid "Artist" msgstr "Artista" +#: msgctxt "#32463" msgid "By Artist" msgstr "Por Artista" +#: msgctxt "#32464" msgid "Player" msgstr "Reprodutor" +#: msgctxt "#32465" msgid "Use skip step settings from Kodi" msgstr "Use as definições de saltar etapas do Kodi" +#: msgctxt "#32466" msgid "Automatically seek selected position after a delay" msgstr "Procurar automaticamente a posição selecionada após atrasar" +#: msgctxt "#32467" msgid "User Interface" msgstr "Interface de Utilizador" +#: +msgctxt "#32468" +msgid "Show dynamic background art" +msgstr "Mostrar arte de fundo dinâmica" + +#: +msgctxt "#32469" +msgid "Background art blur amount" +msgstr "Quantidade de desfocagem da arte de fundo" + +#: +msgctxt "#32470" +msgid "Background art opacity" +msgstr "Opacidade da arte de fundo" + +#: msgctxt "#32471" msgid "Use Plex/Kodi steps for timeline" msgstr "Use as etapas do Plex/Kodi para a linha de tempo" +#: +msgctxt "#32480" +msgid "Theme music" +msgstr "Música tema" + +#: +msgctxt "#32481" +msgid "Off" +msgstr "Desligado" + +#: +msgctxt "#32482" +msgid "%(percentage)s %%" +msgstr "" + +#: +msgctxt "#32483" +msgid "Hide Stream Info" +msgstr "Ocultar informações da transmissão" + +#: +msgctxt "#32484" +msgid "Show Stream Info" +msgstr "Mostrar informações da transmissão" + +#: msgctxt "#32485" msgid "Go back instantly with the previous menu action in scrolled views" msgstr "Recuar instantaneamente com a ação recuar do menu em visualizações deslocáveis" +#: +msgctxt "#32487" +msgid "Seek Delay" +msgstr "" + +#: +msgctxt "#32488" +msgid "Screensaver" +msgstr "Protetor de ecrã" + +#: +msgctxt "#32489" +msgid "Quiz Mode" +msgstr "" + +#: +msgctxt "#32490" +msgid "Collections" +msgstr "Coleções" + +#: +msgctxt "#32491" +msgid "Folders" +msgstr "Pastas" + +#: msgctxt "#32492" msgid "Kodi Subtitle Settings" msgstr "Definições de Legendas do Kodi" + +#: +msgctxt "#32495" +msgid "Skip intro" +msgstr "Saltar introdução" + +#: +msgctxt "#32496" +msgid "Skip credits" +msgstr "Saltar créditos" + +#: +msgctxt "#32500" +msgid "Always show post-play screen (even for short videos)" +msgstr "Mostre sempre o ecrã pós-reprodução (mesmo para vídeos curtos)" + +#: +msgctxt "#32501" +msgid "Time-to-wait between videos on post-play" +msgstr "Tempo de espera entre vídeos pós-reprodução" + +#: +msgctxt "#32505" +msgid "Visit media in video playlist instead of playing it" +msgstr "Visite os media na lista de reprodução de vídeos em vez de os reproduzir" + +#: +msgctxt "#32521" +msgid "Skip Intro Button Timeout" +msgstr "Saltar tempo limite do botão de introdução" + +#: +msgctxt "#32522" +msgid "Automatically Skip Intro" +msgstr "Saltar introdução automaticamente" + +#: +msgctxt "#32524" +msgid "Set how long the skip intro button shows for." +msgstr "" + +#: +msgctxt "#32525" +msgid "Skip Credits Button Timeout" +msgstr "" + +#: +msgctxt "#32526" +msgid "Automatically Skip Credits" +msgstr "" + +#: +msgctxt "#32528" +msgid "Set how long the skip credits button shows for." +msgstr "" + +#: +msgctxt "#32540" +msgid "Show when the current video will end in player" +msgstr "" + +#: +msgctxt "#32541" +msgid "Shows time left and at which time the media will end." +msgstr "" + +#: +msgctxt "#32542" +msgid "Show \"Ends at\" label for the end-time as well" +msgstr "" + +#: +msgctxt "#32543" +msgid "Ends at" +msgstr "" + +#: +msgctxt "#32602" +msgid "Enable this if your hardware can handle AV1. Disable it to force transcoding." +msgstr "" + +#: +msgctxt "#33101" +msgid "By Audience Rating" +msgstr "" + +#: +msgctxt "#33102" +msgid "Audience Rating" +msgstr "" + +#: +msgctxt "#33103" +msgid "By my Rating" +msgstr "" + +#: +msgctxt "#33104" +msgid "My Rating" +msgstr "" + +#: +msgctxt "#33105" +msgid "By Content Rating" +msgstr "" + +#: +msgctxt "#33106" +msgid "Content Rating" +msgstr "" + +#: +msgctxt "#33107" +msgid "By Critic Rating" +msgstr "" + +#: +msgctxt "#33108" +msgid "Critic Rating" +msgstr "" + +#: +msgctxt "#33200" +msgid "Background Color" +msgstr "" + +#: +msgctxt "#33201" +msgid "Specify solid Background Color instead of using media images" +msgstr "Especifique uma cor de fundo sólida em vez de utilizar imagens de média" + +#: +msgctxt "#33400" +msgid "Use old compatibility profile" +msgstr "Utilize o perfil de compatibilidade antigo" + +#: +msgctxt "#33401" +msgid "Uses the Chrome client profile instead of the custom one. Might fix rare issues with 3D playback." +msgstr "Utiliza o perfil do cliente Chrome em vez do perfil personalizado. Pode corrigir problemas raros com a reprodução 3D." + +#: +msgctxt "#32031" +msgid "Burn-in Subtitles" +msgstr "Legendas Burn-in" + +#: +msgctxt "#32061" +msgid "When transcoding audio, target the audio channels set in Kodi." +msgstr "Ao transcodificar o áudio, direcione os canais de áudio definidos no Kodi." + +#: +msgctxt "#32062" +msgid "Transcode audio to AC3" +msgstr "Transcodificar áudio para AC3" + +#: +msgctxt "#32063" +msgid "Transcode audio to AC3 in certain conditions (useful for passthrough)." +msgstr "Transcodifique o áudio para AC3 em determinadas condições (útil para passthrough)." + +#: +msgctxt "#32065" +msgid "When any of the force AC3 settings are enabled, treat DTS the same as AC3 (useful for Optical passthrough)" +msgstr "Quando qualquer uma das definições de forçar AC3 estiver ativada, trate o DTS da mesma forma que o AC3 (útil para Optical passthrough)" + +#: +msgctxt "#32066" +msgid "Force audio to AC3" +msgstr "Forçar áudio para AC3" + +#: +msgctxt "#32067" +msgid "Only force multichannel audio to AC3" +msgstr "Forçar apenas o áudio multicanal para AC3" + +#: +msgctxt "#32493" +msgid "When a media file has a forced/foreign subtitle for a subtitle-enabled language, the Plex Media Server preselects it. This behaviour is usually not necessary and not configurable. This setting fixes that by ignoring the PMSs decision and selecting the same language without a forced flag if possible." +msgstr "Quando um ficheiro de média tem uma legenda forçada/estrangeira para um idioma com legendas, o Plex Media Server pré-seleciona-o. Este comportamento geralmente não é necessário e não é configurável. Esta configuração corrige isto ignorando a decisão do PMS e selecionando o mesmo idioma sem um sinalizador forçado, se possível." + +#: +msgctxt "#32523" +msgid "Automatically skip intros if available. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "Ignore as introduções automaticamente, se disponíveis. Não substitui o modo binge ativado.\n" +"Pode ser desativado/ativado por programa de TV." + +#: +msgctxt "#32527" +msgid "Automatically skip credits if available. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "Ignore os créditos automaticamente, se disponíveis. Não substitui o modo binge ativado.\n" +"Pode ser desativado/ativado por programa de TV." + +#: +msgctxt "#33501" +msgid "Video played threshold" +msgstr "" + +#: +msgctxt "#33502" +msgid "Set this to the same value as your Plex server (Settings>Library>Video played threshold) to avoid certain pitfalls, Default: 90 %" +msgstr "" + +#: +msgctxt "#33503" +msgid "Use alternative hubs refresh" +msgstr "" + +#: +msgctxt "#33504" +msgid "Refreshes all hubs for all libraries after an item's watch-state has changed, instead of only those likely affected. Use this if you find a hub that doesn't update properly." +msgstr "Atualiza todos os hubs de todas as bibliotecas após a alteração do estado de observação de um item, em vez de apenas aqueles provavelmente afetados. Utilize isto se encontrar um hub que não atualiza corretamente." + +#: +msgctxt "#33505" +msgid "Show intro skip button early" +msgstr "Mostrar botão de saltar introdução antecipadamente" + +#: +msgctxt "#33507" +msgid "Enabled" +msgstr "Activado" + +#: +msgctxt "#33508" +msgid "Disabled" +msgstr "Desactivado" + +#: +msgctxt "#33510" +msgid "When showing the intro skip button early, only do so if the intro occurs within the first X seconds." +msgstr "" + +#: +msgctxt "#33600" +msgid "System" +msgstr "" + +#: +msgctxt "#33601" +msgid "Show video chapters" +msgstr "" + +#: +msgctxt "#33602" +msgid "If available, show video chapters from the video-file instead of the timeline-big-seek-steps." +msgstr "" + +#: +msgctxt "#33603" +msgid "Use virtual chapters" +msgstr "" + +#: +msgctxt "#33604" +msgid "When the above is enabled and no video chapters are available, simulate them by using the markers identified by the Plex Server (Intro, Credits)." +msgstr "" + +#: +msgctxt "#33605" +msgid "Video Chapters" +msgstr "" + +#: +msgctxt "#33606" +msgid "Virtual Chapters" +msgstr "" + +#: +msgctxt "#33607" +msgid "Chapter {}" +msgstr "" + +#: +msgctxt "#33608" +msgid "Intro" +msgstr "" + +#: +msgctxt "#33609" +msgid "Credits" +msgstr "" + +#: +msgctxt "#33610" +msgid "Main" +msgstr "" + +#: +msgctxt "#33611" +msgid "Chapters" +msgstr "" + +#: +msgctxt "#33612" +msgid "Markers" +msgstr "" + +#: +msgctxt "#33613" +msgid "Kodi Buffer Size (MB)" +msgstr "" + +#: +msgctxt "#33614" +msgid "Set the Kodi Cache/Buffer size. Free: {} MB, Recommended: ~50 MB, Recommended max: {} MB, Default: 20 MB." +msgstr "" + +#: +msgctxt "#33615" +msgid "{time} left" +msgstr "{time} restante" + +#: +msgctxt "#33616" +msgid "Addon Path" +msgstr "" + +#: +msgctxt "#33617" +msgid "Userdata/Profile Path" +msgstr "" + +#: +msgctxt "#33618" +msgid "TV binge-viewing mode" +msgstr "" + +#: +msgctxt "#33622" +msgid "LAN reachability timeout (ms)" +msgstr "" + +#: +msgctxt "#33623" +msgid "When checking for LAN reachability, use this timeout. Default: 10ms" +msgstr "" + +#: +msgctxt "#33624" +msgid "Network" +msgstr "" + +#: +msgctxt "#33625" +msgid "Smart LAN/local server discovery" +msgstr "" + +#: +msgctxt "#33626" +msgid "Checks whether servers returned from Plex.tv are actually local/in your LAN. For specific setups (e.g. Docker) Plex.tv might not properly detect a local server.\n" +"\n" +"NOTE: Only works on Kodi 19 or above." +msgstr "" + +#: +msgctxt "#33627" +msgid "Prefer LAN/local servers over security" +msgstr "" + +#: +msgctxt "#33628" +msgid "Prioritizes local connections over secure ones. Needs the proper setting in \"Allow Insecure Connections\" and the Plex Server's \"Secure connections\" at \"Preferred\". Can be used to enforce manual servers." +msgstr "" + +#: +msgctxt "#33629" +msgid "Auto-skip intro/credits offset" +msgstr "" + +#: +msgctxt "#33630" +msgid "Intro/credits markers might be a little early in Plex. When auto skipping add (or subtract) this many seconds from the marker. This avoids cutting off content, while possibly skipping the marker a little late." +msgstr "" + +#: +msgctxt "#32631" +msgid "Playback (user-specific)" +msgstr "" + +#: +msgctxt "#33632" +msgid "Server connectivity check timeout (seconds)" +msgstr "" + +#: +msgctxt "#33633" +msgid "Set the maximum amount of time a server connection has to answer a connectivity request. Default: 2.5" +msgstr "" + +#: +msgctxt "#33634" +msgid "Combined Chapters" +msgstr "" + +#: +msgctxt "#33635" +msgid "Final Credits" +msgstr "" + +#: +msgctxt "#32700" +msgid "Action on Sleep event" +msgstr "" + +#: +msgctxt "#32701" +msgid "When Kodi receives a sleep event from the system, run the following action." +msgstr "" + +#: +msgctxt "#32702" +msgid "Nothing" +msgstr "" + +#: +msgctxt "#32703" +msgid "Stop playback" +msgstr "" + +#: +msgctxt "#32704" +msgid "Quit Kodi" +msgstr "" + +#: +msgctxt "#32705" +msgid "CEC Standby" +msgstr "" + +#: +msgctxt "#32800" +msgid "Skipping intro" +msgstr "" + +#: +msgctxt "#32801" +msgid "Skipping credits" +msgstr "" + +#: +msgctxt "#32900" +msgid "While playing back an item and seeking on the seekbar, automatically seek to the selected position after a delay instead of having to confirm the selection." +msgstr "" + +#: +msgctxt "#32901" +msgid "Seek delay in seconds." +msgstr "" + +#: +msgctxt "#32902" +msgid "Kodi has its own skip step settings. Try to use them if they're configured instead of the default ones." +msgstr "" + +#: +msgctxt "#32903" +msgid "Use the above for seeking on the timeline as well." +msgstr "" + +#: +msgctxt "#32904" +msgid "In seconds." +msgstr "" + +#: +msgctxt "#32905" +msgid "Cancel post-play timer by pressing OK/SELECT" +msgstr "" + +#: +msgctxt "#32906" +msgid "Cancel skip marker timer with BACK" +msgstr "" + +#: +msgctxt "#32907" +msgid "When auto-skipping a marker, allow cancelling the timer by pressing BACK." +msgstr "" + +#: +msgctxt "#32908" +msgid "Immediately skip marker with OK/SELECT" +msgstr "" + +#: +msgctxt "#32909" +msgid "When auto-skipping a marker with a timer, allow skipping immediately by pressing OK/SELECT." +msgstr "" + +#: +msgctxt "#32912" +msgid "Show buffer-state on timeline" +msgstr "" + +#: +msgctxt "#32913" +msgid "Shows the current Kodi buffer/cache state on the video player timeline." +msgstr "" + +#: +msgctxt "#32914" +msgid "Loading" +msgstr "" + +#: +msgctxt "#32915" +msgid "Slow connection" +msgstr "" + +#: +msgctxt "#32916" +msgid "Use with a wonky/slow connection, e.g. in a hotel room. Adjusts the UI to visually wait for item refreshes and waits for the buffer to fill when starting playback. Automatically sets readfactor=20, requires Kodi restart." +msgstr "" + +#: +msgctxt "#32917" +msgid "Couldn't fill buffer in time ({}s)" +msgstr "" + +#: +msgctxt "#32918" +msgid "Buffer wait timeout (seconds)" +msgstr "" + +#: +msgctxt "#32919" +msgid "When slow connection is enabled in the addon, wait this long for the buffer to fill. Default: 120 s" +msgstr "" + +#: +msgctxt "#32920" +msgid "Insufficient buffer wait (seconds)" +msgstr "" + +#: +msgctxt "#32921" +msgid "When slow connection is enabled in the addon and the configured buffer isn't big enough for us to determine its fill state, wait this long when starting playback. Default: 10 s" +msgstr "" + +#: +msgctxt "#32922" +msgid "Kodi Cache Readfactor" +msgstr "" + +#: +msgctxt "#32923" +msgid "Sets the Kodi cache readfactor value. Default: {0}, recommended: {1}. With \"Slow connection\" enabled this will be set to {2}, as otherwise the cache doesn't fill fast/aggressively enough." +msgstr "" + +#: +msgctxt "#32924" +msgid "Minimize" +msgstr "Minimizar" + +#: +msgctxt "#32925" +msgid "Playback Settings" +msgstr "" + +#: +msgctxt "#32926" +msgid "Wrong pin entered!" +msgstr "" + +#: +msgctxt "#32927" +msgid "Use episode thumbnails in continue hub" +msgstr "" + +#: +msgctxt "#32928" +msgid "Instead of using media artwork, use thumbnails for episodes in the continue hub on the home screen if available." +msgstr "" + +#: +msgctxt "#32929" +msgid "Use legacy background fallback image" +msgstr "" + +#: +msgctxt "#32930" +msgid "Previous Subtitle" +msgstr "" + +#: +msgctxt "#32931" +msgid "Audio/Subtitles" +msgstr "" + +#: +msgctxt "#32936" +msgid "Show playlist button" +msgstr "" + +#: +msgctxt "#32937" +msgid "Show prev/next button" +msgstr "" + +#: +msgctxt "#32941" +msgid "Forced subtitles fix" +msgstr "" + +#: +msgctxt "#32942" +msgid "Other seasons" +msgstr "" + +#: +msgctxt "#32943" +msgid "Crossfade dynamic background art" +msgstr "" + +#: +msgctxt "#32944" +msgid "Burn-in SSA subtitles (DirectStream)" +msgstr "" + +#: +msgctxt "#32945" +msgid "When Direct Streaming instruct the Plex Server to burn in SSA/ASS subtitles (thus transcoding the video stream). If disabled it will not touch the video stream, but will convert the subtitle to unstyled text." +msgstr "" + +#: +msgctxt "#32946" +msgid "Stop video playback on idle after" +msgstr "" + +#: +msgctxt "#32947" +msgid "Stop video playback on screensaver" +msgstr "" + +#: +msgctxt "#32948" +msgid "Allow auto-skip when transcoding" +msgstr "" + +#: +msgctxt "#32949" +msgid "When transcoding/DirectStreaming, allow auto-skip functionality." +msgstr "" + +#: +msgctxt "#32950" +msgid "Use extended title for subtitles" +msgstr "" + +#: +msgctxt "#32951" +msgid "When displaying subtitles use the extendedDisplayTitle Plex exposes." +msgstr "" + +#: +msgctxt "#32953" +msgid "Reviews" +msgstr "" + +#: +msgctxt "#32954" +msgid "Needs Kodi restart. WARNING: This will overwrite advancedsettings.xml!\n" +"\n" +"To customize other cache/network-related values, copy \"script.plexmod/pm4k_cache_template.xml\" to profile folder and edit it to your liking. (See About section for the file paths)" +msgstr "" + +#: +msgctxt "#32955" +msgid "Use Kodi keyboard for searching" +msgstr "" + +#: +msgctxt "#32956" +msgid "Poster resolution scaling %" +msgstr "" + +#: +msgctxt "#32957" +msgid "In percent. Scales the resolution of all posters/thumbnails for better image quality. May impact PMS/PM4K performance, will increase the cache usage accordingly. Recommended: 200-300 % for for big screens if your hardware can handle it. Needs addon restart." +msgstr "" + +#: +msgctxt "#32958" +msgid "Calculate OpenSubtitles.com hash" +msgstr "" + +#: +msgctxt "#32959" +msgid "When opening the subtitle download feature, automatically calculate the OpenSubtitles.com hash for the given file. Can improve search results, downloads 2*64 KB of the video file to calculate the hash." +msgstr "" + +#: +msgctxt "#32960" +msgid "Similar Artists" +msgstr "" + +#: +msgctxt "#32961" +msgid "Show hub bifurcation lines" +msgstr "" + +#: +msgctxt "#32962" +msgid "Visually separate hubs horizontally using a thin line." +msgstr "" + +#: +msgctxt "#32963" +msgid "Wait between videos (s)" +msgstr "" + +#: +msgctxt "#32964" +msgid "When playing back consecutive videos (e.g. TV shows), wait this long before starting the next one in the queue. Might fix compatibility issues with certain configurations." +msgstr "" + +#: +msgctxt "#32965" +msgid "Quit Kodi on exit by default" +msgstr "" + +#: +msgctxt "#32966" +msgid "When exiting the addon, use \"Quit Kodi\" as default option. Can be dynamically switched using CONTEXT_MENU (often longpress SELECT)" +msgstr "" + +#: +msgctxt "#32967" +msgid "Kodi Colour Management" +msgstr "" + +#: +msgctxt "#32968" +msgid "Kodi Resolution Settings" +msgstr "" + +#: +msgctxt "#32969" +msgid "Always request all library media items at once" +msgstr "" + +#: +msgctxt "#32970" +msgid "Retrieve all media in library up front instead of fetching it in chunks as the user navigates through the library" +msgstr "" + +#: +msgctxt "#32971" +msgid "Library item-request chunk size" +msgstr "" + +#: +msgctxt "#32972" +msgid "Request this amount of media items per chunk request in library view (+6-30 depending on view mode; less can be less straining for the UI at first, but puts more strain on the server)" +msgstr "" + +#: +msgctxt "#32973" +msgid "Episodes: Skip Post Play screen" +msgstr "" + +#: +msgctxt "#32974" +msgid "When finishing an episode, don't show Post Play but go to the next one immediately.\n" +"Can be disabled/enabled per TV show. Doesn't override enabled binge mode. Overrides the Post Play setting." +msgstr "" + +#: +msgctxt "#32975" +msgid "Delete Season" +msgstr "" + +#: +msgctxt "#32976" +msgid "Adaptive" +msgstr "" + +#: +msgctxt "#32978" +msgid "Enable this if your hardware can handle VC1. Disable it to force transcoding." +msgstr "" + +#: +msgctxt "#32979" +msgid "Allows the server to only transcode streams of a video that need transcoding, while streaming the others unaltered. If disabled, force the server to transcode everything not direct playable." +msgstr "" + +#: +msgctxt "#32980" +msgid "Refresh Users" +msgstr "Atualizar Utilizadores" + +#: +msgctxt "#32981" +msgid "Background worker count" +msgstr "" + +#: +msgctxt "#32982" +msgid "Depending on how many cores your CPU has and how much it can handle, increasing this might improve certain situations. If you experience crashes or other annormalities, leave this at its default (3). Needs an addon restart." +msgstr "" + +#: +msgctxt "#32985" +msgid "Modern" +msgstr "" + +#: +msgctxt "#32986" +msgid "Modern (dotted)" +msgstr "" + +#: +msgctxt "#32987" +msgid "Classic" +msgstr "" + +#: +msgctxt "#32988" +msgid "Custom" +msgstr "" + +#: +msgctxt "#32989" +msgid "Modern (colored)" +msgstr "" + +#: +msgctxt "#32990" +msgid "Handle plex.direct mapping" +msgstr "" + +#: +msgctxt "#32991" +msgid "Notify" +msgstr "" + +#: +msgctxt "#32992" +msgid "When using servers with a plex.direct connection (most of them), should we automatically adjust advancedsettings.xml to cope with plex.direct domains? If not, you might want to add plex.direct to your router's DNS rebind exemption list." +msgstr "" + +#: +msgctxt "#32993" +msgid "{} unhandled plex.direct connections found" +msgstr "" + +#: +msgctxt "#32994" +msgid "In order for PM4K to work properly, we need to add special handling for plex.direct connections. We've found {} new unhandled connections. Do you want us to write those to Kodi's advancedsettings.xml automatically? If not, you might want to add plex.direct to your router's DNS rebind exemption list. This can be changed in the settings as well." +msgstr "" + +#: +msgctxt "#32995" +msgid "Advancedsettings.xml modified (plex.direct mappings)" +msgstr "" + +#: +msgctxt "#32996" +msgid "The advancedsettings.xml file has been modified. Please restart Kodi for the changes to apply." +msgstr "" + +#: +msgctxt "#32997" +msgid "OK" +msgstr "" + +#: +msgctxt "#32998" +msgid "Use new Continue Watching hub on Home" +msgstr "" + +#: +msgctxt "#32999" +msgid "Instead of separating Continue Watching and On Deck hubs, behave like the modern Plex clients, which combine those two types of hubs into one Continue Watching hub." +msgstr "" + +#: +msgctxt "#33000" +msgid "Enable path mapping" +msgstr "" + +#: +msgctxt "#33002" +msgid "Verify mapped files exist" +msgstr "" + +#: +msgctxt "#33003" +msgid "When path mapping is enabled and we've successfully mapped a file, verify its existence." +msgstr "" + +#: +msgctxt "#33004" +msgid "No spoilers without OSD" +msgstr "" + +#: +msgctxt "#33005" +msgid "When seeking without the OSD open, hide all time-related information from the user." +msgstr "" + +#: +msgctxt "#33006" +msgid "No TV spoilers" +msgstr "" + +#: +msgctxt "#33008" +msgid "[Spoilers removed]" +msgstr "" + +#: +msgctxt "#33013" +msgid "When the above is anything but \"off\", hide episode titles as well." +msgstr "" + +#: +msgctxt "#33014" +msgid "Ignore plex.direct docker hosts" +msgstr "" + +#: +msgctxt "#33015" +msgid "When checking for plex.direct host mapping, ignore local Docker IPv4 addresses (172.16.0.0/12)." +msgstr "" + +#: +msgctxt "#32303" +msgid "Season {}" +msgstr "" + +#: +msgctxt "#32304" +msgid "Episode {}" +msgstr "" + +#: +msgctxt "#32310" +msgid "S{}" +msgstr "" + +#: +msgctxt "#32311" +msgid "E{}" +msgstr "" + +#: +msgctxt "#32938" +msgid "Only for Episodes/Playlists" +msgstr "" + +#: +msgctxt "#33018" +msgid "Cache Plex Home users" +msgstr "" + +#: +msgctxt "#33019" +msgid "Visit media item" +msgstr "" + +#: +msgctxt "#33020" +msgid "Play" +msgstr "" + +#: +msgctxt "#33021" +msgid "Choose action" +msgstr "" + +#: +msgctxt "#33026" +msgid "Map path: {}" +msgstr "" + +#: +msgctxt "#33027" +msgid "Remove mapping: {}" +msgstr "" + +#: +msgctxt "#33028" +msgid "Hide library" +msgstr "" + +#: +msgctxt "#33029" +msgid "Show library: {}" +msgstr "" + +#: +msgctxt "#33030" +msgid "Choose action for: {}" +msgstr "" + +#: +msgctxt "#33031" +msgid "Select Kodi source for {}" +msgstr "" + +#: +msgctxt "#33032" +msgid "Show path mapping indicators" +msgstr "" + +#: +msgctxt "#33033" +msgid "When path mapping is active for a library, display an indicator." +msgstr "" + +#: +msgctxt "#33035" +msgid "Delete {}: {}?" +msgstr "" + +#: +msgctxt "#33036" +msgid "Delete episode S{0:02d}E{1:02d} from {2}?" +msgstr "" + +#: +msgctxt "#33037" +msgid "Maximum intro offset to consider" +msgstr "" + +#: +msgctxt "#33039" +msgid "Move" +msgstr "" + +#: +msgctxt "#33040" +msgid "Reset library order" +msgstr "" + +#: +msgctxt "#33034" +msgid "Library settings" +msgstr "" + +#: +msgctxt "#32357" +msgid "By Title" +msgstr "" + +#: +msgctxt "#32358" +msgid "Title" +msgstr "" + +#: +msgctxt "#33041" +msgid "Show hub: {}" +msgstr "" + +#: +msgctxt "#33042" +msgid "Episode Date Added" +msgstr "" + +#: +msgctxt "#33001" +msgid "Long-press (or context menu) on a library item or: Honor path_mapping.json in the addon_data/script.plexmod folder when DirectPlaying media. This can be used to stream using other techniques such as SMB/NFS/etc. instead of the default HTTP handler. path_mapping.example.json is included in the addon's main directory." +msgstr "" + +#: +msgctxt "#33043" +msgid "Hubs round-robin" +msgstr "" + +#: +msgctxt "#33045" +msgid "Behave like official Plex clients" +msgstr "" + +#: +msgctxt "#32025" +msgid "Direct Play" +msgstr "" + +#: +msgctxt "#32026" +msgid "Direct Stream" +msgstr "" + +#: +msgctxt "#32036" +msgid "4K" +msgstr "" + +#: +msgctxt "#32037" +msgid "HEVC (h265)" +msgstr "" + +#: +msgctxt "#32601" +msgid "AV1" +msgstr "" + +#: +msgctxt "#32932" +msgid "Subtitle quick-actions" +msgstr "" + +#: +msgctxt "#32933" +msgid "FFWD/RWD" +msgstr "" + +#: +msgctxt "#32934" +msgid "Repeat" +msgstr "" + +#: +msgctxt "#32935" +msgid "Shuffle" +msgstr "" + +#: +msgctxt "#32939" +msgid "User-specific.\n" +"Only applies to video player UI" +msgstr "" + +#: +msgctxt "#32977" +msgid "VC1" +msgstr "" + +#: +msgctxt "#32983" +msgid "Theme" +msgstr "" + +#: +msgctxt "#32984" +msgid "Sets the theme. Currently only customizes all control buttons. ATTENTION: [I]Might[/I] need an addon restart.\n" +"In order to customize this, copy one of the xml's in script.plexmod/resources/skins/Main/1080i/templates to addon_data/script.plexmod/templates/{templatename}_custom.xml and adjust it to your liking, then select \"Custom\" as your theme." +msgstr "" + +#: +msgctxt "#33011" +msgid "In progress" +msgstr "" + +#: +msgctxt "#33016" +msgid "Allow TV spoilers for" +msgstr "" + +#: +msgctxt "#33017" +msgid "Overrides the above for specific genres. Default: Reality, Game Show, Documentary, Sport" +msgstr "" + +#: +msgctxt "#33024" +msgid "Hide background in modern indicators" +msgstr "" + +#: +msgctxt "#33044" +msgid "Allow round-robining in hubs. Attention: Loads a lot of items. Depending on the hub size, this can lead to crashes. Current limit: {}; can be adjusted in addon settings." +msgstr "" + +#: +msgctxt "#33046" +msgid "When pressing up/down while the OSD isn't shown, show OSD instead of skipping chapters. Additionally, when pressing down while the OSD is shown, open the chapters (if available)." +msgstr "" + +#: +msgctxt "#33047" +msgid "Hubs round-robin item limit" +msgstr "" + +#: +msgctxt "#33048" +msgid "When hubs round-robin is enabled, only round-robin until this item limit. If the hub size exceeds this limit, the remaining items will be lazy loaded as usual. Tested minimum safe value on NVIDIA SHIELD 2019 is 1000. Default: 250" +msgstr "" + +#: +msgctxt "#33049" +msgid "Max retries" +msgstr "" + +#: +msgctxt "#33051" +msgid "Use CA certificate bundle" +msgstr "" + +#: +msgctxt "#33053" +msgid "System" +msgstr "" + +#: +msgctxt "#33055" +msgid "Custom" +msgstr "" + +#: +msgctxt "#33056" +msgid "None" +msgstr "" + +#: +msgctxt "#33057" +msgid "Show buttons" +msgstr "" + +#: +msgctxt "#33058" +msgid "Playback features" +msgstr "" + +#: +msgctxt "#33059" +msgid "Additional codecs" +msgstr "" + +#: +msgctxt "#33060" +msgid "{feature_ds}: {desc_ds}\n" +"{feature_4k}: {desc_4k}" +msgstr "" + +#: +msgctxt "#33061" +msgid "Enable certain codecs if your hardware supports them. Disable them to force transcoding." +msgstr "" + +#: +msgctxt "#33062" +msgid "Compiling templates" +msgstr "" + +#: +msgctxt "#33063" +msgid "Looking for custom templates" +msgstr "" + +#: +msgctxt "#33064" +msgid "Rendering: {}" +msgstr "" + +#: +msgctxt "#33065" +msgid "Complete" +msgstr "" + +#: +msgctxt "#33066" +msgid "Cache template files" +msgstr "" + +#: +msgctxt "#33067" +msgid "Doesn't throw away the template source files after compiling them. Uses slightly more memory but increases the speed of theme-related changes." +msgstr "" + +#: +msgctxt "#33068" +msgid "Always compile templates" +msgstr "" + +#: +msgctxt "#33069" +msgid "Recompiles all templates on every startup. Useful for template/theme development." +msgstr "" + +#: +msgctxt "#33070" +msgid "Action on Wake event" +msgstr "" + +#: +msgctxt "#33071" +msgid "Restart PM4K" +msgstr "" + +#: +msgctxt "#33072" +msgid "Wait for {}s" +msgstr "" + +#: +msgctxt "#33073" +msgid "Wait after wakeup" +msgstr "" + +#: +msgctxt "#33074" +msgid "Waiting {} second(s)" +msgstr "" + +#: +msgctxt "#33076" +msgid "Modern (2024)" +msgstr "" + +#: +msgctxt "#33077" +msgid "Scale modern indicators" +msgstr "" + +#: +msgctxt "#33078" +msgid "Scale the modern indicators based on the poster size used. Default: On (tiny: 0.75, small: 1.0, medium: 1.175, big: 1.3)" +msgstr "" + +#: +msgctxt "#33079" +msgid "Hi-Res Music" +msgstr "" + +#: +msgctxt "#33080" +msgid "Allow DirectPlay of high resolution music (e.g. FLAC, >= 192 kHz)" +msgstr "" + +#: +msgctxt "#33081" +msgid "Blur chapter images" +msgstr "" + +#: +msgctxt "#33082" +msgid "Scan Library Files" +msgstr "" + +#: +msgctxt "#33083" +msgid "Empty Trash" +msgstr "" + +#: +msgctxt "#33084" +msgid "Analyze" +msgstr "" + +#: +msgctxt "#33085" +msgid "Map key to home" +msgstr "" + +#: +msgctxt "#33086" +msgid "Press the key you want to map to go to home within {} seconds" +msgstr "" + +#: +msgctxt "#33620" +msgid "Plex server connect timeout" +msgstr "" + +#: +msgctxt "#33621" +msgid "Sets the maximum amount of time to connect to a Plex Server in seconds. Default: 5" +msgstr "" + +#: +msgctxt "#32940" +msgid "Video Player" +msgstr "" + +#: +msgctxt "#33050" +msgid "The maximum number of retries each connection should attempt. The default (3) helps with hibernation/wakeup issues. Set higher for bad connections, setting this to 0 was the old default before 0.7.9" +msgstr "" + +#: +msgctxt "#33075" +msgid "Do something after waking up. Waiting for a time until resuming normal execution helps with networks coming back up after sleep events. Default: 1 second (5 seconds, CoreELEC)." +msgstr "" + +#: +msgctxt "#33087" +msgid "Bind a key to immediately go to the Home view. Cancel using BACK/PREVIOUS_MENU. Clear bound key using the STOP or CONTEXT_MENU (long press OK/Enter) button on the setting." +msgstr "" + +#: +msgctxt "#33088" +msgid "Only applies to video player UI" +msgstr "" + +#: +msgctxt "#33089" +msgid "Automatic seek-back" +msgstr "" + +#: +msgctxt "#33090" +msgid "If your audio doesn't resume as fast as the video, or you want to catch up after a longer pause, use this to seek back after resuming from pause to compensate for the delay. Default: Off" +msgstr "" + +#: +msgctxt "#33091" +msgid "{sec_or_ms} {unit_s_or_ms}" +msgstr "" + +#: +msgctxt "#33092" +msgid "Seek back on pause" +msgstr "" + +#: +msgctxt "#33093" +msgid "Seek back after" +msgstr "" + +#: +msgctxt "#33094" +msgid "Only seek back after having paused at least a certain amount of seconds" +msgstr "" + +#: +msgctxt "#33095" +msgid "Seek back on pause instead of on resume. When Transcoding you should enable this." +msgstr "" + +#: +msgctxt "#33096" +msgid "Only with Direct Play" +msgstr "" + +#: +msgctxt "#33097" +msgid "Enable seek back ony when we're Direct Playing, not Transcoding." +msgstr "" + +#: +msgctxt "#33098" +msgid "Tickrate (Hz)" +msgstr "" + +#: +msgctxt "#33099" +msgid "Controls how often certain ticks are performed on GUI windows and the SeekDialog/VideoPlayer and how fast certain events are handled. Can be expensive when bigger than 1 Hz (once per second), can cause quirks when lower than 1 Hz (less than once per second). Depends on the hardware. Default: 1 Hz, Max: 10 Hz (every 100 ms), Sane highest and old default: 10 Hz (every 100 ms)" +msgstr "" + +#: +msgctxt "#33636" +msgid "Plex server read timeout" +msgstr "" + +#: +msgctxt "#33637" +msgid "Sets the maximum amount of time to read from a Plex Server in seconds. Default: 10" +msgstr "" + +#: +msgctxt "#33638" +msgid "Plex.tv connect timeout" +msgstr "" + +#: +msgctxt "#33640" +msgid "Plex.tv read timeout" +msgstr "" + +#: +msgctxt "#33642" +msgid "Dump config" +msgstr "" + +#: +msgctxt "#33643" +msgid "Dumps all user settings into the log on startup, when DEBUG logging is enabled. Masks private information." +msgstr "" + +#: +msgctxt "#33644" +msgid "Tracks" +msgstr "" + +#: +msgctxt "#33645" +msgid "plex.direct: Honor plex.tv's dnsRebindingProtection flag (DNS)" +msgstr "" + +#: +msgctxt "#33646" +msgid "Only handle plex.direct hosts when the server's attributes dnsRebindingProtection=1. Might not work in certain situations. Disable if you have connection issues. Default: On" +msgstr "" + +#: +msgctxt "#33647" +msgid "plex.direct: Honor plex.tv's publicAddressMatches flag (DNS)" +msgstr "" + +#: +msgctxt "#33648" +msgid "Only handle plex.direct hosts when the server's attributes publicAddressMatches=1. Might not work in certain situations. Disable if you have connection issues. Default: On" +msgstr "" + +#: +msgctxt "#32101" +msgid "If enabled, when playback ends and there is a 'Next Up' item available, it will be automatically be played after a {} second delay." +msgstr "" + +#: +msgctxt "#33651" +msgid "Startup delay" +msgstr "" + +#: +msgctxt "#33652" +msgid "Never show Post Play" +msgstr "" + +#: +msgctxt "#33506" +msgid "Show the intro skip button from the start of a video with an intro marker. The auto-skipping setting applies. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "" + +#: +msgctxt "#33619" +msgid "Automatically skips episode intros, credits and tries to skip episode recaps. Doesn't skip the intro of the first episode of a season and doesn't skip the final credits of a show.\n" +"\n" +"Can be disabled/enabled per TV show.\n" +"Overrides any setting below." +msgstr "" + +#: +msgctxt "#33052" +msgid "Which CA certificate bundle to use for request HTTPS verification. \"system\" uses the system-provided certificate bundle. \"ACME\" uses an extremely small bundle provided with the addon, which includes root certificates for Letsencrypt (plex.direct) and ZeroSSL. \"custom\" allows for a ca-certificate bundle in userdata/addon_data/script.plexmod/custom_bundle.crt" +msgstr "" + +#: +msgctxt "#33054" +msgid "ACME (addon-supplied)" +msgstr "" + +#: +msgctxt "#33639" +msgid "Plex.tv: Sets the maximum amount of time to connect to Plex.tv in seconds. Default: 1" +msgstr "" + +#: +msgctxt "#33641" +msgid "Plex.tv: Sets the maximum amount of time to read from Plex.tv in seconds. Default: 2" +msgstr "" + +#: +msgctxt "#33653" +msgid "Background image resolution scaling %" +msgstr "" + +#: +msgctxt "#33654" +msgid "In percent, based on 1080p. Scales the resolution of all backgrounds for better image quality. May impact PMS/PM4K performance, will increase the cache usage accordingly. Can lead to crashes if your hardware is low on RAM. Needs addon restart. Use 400% for 4K. ATTENTION: In order for this to work you need to add 2160 and 2160 to your advancedsettings.xml." +msgstr "" + +#: +msgctxt "#33655" +msgid "Auto-Sync Subtitles" +msgstr "" + +#: +msgctxt "#33656" +msgid "Only for External SRT subtitles. The PMS setting for voice activity detection has to be enabled for this to work." +msgstr "" + +#: in player quick settings UI, needs to be short +msgctxt "#33657" +msgid "Enable Auto-Sync" +msgstr "" + +#: in player quick settings UI, needs to be short +msgctxt "#33658" +msgid "Disable Auto-Sync" +msgstr "" + +#: +msgctxt "#33659" +msgid "Hide hub: {}" +msgstr "" + +#: +msgctxt "#33660" +msgid "Disable HDR" +msgstr "" + +#: +msgctxt "#33661" +msgid "If you don't want your client to handle HDR (or HDR-fallback), enable this to force transcoding. Doesn't apply to DV Profile 5." +msgstr "" + +#: +msgctxt "#33662" +msgid "Remove from Continue Watching" +msgstr "" + +#: +msgctxt "#33663" +msgid "Home: Confirm item actions" +msgstr "" + +#: +msgctxt "#33664" +msgid "When acting on items in the Home view, such as mark played, hide from continue watching etc., show a confirmation dialog." +msgstr "" + +#: +msgctxt "#33665" +msgid "Disable audio codecs" +msgstr "" + +#: +msgctxt "#33666" +msgid "Audio codecs you can't play back. Disables Direct Play for such media items, enables Direct Stream if possible, transcodes audio stream to compatible format." +msgstr "" + +#: +msgctxt "#32002" +msgid "20 Mbps" +msgstr "20 Mbps" + +#: +msgctxt "#32003" +msgid "12 Mbps" +msgstr "12 Mbps" + +#: +msgctxt "#32004" +msgid "10 Mbps" +msgstr "10 Mbps" + +#: +msgctxt "#32005" +msgid "8 Mbps" +msgstr "8 Mbps" + +#: +msgctxt "#32006" +msgid "4 Mbps" +msgstr "4 Mbps" + +#: +msgctxt "#32007" +msgid "3 Mbps" +msgstr "3 Mbps" + +#: +msgctxt "#32008" +msgid "2 Mbps" +msgstr "2 Mbps" + +#: +msgctxt "#32009" +msgid "1.5 Mbps" +msgstr "1.5 Mbps" + +#: +msgctxt "#33669" +msgid "Really sign out?" +msgstr "" + +#: +msgctxt "#33670" +msgid "Update available" +msgstr "" + +#: +msgctxt "#33672" +msgid "Check for updates" +msgstr "" + +#: +msgctxt "#33673" +msgid "Automatically check for updates periodically. If installed from a Kodi repository and the Update Source setting is set to Repository, Kodi itself will handle the updating of this addon. Needs a Kodi restart when changed." +msgstr "" + +#: +msgctxt "#33674" +msgid "Check for updates on start" +msgstr "" + +#: +msgctxt "#33675" +msgid "Automatically check for updates on startup. Doesn't do much when Update source is Repository. Needs a Kodi restart when changed." +msgstr "" + +#: +msgctxt "#33676" +msgid "Update source" +msgstr "" + +#: +msgctxt "#33677" +msgid "Specifies the update mode. Will immediately check for a new version when changed and closing settings.\n" +"Default: Repository\n" +"\n" +"Beta: Bleeding edge (possibly unstable)\n" +"Stable: Stable branch (faster than Repository)\n" +"Repository: Kodi repository (official (slow) or Don't Panic)" +msgstr "" + +#: +msgctxt "#33678" +msgid "Beta" +msgstr "" + +#: Update mode as in (beta, stable, repository) +msgctxt "#33679" +msgid "Stable" +msgstr "" + +#: +msgctxt "#33680" +msgid "Repository" +msgstr "" + +#: +msgctxt "#33683" +msgid "Exit, download and install" +msgstr "" + +#: +msgctxt "#33684" +msgid "Later" +msgstr "" + +#: +msgctxt "#32015" +msgid "6 Mbps" +msgstr "" + +#: +msgctxt "#32016" +msgid "16 Mbps" +msgstr "" + +#: +msgctxt "#32017" +msgid "26 Mbps" +msgstr "" + +#: +msgctxt "#33681" +msgid "Service updated" +msgstr "" + +#: +msgctxt "#33682" +msgid "The update {} has had changes to the updater itself. In order for the updated updater service to work, a Kodi restart is necessary. The addon will work normally, though. Do you still want to run the addon?" +msgstr "" + +#: +msgctxt "#33685" +msgid "Clamp video bitrate" +msgstr "" + +#: +msgctxt "#33686" +msgid "Only show transcode bitrate targets lower than the current video's bitrate." +msgstr "" + +#: +msgctxt "#33687" +msgid "Translation updated" +msgstr "" + +#: +msgctxt "#33688" +msgid "The currently in-use translation has been updated. In order to load the new translation, a Kodi restart is necessary. The addon will still run properly, but you might see badly or untranslated strings. Do you still want to run the addon?" +msgstr "" + +#: +msgctxt "#33509" +msgid "Early intro skip threshold (default: < 120s/2m)" +msgstr "" + +#: +msgctxt "#33007" +msgid "Select in which cases to blur TV episode thumbnails, previews, redact summaries, hide episode titles etc." +msgstr "" + +#: +msgctxt "#33009" +msgid "Blur amount for unplayed/in-progress episodes" +msgstr "" + +#: +msgctxt "#33010" +msgid "Unplayed" +msgstr "" + +#: +msgctxt "#33012" +msgid "No unplayed episode titles" +msgstr "" + +#: +msgctxt "#33022" +msgid "Played indicators" +msgstr "" + +#: +msgctxt "#33023" +msgid "Classic: Show orange triangle for unplayed items\n" +"Modern: Show green checkmark for played items\n" +"Modern (2024): Show white checkmark for played items\n" +"(default: Modern (2024))" +msgstr "" + +#: +msgctxt "#33025" +msgid "When the above is enabled, hide the black backdrop of the played state." +msgstr "" + +#: +msgctxt "#33689" +msgid "Service running" +msgstr "" + +#: +msgctxt "#33690" +msgid "Last update check" +msgstr "" + +#: +msgctxt "#33691" +msgid "Native languages" +msgstr "" + +#: +msgctxt "#33692" +msgid "When you usually watch things in a different language with subtitles, but are a native speaker of other languages, which you don't need subtitles for, prevent Plex from auto-selecting subtitles for those languages." +msgstr "" + +#: +msgctxt "#33693" +msgid "Download subtitles using" +msgstr "" + +#: +msgctxt "#33694" +msgid "Ask" +msgstr "" + +#: +msgctxt "#33695" +msgid "Where do you want to download subtitles from? Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "" + +#: +msgctxt "#33696" +msgid "No subtitles found." +msgstr "" + +#: things in curly brackets are variables, don't translate them! +msgctxt "#33697" +msgid "{provider_title}, Score: {subtitle_score}{subtitle_info}" +msgstr "" + +#: hearing impaired subtitle flag +msgctxt "#33698" +msgid "HI" +msgstr "" + +#: forced subtitle flag +msgctxt "#33699" +msgid "forced" +msgstr "" + +#: +msgctxt "#33700" +msgid "Download subtitles: {}" +msgstr "" + +#: +msgctxt "#33701" +msgid "Fallback to Kodi" +msgstr "" + +#: +msgctxt "#33702" +msgid "When no subtitles are found via the Plex subtitle search, fall back to Kodi subtitle search. Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "" + +#: +msgctxt "#33703" +msgid "Download subtitles" +msgstr "" + +#: +msgctxt "#33704" +msgid "Using which service?" +msgstr "" + +#: +msgctxt "#33705" +msgid "Hide ratings" +msgstr "" + +#: +msgctxt "#33706" +msgid "Blur images" +msgstr "" + +#: +msgctxt "#33707" +msgid "Blur images on home (in progress)" +msgstr "" + +#: +msgctxt "#33708" +msgid "Hide summaries" +msgstr "" + +#: +msgctxt "#33709" +msgid "Show ratings for" +msgstr "" + +#: +msgctxt "#33710" +msgid "Show reviews for" +msgstr "" + +#: +msgctxt "#33711" +msgid "Always resume media" +msgstr "" + +#: +msgctxt "#33712" +msgid "When playback of an in-progress media is requested, resume it by default instead of asking whether to resume or start from the beginning." +msgstr "" + +#: +msgctxt "#33713" +msgid "Home: Resume in-progress items" +msgstr "" + +#: +msgctxt "#33714" +msgid "Resume in-progress items directly instead of visiting the media." +msgstr "" + +#: +msgctxt "#33715" +msgid "OSD hide-delay" +msgstr "" + +#: +msgctxt "#33716" +msgid "How long to wait until hiding the OSD. Only works with Plextuary skin or others with configurable OSD timeout. Default: 4s (+/- 1s)" +msgstr "" + +#: +msgctxt "#33717" +msgid "Debug requests" +msgstr "" + +#: +msgctxt "#33718" +msgid "Played" +msgstr "" + +#: +msgctxt "#33719" +msgid "Refresh metadata" +msgstr "" + +#: +msgctxt "#33038" +msgid "When encountering an intro marker with a start time offset greater than this, ignore it (default: 1400s/23m)" +msgstr "" + +#: +msgctxt "#33649" +msgid "\"Use alternate seek\" wait for seek" +msgstr "" + +#: +msgctxt "#33650" +msgid "This adjusts the delay between seek-tries on problematic platforms when \"Use alternate seek\" is enabled, which fixes resume not always working or double-seeking. When you have resume/double-seek issues, increase this. Default: 500ms" +msgstr "" + +#: +msgctxt "#33667" +msgid "Use alternate seek" +msgstr "" + +#: +msgctxt "#33668" +msgid "ATTENTION: Only enable this if you have reproducible audio issues after seeking/resuming.\n" +"\n" +"Use an alternative seek method in videos, which can help in problematic scenarios; brings its own issues/quirks. Disabled by default (enabled by default for CoreELEC and LG WebOS)" +msgstr "" + +#: +msgctxt "#33720" +msgid "Clear all caches" +msgstr "" + +#: +msgctxt "#33721" +msgid "Clear library cache (not items)" +msgstr "" + +#: +msgctxt "#33722" +msgid "Libraries" +msgstr "" + +#: +msgctxt "#33723" +msgid "Media Items" +msgstr "" + +#: +msgctxt "#33724" +msgid "Cache Plex data for" +msgstr "" + +#: +msgctxt "#33725" +msgid "Persist cached Plex data" +msgstr "" + +#: +msgctxt "#33726" +msgid "Instead of clearing the cache when exiting the addon, persist it instead. Warning: You'll most likely encounter missing items in libraries or outdated data. Use the corresponding menu functionalities to clear the cache for specific items or libraries." +msgstr "" + +#: +msgctxt "#33727" +msgid "Store Plex server responses for items and library views in a local SQLite database. Doesn't cache anything else (Home/Hubs are always up to date). Massively speeds up consecutive visits to items and libraries. Certain important events, such as watch state changes, automatically delete the item cache and its corresponding library cache. (Default: Off)" +msgstr "" + +#: +msgctxt "#33728" +msgid "Clear cache for item" +msgstr "" + +#: +msgctxt "#33729" +msgid "Expire cached Plex data after (hours)" +msgstr "" + +#: +msgctxt "#33730" +msgid "Randomly" +msgstr "" + +#: +msgctxt "#33731" +msgid "By Bitrate" +msgstr "" + +#: +msgctxt "#33732" +msgid "Bitrate" +msgstr "" + +#: +msgctxt "#33733" +msgid "Transcode target codec" +msgstr "" + +#: +msgctxt "#33734" +msgid "Sets the target codec when transcoding/direct streaming. Overridden when \"Transcode audio to AC3\" is set." +msgstr "" + +#: +msgctxt "#33735" +msgid "Always auto-start" +msgstr "" + +#: +msgctxt "#33736" +msgid "By default PM4K won't auto-start when the current window isn't a Home-derived window (e.g.: Settings). Enable this if you always want to auto-start PM4K regardless of the current window. Can also improve start-up time." +msgstr "" + +#: +msgctxt "#33737" +msgid "Loop theme music" +msgstr "" + +#: +msgctxt "#33671" +msgid "Current: {current_version}\n" +"New: {new_version}\n" +"\n" +"Changelog:\n" +"{changelog}" +msgstr "" + +#: +msgctxt "#33738" +msgid "Max playlist size" +msgstr "" + +#: +msgctxt "#33739" +msgid "How many items to load into a Kodi playlist before chunking them remotely using Plex PlayQueues (default: 500); Reduce on memory issues/crashes." +msgstr "" + +#: +msgctxt "#33740" +msgid "Home: Episodes season thumbnails" +msgstr "" + +#: +msgctxt "#33741" +msgid "Use season thumbnails/posters when displaying episodes in hubs instead of the TV show's." +msgstr "" + +#: +msgctxt "#34000" +msgid "Watchlist" +msgstr "" + +#: +msgctxt "#34001" +msgid "Released" +msgstr "" + +#: +msgctxt "#34002" +msgid "Movies & Shows" +msgstr "" + +#: e.g.: 8 seasons +msgctxt "#34003" +msgid "{} seasons" +msgstr "" + +#: +msgctxt "#34004" +msgid "Choose server" +msgstr "" + +#: +msgctxt "#34005" +msgid "Availability" +msgstr "" + +#: e.g.: 1 season +msgctxt "#34006" +msgid "{} season" +msgstr "" + +#: +msgctxt "#34007" +msgid "Use Watchlist" +msgstr "" + +#: +msgctxt "#34008" +msgid "Activates the current user's Plex watchlist as a section item. Adds watchlist functionality to certain media screens. Per-user setting. Default: On" +msgstr "" + +#: +msgctxt "#34009" +msgid "Watchlist auto-remove" +msgstr "" + +#: +msgctxt "#34010" +msgid "Automatically remove fully watched items from watchlist. Default: On" +msgstr "" + +#: +msgctxt "#34011" +msgid "Remove from watchlist" +msgstr "" + +#: +msgctxt "#34012" +msgid "Fast pause/resume" +msgstr "" + +#: +msgctxt "#34013" +msgid "when paused" +msgstr "" + +#: +msgctxt "#34014" +msgid "when playing" +msgstr "" + +#: +msgctxt "#34015" +msgid "User-specific. Use OK/ENTER button to pause instead of showing the OSD (which can then only be accessed using DOWN), or resume when paused. Only works with 'Behave like official Plex clients' enabled." +msgstr "" + +#: +msgctxt "#34016" +msgid "Max shutdown wait" +msgstr "" + +#: +msgctxt "#34017" +msgid "The plugin will try to hard exit once this time limit is reached (default: 5 seconds)" +msgstr "" + +#: +msgctxt "#34018" +msgid "Related Media" +msgstr "" + +#: watchlist discover hub title prefix +msgctxt "#34019" +msgid "Discover: {}" +msgstr "" + +#: +msgctxt "#34020" +msgid "Loading external hubs" +msgstr "" + +#: +msgctxt "#34021" +msgid "Please wait ..." +msgstr "" + +#: +msgctxt "#34022" +msgid "Video play completion behaviour" +msgstr "" + +#: +msgctxt "#34023" +msgid "Decide whether to use end credits markers to determine the 'watched' state of video items. When markers are not available the selected threshold percentage will be used." +msgstr "" + +#: +msgctxt "#34024" +msgid "at selected threshold percentage" +msgstr "" + +#: +msgctxt "#34025" +msgid "at final credits marker position" +msgstr "" + +#: +msgctxt "#34026" +msgid "at first credits marker position" +msgstr "" + +#: +msgctxt "#34027" +msgid "earliest between threshold percent and first credits marker" +msgstr "" + +#: +msgctxt "#32973" +msgid "Episodes: Continuous playback" +msgstr "" + +#: +msgctxt "#34028" +msgid "\"Use alternate seek\" valid seek window" +msgstr "" + +#: +msgctxt "#34029" +msgid "When \"Use alternate seek\" is enabled, any seek amount outside of this threshold window will be applied, otherwise the seek attempt will be ignored. If you encounter any seek issues, play with this value (ms, default: 2000)" +msgstr "" + +#: +msgctxt "#34030" +msgid "Unlock Direct Play for above 4K" +msgstr "" + +#: +msgctxt "#34031" +msgid "Producer" +msgstr "" + +#: +msgctxt "#34032" +msgid "Audio Language" +msgstr "" + +#: +msgctxt "#34033" +msgid "Subtitle Language" +msgstr "" + +#: +msgctxt "#34034" +msgid "Folder Location" +msgstr "" + +#: +msgctxt "#34035" +msgid "Editions" +msgstr "" + +#: +msgctxt "#34036" +msgid "DOVI" +msgstr "" + +#: +msgctxt "#34037" +msgid "HDR" +msgstr "" + +#: +msgctxt "#34038" +msgid "Force plex.direct mapping" +msgstr "" + +#: +msgctxt "#34039" +msgid "If you (still) don't see any posters: Under certain circumstances when Kodi still can't resolve hostnames when loading assets, even though the host resolves natively, enable this to force plex.direct mapping via advancedsettings.xml." +msgstr "" + +#: +msgctxt "#34040" +msgid "By Progress" +msgstr "" + +#: +msgctxt "#34041" +msgid "Progress" +msgstr "" + +#: +msgctxt "#34042" +msgid "By Album" +msgstr "" + +#: +msgctxt "#34043" +msgid "Album" +msgstr "" + diff --git a/script.plexmod/resources/language/resource.language.ru_ru/strings.po b/script.plexmod/resources/language/resource.language.ru_ru/strings.po index 50b5ba08d2..2ff215aa6c 100644 --- a/script.plexmod/resources/language/resource.language.ru_ru/strings.po +++ b/script.plexmod/resources/language/resource.language.ru_ru/strings.po @@ -1,951 +1,3542 @@ -# XBMC Media Center language file msgid "" msgstr "" -"Project-Id-Version: XBMC-Addons\n" -"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n" -"POT-Creation-Date: 2013-12-12 22:56+0000\n" -"PO-Revision-Date: 2018-01-15 11:26+0300\n" -"Language-Team: LANGUAGE\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"X-Generator: POEditor.com\n" +"Project-Id-Version: PM4K / PlexMod for Kodi\n" "Language: ru\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -"Last-Translator: \n" -"X-Generator: Poedit 2.0.5\n" +#: msgctxt "#32000" msgid "Main" msgstr "Главный" +#: msgctxt "#32001" msgid "Original" msgstr "Оригинал" -msgctxt "#32002" -msgid "20 Mbps 1080p" -msgstr "20 Мбит/с 1080p" - -msgctxt "#32003" -msgid "12 Mbps 1080p" -msgstr "12 Мбит/с 1080p" - -msgctxt "#32004" -msgid "10 Mbps 1080p" -msgstr "10 Мбит/с 1080p" - -msgctxt "#32005" -msgid "8 Mbps 1080p" -msgstr "8 Мбит/с 1080p" - -msgctxt "#32006" -msgid "4 Mbps 720p" -msgstr "4 Мбит/с 720p" - -msgctxt "#32007" -msgid "3 Mbps 720p" -msgstr "3 Мбит/с 720p" - -msgctxt "#32008" -msgid "2 Mbps 720p" -msgstr "2 Мбит/с 720p" - -msgctxt "#32009" -msgid "1.5 Mbps 480p" -msgstr "1.5 Мбит/с 480p" - +#: msgctxt "#32010" msgid "720 kbps" msgstr "720 кбит/с" +#: msgctxt "#32011" msgid "320 kbps" msgstr "320 кбит/с" +#: msgctxt "#32012" msgid "208 kbps" msgstr "208 кбит/с" +#: msgctxt "#32013" msgid "96 kbps" msgstr "96 кбит/с" +#: msgctxt "#32014" msgid "64 kbps" msgstr "64 кбит/с" +#: msgctxt "#32020" msgid "Local Quality" msgstr "Локальное качество" +#: msgctxt "#32021" msgid "Remote Quality" msgstr "Удаленное качество" +#: msgctxt "#32022" msgid "Online Quality" msgstr "Онлайн качество" +#: msgctxt "#32023" msgid "Transcode Format" msgstr "Формат перекодирования" +#: msgctxt "#32024" msgid "Debug Logging" msgstr "Отладка" -msgctxt "#32025" -msgid "Allow Direct Play" -msgstr "Разрешить прямое воспроизведение" - -msgctxt "#32026" -msgid "Allow Direct Stream" -msgstr "Разрешить прямую трансляцию" - +#: msgctxt "#32027" msgid "Force" msgstr "Принудительно" +#: msgctxt "#32028" msgid "Always" msgstr "Всегда" +#: msgctxt "#32029" msgid "Only Image Formats" msgstr "Только изображения" +#: msgctxt "#32030" msgid "Auto" msgstr "Авто" -msgctxt "#32031" -msgid "Burn Subtitles (Direct Play Only)" -msgstr "Наложение субтитров (только прямое воспроизведение)" - +#: msgctxt "#32032" msgid "Allow Insecure Connections" msgstr "Разрешить небезопасные соединения" +#: msgctxt "#32033" msgid "Never" msgstr "Никогда" +#: msgctxt "#32034" msgid "On Same network" msgstr "В той же сети" +#: msgctxt "#32035" msgid "Always" msgstr "Всегда" -msgctxt "#32036" -msgid "Allow 4K" -msgstr "Разрешить 4K" - -msgctxt "#32037" -msgid "Allow HEVC (h265)" -msgstr "Разрешить HEVC (h265)" - +#: msgctxt "#32038" msgid "Automatically Sign In" msgstr "Автоматический вход" +#: msgctxt "#32039" msgid "Post Play Auto Play" msgstr "Автоматическое воспроизведение" +#: msgctxt "#32040" msgid "Enable Subtitle Downloading" msgstr "Включить скачивание субтитров" +#: msgctxt "#32041" msgid "Enable Subtitle Downloading" msgstr "Включить скачивание субтитров" +#: msgctxt "#32042" msgid "Server Discovery (GDM)" msgstr "Обнаружение сервера (GDM)" +#: msgctxt "#32043" msgid "Start Plex On Kodi Startup" msgstr "Запускать Plex при старте Kodi" +#: msgctxt "#32044" msgid "Connection 1 IP" msgstr "Соединение 1 IP" +#: msgctxt "#32045" msgid "Connection 1 Port" msgstr "Соединение 1 порт" +#: msgctxt "#32046" msgid "Connection 2 IP" msgstr "Соединение 2 IP" +#: msgctxt "#32047" msgid "Connection 2 Port" msgstr "Соединение 2 порт" +#: msgctxt "#32048" msgid "Audio" msgstr "Аудио" +#: msgctxt "#32049" msgid "Advanced" msgstr "Продвинутый" +#: msgctxt "#32050" msgid "Manual Servers" msgstr "Задать сервер вручную" +#: msgctxt "#32051" msgid "Privacy" msgstr "Приватность" +#: msgctxt "#32052" msgid "About" msgstr "Сведения" +#: msgctxt "#32053" msgid "Video" msgstr "Видео" +#: msgctxt "#32054" msgid "Addon Version" msgstr "Версия аддона" +#: msgctxt "#32055" msgid "Kodi Version" msgstr "Версия Kodi" +#: msgctxt "#32056" msgid "Screen Resolution" msgstr "Разрешение экрана" +#: msgctxt "#32057" msgid "Current Server Version" msgstr "Версия сервера" +#: +msgctxt "#32058" +msgid "Never exceed original audio codec" +msgstr "" + +#: +msgctxt "#32059" +msgid "When transcoding audio, never exceed the original audio bitrate or channel count on the same codec." +msgstr "" + +#: +msgctxt "#32060" +msgid "Use Kodi audio channels" +msgstr "" + +#: +msgctxt "#32064" +msgid "Treat DTS like AC3" +msgstr "" + +#: msgctxt "#32100" msgid "Skip user selection and pin entry on startup." msgstr "Пропустить выбор пользователя и ввод пин-кода при старте." -msgctxt "#32101" -msgid "If enabled, when playback ends and there is a 'Next Up' item available, it will be automatically be played after a 15 second delay." -msgstr "Если включено, после окончании воспроизведения, если есть следующая запись, она будет автоматически запущена после 15 секундной задержки." - +#: msgctxt "#32102" msgid "Enable this if your hardware can handle 4K playback. Disable it to force transcoding." msgstr "Включите, если ваше оборудование поддерживает воспроизведение 4K. Отключите, чтобы использовать преобразование." +#: msgctxt "#32103" msgid "Enable this if your hardware can handle HEVC/h265. Disable it to force transcoding." msgstr "Включите, если ваше оборудование поддерживает HEVC/h265. Отключите, чтобы использовать преобразование." +#: msgctxt "#32104" msgid "When to connect to servers with no secure connections.[CR][CR]* [B]Never[/B]: Never connect to a server insecurely[CR]* [B]On Same Network[/B]: Allow if on the same network[CR]* [B]Always[/B]: Allow same network and remote connections" msgstr "Когда подключаться к серверам, не поддерживающим безопасные соединения.[CR][CR]* [B]Никогда[/B]: Никогда не подключаться к серверам с небезопасным соединением[CR]* [B]В той же сети[/B]: Разрешить, если сервер находится в той же сети[CR]* [B]Всегда[/B]: Разрешить в той же сети и удаленные соединения" +#: msgctxt "#32201" msgid "Trailer" msgstr "Трейлер" +#: msgctxt "#32202" msgid "Deleted Scene" msgstr "Удаленные эпизоды" +#: msgctxt "#32203" msgid "Interview" msgstr "Интервью" +#: msgctxt "#32204" msgid "Music Video" msgstr "Клип" +#: msgctxt "#32205" msgid "Behind the Scenes" msgstr "За кулисами" +#: msgctxt "#32206" msgid "Scene" msgstr "Эпизод" +#: msgctxt "#32207" msgid "Live Music Video" msgstr "Живая музыка" +#: msgctxt "#32208" msgid "Lyric Music Video" msgstr "Лирический клип" +#: msgctxt "#32209" msgid "Concert" msgstr "Концерт" +#: msgctxt "#32210" msgid "Featurette" msgstr "Короткометражный фильм" +#: msgctxt "#32211" msgid "Short" msgstr "Короткий" +#: msgctxt "#32212" msgid "Other" msgstr "Прочее" +#: msgctxt "#32300" msgid "Go to Album" msgstr "Перейти к альбому" +#: msgctxt "#32301" msgid "Go to Artist" msgstr "Перейти к артисту" +#: msgctxt "#32302" msgid "Go to {0}" msgstr "Перейти к {0}" -msgctxt "#32303" -msgid "Season" -msgstr "Сезон" - -msgctxt "#32304" -msgid "Episode" -msgstr "Эпизод" - +#: msgctxt "#32305" msgid "Extras" msgstr "Дополнения" +#: msgctxt "#32306" msgid "Related Shows" msgstr "Связанные вупыски" +#: msgctxt "#32307" msgid "More" msgstr "Еще" +#: msgctxt "#32308" msgid "Available" msgstr "Доступно" +#: msgctxt "#32309" msgid "None" msgstr "Нет" -msgctxt "#32310" -msgid "S" -msgstr "S" - -msgctxt "#32311" -msgid "E" -msgstr "E" - +#: msgctxt "#32312" msgid "Unavailable" msgstr "Недосутпно" +#: msgctxt "#32313" msgid "This item is currently unavailable." msgstr "Эта запись сейчас недоступна." +#: msgctxt "#32314" msgid "In Progress" msgstr "В работе" +#: msgctxt "#32315" msgid "Resume playback?" msgstr "Возобновить воспроизведение?" +#: msgctxt "#32316" msgid "Resume" msgstr "Возобновить" +#: msgctxt "#32317" msgid "Play from beginning" msgstr "Начать сначала" +#: msgctxt "#32318" msgid "Mark Unplayed" msgstr "Пометить как не просмотренное" +#: msgctxt "#32319" msgid "Mark Played" msgstr "Пометить как просмотренное" +#: msgctxt "#32320" msgid "Mark Season Unplayed" msgstr "Пометить сезон как не просмотренный" +#: msgctxt "#32321" msgid "Mark Season Played" msgstr "Пометить сезон как просмотренный" +#: msgctxt "#32322" msgid "Delete" msgstr "Удалить" +#: msgctxt "#32323" msgid "Go To Show" msgstr "Перейти к просмотру" +#: msgctxt "#32324" msgid "Go To {0}" msgstr "Перейти к {0}" +#: msgctxt "#32325" msgid "Play Next" msgstr "Воспроизвести следующий" +#: msgctxt "#32326" msgid "Really Delete?" msgstr "Действительно удалить?" +#: msgctxt "#32327" msgid "Are you sure you really want to delete this media?" msgstr "Вы действительно хотите удалить?" +#: msgctxt "#32328" msgid "Yes" msgstr "Да" +#: msgctxt "#32329" msgid "No" msgstr "Нет" +#: msgctxt "#32330" msgid "Message" msgstr "Сообщение" +#: msgctxt "#32331" msgid "There was a problem while attempting to delete the media." msgstr "Возникла проблема при удалении." +#: msgctxt "#32332" msgid "Home" msgstr "Домой" +#: msgctxt "#32333" msgid "Playlists" msgstr "Плейлисты" +#: msgctxt "#32334" msgid "Confirm Exit" msgstr "Подтверждение выхода" +#: msgctxt "#32335" msgid "Are you ready to exit Plex?" msgstr "Вы действительно хотите выйти из Plex?" +#: msgctxt "#32336" msgid "Exit" msgstr "Выход" +#: msgctxt "#32337" msgid "Cancel" msgstr "Отмена" +#: msgctxt "#32338" msgid "No Servers Found" msgstr "Сервера не найдены" +#: msgctxt "#32339" msgid "Server is not accessible" msgstr "Сервер недоступен" +#: msgctxt "#32340" msgid "Connection tests are in progress. Please wait." msgstr "Пожалуйста, подождите, идет проверка соединения." +#: msgctxt "#32341" msgid "Server is not accessible. Please sign into your server and check your connection." msgstr "Сервер недоступен. Пожалуйста, войдите на ваш сервер и проверьте соединение." +#: msgctxt "#32342" msgid "Switch User" msgstr "Сменить пользователя" +#: msgctxt "#32343" msgid "Settings" msgstr "Настройки" +#: msgctxt "#32344" msgid "Sign Out" msgstr "Выйти" +#: msgctxt "#32345" msgid "All" msgstr "Все" +#: msgctxt "#32346" msgid "By Name" msgstr "По имени" +#: msgctxt "#32347" msgid "Artists" msgstr "Артисты" +#: msgctxt "#32348" -msgid "movies" -msgstr "фильмы" +msgid "Movies" +msgstr "" +#: msgctxt "#32349" msgid "photos" msgstr "фото" +#: msgctxt "#32350" msgid "Shows" msgstr "Сериалы" +#: msgctxt "#32351" msgid "By Date Added" msgstr "По дате добавления" +#: msgctxt "#32352" msgid "Date Added" msgstr "Дата добавления" +#: msgctxt "#32353" msgid "By Release Date" msgstr "По дате выхода" +#: msgctxt "#32354" msgid "Release Date" msgstr "Дата выхода" +#: msgctxt "#32355" msgid "By Date Viewed" msgstr "По дате просмотра" +#: msgctxt "#32356" msgid "Date Viewed" msgstr "Дата просмотра" -msgctxt "#32357" -msgid "By Name" -msgstr "По имени" - -msgctxt "#32358" -msgid "Name" -msgstr "Имя" - +#: msgctxt "#32359" msgid "By Rating" msgstr "По рейтингу" +#: msgctxt "#32360" msgid "Rating" msgstr "Рейтинг" +#: msgctxt "#32361" msgid "By Resolution" msgstr "По разрешению" +#: msgctxt "#32362" msgid "Resolution" msgstr "Разрешение" +#: msgctxt "#32363" msgid "By Duration" msgstr "По длительности" +#: msgctxt "#32364" msgid "Duration" msgstr "Длительность" +#: msgctxt "#32365" msgid "By First Aired" msgstr "По дате показа" +#: msgctxt "#32366" msgid "First Aired" msgstr "Дата показа" +#: msgctxt "#32367" msgid "By Unplayed" msgstr "По не просмотренным" +#: msgctxt "#32368" msgid "Unplayed" msgstr "Не просмотренные" +#: msgctxt "#32369" msgid "By Date Played" msgstr "По дате воспроизведения" +#: msgctxt "#32370" msgid "Date Played" msgstr "Дата воспроизведения" +#: msgctxt "#32371" msgid "By Play Count" msgstr "По количеству просмотров" +#: msgctxt "#32372" msgid "Play Count" msgstr "Количество просмотров" +#: msgctxt "#32373" msgid "By Date Taken" msgstr "По выбранной дате" +#: msgctxt "#32374" msgid "Date Taken" msgstr "Выбранная дата" +#: msgctxt "#32375" msgid "No filters available" msgstr "Нет фильтров" +#: msgctxt "#32376" msgid "Clear Filter" msgstr "Очистить фильтр" +#: msgctxt "#32377" msgid "Year" msgstr "Год" +#: msgctxt "#32378" msgid "Decade" msgstr "Десятилетие" +#: msgctxt "#32379" msgid "Genre" msgstr "Жанр" +#: msgctxt "#32380" msgid "Content Rating" msgstr "Рейтинг контента" +#: msgctxt "#32381" msgid "Network" msgstr "Сеть" +#: msgctxt "#32382" msgid "Collection" msgstr "Коллекция" +#: msgctxt "#32383" msgid "Director" msgstr "Режиссер" +#: msgctxt "#32384" msgid "Actor" msgstr "Актер" +#: msgctxt "#32385" msgid "Country" msgstr "Страна" +#: msgctxt "#32386" msgid "Studio" msgstr "Студия" +#: msgctxt "#32387" msgid "Labels" msgstr "Лейбл" +#: msgctxt "#32388" msgid "Camera Make" msgstr "Камера" +#: msgctxt "#32389" msgid "Camera Model" msgstr "Модель камеры" +#: msgctxt "#32390" msgid "Aperture" msgstr "Диафрагма" +#: msgctxt "#32391" msgid "Shutter Speed" msgstr "Скорость затвора" +#: msgctxt "#32392" msgid "Lens" msgstr "Объектив" +#: msgctxt "#32393" msgid "TV Shows" msgstr "Сериалы" +#: msgctxt "#32394" msgid "Music" msgstr "Музыка" +#: msgctxt "#32395" msgid "Audio" msgstr "Аудио" +#: msgctxt "#32396" msgid "Subtitles" msgstr "Субтитры" +#: msgctxt "#32397" msgid "Quality" -msgstr "Качетсво" +msgstr "Качество" +#: msgctxt "#32398" msgid "Kodi Video Settings" msgstr "Настройки видео Kodi" +#: msgctxt "#32399" msgid "Kodi Audio Settings" msgstr "Настройки аудио Kodi" +#: msgctxt "#32400" msgid "Go To Season" msgstr "Перейти к сезону" +#: msgctxt "#32401" msgid "Directors" msgstr "Режиссеры" +#: msgctxt "#32402" msgid "Writer" msgstr "Сценарист" +#: msgctxt "#32403" msgid "Writers" msgstr "Сценаристы" +#: msgctxt "#32404" msgid "Related Movies" msgstr "Похожие фильмы" +#: msgctxt "#32405" msgid "Download Subtitles" msgstr "Скачать субтитры" +#: msgctxt "#32406" msgid "Subtitle Delay" msgstr "Задержка субтитров" +#: msgctxt "#32407" msgid "Next Subtitle" msgstr "Следующие субтитры" +#: msgctxt "#32408" msgid "Disable Subtitles" msgstr "Отключить субтитры" +#: msgctxt "#32409" msgid "Enable Subtitles" msgstr "Включить субтитры" +#: msgctxt "#32410" msgid "Platform Version" msgstr "Версия платформы" +#: msgctxt "#32411" msgid "Unknown" msgstr "Неизсвестно" +#: msgctxt "#32412" msgid "Edit Or Clear" msgstr "Изменить или удалить" +#: msgctxt "#32413" msgid "Edit IP address or clear the current setting?" msgstr "Изменить IP адрес или удалить настройки?" +#: msgctxt "#32414" msgid "Clear" msgstr "Удалить" +#: msgctxt "#32415" msgid "Edit" msgstr "Изменить" +#: msgctxt "#32416" msgid "Enter IP Address" msgstr "Введите IP адрес" +#: msgctxt "#32417" msgid "Enter Port Number" msgstr "Введите номер порта" +#: msgctxt "#32418" msgid "Creator" msgstr "Автор" +#: msgctxt "#32419" msgid "Cast" msgstr "В ролях" +#: msgctxt "#32420" msgid "Disc" msgstr "Диск" +#: msgctxt "#32421" msgid "Sign Out" msgstr "Выйти из учетной записи" +#: msgctxt "#32422" msgid "Exit" msgstr "Выход" +#: msgctxt "#32423" msgid "Shutdown" msgstr "Завершить работу" +#: msgctxt "#32424" msgid "Suspend" msgstr "Перейти в режим сна" +#: msgctxt "#32425" msgid "Hibernate" msgstr "Гибернация" +#: msgctxt "#32426" msgid "Reboot" msgstr "Перезагрузка" +#: msgctxt "#32427" msgid "Failed" msgstr "Ошибка" +#: msgctxt "#32428" msgid "Login failed!" msgstr "Ошибка входа!" +#: msgctxt "#32429" msgid "Resume from {0}" msgstr "Начать с {0}" +#: msgctxt "#32430" msgid "Discovery" msgstr "Обнаружение" +#: msgctxt "#32431" msgid "Search" msgstr "Поиск" +#: msgctxt "#32432" msgid "Space" msgstr "Пробел" +#: msgctxt "#32433" msgid "Clear" msgstr "Удалить" +#: msgctxt "#32434" msgid "Searching..." msgstr "Поиск..." +#: msgctxt "#32435" msgid "No Results" msgstr "Нет результатов" +#: msgctxt "#32436" msgid "Paused" msgstr "Пауза" +#: msgctxt "#32437" msgid "Welcome" msgstr "Добро пожаловать" +#: msgctxt "#32438" msgid "Previous" msgstr "Предыдущий" +#: msgctxt "#32439" msgid "Playing Next" msgstr "Следующий" +#: msgctxt "#32440" msgid "On Deck" msgstr "Сейчас просматривается" +#: msgctxt "#32441" msgid "Unknown" msgstr "Неизвестно" +#: msgctxt "#32442" msgid "Embedded" msgstr "Встроенный" +#: msgctxt "#32443" msgid "Forced" msgstr "Принудительно" +#: msgctxt "#32444" msgid "Lyrics" msgstr "Текст песни" +#: msgctxt "#32445" msgid "Mono" msgstr "Моно" +#: msgctxt "#32446" msgid "Stereo" msgstr "Стерео" +#: msgctxt "#32447" msgid "None" msgstr "Нет" +#: msgctxt "#32448" msgid "Playback Failed!" msgstr "Ошибка воспроизведения!" +#: msgctxt "#32449" msgid "Can't connect to plex.tv[CR]Check your internet connection and try again." msgstr "Нет соединения с plex.tv[CR]Проверьте ваше интернет-соединение и попробуйте еще раз." +#: msgctxt "#32450" msgid "Choose Version" msgstr "Выберите версию" +#: msgctxt "#32451" msgid "Play Version..." msgstr "Версия воспроизведения..." +#: msgctxt "#32452" msgid "No Content available in this library" msgstr "В этой библиотеке ничего нет" +#: msgctxt "#32453" msgid "Please add content and/or check that 'Include in dashboard' is enabled." msgstr "Пожалуйста, добавьте контент и/или проверьте включено ли \"Добавить на панель\"." +#: msgctxt "#32454" msgid "No Content available for this filter" msgstr "Нет результатов с выбранными условиями" +#: msgctxt "#32455" msgid "Please change change or remove the current filter" msgstr "Пожалуйста, измените или удалите текущий фильтр" +#: msgctxt "#32456" msgid "Show" msgstr "Показ" +#: msgctxt "#32457" msgid "By Show" msgstr "По показу" +#: msgctxt "#32458" msgid "Episodes" msgstr "Эпизоды" +#: msgctxt "#32459" msgid "Offline Mode" msgstr "Автономный режим" +#: msgctxt "#32460" msgid "Sign In" msgstr "Войти" +#: msgctxt "#32461" msgid "Albums" msgstr "Альбомы" +#: msgctxt "#32462" msgid "Artist" msgstr "Артист" +#: msgctxt "#32463" msgid "By Artist" msgstr "По артисту" + +#: +msgctxt "#32464" +msgid "Player" +msgstr "" + +#: +msgctxt "#32465" +msgid "Use skip step settings from Kodi" +msgstr "" + +#: +msgctxt "#32466" +msgid "Automatically seek selected position after a delay" +msgstr "" + +#: +msgctxt "#32467" +msgid "User Interface" +msgstr "" + +#: +msgctxt "#32468" +msgid "Show dynamic background art" +msgstr "" + +#: +msgctxt "#32469" +msgid "Background art blur amount" +msgstr "" + +#: +msgctxt "#32470" +msgid "Background art opacity" +msgstr "" + +#: +msgctxt "#32471" +msgid "Use Plex/Kodi steps for timeline" +msgstr "" + +#: +msgctxt "#32480" +msgid "Theme music" +msgstr "" + +#: +msgctxt "#32481" +msgid "Off" +msgstr "" + +#: +msgctxt "#32482" +msgid "%(percentage)s %%" +msgstr "" + +#: +msgctxt "#32483" +msgid "Hide Stream Info" +msgstr "" + +#: +msgctxt "#32484" +msgid "Show Stream Info" +msgstr "" + +#: +msgctxt "#32485" +msgid "Go back instantly with the previous menu action in scrolled views" +msgstr "" + +#: +msgctxt "#32487" +msgid "Seek Delay" +msgstr "" + +#: +msgctxt "#32488" +msgid "Screensaver" +msgstr "" + +#: +msgctxt "#32489" +msgid "Quiz Mode" +msgstr "" + +#: +msgctxt "#32490" +msgid "Collections" +msgstr "Коллекции" + +#: +msgctxt "#32491" +msgid "Folders" +msgstr "Папки" + +#: +msgctxt "#32492" +msgid "Kodi Subtitle Settings" +msgstr "" + +#: +msgctxt "#32495" +msgid "Skip intro" +msgstr "" + +#: +msgctxt "#32496" +msgid "Skip credits" +msgstr "" + +#: +msgctxt "#32500" +msgid "Always show post-play screen (even for short videos)" +msgstr "" + +#: +msgctxt "#32501" +msgid "Time-to-wait between videos on post-play" +msgstr "" + +#: +msgctxt "#32505" +msgid "Visit media in video playlist instead of playing it" +msgstr "" + +#: +msgctxt "#32521" +msgid "Skip Intro Button Timeout" +msgstr "" + +#: +msgctxt "#32522" +msgid "Automatically Skip Intro" +msgstr "" + +#: +msgctxt "#32524" +msgid "Set how long the skip intro button shows for." +msgstr "" + +#: +msgctxt "#32525" +msgid "Skip Credits Button Timeout" +msgstr "" + +#: +msgctxt "#32526" +msgid "Automatically Skip Credits" +msgstr "" + +#: +msgctxt "#32528" +msgid "Set how long the skip credits button shows for." +msgstr "" + +#: +msgctxt "#32540" +msgid "Show when the current video will end in player" +msgstr "" + +#: +msgctxt "#32541" +msgid "Shows time left and at which time the media will end." +msgstr "" + +#: +msgctxt "#32542" +msgid "Show \"Ends at\" label for the end-time as well" +msgstr "" + +#: +msgctxt "#32543" +msgid "Ends at" +msgstr "Конец в" + +#: +msgctxt "#32602" +msgid "Enable this if your hardware can handle AV1. Disable it to force transcoding." +msgstr "" + +#: +msgctxt "#33101" +msgid "By Audience Rating" +msgstr "" + +#: +msgctxt "#33102" +msgid "Audience Rating" +msgstr "" + +#: +msgctxt "#33103" +msgid "By my Rating" +msgstr "" + +#: +msgctxt "#33104" +msgid "My Rating" +msgstr "" + +#: +msgctxt "#33105" +msgid "By Content Rating" +msgstr "" + +#: +msgctxt "#33106" +msgid "Content Rating" +msgstr "" + +#: +msgctxt "#33107" +msgid "By Critic Rating" +msgstr "" + +#: +msgctxt "#33108" +msgid "Critic Rating" +msgstr "" + +#: +msgctxt "#33200" +msgid "Background Color" +msgstr "" + +#: +msgctxt "#33201" +msgid "Specify solid Background Color instead of using media images" +msgstr "" + +#: +msgctxt "#33400" +msgid "Use old compatibility profile" +msgstr "" + +#: +msgctxt "#33401" +msgid "Uses the Chrome client profile instead of the custom one. Might fix rare issues with 3D playback." +msgstr "" + +#: +msgctxt "#32031" +msgid "Burn-in Subtitles" +msgstr "" + +#: +msgctxt "#32061" +msgid "When transcoding audio, target the audio channels set in Kodi." +msgstr "" + +#: +msgctxt "#32062" +msgid "Transcode audio to AC3" +msgstr "" + +#: +msgctxt "#32063" +msgid "Transcode audio to AC3 in certain conditions (useful for passthrough)." +msgstr "" + +#: +msgctxt "#32065" +msgid "When any of the force AC3 settings are enabled, treat DTS the same as AC3 (useful for Optical passthrough)" +msgstr "" + +#: +msgctxt "#32066" +msgid "Force audio to AC3" +msgstr "" + +#: +msgctxt "#32067" +msgid "Only force multichannel audio to AC3" +msgstr "" + +#: +msgctxt "#32493" +msgid "When a media file has a forced/foreign subtitle for a subtitle-enabled language, the Plex Media Server preselects it. This behaviour is usually not necessary and not configurable. This setting fixes that by ignoring the PMSs decision and selecting the same language without a forced flag if possible." +msgstr "" + +#: +msgctxt "#32523" +msgid "Automatically skip intros if available. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "" + +#: +msgctxt "#32527" +msgid "Automatically skip credits if available. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "" + +#: +msgctxt "#33501" +msgid "Video played threshold" +msgstr "" + +#: +msgctxt "#33502" +msgid "Set this to the same value as your Plex server (Settings>Library>Video played threshold) to avoid certain pitfalls, Default: 90 %" +msgstr "" + +#: +msgctxt "#33503" +msgid "Use alternative hubs refresh" +msgstr "" + +#: +msgctxt "#33504" +msgid "Refreshes all hubs for all libraries after an item's watch-state has changed, instead of only those likely affected. Use this if you find a hub that doesn't update properly." +msgstr "" + +#: +msgctxt "#33505" +msgid "Show intro skip button early" +msgstr "" + +#: +msgctxt "#33507" +msgid "Enabled" +msgstr "" + +#: +msgctxt "#33508" +msgid "Disabled" +msgstr "" + +#: +msgctxt "#33510" +msgid "When showing the intro skip button early, only do so if the intro occurs within the first X seconds." +msgstr "" + +#: +msgctxt "#33600" +msgid "System" +msgstr "" + +#: +msgctxt "#33601" +msgid "Show video chapters" +msgstr "" + +#: +msgctxt "#33602" +msgid "If available, show video chapters from the video-file instead of the timeline-big-seek-steps." +msgstr "" + +#: +msgctxt "#33603" +msgid "Use virtual chapters" +msgstr "" + +#: +msgctxt "#33604" +msgid "When the above is enabled and no video chapters are available, simulate them by using the markers identified by the Plex Server (Intro, Credits)." +msgstr "" + +#: +msgctxt "#33605" +msgid "Video Chapters" +msgstr "" + +#: +msgctxt "#33606" +msgid "Virtual Chapters" +msgstr "" + +#: +msgctxt "#33607" +msgid "Chapter {}" +msgstr "" + +#: +msgctxt "#33608" +msgid "Intro" +msgstr "" + +#: +msgctxt "#33609" +msgid "Credits" +msgstr "" + +#: +msgctxt "#33610" +msgid "Main" +msgstr "" + +#: +msgctxt "#33611" +msgid "Chapters" +msgstr "" + +#: +msgctxt "#33612" +msgid "Markers" +msgstr "" + +#: +msgctxt "#33613" +msgid "Kodi Buffer Size (MB)" +msgstr "" + +#: +msgctxt "#33614" +msgid "Set the Kodi Cache/Buffer size. Free: {} MB, Recommended: ~50 MB, Recommended max: {} MB, Default: 20 MB." +msgstr "" + +#: +msgctxt "#33615" +msgid "{time} left" +msgstr "" + +#: +msgctxt "#33616" +msgid "Addon Path" +msgstr "" + +#: +msgctxt "#33617" +msgid "Userdata/Profile Path" +msgstr "" + +#: +msgctxt "#33618" +msgid "TV binge-viewing mode" +msgstr "" + +#: +msgctxt "#33622" +msgid "LAN reachability timeout (ms)" +msgstr "" + +#: +msgctxt "#33623" +msgid "When checking for LAN reachability, use this timeout. Default: 10ms" +msgstr "" + +#: +msgctxt "#33624" +msgid "Network" +msgstr "" + +#: +msgctxt "#33625" +msgid "Smart LAN/local server discovery" +msgstr "" + +#: +msgctxt "#33626" +msgid "Checks whether servers returned from Plex.tv are actually local/in your LAN. For specific setups (e.g. Docker) Plex.tv might not properly detect a local server.\n" +"\n" +"NOTE: Only works on Kodi 19 or above." +msgstr "" + +#: +msgctxt "#33627" +msgid "Prefer LAN/local servers over security" +msgstr "" + +#: +msgctxt "#33628" +msgid "Prioritizes local connections over secure ones. Needs the proper setting in \"Allow Insecure Connections\" and the Plex Server's \"Secure connections\" at \"Preferred\". Can be used to enforce manual servers." +msgstr "" + +#: +msgctxt "#33629" +msgid "Auto-skip intro/credits offset" +msgstr "" + +#: +msgctxt "#33630" +msgid "Intro/credits markers might be a little early in Plex. When auto skipping add (or subtract) this many seconds from the marker. This avoids cutting off content, while possibly skipping the marker a little late." +msgstr "" + +#: +msgctxt "#32631" +msgid "Playback (user-specific)" +msgstr "" + +#: +msgctxt "#33632" +msgid "Server connectivity check timeout (seconds)" +msgstr "" + +#: +msgctxt "#33633" +msgid "Set the maximum amount of time a server connection has to answer a connectivity request. Default: 2.5" +msgstr "" + +#: +msgctxt "#33634" +msgid "Combined Chapters" +msgstr "" + +#: +msgctxt "#33635" +msgid "Final Credits" +msgstr "" + +#: +msgctxt "#32700" +msgid "Action on Sleep event" +msgstr "" + +#: +msgctxt "#32701" +msgid "When Kodi receives a sleep event from the system, run the following action." +msgstr "" + +#: +msgctxt "#32702" +msgid "Nothing" +msgstr "" + +#: +msgctxt "#32703" +msgid "Stop playback" +msgstr "" + +#: +msgctxt "#32704" +msgid "Quit Kodi" +msgstr "" + +#: +msgctxt "#32705" +msgid "CEC Standby" +msgstr "" + +#: +msgctxt "#32800" +msgid "Skipping intro" +msgstr "" + +#: +msgctxt "#32801" +msgid "Skipping credits" +msgstr "" + +#: +msgctxt "#32900" +msgid "While playing back an item and seeking on the seekbar, automatically seek to the selected position after a delay instead of having to confirm the selection." +msgstr "" + +#: +msgctxt "#32901" +msgid "Seek delay in seconds." +msgstr "" + +#: +msgctxt "#32902" +msgid "Kodi has its own skip step settings. Try to use them if they're configured instead of the default ones." +msgstr "" + +#: +msgctxt "#32903" +msgid "Use the above for seeking on the timeline as well." +msgstr "" + +#: +msgctxt "#32904" +msgid "In seconds." +msgstr "" + +#: +msgctxt "#32905" +msgid "Cancel post-play timer by pressing OK/SELECT" +msgstr "" + +#: +msgctxt "#32906" +msgid "Cancel skip marker timer with BACK" +msgstr "" + +#: +msgctxt "#32907" +msgid "When auto-skipping a marker, allow cancelling the timer by pressing BACK." +msgstr "" + +#: +msgctxt "#32908" +msgid "Immediately skip marker with OK/SELECT" +msgstr "" + +#: +msgctxt "#32909" +msgid "When auto-skipping a marker with a timer, allow skipping immediately by pressing OK/SELECT." +msgstr "" + +#: +msgctxt "#32912" +msgid "Show buffer-state on timeline" +msgstr "" + +#: +msgctxt "#32913" +msgid "Shows the current Kodi buffer/cache state on the video player timeline." +msgstr "" + +#: +msgctxt "#32914" +msgid "Loading" +msgstr "" + +#: +msgctxt "#32915" +msgid "Slow connection" +msgstr "" + +#: +msgctxt "#32916" +msgid "Use with a wonky/slow connection, e.g. in a hotel room. Adjusts the UI to visually wait for item refreshes and waits for the buffer to fill when starting playback. Automatically sets readfactor=20, requires Kodi restart." +msgstr "" + +#: +msgctxt "#32917" +msgid "Couldn't fill buffer in time ({}s)" +msgstr "" + +#: +msgctxt "#32918" +msgid "Buffer wait timeout (seconds)" +msgstr "" + +#: +msgctxt "#32919" +msgid "When slow connection is enabled in the addon, wait this long for the buffer to fill. Default: 120 s" +msgstr "" + +#: +msgctxt "#32920" +msgid "Insufficient buffer wait (seconds)" +msgstr "" + +#: +msgctxt "#32921" +msgid "When slow connection is enabled in the addon and the configured buffer isn't big enough for us to determine its fill state, wait this long when starting playback. Default: 10 s" +msgstr "" + +#: +msgctxt "#32922" +msgid "Kodi Cache Readfactor" +msgstr "" + +#: +msgctxt "#32923" +msgid "Sets the Kodi cache readfactor value. Default: {0}, recommended: {1}. With \"Slow connection\" enabled this will be set to {2}, as otherwise the cache doesn't fill fast/aggressively enough." +msgstr "" + +#: +msgctxt "#32924" +msgid "Minimize" +msgstr "" + +#: +msgctxt "#32925" +msgid "Playback Settings" +msgstr "" + +#: +msgctxt "#32926" +msgid "Wrong pin entered!" +msgstr "" + +#: +msgctxt "#32927" +msgid "Use episode thumbnails in continue hub" +msgstr "" + +#: +msgctxt "#32928" +msgid "Instead of using media artwork, use thumbnails for episodes in the continue hub on the home screen if available." +msgstr "" + +#: +msgctxt "#32929" +msgid "Use legacy background fallback image" +msgstr "" + +#: +msgctxt "#32930" +msgid "Previous Subtitle" +msgstr "" + +#: +msgctxt "#32931" +msgid "Audio/Subtitles" +msgstr "" + +#: +msgctxt "#32936" +msgid "Show playlist button" +msgstr "" + +#: +msgctxt "#32937" +msgid "Show prev/next button" +msgstr "" + +#: +msgctxt "#32941" +msgid "Forced subtitles fix" +msgstr "" + +#: +msgctxt "#32942" +msgid "Other seasons" +msgstr "" + +#: +msgctxt "#32943" +msgid "Crossfade dynamic background art" +msgstr "" + +#: +msgctxt "#32944" +msgid "Burn-in SSA subtitles (DirectStream)" +msgstr "" + +#: +msgctxt "#32945" +msgid "When Direct Streaming instruct the Plex Server to burn in SSA/ASS subtitles (thus transcoding the video stream). If disabled it will not touch the video stream, but will convert the subtitle to unstyled text." +msgstr "" + +#: +msgctxt "#32946" +msgid "Stop video playback on idle after" +msgstr "" + +#: +msgctxt "#32947" +msgid "Stop video playback on screensaver" +msgstr "" + +#: +msgctxt "#32948" +msgid "Allow auto-skip when transcoding" +msgstr "" + +#: +msgctxt "#32949" +msgid "When transcoding/DirectStreaming, allow auto-skip functionality." +msgstr "" + +#: +msgctxt "#32950" +msgid "Use extended title for subtitles" +msgstr "" + +#: +msgctxt "#32951" +msgid "When displaying subtitles use the extendedDisplayTitle Plex exposes." +msgstr "" + +#: +msgctxt "#32953" +msgid "Reviews" +msgstr "" + +#: +msgctxt "#32954" +msgid "Needs Kodi restart. WARNING: This will overwrite advancedsettings.xml!\n" +"\n" +"To customize other cache/network-related values, copy \"script.plexmod/pm4k_cache_template.xml\" to profile folder and edit it to your liking. (See About section for the file paths)" +msgstr "" + +#: +msgctxt "#32955" +msgid "Use Kodi keyboard for searching" +msgstr "" + +#: +msgctxt "#32956" +msgid "Poster resolution scaling %" +msgstr "" + +#: +msgctxt "#32957" +msgid "In percent. Scales the resolution of all posters/thumbnails for better image quality. May impact PMS/PM4K performance, will increase the cache usage accordingly. Recommended: 200-300 % for for big screens if your hardware can handle it. Needs addon restart." +msgstr "" + +#: +msgctxt "#32958" +msgid "Calculate OpenSubtitles.com hash" +msgstr "" + +#: +msgctxt "#32959" +msgid "When opening the subtitle download feature, automatically calculate the OpenSubtitles.com hash for the given file. Can improve search results, downloads 2*64 KB of the video file to calculate the hash." +msgstr "" + +#: +msgctxt "#32960" +msgid "Similar Artists" +msgstr "" + +#: +msgctxt "#32961" +msgid "Show hub bifurcation lines" +msgstr "" + +#: +msgctxt "#32962" +msgid "Visually separate hubs horizontally using a thin line." +msgstr "" + +#: +msgctxt "#32963" +msgid "Wait between videos (s)" +msgstr "" + +#: +msgctxt "#32964" +msgid "When playing back consecutive videos (e.g. TV shows), wait this long before starting the next one in the queue. Might fix compatibility issues with certain configurations." +msgstr "" + +#: +msgctxt "#32965" +msgid "Quit Kodi on exit by default" +msgstr "" + +#: +msgctxt "#32966" +msgid "When exiting the addon, use \"Quit Kodi\" as default option. Can be dynamically switched using CONTEXT_MENU (often longpress SELECT)" +msgstr "" + +#: +msgctxt "#32967" +msgid "Kodi Colour Management" +msgstr "" + +#: +msgctxt "#32968" +msgid "Kodi Resolution Settings" +msgstr "" + +#: +msgctxt "#32969" +msgid "Always request all library media items at once" +msgstr "" + +#: +msgctxt "#32970" +msgid "Retrieve all media in library up front instead of fetching it in chunks as the user navigates through the library" +msgstr "" + +#: +msgctxt "#32971" +msgid "Library item-request chunk size" +msgstr "" + +#: +msgctxt "#32972" +msgid "Request this amount of media items per chunk request in library view (+6-30 depending on view mode; less can be less straining for the UI at first, but puts more strain on the server)" +msgstr "" + +#: +msgctxt "#32973" +msgid "Episodes: Skip Post Play screen" +msgstr "" + +#: +msgctxt "#32974" +msgid "When finishing an episode, don't show Post Play but go to the next one immediately.\n" +"Can be disabled/enabled per TV show. Doesn't override enabled binge mode. Overrides the Post Play setting." +msgstr "" + +#: +msgctxt "#32975" +msgid "Delete Season" +msgstr "" + +#: +msgctxt "#32976" +msgid "Adaptive" +msgstr "" + +#: +msgctxt "#32978" +msgid "Enable this if your hardware can handle VC1. Disable it to force transcoding." +msgstr "" + +#: +msgctxt "#32979" +msgid "Allows the server to only transcode streams of a video that need transcoding, while streaming the others unaltered. If disabled, force the server to transcode everything not direct playable." +msgstr "" + +#: +msgctxt "#32980" +msgid "Refresh Users" +msgstr "" + +#: +msgctxt "#32981" +msgid "Background worker count" +msgstr "" + +#: +msgctxt "#32982" +msgid "Depending on how many cores your CPU has and how much it can handle, increasing this might improve certain situations. If you experience crashes or other annormalities, leave this at its default (3). Needs an addon restart." +msgstr "" + +#: +msgctxt "#32985" +msgid "Modern" +msgstr "" + +#: +msgctxt "#32986" +msgid "Modern (dotted)" +msgstr "" + +#: +msgctxt "#32987" +msgid "Classic" +msgstr "" + +#: +msgctxt "#32988" +msgid "Custom" +msgstr "" + +#: +msgctxt "#32989" +msgid "Modern (colored)" +msgstr "" + +#: +msgctxt "#32990" +msgid "Handle plex.direct mapping" +msgstr "" + +#: +msgctxt "#32991" +msgid "Notify" +msgstr "" + +#: +msgctxt "#32992" +msgid "When using servers with a plex.direct connection (most of them), should we automatically adjust advancedsettings.xml to cope with plex.direct domains? If not, you might want to add plex.direct to your router's DNS rebind exemption list." +msgstr "" + +#: +msgctxt "#32993" +msgid "{} unhandled plex.direct connections found" +msgstr "" + +#: +msgctxt "#32994" +msgid "In order for PM4K to work properly, we need to add special handling for plex.direct connections. We've found {} new unhandled connections. Do you want us to write those to Kodi's advancedsettings.xml automatically? If not, you might want to add plex.direct to your router's DNS rebind exemption list. This can be changed in the settings as well." +msgstr "" + +#: +msgctxt "#32995" +msgid "Advancedsettings.xml modified (plex.direct mappings)" +msgstr "" + +#: +msgctxt "#32996" +msgid "The advancedsettings.xml file has been modified. Please restart Kodi for the changes to apply." +msgstr "" + +#: +msgctxt "#32997" +msgid "OK" +msgstr "" + +#: +msgctxt "#32998" +msgid "Use new Continue Watching hub on Home" +msgstr "" + +#: +msgctxt "#32999" +msgid "Instead of separating Continue Watching and On Deck hubs, behave like the modern Plex clients, which combine those two types of hubs into one Continue Watching hub." +msgstr "" + +#: +msgctxt "#33000" +msgid "Enable path mapping" +msgstr "" + +#: +msgctxt "#33002" +msgid "Verify mapped files exist" +msgstr "" + +#: +msgctxt "#33003" +msgid "When path mapping is enabled and we've successfully mapped a file, verify its existence." +msgstr "" + +#: +msgctxt "#33004" +msgid "No spoilers without OSD" +msgstr "" + +#: +msgctxt "#33005" +msgid "When seeking without the OSD open, hide all time-related information from the user." +msgstr "" + +#: +msgctxt "#33006" +msgid "No TV spoilers" +msgstr "" + +#: +msgctxt "#33008" +msgid "[Spoilers removed]" +msgstr "" + +#: +msgctxt "#33013" +msgid "When the above is anything but \"off\", hide episode titles as well." +msgstr "" + +#: +msgctxt "#33014" +msgid "Ignore plex.direct docker hosts" +msgstr "" + +#: +msgctxt "#33015" +msgid "When checking for plex.direct host mapping, ignore local Docker IPv4 addresses (172.16.0.0/12)." +msgstr "" + +#: +msgctxt "#32303" +msgid "Season {}" +msgstr "" + +#: +msgctxt "#32304" +msgid "Episode {}" +msgstr "" + +#: +msgctxt "#32310" +msgid "S{}" +msgstr "" + +#: +msgctxt "#32311" +msgid "E{}" +msgstr "" + +#: +msgctxt "#32938" +msgid "Only for Episodes/Playlists" +msgstr "" + +#: +msgctxt "#33018" +msgid "Cache Plex Home users" +msgstr "" + +#: +msgctxt "#33019" +msgid "Visit media item" +msgstr "" + +#: +msgctxt "#33020" +msgid "Play" +msgstr "" + +#: +msgctxt "#33021" +msgid "Choose action" +msgstr "" + +#: +msgctxt "#33026" +msgid "Map path: {}" +msgstr "" + +#: +msgctxt "#33027" +msgid "Remove mapping: {}" +msgstr "" + +#: +msgctxt "#33028" +msgid "Hide library" +msgstr "" + +#: +msgctxt "#33029" +msgid "Show library: {}" +msgstr "" + +#: +msgctxt "#33030" +msgid "Choose action for: {}" +msgstr "" + +#: +msgctxt "#33031" +msgid "Select Kodi source for {}" +msgstr "" + +#: +msgctxt "#33032" +msgid "Show path mapping indicators" +msgstr "" + +#: +msgctxt "#33033" +msgid "When path mapping is active for a library, display an indicator." +msgstr "" + +#: +msgctxt "#33035" +msgid "Delete {}: {}?" +msgstr "" + +#: +msgctxt "#33036" +msgid "Delete episode S{0:02d}E{1:02d} from {2}?" +msgstr "" + +#: +msgctxt "#33037" +msgid "Maximum intro offset to consider" +msgstr "" + +#: +msgctxt "#33039" +msgid "Move" +msgstr "" + +#: +msgctxt "#33040" +msgid "Reset library order" +msgstr "" + +#: +msgctxt "#33034" +msgid "Library settings" +msgstr "" + +#: +msgctxt "#32357" +msgid "By Title" +msgstr "" + +#: +msgctxt "#32358" +msgid "Title" +msgstr "" + +#: +msgctxt "#33041" +msgid "Show hub: {}" +msgstr "" + +#: +msgctxt "#33042" +msgid "Episode Date Added" +msgstr "" + +#: +msgctxt "#33001" +msgid "Long-press (or context menu) on a library item or: Honor path_mapping.json in the addon_data/script.plexmod folder when DirectPlaying media. This can be used to stream using other techniques such as SMB/NFS/etc. instead of the default HTTP handler. path_mapping.example.json is included in the addon's main directory." +msgstr "" + +#: +msgctxt "#33043" +msgid "Hubs round-robin" +msgstr "" + +#: +msgctxt "#33045" +msgid "Behave like official Plex clients" +msgstr "" + +#: +msgctxt "#32025" +msgid "Direct Play" +msgstr "" + +#: +msgctxt "#32026" +msgid "Direct Stream" +msgstr "" + +#: +msgctxt "#32036" +msgid "4K" +msgstr "" + +#: +msgctxt "#32037" +msgid "HEVC (h265)" +msgstr "" + +#: +msgctxt "#32601" +msgid "AV1" +msgstr "" + +#: +msgctxt "#32932" +msgid "Subtitle quick-actions" +msgstr "" + +#: +msgctxt "#32933" +msgid "FFWD/RWD" +msgstr "" + +#: +msgctxt "#32934" +msgid "Repeat" +msgstr "" + +#: +msgctxt "#32935" +msgid "Shuffle" +msgstr "" + +#: +msgctxt "#32939" +msgid "User-specific.\n" +"Only applies to video player UI" +msgstr "" + +#: +msgctxt "#32977" +msgid "VC1" +msgstr "" + +#: +msgctxt "#32983" +msgid "Theme" +msgstr "" + +#: +msgctxt "#32984" +msgid "Sets the theme. Currently only customizes all control buttons. ATTENTION: [I]Might[/I] need an addon restart.\n" +"In order to customize this, copy one of the xml's in script.plexmod/resources/skins/Main/1080i/templates to addon_data/script.plexmod/templates/{templatename}_custom.xml and adjust it to your liking, then select \"Custom\" as your theme." +msgstr "" + +#: +msgctxt "#33011" +msgid "In progress" +msgstr "" + +#: +msgctxt "#33016" +msgid "Allow TV spoilers for" +msgstr "" + +#: +msgctxt "#33017" +msgid "Overrides the above for specific genres. Default: Reality, Game Show, Documentary, Sport" +msgstr "" + +#: +msgctxt "#33024" +msgid "Hide background in modern indicators" +msgstr "" + +#: +msgctxt "#33044" +msgid "Allow round-robining in hubs. Attention: Loads a lot of items. Depending on the hub size, this can lead to crashes. Current limit: {}; can be adjusted in addon settings." +msgstr "" + +#: +msgctxt "#33046" +msgid "When pressing up/down while the OSD isn't shown, show OSD instead of skipping chapters. Additionally, when pressing down while the OSD is shown, open the chapters (if available)." +msgstr "" + +#: +msgctxt "#33047" +msgid "Hubs round-robin item limit" +msgstr "" + +#: +msgctxt "#33048" +msgid "When hubs round-robin is enabled, only round-robin until this item limit. If the hub size exceeds this limit, the remaining items will be lazy loaded as usual. Tested minimum safe value on NVIDIA SHIELD 2019 is 1000. Default: 250" +msgstr "" + +#: +msgctxt "#33049" +msgid "Max retries" +msgstr "" + +#: +msgctxt "#33051" +msgid "Use CA certificate bundle" +msgstr "" + +#: +msgctxt "#33053" +msgid "System" +msgstr "" + +#: +msgctxt "#33055" +msgid "Custom" +msgstr "" + +#: +msgctxt "#33056" +msgid "None" +msgstr "" + +#: +msgctxt "#33057" +msgid "Show buttons" +msgstr "" + +#: +msgctxt "#33058" +msgid "Playback features" +msgstr "" + +#: +msgctxt "#33059" +msgid "Additional codecs" +msgstr "" + +#: +msgctxt "#33060" +msgid "{feature_ds}: {desc_ds}\n" +"{feature_4k}: {desc_4k}" +msgstr "" + +#: +msgctxt "#33061" +msgid "Enable certain codecs if your hardware supports them. Disable them to force transcoding." +msgstr "" + +#: +msgctxt "#33062" +msgid "Compiling templates" +msgstr "" + +#: +msgctxt "#33063" +msgid "Looking for custom templates" +msgstr "" + +#: +msgctxt "#33064" +msgid "Rendering: {}" +msgstr "" + +#: +msgctxt "#33065" +msgid "Complete" +msgstr "" + +#: +msgctxt "#33066" +msgid "Cache template files" +msgstr "" + +#: +msgctxt "#33067" +msgid "Doesn't throw away the template source files after compiling them. Uses slightly more memory but increases the speed of theme-related changes." +msgstr "" + +#: +msgctxt "#33068" +msgid "Always compile templates" +msgstr "" + +#: +msgctxt "#33069" +msgid "Recompiles all templates on every startup. Useful for template/theme development." +msgstr "" + +#: +msgctxt "#33070" +msgid "Action on Wake event" +msgstr "" + +#: +msgctxt "#33071" +msgid "Restart PM4K" +msgstr "" + +#: +msgctxt "#33072" +msgid "Wait for {}s" +msgstr "" + +#: +msgctxt "#33073" +msgid "Wait after wakeup" +msgstr "" + +#: +msgctxt "#33074" +msgid "Waiting {} second(s)" +msgstr "" + +#: +msgctxt "#33076" +msgid "Modern (2024)" +msgstr "" + +#: +msgctxt "#33077" +msgid "Scale modern indicators" +msgstr "" + +#: +msgctxt "#33078" +msgid "Scale the modern indicators based on the poster size used. Default: On (tiny: 0.75, small: 1.0, medium: 1.175, big: 1.3)" +msgstr "" + +#: +msgctxt "#33079" +msgid "Hi-Res Music" +msgstr "" + +#: +msgctxt "#33080" +msgid "Allow DirectPlay of high resolution music (e.g. FLAC, >= 192 kHz)" +msgstr "" + +#: +msgctxt "#33081" +msgid "Blur chapter images" +msgstr "" + +#: +msgctxt "#33082" +msgid "Scan Library Files" +msgstr "" + +#: +msgctxt "#33083" +msgid "Empty Trash" +msgstr "" + +#: +msgctxt "#33084" +msgid "Analyze" +msgstr "" + +#: +msgctxt "#33085" +msgid "Map key to home" +msgstr "" + +#: +msgctxt "#33086" +msgid "Press the key you want to map to go to home within {} seconds" +msgstr "" + +#: +msgctxt "#33620" +msgid "Plex server connect timeout" +msgstr "" + +#: +msgctxt "#33621" +msgid "Sets the maximum amount of time to connect to a Plex Server in seconds. Default: 5" +msgstr "" + +#: +msgctxt "#32940" +msgid "Video Player" +msgstr "" + +#: +msgctxt "#33050" +msgid "The maximum number of retries each connection should attempt. The default (3) helps with hibernation/wakeup issues. Set higher for bad connections, setting this to 0 was the old default before 0.7.9" +msgstr "" + +#: +msgctxt "#33075" +msgid "Do something after waking up. Waiting for a time until resuming normal execution helps with networks coming back up after sleep events. Default: 1 second (5 seconds, CoreELEC)." +msgstr "" + +#: +msgctxt "#33087" +msgid "Bind a key to immediately go to the Home view. Cancel using BACK/PREVIOUS_MENU. Clear bound key using the STOP or CONTEXT_MENU (long press OK/Enter) button on the setting." +msgstr "" + +#: +msgctxt "#33088" +msgid "Only applies to video player UI" +msgstr "" + +#: +msgctxt "#33089" +msgid "Automatic seek-back" +msgstr "" + +#: +msgctxt "#33090" +msgid "If your audio doesn't resume as fast as the video, or you want to catch up after a longer pause, use this to seek back after resuming from pause to compensate for the delay. Default: Off" +msgstr "" + +#: +msgctxt "#33091" +msgid "{sec_or_ms} {unit_s_or_ms}" +msgstr "" + +#: +msgctxt "#33092" +msgid "Seek back on pause" +msgstr "" + +#: +msgctxt "#33093" +msgid "Seek back after" +msgstr "" + +#: +msgctxt "#33094" +msgid "Only seek back after having paused at least a certain amount of seconds" +msgstr "" + +#: +msgctxt "#33095" +msgid "Seek back on pause instead of on resume. When Transcoding you should enable this." +msgstr "" + +#: +msgctxt "#33096" +msgid "Only with Direct Play" +msgstr "" + +#: +msgctxt "#33097" +msgid "Enable seek back ony when we're Direct Playing, not Transcoding." +msgstr "" + +#: +msgctxt "#33098" +msgid "Tickrate (Hz)" +msgstr "" + +#: +msgctxt "#33099" +msgid "Controls how often certain ticks are performed on GUI windows and the SeekDialog/VideoPlayer and how fast certain events are handled. Can be expensive when bigger than 1 Hz (once per second), can cause quirks when lower than 1 Hz (less than once per second). Depends on the hardware. Default: 1 Hz, Max: 10 Hz (every 100 ms), Sane highest and old default: 10 Hz (every 100 ms)" +msgstr "" + +#: +msgctxt "#33636" +msgid "Plex server read timeout" +msgstr "" + +#: +msgctxt "#33637" +msgid "Sets the maximum amount of time to read from a Plex Server in seconds. Default: 10" +msgstr "" + +#: +msgctxt "#33638" +msgid "Plex.tv connect timeout" +msgstr "" + +#: +msgctxt "#33640" +msgid "Plex.tv read timeout" +msgstr "" + +#: +msgctxt "#33642" +msgid "Dump config" +msgstr "" + +#: +msgctxt "#33643" +msgid "Dumps all user settings into the log on startup, when DEBUG logging is enabled. Masks private information." +msgstr "" + +#: +msgctxt "#33644" +msgid "Tracks" +msgstr "" + +#: +msgctxt "#33645" +msgid "plex.direct: Honor plex.tv's dnsRebindingProtection flag (DNS)" +msgstr "" + +#: +msgctxt "#33646" +msgid "Only handle plex.direct hosts when the server's attributes dnsRebindingProtection=1. Might not work in certain situations. Disable if you have connection issues. Default: On" +msgstr "" + +#: +msgctxt "#33647" +msgid "plex.direct: Honor plex.tv's publicAddressMatches flag (DNS)" +msgstr "" + +#: +msgctxt "#33648" +msgid "Only handle plex.direct hosts when the server's attributes publicAddressMatches=1. Might not work in certain situations. Disable if you have connection issues. Default: On" +msgstr "" + +#: +msgctxt "#32101" +msgid "If enabled, when playback ends and there is a 'Next Up' item available, it will be automatically be played after a {} second delay." +msgstr "" + +#: +msgctxt "#33651" +msgid "Startup delay" +msgstr "" + +#: +msgctxt "#33652" +msgid "Never show Post Play" +msgstr "" + +#: +msgctxt "#33506" +msgid "Show the intro skip button from the start of a video with an intro marker. The auto-skipping setting applies. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "" + +#: +msgctxt "#33619" +msgid "Automatically skips episode intros, credits and tries to skip episode recaps. Doesn't skip the intro of the first episode of a season and doesn't skip the final credits of a show.\n" +"\n" +"Can be disabled/enabled per TV show.\n" +"Overrides any setting below." +msgstr "" + +#: +msgctxt "#33052" +msgid "Which CA certificate bundle to use for request HTTPS verification. \"system\" uses the system-provided certificate bundle. \"ACME\" uses an extremely small bundle provided with the addon, which includes root certificates for Letsencrypt (plex.direct) and ZeroSSL. \"custom\" allows for a ca-certificate bundle in userdata/addon_data/script.plexmod/custom_bundle.crt" +msgstr "" + +#: +msgctxt "#33054" +msgid "ACME (addon-supplied)" +msgstr "" + +#: +msgctxt "#33639" +msgid "Plex.tv: Sets the maximum amount of time to connect to Plex.tv in seconds. Default: 1" +msgstr "" + +#: +msgctxt "#33641" +msgid "Plex.tv: Sets the maximum amount of time to read from Plex.tv in seconds. Default: 2" +msgstr "" + +#: +msgctxt "#33653" +msgid "Background image resolution scaling %" +msgstr "" + +#: +msgctxt "#33654" +msgid "In percent, based on 1080p. Scales the resolution of all backgrounds for better image quality. May impact PMS/PM4K performance, will increase the cache usage accordingly. Can lead to crashes if your hardware is low on RAM. Needs addon restart. Use 400% for 4K. ATTENTION: In order for this to work you need to add 2160 and 2160 to your advancedsettings.xml." +msgstr "" + +#: +msgctxt "#33655" +msgid "Auto-Sync Subtitles" +msgstr "" + +#: +msgctxt "#33656" +msgid "Only for External SRT subtitles. The PMS setting for voice activity detection has to be enabled for this to work." +msgstr "" + +#: in player quick settings UI, needs to be short +msgctxt "#33657" +msgid "Enable Auto-Sync" +msgstr "" + +#: in player quick settings UI, needs to be short +msgctxt "#33658" +msgid "Disable Auto-Sync" +msgstr "" + +#: +msgctxt "#33659" +msgid "Hide hub: {}" +msgstr "" + +#: +msgctxt "#33660" +msgid "Disable HDR" +msgstr "" + +#: +msgctxt "#33661" +msgid "If you don't want your client to handle HDR (or HDR-fallback), enable this to force transcoding. Doesn't apply to DV Profile 5." +msgstr "" + +#: +msgctxt "#33662" +msgid "Remove from Continue Watching" +msgstr "" + +#: +msgctxt "#33663" +msgid "Home: Confirm item actions" +msgstr "" + +#: +msgctxt "#33664" +msgid "When acting on items in the Home view, such as mark played, hide from continue watching etc., show a confirmation dialog." +msgstr "" + +#: +msgctxt "#33665" +msgid "Disable audio codecs" +msgstr "" + +#: +msgctxt "#33666" +msgid "Audio codecs you can't play back. Disables Direct Play for such media items, enables Direct Stream if possible, transcodes audio stream to compatible format." +msgstr "" + +#: +msgctxt "#32002" +msgid "20 Mbps" +msgstr "20 Мбит/с" + +#: +msgctxt "#32003" +msgid "12 Mbps" +msgstr "12 Мбит/с" + +#: +msgctxt "#32004" +msgid "10 Mbps" +msgstr "10 Мбит/с" + +#: +msgctxt "#32005" +msgid "8 Mbps" +msgstr "8 Мбит/с" + +#: +msgctxt "#32006" +msgid "4 Mbps" +msgstr "4 Мбит/с" + +#: +msgctxt "#32007" +msgid "3 Mbps" +msgstr "3 Мбит/с" + +#: +msgctxt "#32008" +msgid "2 Mbps" +msgstr "2 Мбит/с" + +#: +msgctxt "#32009" +msgid "1.5 Mbps" +msgstr "1.5 Мбит/с" + +#: +msgctxt "#33669" +msgid "Really sign out?" +msgstr "" + +#: +msgctxt "#33670" +msgid "Update available" +msgstr "" + +#: +msgctxt "#33672" +msgid "Check for updates" +msgstr "" + +#: +msgctxt "#33673" +msgid "Automatically check for updates periodically. If installed from a Kodi repository and the Update Source setting is set to Repository, Kodi itself will handle the updating of this addon. Needs a Kodi restart when changed." +msgstr "" + +#: +msgctxt "#33674" +msgid "Check for updates on start" +msgstr "" + +#: +msgctxt "#33675" +msgid "Automatically check for updates on startup. Doesn't do much when Update source is Repository. Needs a Kodi restart when changed." +msgstr "" + +#: +msgctxt "#33676" +msgid "Update source" +msgstr "" + +#: +msgctxt "#33677" +msgid "Specifies the update mode. Will immediately check for a new version when changed and closing settings.\n" +"Default: Repository\n" +"\n" +"Beta: Bleeding edge (possibly unstable)\n" +"Stable: Stable branch (faster than Repository)\n" +"Repository: Kodi repository (official (slow) or Don't Panic)" +msgstr "" + +#: +msgctxt "#33678" +msgid "Beta" +msgstr "" + +#: Update mode as in (beta, stable, repository) +msgctxt "#33679" +msgid "Stable" +msgstr "" + +#: +msgctxt "#33680" +msgid "Repository" +msgstr "" + +#: +msgctxt "#33683" +msgid "Exit, download and install" +msgstr "" + +#: +msgctxt "#33684" +msgid "Later" +msgstr "" + +#: +msgctxt "#32015" +msgid "6 Mbps" +msgstr "" + +#: +msgctxt "#32016" +msgid "16 Mbps" +msgstr "" + +#: +msgctxt "#32017" +msgid "26 Mbps" +msgstr "" + +#: +msgctxt "#33681" +msgid "Service updated" +msgstr "" + +#: +msgctxt "#33682" +msgid "The update {} has had changes to the updater itself. In order for the updated updater service to work, a Kodi restart is necessary. The addon will work normally, though. Do you still want to run the addon?" +msgstr "" + +#: +msgctxt "#33685" +msgid "Clamp video bitrate" +msgstr "" + +#: +msgctxt "#33686" +msgid "Only show transcode bitrate targets lower than the current video's bitrate." +msgstr "" + +#: +msgctxt "#33687" +msgid "Translation updated" +msgstr "" + +#: +msgctxt "#33688" +msgid "The currently in-use translation has been updated. In order to load the new translation, a Kodi restart is necessary. The addon will still run properly, but you might see badly or untranslated strings. Do you still want to run the addon?" +msgstr "" + +#: +msgctxt "#33509" +msgid "Early intro skip threshold (default: < 120s/2m)" +msgstr "" + +#: +msgctxt "#33007" +msgid "Select in which cases to blur TV episode thumbnails, previews, redact summaries, hide episode titles etc." +msgstr "" + +#: +msgctxt "#33009" +msgid "Blur amount for unplayed/in-progress episodes" +msgstr "" + +#: +msgctxt "#33010" +msgid "Unplayed" +msgstr "" + +#: +msgctxt "#33012" +msgid "No unplayed episode titles" +msgstr "" + +#: +msgctxt "#33022" +msgid "Played indicators" +msgstr "" + +#: +msgctxt "#33023" +msgid "Classic: Show orange triangle for unplayed items\n" +"Modern: Show green checkmark for played items\n" +"Modern (2024): Show white checkmark for played items\n" +"(default: Modern (2024))" +msgstr "" + +#: +msgctxt "#33025" +msgid "When the above is enabled, hide the black backdrop of the played state." +msgstr "" + +#: +msgctxt "#33689" +msgid "Service running" +msgstr "" + +#: +msgctxt "#33690" +msgid "Last update check" +msgstr "" + +#: +msgctxt "#33691" +msgid "Native languages" +msgstr "" + +#: +msgctxt "#33692" +msgid "When you usually watch things in a different language with subtitles, but are a native speaker of other languages, which you don't need subtitles for, prevent Plex from auto-selecting subtitles for those languages." +msgstr "" + +#: +msgctxt "#33693" +msgid "Download subtitles using" +msgstr "" + +#: +msgctxt "#33694" +msgid "Ask" +msgstr "" + +#: +msgctxt "#33695" +msgid "Where do you want to download subtitles from? Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "" + +#: +msgctxt "#33696" +msgid "No subtitles found." +msgstr "" + +#: things in curly brackets are variables, don't translate them! +msgctxt "#33697" +msgid "{provider_title}, Score: {subtitle_score}{subtitle_info}" +msgstr "" + +#: hearing impaired subtitle flag +msgctxt "#33698" +msgid "HI" +msgstr "" + +#: forced subtitle flag +msgctxt "#33699" +msgid "forced" +msgstr "" + +#: +msgctxt "#33700" +msgid "Download subtitles: {}" +msgstr "" + +#: +msgctxt "#33701" +msgid "Fallback to Kodi" +msgstr "" + +#: +msgctxt "#33702" +msgid "When no subtitles are found via the Plex subtitle search, fall back to Kodi subtitle search. Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "" + +#: +msgctxt "#33703" +msgid "Download subtitles" +msgstr "" + +#: +msgctxt "#33704" +msgid "Using which service?" +msgstr "" + +#: +msgctxt "#33705" +msgid "Hide ratings" +msgstr "" + +#: +msgctxt "#33706" +msgid "Blur images" +msgstr "" + +#: +msgctxt "#33707" +msgid "Blur images on home (in progress)" +msgstr "" + +#: +msgctxt "#33708" +msgid "Hide summaries" +msgstr "" + +#: +msgctxt "#33709" +msgid "Show ratings for" +msgstr "" + +#: +msgctxt "#33710" +msgid "Show reviews for" +msgstr "" + +#: +msgctxt "#33711" +msgid "Always resume media" +msgstr "" + +#: +msgctxt "#33712" +msgid "When playback of an in-progress media is requested, resume it by default instead of asking whether to resume or start from the beginning." +msgstr "" + +#: +msgctxt "#33713" +msgid "Home: Resume in-progress items" +msgstr "" + +#: +msgctxt "#33714" +msgid "Resume in-progress items directly instead of visiting the media." +msgstr "" + +#: +msgctxt "#33715" +msgid "OSD hide-delay" +msgstr "" + +#: +msgctxt "#33716" +msgid "How long to wait until hiding the OSD. Only works with Plextuary skin or others with configurable OSD timeout. Default: 4s (+/- 1s)" +msgstr "" + +#: +msgctxt "#33717" +msgid "Debug requests" +msgstr "" + +#: +msgctxt "#33718" +msgid "Played" +msgstr "" + +#: +msgctxt "#33719" +msgid "Refresh metadata" +msgstr "" + +#: +msgctxt "#33038" +msgid "When encountering an intro marker with a start time offset greater than this, ignore it (default: 1400s/23m)" +msgstr "" + +#: +msgctxt "#33649" +msgid "\"Use alternate seek\" wait for seek" +msgstr "" + +#: +msgctxt "#33650" +msgid "This adjusts the delay between seek-tries on problematic platforms when \"Use alternate seek\" is enabled, which fixes resume not always working or double-seeking. When you have resume/double-seek issues, increase this. Default: 500ms" +msgstr "" + +#: +msgctxt "#33667" +msgid "Use alternate seek" +msgstr "" + +#: +msgctxt "#33668" +msgid "ATTENTION: Only enable this if you have reproducible audio issues after seeking/resuming.\n" +"\n" +"Use an alternative seek method in videos, which can help in problematic scenarios; brings its own issues/quirks. Disabled by default (enabled by default for CoreELEC and LG WebOS)" +msgstr "" + +#: +msgctxt "#33720" +msgid "Clear all caches" +msgstr "" + +#: +msgctxt "#33721" +msgid "Clear library cache (not items)" +msgstr "" + +#: +msgctxt "#33722" +msgid "Libraries" +msgstr "" + +#: +msgctxt "#33723" +msgid "Media Items" +msgstr "" + +#: +msgctxt "#33724" +msgid "Cache Plex data for" +msgstr "" + +#: +msgctxt "#33725" +msgid "Persist cached Plex data" +msgstr "" + +#: +msgctxt "#33726" +msgid "Instead of clearing the cache when exiting the addon, persist it instead. Warning: You'll most likely encounter missing items in libraries or outdated data. Use the corresponding menu functionalities to clear the cache for specific items or libraries." +msgstr "" + +#: +msgctxt "#33727" +msgid "Store Plex server responses for items and library views in a local SQLite database. Doesn't cache anything else (Home/Hubs are always up to date). Massively speeds up consecutive visits to items and libraries. Certain important events, such as watch state changes, automatically delete the item cache and its corresponding library cache. (Default: Off)" +msgstr "" + +#: +msgctxt "#33728" +msgid "Clear cache for item" +msgstr "" + +#: +msgctxt "#33729" +msgid "Expire cached Plex data after (hours)" +msgstr "" + +#: +msgctxt "#33730" +msgid "Randomly" +msgstr "" + +#: +msgctxt "#33731" +msgid "By Bitrate" +msgstr "" + +#: +msgctxt "#33732" +msgid "Bitrate" +msgstr "" + +#: +msgctxt "#33733" +msgid "Transcode target codec" +msgstr "" + +#: +msgctxt "#33734" +msgid "Sets the target codec when transcoding/direct streaming. Overridden when \"Transcode audio to AC3\" is set." +msgstr "" + +#: +msgctxt "#33735" +msgid "Always auto-start" +msgstr "" + +#: +msgctxt "#33736" +msgid "By default PM4K won't auto-start when the current window isn't a Home-derived window (e.g.: Settings). Enable this if you always want to auto-start PM4K regardless of the current window. Can also improve start-up time." +msgstr "" + +#: +msgctxt "#33737" +msgid "Loop theme music" +msgstr "" + +#: +msgctxt "#33671" +msgid "Current: {current_version}\n" +"New: {new_version}\n" +"\n" +"Changelog:\n" +"{changelog}" +msgstr "" + +#: +msgctxt "#33738" +msgid "Max playlist size" +msgstr "" + +#: +msgctxt "#33739" +msgid "How many items to load into a Kodi playlist before chunking them remotely using Plex PlayQueues (default: 500); Reduce on memory issues/crashes." +msgstr "" + +#: +msgctxt "#33740" +msgid "Home: Episodes season thumbnails" +msgstr "" + +#: +msgctxt "#33741" +msgid "Use season thumbnails/posters when displaying episodes in hubs instead of the TV show's." +msgstr "" + +#: +msgctxt "#34000" +msgid "Watchlist" +msgstr "" + +#: +msgctxt "#34001" +msgid "Released" +msgstr "" + +#: +msgctxt "#34002" +msgid "Movies & Shows" +msgstr "" + +#: e.g.: 8 seasons +msgctxt "#34003" +msgid "{} seasons" +msgstr "" + +#: +msgctxt "#34004" +msgid "Choose server" +msgstr "" + +#: +msgctxt "#34005" +msgid "Availability" +msgstr "" + +#: e.g.: 1 season +msgctxt "#34006" +msgid "{} season" +msgstr "" + +#: +msgctxt "#34007" +msgid "Use Watchlist" +msgstr "" + +#: +msgctxt "#34008" +msgid "Activates the current user's Plex watchlist as a section item. Adds watchlist functionality to certain media screens. Per-user setting. Default: On" +msgstr "" + +#: +msgctxt "#34009" +msgid "Watchlist auto-remove" +msgstr "" + +#: +msgctxt "#34010" +msgid "Automatically remove fully watched items from watchlist. Default: On" +msgstr "" + +#: +msgctxt "#34011" +msgid "Remove from watchlist" +msgstr "" + +#: +msgctxt "#34012" +msgid "Fast pause/resume" +msgstr "" + +#: +msgctxt "#34013" +msgid "when paused" +msgstr "" + +#: +msgctxt "#34014" +msgid "when playing" +msgstr "" + +#: +msgctxt "#34015" +msgid "User-specific. Use OK/ENTER button to pause instead of showing the OSD (which can then only be accessed using DOWN), or resume when paused. Only works with 'Behave like official Plex clients' enabled." +msgstr "" + +#: +msgctxt "#34016" +msgid "Max shutdown wait" +msgstr "" + +#: +msgctxt "#34017" +msgid "The plugin will try to hard exit once this time limit is reached (default: 5 seconds)" +msgstr "" + +#: +msgctxt "#34018" +msgid "Related Media" +msgstr "" + +#: watchlist discover hub title prefix +msgctxt "#34019" +msgid "Discover: {}" +msgstr "" + +#: +msgctxt "#34020" +msgid "Loading external hubs" +msgstr "" + +#: +msgctxt "#34021" +msgid "Please wait ..." +msgstr "" + +#: +msgctxt "#34022" +msgid "Video play completion behaviour" +msgstr "" + +#: +msgctxt "#34023" +msgid "Decide whether to use end credits markers to determine the 'watched' state of video items. When markers are not available the selected threshold percentage will be used." +msgstr "" + +#: +msgctxt "#34024" +msgid "at selected threshold percentage" +msgstr "" + +#: +msgctxt "#34025" +msgid "at final credits marker position" +msgstr "" + +#: +msgctxt "#34026" +msgid "at first credits marker position" +msgstr "" + +#: +msgctxt "#34027" +msgid "earliest between threshold percent and first credits marker" +msgstr "" + +#: +msgctxt "#32973" +msgid "Episodes: Continuous playback" +msgstr "" + +#: +msgctxt "#34028" +msgid "\"Use alternate seek\" valid seek window" +msgstr "" + +#: +msgctxt "#34029" +msgid "When \"Use alternate seek\" is enabled, any seek amount outside of this threshold window will be applied, otherwise the seek attempt will be ignored. If you encounter any seek issues, play with this value (ms, default: 2000)" +msgstr "" + +#: +msgctxt "#34030" +msgid "Unlock Direct Play for above 4K" +msgstr "" + +#: +msgctxt "#34031" +msgid "Producer" +msgstr "" + +#: +msgctxt "#34032" +msgid "Audio Language" +msgstr "" + +#: +msgctxt "#34033" +msgid "Subtitle Language" +msgstr "" + +#: +msgctxt "#34034" +msgid "Folder Location" +msgstr "" + +#: +msgctxt "#34035" +msgid "Editions" +msgstr "" + +#: +msgctxt "#34036" +msgid "DOVI" +msgstr "" + +#: +msgctxt "#34037" +msgid "HDR" +msgstr "" + +#: +msgctxt "#34038" +msgid "Force plex.direct mapping" +msgstr "" + +#: +msgctxt "#34039" +msgid "If you (still) don't see any posters: Under certain circumstances when Kodi still can't resolve hostnames when loading assets, even though the host resolves natively, enable this to force plex.direct mapping via advancedsettings.xml." +msgstr "" + +#: +msgctxt "#34040" +msgid "By Progress" +msgstr "" + +#: +msgctxt "#34041" +msgid "Progress" +msgstr "" + +#: +msgctxt "#34042" +msgid "By Album" +msgstr "" + +#: +msgctxt "#34043" +msgid "Album" +msgstr "" + diff --git a/script.plexmod/resources/language/resource.language.zh_cn/strings.po b/script.plexmod/resources/language/resource.language.zh_cn/strings.po index be188a2d8c..12801419c7 100644 --- a/script.plexmod/resources/language/resource.language.zh_cn/strings.po +++ b/script.plexmod/resources/language/resource.language.zh_cn/strings.po @@ -17,46 +17,6 @@ msgctxt "#32001" msgid "Original" msgstr "原始" -#: -msgctxt "#32002" -msgid "20 Mbps 1080p" -msgstr "20 Mbps 1080p" - -#: -msgctxt "#32003" -msgid "12 Mbps 1080p" -msgstr "12 Mbps 1080p" - -#: -msgctxt "#32004" -msgid "10 Mbps 1080p" -msgstr "10 Mbps 1080p" - -#: -msgctxt "#32005" -msgid "8 Mbps 1080p" -msgstr "8 Mbps 1080p" - -#: -msgctxt "#32006" -msgid "4 Mbps 720p" -msgstr "4 Mbps 720p" - -#: -msgctxt "#32007" -msgid "3 Mbps 720p" -msgstr "3 Mbps 720p" - -#: -msgctxt "#32008" -msgid "2 Mbps 720p" -msgstr "2 Mbps 720p" - -#: -msgctxt "#32009" -msgid "1.5 Mbps 480p" -msgstr "1.5 Mbps 480p" - #: msgctxt "#32010" msgid "720 kbps" @@ -107,16 +67,6 @@ msgctxt "#32024" msgid "Debug Logging" msgstr "调试日志" -#: -msgctxt "#32025" -msgid "Direct Play" -msgstr "直接播放" - -#: -msgctxt "#32026" -msgid "Direct Stream" -msgstr "直接串流" - #: msgctxt "#32027" msgid "Force" @@ -282,11 +232,6 @@ msgctxt "#32100" msgid "Skip user selection and pin entry on startup." msgstr "在启动时跳过用户选择和 PIN 码输入。" -#: -msgctxt "#32101" -msgid "If enabled, when playback ends and there is a 'Next Up' item available, it will be automatically be played after a {} second delay." -msgstr "如果启用,当播放结束且有 “下一个” 项目可用时,它将在 {} 秒延迟后自动播放。" - #: msgctxt "#32102" msgid "Enable this if your hardware can handle 4K playback. Disable it to force transcoding." @@ -627,16 +572,6 @@ msgctxt "#32356" msgid "Date Viewed" msgstr "观看日期" -#: -msgctxt "#32357" -msgid "By Title" -msgstr "按标题" - -#: -msgctxt "#32358" -msgid "Title" -msgstr "标题" - #: msgctxt "#32359" msgid "By Rating" @@ -1481,13 +1416,6 @@ msgctxt "#33505" msgid "Show intro skip button early" msgstr "提前显示跳过片头按钮" -#: -msgctxt "#33506" -msgid "Show the intro skip button from the start of a video with an intro marker. The auto-skipping setting applies. Doesn't override enabled binge mode.\n" -"Can be disabled/enabled per TV show." -msgstr "在有跳过片头标记的视频开始时显示跳过片头按钮。适用于自动跳过。不会覆盖启用的追剧模式。\n" -"可以单独为每个电视节目禁用/启用。" - #: msgctxt "#33507" msgid "Enabled" @@ -1498,11 +1426,6 @@ msgctxt "#33508" msgid "Disabled" msgstr "禁用" -#: -msgctxt "#33509" -msgid "Early intro skip threshold (default: < 60s/1m)" -msgstr "提前跳过片头阈值(默认:< 60s/1m)" - #: msgctxt "#33510" msgid "When showing the intro skip button early, only do so if the intro occurs within the first X seconds." @@ -1603,27 +1526,6 @@ msgctxt "#33618" msgid "TV binge-viewing mode" msgstr "追剧模式" -#: -msgctxt "#33619" -msgid "Automatically skips episode intros, credits and tries to skip episode recaps. Doesn't skip the intro of the first episode of a season and doesn't skip the final credits of a show.\n" -"\n" -"Can be disabled/enabled per TV show.\n" -"Overrides any setting below." -msgstr "自动跳过片头、片尾,并尝试跳过剧集回顾。不跳过每季第一集的片头,不跳过电视节目的最后一个片尾。\n" -"\n" -"可以单独为每个电视节目禁用/启用。\n" -"覆盖任何以下设置。" - -#: -msgctxt "#33620" -msgid "Plex server connect timeout" -msgstr "Plex 服务器连接超时" - -#: -msgctxt "#33621" -msgid "Sets the maximum amount of time to connect to a Plex Server in seconds. Default: 5" -msgstr "设置尝试连接 Plex 服务器的最长时间(以秒为单位)。默认:5" - #: msgctxt "#33622" msgid "LAN reachability timeout (ms)" @@ -1888,26 +1790,6 @@ msgctxt "#32931" msgid "Audio/Subtitles" msgstr "音频/字幕" -#: -msgctxt "#32932" -msgid "Subtitle quick-actions" -msgstr "字幕快捷操作" - -#: -msgctxt "#32933" -msgid "FFWD/RWD" -msgstr "快进/快退" - -#: -msgctxt "#32934" -msgid "Repeat" -msgstr "循环播放" - -#: -msgctxt "#32935" -msgid "Shuffle" -msgstr "随机播放" - #: msgctxt "#32936" msgid "Show playlist button" @@ -1918,16 +1800,6 @@ msgctxt "#32937" msgid "Show prev/next button" msgstr "显示上一个/下一个按钮" -#: -msgctxt "#32939" -msgid "User-specific.\nOnly applies to video player UI" -msgstr "用户自定义。\n仅适用于视频播放器界面" - -#: -msgctxt "#32940" -msgid "Video Player" -msgstr "视频播放器" - #: msgctxt "#32941" msgid "Forced subtitles fix" @@ -2109,11 +1981,6 @@ msgctxt "#32976" msgid "Adaptive" msgstr "自适应" -#: -msgctxt "#32977" -msgid "VC1" -msgstr "VC1" - #: msgctxt "#32978" msgid "Enable this if your hardware can handle VC1. Disable it to force transcoding." @@ -2139,18 +2006,6 @@ msgctxt "#32982" msgid "Depending on how many cores your CPU has and how much it can handle, increasing this might improve certain situations. If you experience crashes or other annormalities, leave this at its default (3). Needs an addon restart." msgstr "根据您的 CPU 核心数及其处理能力,增加此值可能会改善某些使用场景的体验。如果您遇到崩溃或其他异常情况,请使用默认值(3)。重启插件后生效。" -#: -msgctxt "#32983" -msgid "Theme" -msgstr "主题" - -#: -msgctxt "#32984" -msgid "Sets the theme. Currently only customizes all control buttons. ATTENTION: [I]Might[/I] need an addon restart.\n" -"In order to customize this, copy one of the xml's in script.plexmod/resources/skins/Main/1080i/templates to addon_data/script.plexmod/templates/{templatename}_custom.xml and adjust it to your liking, then select \"Custom\" as your theme." -msgstr "设置主题。目前仅支持自定义控制按钮。注意:[I]可能[/I]需要重启插件。\n" -"要自定义此项,请将 script.plexmod/resources/skins/Main/1080i/templates 中的一个 XML 文件复制到 addon_data/script.plexmod/templates/{templatename}_custom.xml 并根据您的喜好进行调整,然后选择 “自定义” 作为主题。" - #: msgctxt "#32985" msgid "Modern" @@ -2231,11 +2086,6 @@ msgctxt "#33000" msgid "Enable path mapping" msgstr "启用路径映射" -#: -msgctxt "#33001" -msgid "Long-press (or context menu) on a library item or: Honor path_mapping.json in the addon_data/script.plexmod folder when DirectPlaying media. This can be used to stream using other techniques such as SMB/NFS/etc. instead of the default HTTP handler. path_mapping.example.json is included in the addon's main directory." -msgstr "长按(或右键菜单)库项目或:在 DirectPlaying 媒体时遵循 addon_data/script.plexmod 文件夹中的 path_mapping.json。这可以用于使用 SMB/NFS 等其他技术进行流式传输,而不是默认的 HTTP 处理程序。path_mapping.example.json 包含在插件的主目录中。" - #: msgctxt "#33002" msgid "Verify mapped files exist" @@ -2261,36 +2111,11 @@ msgctxt "#33006" msgid "No TV spoilers" msgstr "无剧透" -#: -msgctxt "#33007" -msgid "Select in which cases to blur TV episode thumbnails, previews, redact summaries, hide episode titles and whether to blur chapter images. When the Addon Setting \"Use episode thumbnails in continue hub\" is enabled, blur them as well." -msgstr "选择在哪些情况下模糊电视节目的剧集缩略图、预览、简介、隐藏剧集标题以及是否模糊章节图像。即使在插件设置种启用了 “在继续观看使用剧集缩略图”,也会模糊它们。" - #: msgctxt "#33008" msgid "[Spoilers removed]" msgstr "[剧透已移除]" -#: -msgctxt "#33009" -msgid "Blur amount for unwatched/in-progress episodes" -msgstr "未观看/观看中剧集的缩略图的模糊程度" - -#: -msgctxt "#33010" -msgid "Unwatched" -msgstr "未观看" - -#: -msgctxt "#33011" -msgid "In progress" -msgstr "观看中" - -#: -msgctxt "#33012" -msgid "No unwatched episode titles" -msgstr "无未观看的剧集" - #: msgctxt "#33013" msgid "When the above is anything but \"off\", hide episode titles as well." @@ -2306,16 +2131,6 @@ msgctxt "#33015" msgid "When checking for plex.direct host mapping, ignore local Docker IPv4 addresses (172.16.0.0/12)." msgstr "在检查 plex.direct 主机映射时,忽略本地 Docker IPv4 地址(172.16.0.0/12)。" -#: -msgctxt "#33016" -msgid "Allow TV spoilers for" -msgstr "允许剧透" - -#: -msgctxt "#33017" -msgid "Overrides the above for specific genres. Default: Reality, Game Show, Documentary, Sport" -msgstr "为指定类型的电视节目应用上述设置。默认:真人秀、游戏、纪录片、体育" - #: msgctxt "#32303" msgid "Season {}" @@ -2361,32 +2176,6 @@ msgctxt "#33021" msgid "Choose action" msgstr "选择操作" -#: -msgctxt "#33022" -msgid "Watched indicators" -msgstr "观看状态角标" - -#: -msgctxt "#33023" -msgid "Classic: Show orange triangle for unwatched items\n" -"Modern: Show green checkmark for watched items\n" -"Modern (2024): Show white checkmark for watched items\n" -"(default: Modern (2024))" -msgstr "经典:为未观看的项目显示橙色三角形角标\n" -"现代:为已观看的项目显示绿色对钩角标\n" -"现代(2024):为已观看的项目显示白色对钩角标\n" -"(默认:现代(2024))" - -#: -msgctxt "#33024" -msgid "Hide background in modern indicators" -msgstr "为现代角标隐藏背景" - -#: -msgctxt "#33025" -msgid "When the above is enabled, hide the black backdrop of the watched state." -msgstr "启用上述功能时,隐藏观看状态角标的黑色背景。" - #: msgctxt "#33026" msgid "Map path: {}" @@ -2442,11 +2231,6 @@ msgctxt "#33037" msgid "Maximum intro offset to consider" msgstr "最大片头偏移量" -#: -msgctxt "#33038" -msgid "When encountering an intro marker with a start time offset greater than this, ignore it (default: 600s/10m)" -msgstr "当遇到开始时间偏移量大于此值的片头标记时,忽略它(默认:600 秒/10 分钟)" - #: msgctxt "#33039" msgid "Move" @@ -2462,6 +2246,16 @@ msgctxt "#33034" msgid "Library settings" msgstr "资料库设置" +#: +msgctxt "#32357" +msgid "By Title" +msgstr "按标题" + +#: +msgctxt "#32358" +msgid "Title" +msgstr "标题" + #: msgctxt "#33041" msgid "Show hub: {}" @@ -2472,89 +2266,153 @@ msgctxt "#33042" msgid "Episode Date Added" msgstr "剧集添加日期" +#: +msgctxt "#33001" +msgid "Long-press (or context menu) on a library item or: Honor path_mapping.json in the addon_data/script.plexmod folder when DirectPlaying media. This can be used to stream using other techniques such as SMB/NFS/etc. instead of the default HTTP handler. path_mapping.example.json is included in the addon's main directory." +msgstr "长按(或右键菜单)库项目或:在 DirectPlaying 媒体时遵循 addon_data/script.plexmod 文件夹中的 path_mapping.json。这可以用于使用 SMB/NFS 等其他技术进行流式传输,而不是默认的 HTTP 处理程序。path_mapping.example.json 包含在插件的主目录中。" + #: msgctxt "#33043" msgid "Hubs round-robin" msgstr "内容版块循环" -#: -msgctxt "#33044" -msgid "Allow round-robining in hubs. Attention: Loads a lot of items. Depending on the hub size, this can lead to crashes. Current limit: {}; can be adjusted in addon settings." -msgstr "允许内容版块循环显示项目。注意:会加载大量项目。根据版块内容的多少,有可能导致崩溃。当前限制:{};可以在插件设置中进行调整。" - #: msgctxt "#33045" msgid "Behave like official Plex clients" msgstr "遵循 Plex 官方客户端" #: -msgctxt "#33046" -msgid "When pressing up/down while the OSD isn't shown, show OSD instead of skipping chapters. Additionally, when pressing down while the OSD is shown, open the chapters (if available)." -msgstr "在未显示屏幕菜单时,按上下键显示屏幕菜单而不是章节。同时,在显示屏幕菜单时,按下键打开章节(如果有)。" +msgctxt "#32025" +msgid "Direct Play" +msgstr "直接播放" + +#: +msgctxt "#32026" +msgid "Direct Stream" +msgstr "直接串流" -#: +#: msgctxt "#32036" msgid "4K" msgstr "4K" -#: +#: msgctxt "#32037" msgid "HEVC (h265)" msgstr "HEVC (h265)" -#: +#: msgctxt "#32601" msgid "AV1" msgstr "AV1" #: -msgctxt "#33047" -msgid "Hubs round-robin item limit" -msgstr "内容版块循环项目数量限制" +msgctxt "#32932" +msgid "Subtitle quick-actions" +msgstr "字幕快捷操作" #: -msgctxt "#33048" -msgid "When hubs round-robin is enabled, only round-robin until this item limit. If the hub size exceeds this limit, the remaining items will be lazy loaded as usual. Tested minimum safe value on NVIDIA SHIELD 2019 is 1000. Default: 250" -msgstr "在启用内容版块循环时,仅循环数量限制以内的项目。如果版块项目超过此限制,剩余项目将延迟加载。NVIDIA SHIELD 2019 上测试的最小安全值为 1000。默认值:250" +msgctxt "#32933" +msgid "FFWD/RWD" +msgstr "快进/快退" #: -msgctxt "#33049" -msgid "Max retries" -msgstr "最大重试次数" +msgctxt "#32934" +msgid "Repeat" +msgstr "循环播放" #: -msgctxt "#33050" -msgid "The maximum number of retries each connection should attempt. The default (3) helps with hibernation/wakeup issues. Set higher for bad connections, setting this to 0 was the old default before 0.7.9" -msgstr "每次连接可尝试的最大重试次数。默认值(3)有助于解决休眠/唤醒问题。若连接情况不佳,可以设置的大一些。版本 0.7.9 之前的默认值为 0" +msgctxt "#32935" +msgid "Shuffle" +msgstr "随机播放" #: -msgctxt "#33051" -msgid "Use CA certificate bundle" -msgstr "使用 CA 证书包" +msgctxt "#32939" +msgid "User-specific.\n" +"Only applies to video player UI" +msgstr "用户自定义。\n" +"仅适用于视频播放器界面" #: -msgctxt "#33052" -msgid "Which CA certificate bundle to use for request HTTPS verification. Default is \"system\", which uses the system-provided certificate bundle. \"plex.direct\" uses an extremely small bundle provided with the addon, which only applies to plex.direct connections (any other connections will use the system bundle) and might slightly improve performance. \"custom\" allows for a certificate bundle in userdata/addon_data/script.plexmod/custom_bundle.crt" -msgstr "用于请求 HTTPS 验证的 CA 证书包。默认是 “系统”,使用系统提供的证书包。“plex.direct” 使用插件提供的极小包,仅适用于 plex.direct 连接(任何其他连接将使用系统包),可能略微提高性能。“自定义” 允许在 userdata/addon_data/script.plexmod/custom_bundle.crt 中使用证书包。" +msgctxt "#32977" +msgid "VC1" +msgstr "VC1" #: -msgctxt "#33053" -msgid "System" -msgstr "系统" +msgctxt "#32983" +msgid "Theme" +msgstr "主题" #: -msgctxt "#33054" -msgid "plex.direct (addon-supplied)" -msgstr "plex.direct(插件提供)" +msgctxt "#32984" +msgid "Sets the theme. Currently only customizes all control buttons. ATTENTION: [I]Might[/I] need an addon restart.\n" +"In order to customize this, copy one of the xml's in script.plexmod/resources/skins/Main/1080i/templates to addon_data/script.plexmod/templates/{templatename}_custom.xml and adjust it to your liking, then select \"Custom\" as your theme." +msgstr "设置主题。目前仅支持自定义控制按钮。注意:[I]可能[/I]需要重启插件。\n" +"要自定义此项,请将 script.plexmod/resources/skins/Main/1080i/templates 中的一个 XML 文件复制到 addon_data/script.plexmod/templates/{templatename}_custom.xml 并根据您的喜好进行调整,然后选择 “自定义” 作为主题。" #: -msgctxt "#33055" -msgid "Custom" -msgstr "自定义" +msgctxt "#33011" +msgid "In progress" +msgstr "观看中" #: -msgctxt "#33056" -msgid "None" +msgctxt "#33016" +msgid "Allow TV spoilers for" +msgstr "允许剧透" + +#: +msgctxt "#33017" +msgid "Overrides the above for specific genres. Default: Reality, Game Show, Documentary, Sport" +msgstr "为指定类型的电视节目应用上述设置。默认:真人秀、游戏、纪录片、体育" + +#: +msgctxt "#33024" +msgid "Hide background in modern indicators" +msgstr "为现代角标隐藏背景" + +#: +msgctxt "#33044" +msgid "Allow round-robining in hubs. Attention: Loads a lot of items. Depending on the hub size, this can lead to crashes. Current limit: {}; can be adjusted in addon settings." +msgstr "允许内容版块循环显示项目。注意:会加载大量项目。根据版块内容的多少,有可能导致崩溃。当前限制:{};可以在插件设置中进行调整。" + +#: +msgctxt "#33046" +msgid "When pressing up/down while the OSD isn't shown, show OSD instead of skipping chapters. Additionally, when pressing down while the OSD is shown, open the chapters (if available)." +msgstr "在未显示屏幕菜单时,按上下键显示屏幕菜单而不是章节。同时,在显示屏幕菜单时,按下键打开章节(如果有)。" + +#: +msgctxt "#33047" +msgid "Hubs round-robin item limit" +msgstr "内容版块循环项目数量限制" + +#: +msgctxt "#33048" +msgid "When hubs round-robin is enabled, only round-robin until this item limit. If the hub size exceeds this limit, the remaining items will be lazy loaded as usual. Tested minimum safe value on NVIDIA SHIELD 2019 is 1000. Default: 250" +msgstr "在启用内容版块循环时,仅循环数量限制以内的项目。如果版块项目超过此限制,剩余项目将延迟加载。NVIDIA SHIELD 2019 上测试的最小安全值为 1000。默认值:250" + +#: +msgctxt "#33049" +msgid "Max retries" +msgstr "最大重试次数" + +#: +msgctxt "#33051" +msgid "Use CA certificate bundle" +msgstr "使用 CA 证书包" + +#: +msgctxt "#33053" +msgid "System" +msgstr "系统" + +#: +msgctxt "#33055" +msgid "Custom" +msgstr "自定义" + +#: +msgctxt "#33056" +msgid "None" msgstr "无" #: @@ -2649,11 +2507,6 @@ msgctxt "#33074" msgid "Waiting {} second(s)" msgstr "等待 {} 秒" -#: -msgctxt "#33075" -msgid "Do something after waking up. Waiting for a time until resuming normal execution helps with networks coming back up after sleep events. Default: 1 second (5 seconds, CoreELEC)." -msgstr "唤醒后执行一些操作。等待一段时间后恢复正常操作有助于网络在休眠事件后恢复。默认:1 秒(5 秒,CoreELEC)" - #: msgctxt "#33076" msgid "Modern (2024)" @@ -2709,6 +2562,31 @@ msgctxt "#33086" msgid "Press the key you want to map to go to home within {} seconds" msgstr "在 {} 秒内按下您想映射的前往主页的按键" +#: +msgctxt "#33620" +msgid "Plex server connect timeout" +msgstr "Plex 服务器连接超时" + +#: +msgctxt "#33621" +msgid "Sets the maximum amount of time to connect to a Plex Server in seconds. Default: 5" +msgstr "设置尝试连接 Plex 服务器的最长时间(以秒为单位)。默认:5" + +#: +msgctxt "#32940" +msgid "Video Player" +msgstr "视频播放器" + +#: +msgctxt "#33050" +msgid "The maximum number of retries each connection should attempt. The default (3) helps with hibernation/wakeup issues. Set higher for bad connections, setting this to 0 was the old default before 0.7.9" +msgstr "每次连接可尝试的最大重试次数。默认值(3)有助于解决休眠/唤醒问题。若连接情况不佳,可以设置的大一些。版本 0.7.9 之前的默认值为 0" + +#: +msgctxt "#33075" +msgid "Do something after waking up. Waiting for a time until resuming normal execution helps with networks coming back up after sleep events. Default: 1 second (5 seconds, CoreELEC)." +msgstr "唤醒后执行一些操作。等待一段时间后恢复正常操作有助于网络在休眠事件后恢复。默认:1 秒(5 秒,CoreELEC)" + #: msgctxt "#33087" msgid "Bind a key to immediately go to the Home view. Cancel using BACK/PREVIOUS_MENU. Clear bound key using the STOP or CONTEXT_MENU (long press OK/Enter) button on the setting." @@ -2789,21 +2667,11 @@ msgctxt "#33638" msgid "Plex.tv connect timeout" msgstr "Plex.tv 连接超时" -#: -msgctxt "#33639" -msgid "Plex.tv: Sets the maximum amount of time to connect to Plex.tv in seconds. Default: 5" -msgstr "Plex.tv:设置尝试连接 Plex.tv 的最长时间(以秒为单位)。默认:5" - #: msgctxt "#33640" msgid "Plex.tv read timeout" msgstr "Plex.tv 读取超时" -#: -msgctxt "#33641" -msgid "Plex.tv: Sets the maximum amount of time to read from Plex.tv in seconds. Default: 20" -msgstr "Plex.tv:设置尝试从 Plex.tv 读取的最长时间(以秒为单位)。默认:20" - #: msgctxt "#33642" msgid "Dump config" @@ -2840,14 +2708,9 @@ msgid "Only handle plex.direct hosts when the server's attributes publicAddressM msgstr "仅当服务器属性 publicAddressMatches=1 时处理 plex.direct 主机。在某些情况下可能不起作用。如果出现连接问题,请禁用此选项。默认:启用" #: -msgctxt "#33649" -msgid "CoreELEC: Resume-fix wait for seek" -msgstr "CoreELEC:恢复修复等待跳转" - -#: -msgctxt "#33650" -msgid "This adjusts the delay between seek-tries on CoreELEC, which fixes resume not always working or double-seeking. When you have resume/double-seek issues, increase this. 100ms should be stable as well. Default: 350ms" -msgstr "调整 CoreELEC 上跳转尝试之间的延迟,修复恢复播放不总是有效或双重跳转的问题。当出现恢复/双重跳转问题时,请增加此值。100ms 也应是稳定的。默认值:350ms" +msgctxt "#32101" +msgid "If enabled, when playback ends and there is a 'Next Up' item available, it will be automatically be played after a {} second delay." +msgstr "如果启用,当播放结束且有 “下一个” 项目可用时,它将在 {} 秒延迟后自动播放。" #: msgctxt "#33651" @@ -2859,3 +2722,844 @@ msgctxt "#33652" msgid "Never show Post Play" msgstr "禁用播放结束界面" +#: +msgctxt "#33506" +msgid "Show the intro skip button from the start of a video with an intro marker. The auto-skipping setting applies. Doesn't override enabled binge mode.\n" +"Can be disabled/enabled per TV show." +msgstr "在有跳过片头标记的视频开始时显示跳过片头按钮。适用于自动跳过。不会覆盖启用的追剧模式。\n" +"可以单独为每个电视节目禁用/启用。" + +#: +msgctxt "#33619" +msgid "Automatically skips episode intros, credits and tries to skip episode recaps. Doesn't skip the intro of the first episode of a season and doesn't skip the final credits of a show.\n" +"\n" +"Can be disabled/enabled per TV show.\n" +"Overrides any setting below." +msgstr "自动跳过片头、片尾,并尝试跳过剧集回顾。不跳过每季第一集的片头,不跳过电视节目的最后一个片尾。\n" +"\n" +"可以单独为每个电视节目禁用/启用。\n" +"覆盖任何以下设置。" + +#: +msgctxt "#33052" +msgid "Which CA certificate bundle to use for request HTTPS verification. \"system\" uses the system-provided certificate bundle. \"ACME\" uses an extremely small bundle provided with the addon, which includes root certificates for Letsencrypt (plex.direct) and ZeroSSL. \"custom\" allows for a ca-certificate bundle in userdata/addon_data/script.plexmod/custom_bundle.crt" +msgstr "用于请求 HTTPS 验证的 CA 证书包。“system” 使用系统提供的证书捆绑包。“ACME” 使用插件提供的极小捆绑包,其中包括 Letsencrypt (plex.direct) 和 ZeroSSL 的根证书。“custom” 允许在userdata/addon_data/script.plexmod/custom_bundle.crt 中使用 CA 证书包" + +#: +msgctxt "#33054" +msgid "ACME (addon-supplied)" +msgstr "ACME (插件提供)" + +#: +msgctxt "#33639" +msgid "Plex.tv: Sets the maximum amount of time to connect to Plex.tv in seconds. Default: 1" +msgstr "Plex.tv:设置连接到 Plex.tv 的最长时间(以秒为单位)。默认值:1" + +#: +msgctxt "#33641" +msgid "Plex.tv: Sets the maximum amount of time to read from Plex.tv in seconds. Default: 2" +msgstr "Plex.tv:设置从 Plex.tv 读取的最长时间(以秒为单位)。默认值:2" + +#: +msgctxt "#33653" +msgid "Background image resolution scaling %" +msgstr "背景图像分辨率缩放 %" + +#: +msgctxt "#33654" +msgid "In percent, based on 1080p. Scales the resolution of all backgrounds for better image quality. May impact PMS/PM4K performance, will increase the cache usage accordingly. Can lead to crashes if your hardware is low on RAM. Needs addon restart. Use 400% for 4K. ATTENTION: In order for this to work you need to add 2160 and 2160 to your advancedsettings.xml." +msgstr "以百分比表示,基于 1080p。缩放所有背景的分辨率以获得更好的图像质量。可能会影响 PMS/PM4K 性能,将相应地增加缓存使用量。如果您的硬件 RAM 不足,可能会导致崩溃。需要重新启动插件。4K 使用 400%。注意:要使其正常工作,您需要将 21602160 添加到您的 advancedsettings.xml 中。" + +#: +msgctxt "#33655" +msgid "Auto-Sync Subtitles" +msgstr "自动同步字幕" + +#: +msgctxt "#33656" +msgid "Only for External SRT subtitles. The PMS setting for voice activity detection has to be enabled for this to work." +msgstr "仅适用于外部 SRT 字幕。必须启用语音活动检测的 PMS 设置才能正常工作。" + +#: in player quick settings UI, needs to be short +msgctxt "#33657" +msgid "Enable Auto-Sync" +msgstr "启用自动同步" + +#: in player quick settings UI, needs to be short +msgctxt "#33658" +msgid "Disable Auto-Sync" +msgstr "禁用自动同步" + +#: +msgctxt "#33659" +msgid "Hide hub: {}" +msgstr "隐藏集线器: {}" + +#: +msgctxt "#33660" +msgid "Disable HDR" +msgstr "禁用 HDR" + +#: +msgctxt "#33661" +msgid "If you don't want your client to handle HDR (or HDR-fallback), enable this to force transcoding. Doesn't apply to DV Profile 5." +msgstr "如果您不希望客户端处理 HDR(或 HDR-fallback),请启用此选项以强制转码。不适用于 DV Profile 5。" + +#: +msgctxt "#33662" +msgid "Remove from Continue Watching" +msgstr "Remove from Continue Watching" + +#: +msgctxt "#33663" +msgid "Home: Confirm item actions" +msgstr "主页:确认项目操作" + +#: +msgctxt "#33664" +msgid "When acting on items in the Home view, such as mark played, hide from continue watching etc., show a confirmation dialog." +msgstr "对主页视图中的项目执行操作时,如标记为已播放、隐藏继续播放等,会显示一个确认对话框。" + +#: +msgctxt "#33665" +msgid "Disable audio codecs" +msgstr "禁用音频编解码器" + +#: +msgctxt "#33666" +msgid "Audio codecs you can't play back. Disables Direct Play for such media items, enables Direct Stream if possible, transcodes audio stream to compatible format." +msgstr "无法播放的音频编解码器。对此类媒体项禁用 Direct Play,如果可能,启用 Direct Stream,将音频流转码为兼容格式。" + +#: +msgctxt "#32002" +msgid "20 Mbps" +msgstr "20 Mbps" + +#: +msgctxt "#32003" +msgid "12 Mbps" +msgstr "12 Mbps" + +#: +msgctxt "#32004" +msgid "10 Mbps" +msgstr "10 Mbps" + +#: +msgctxt "#32005" +msgid "8 Mbps" +msgstr "8 Mbps" + +#: +msgctxt "#32006" +msgid "4 Mbps" +msgstr "4 Mbps" + +#: +msgctxt "#32007" +msgid "3 Mbps" +msgstr "3 Mbps" + +#: +msgctxt "#32008" +msgid "2 Mbps" +msgstr "2 Mbps" + +#: +msgctxt "#32009" +msgid "1.5 Mbps" +msgstr "1.5 Mbps" + +#: +msgctxt "#33669" +msgid "Really sign out?" +msgstr "真的要退出登录吗?" + +#: +msgctxt "#33670" +msgid "Update available" +msgstr "可用更新" + +#: +msgctxt "#33672" +msgid "Check for updates" +msgstr "检查更新" + +#: +msgctxt "#33673" +msgid "Automatically check for updates periodically. If installed from a Kodi repository and the Update Source setting is set to Repository, Kodi itself will handle the updating of this addon. Needs a Kodi restart when changed." +msgstr "定期自动检查更新。如果从 Kodi 存储库安装并且 Update Source 设置设置为 Repository,则 Kodi 本身将处理此插件的更新。更改后需要重新启动 Kodi。" + +#: +msgctxt "#33674" +msgid "Check for updates on start" +msgstr "启动时检查更新" + +#: +msgctxt "#33675" +msgid "Automatically check for updates on startup. Doesn't do much when Update source is Repository. Needs a Kodi restart when changed." +msgstr "启动时自动检查更新。当更新源为 Repository (存储库) 时,不总是检查。更改后需要重新启动 Kodi。" + +#: +msgctxt "#33676" +msgid "Update source" +msgstr "更新源" + +#: +msgctxt "#33677" +msgid "Specifies the update mode. Will immediately check for a new version when changed and closing settings.\n" +"Default: Repository\n" +"\n" +"Beta: Bleeding edge (possibly unstable)\n" +"Stable: Stable branch (faster than Repository)\n" +"Repository: Kodi repository (official (slow) or Don't Panic)" +msgstr "指定更新模式。更改并关闭设置时,将立即检查新版本。\n" +"默认值:Repository\n" +"\n" +"Beta: Bleeding edge (possibly unstable)\n" +"Stable: Stable branch (faster than Repository)\n" +"Repository: Kodi repository (official (slow) or Don't Panic)" + +#: +msgctxt "#33678" +msgid "Beta" +msgstr "试用版" + +#: Update mode as in (beta, stable, repository) +msgctxt "#33679" +msgid "Stable" +msgstr "稳定" + +#: +msgctxt "#33680" +msgid "Repository" +msgstr "存储库" + +#: +msgctxt "#33683" +msgid "Exit, download and install" +msgstr "" + +#: +msgctxt "#33684" +msgid "Later" +msgstr "" + +#: +msgctxt "#32015" +msgid "6 Mbps" +msgstr "" + +#: +msgctxt "#32016" +msgid "16 Mbps" +msgstr "" + +#: +msgctxt "#32017" +msgid "26 Mbps" +msgstr "" + +#: +msgctxt "#33681" +msgid "Service updated" +msgstr "服务已更新" + +#: +msgctxt "#33682" +msgid "The update {} has had changes to the updater itself. In order for the updated updater service to work, a Kodi restart is necessary. The addon will work normally, though. Do you still want to run the addon?" +msgstr "update {} 对更新程序本身进行了更改。为了使更新的更新程序服务正常工作,必须重新启动 Kodi。不过,该插件将正常工作。您仍要运行插件吗?" + +#: +msgctxt "#33685" +msgid "Clamp video bitrate" +msgstr "限制视频比特率" + +#: +msgctxt "#33686" +msgid "Only show transcode bitrate targets lower than the current video's bitrate." +msgstr "仅显示低于当前视频比特率的转码比特率目标。" + +#: +msgctxt "#33687" +msgid "Translation updated" +msgstr "翻译已更新" + +#: +msgctxt "#33688" +msgid "The currently in-use translation has been updated. In order to load the new translation, a Kodi restart is necessary. The addon will still run properly, but you might see badly or untranslated strings. Do you still want to run the addon?" +msgstr "当前正在使用的翻译已更新。为了加载新的翻译,需要重新启动 Kodi。插件仍然可以正常运行,但您可能会看到翻译不好的或未翻译的字符串。您仍要运行插件吗?" + +#: +msgctxt "#33509" +msgid "Early intro skip threshold (default: < 120s/2m)" +msgstr "更早的跳过片头阈值(默认:< 120s/2m)" + +#: +msgctxt "#33007" +msgid "Select in which cases to blur TV episode thumbnails, previews, redact summaries, hide episode titles etc." +msgstr "选择在哪些情况下对电视剧集缩略图、预览、密文摘要进行模糊处理、隐藏剧集标题等。" + +#: +msgctxt "#33009" +msgid "Blur amount for unplayed/in-progress episodes" +msgstr "未播放/正在进行的剧集的模糊量" + +#: +msgctxt "#33010" +msgid "Unplayed" +msgstr "未播放" + +#: +msgctxt "#33012" +msgid "No unplayed episode titles" +msgstr "没有未播放的剧集标题" + +#: +msgctxt "#33022" +msgid "Played indicators" +msgstr "已播放指标" + +#: +msgctxt "#33023" +msgid "Classic: Show orange triangle for unplayed items\n" +"Modern: Show green checkmark for played items\n" +"Modern (2024): Show white checkmark for played items\n" +"(default: Modern (2024))" +msgstr "经典:为未播放的项目显示橙色三角形\n" +"现代:为已播放的项目显示绿色对勾标记\n" +"现代(2024 年):为已播放的项目显示白色对勾标记\n" +"(默认:现代 (2024))" + +#: +msgctxt "#33025" +msgid "When the above is enabled, hide the black backdrop of the played state." +msgstr "启用上述功能后,隐藏播放状态的黑色背景。" + +#: +msgctxt "#33689" +msgid "Service running" +msgstr "服务运行" + +#: +msgctxt "#33690" +msgid "Last update check" +msgstr "上次更新检查" + +#: +msgctxt "#33691" +msgid "Native languages" +msgstr "母语" + +#: +msgctxt "#33692" +msgid "When you usually watch things in a different language with subtitles, but are a native speaker of other languages, which you don't need subtitles for, prevent Plex from auto-selecting subtitles for those languages." +msgstr "当您通常使用其他语言观看带有字幕的内容,但母语为其他语言时,您不需要字幕,请阻止 Plex 自动选择这些语言的字幕。" + +#: +msgctxt "#33693" +msgid "Download subtitles using" +msgstr "下载字幕" + +#: +msgctxt "#33694" +msgid "Ask" +msgstr "问" + +#: +msgctxt "#33695" +msgid "Where do you want to download subtitles from? Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "您想从哪里下载字幕?注意:目前,这仅适用于播放器中的字幕快速操作。流设置中的字幕下载始终使用 Plex 作为源。" + +#: +msgctxt "#33696" +msgid "No subtitles found." +msgstr "未找到字幕。" + +#: things in curly brackets are variables, don't translate them! +msgctxt "#33697" +msgid "{provider_title}, Score: {subtitle_score}{subtitle_info}" +msgstr "{provider_title}, 分数: {subtitle_score}{subtitle_info}" + +#: hearing impaired subtitle flag +msgctxt "#33698" +msgid "HI" +msgstr "你好" + +#: forced subtitle flag +msgctxt "#33699" +msgid "forced" +msgstr "强制" + +#: +msgctxt "#33700" +msgid "Download subtitles: {}" +msgstr "下载字幕: {}" + +#: +msgctxt "#33701" +msgid "Fallback to Kodi" +msgstr "回退到 Kodi\n" +"Fallback to Kodi" + +#: +msgctxt "#33702" +msgid "When no subtitles are found via the Plex subtitle search, fall back to Kodi subtitle search. Note: Currently this only applies to the subtitle quick actions in the player. The subtitle download in stream settings always uses Plex as a source." +msgstr "如果通过 Plex 字幕搜索未找到字幕,请回退到 Kodi 字幕搜索。注意:目前,这仅适用于播放器中的字幕快速操作。流设置中的字幕下载始终使用 Plex 作为源。" + +#: +msgctxt "#33703" +msgid "Download subtitles" +msgstr "下载字幕" + +#: +msgctxt "#33704" +msgid "Using which service?" +msgstr "使用哪个服务?" + +#: +msgctxt "#33705" +msgid "Hide ratings" +msgstr "隐藏评分" + +#: +msgctxt "#33706" +msgid "Blur images" +msgstr "模糊图像" + +#: +msgctxt "#33707" +msgid "Blur images on home (in progress)" +msgstr "在家中对图像进行模糊处理(正在进行)" + +#: +msgctxt "#33708" +msgid "Hide summaries" +msgstr "隐藏摘要" + +#: +msgctxt "#33709" +msgid "Show ratings for" +msgstr "" + +#: +msgctxt "#33710" +msgid "Show reviews for" +msgstr "显示相关评论" + +#: +msgctxt "#33711" +msgid "Always resume media" +msgstr "始终恢复媒体" + +#: +msgctxt "#33712" +msgid "When playback of an in-progress media is requested, resume it by default instead of asking whether to resume or start from the beginning." +msgstr "请求播放正在进行的媒体时,默认情况下会恢复它,而不是询问是继续播放还是从头开始播放。" + +#: +msgctxt "#33713" +msgid "Home: Resume in-progress items" +msgstr "主页: 继续进行中的项目" + +#: +msgctxt "#33714" +msgid "Resume in-progress items directly instead of visiting the media." +msgstr "直接恢复正在进行的项目,而不是访问媒体。" + +#: +msgctxt "#33715" +msgid "OSD hide-delay" +msgstr "OSD 隐藏延迟" + +#: +msgctxt "#33716" +msgid "How long to wait until hiding the OSD. Only works with Plextuary skin or others with configurable OSD timeout. Default: 4s (+/- 1s)" +msgstr "要等待多长时间才能隐藏 OSD。仅适用于 Plextuary 皮肤或其他具有可配置 OSD 超时的皮肤。默认值:4 秒 (+/- 1 秒)" + +#: +msgctxt "#33717" +msgid "Debug requests" +msgstr "调试请求" + +#: +msgctxt "#33718" +msgid "Played" +msgstr "已播放" + +#: +msgctxt "#33719" +msgid "Refresh metadata" +msgstr "刷新元数据" + +#: +msgctxt "#33038" +msgid "When encountering an intro marker with a start time offset greater than this, ignore it (default: 1400s/23m)" +msgstr "" + +#: +msgctxt "#33649" +msgid "\"Use alternate seek\" wait for seek" +msgstr "" + +#: +msgctxt "#33650" +msgid "This adjusts the delay between seek-tries on problematic platforms when \"Use alternate seek\" is enabled, which fixes resume not always working or double-seeking. When you have resume/double-seek issues, increase this. Default: 500ms" +msgstr "" + +#: +msgctxt "#33667" +msgid "Use alternate seek" +msgstr "" + +#: +msgctxt "#33668" +msgid "ATTENTION: Only enable this if you have reproducible audio issues after seeking/resuming.\n" +"\n" +"Use an alternative seek method in videos, which can help in problematic scenarios; brings its own issues/quirks. Disabled by default (enabled by default for CoreELEC and LG WebOS)" +msgstr "" + +#: +msgctxt "#33720" +msgid "Clear all caches" +msgstr "清除所有缓存" + +#: +msgctxt "#33721" +msgid "Clear library cache (not items)" +msgstr "" + +#: +msgctxt "#33722" +msgid "Libraries" +msgstr "" + +#: +msgctxt "#33723" +msgid "Media Items" +msgstr "" + +#: +msgctxt "#33724" +msgid "Cache Plex data for" +msgstr "" + +#: +msgctxt "#33725" +msgid "Persist cached Plex data" +msgstr "" + +#: +msgctxt "#33726" +msgid "Instead of clearing the cache when exiting the addon, persist it instead. Warning: You'll most likely encounter missing items in libraries or outdated data. Use the corresponding menu functionalities to clear the cache for specific items or libraries." +msgstr "" + +#: +msgctxt "#33727" +msgid "Store Plex server responses for items and library views in a local SQLite database. Doesn't cache anything else (Home/Hubs are always up to date). Massively speeds up consecutive visits to items and libraries. Certain important events, such as watch state changes, automatically delete the item cache and its corresponding library cache. (Default: Off)" +msgstr "" + +#: +msgctxt "#33728" +msgid "Clear cache for item" +msgstr "" + +#: +msgctxt "#33729" +msgid "Expire cached Plex data after (hours)" +msgstr "" + +#: +msgctxt "#33730" +msgid "Randomly" +msgstr "" + +#: +msgctxt "#33731" +msgid "By Bitrate" +msgstr "" + +#: +msgctxt "#33732" +msgid "Bitrate" +msgstr "" + +#: +msgctxt "#33733" +msgid "Transcode target codec" +msgstr "" + +#: +msgctxt "#33734" +msgid "Sets the target codec when transcoding/direct streaming. Overridden when \"Transcode audio to AC3\" is set." +msgstr "" + +#: +msgctxt "#33735" +msgid "Always auto-start" +msgstr "" + +#: +msgctxt "#33736" +msgid "By default PM4K won't auto-start when the current window isn't a Home-derived window (e.g.: Settings). Enable this if you always want to auto-start PM4K regardless of the current window. Can also improve start-up time." +msgstr "" + +#: +msgctxt "#33737" +msgid "Loop theme music" +msgstr "" + +#: +msgctxt "#33671" +msgid "Current: {current_version}\n" +"New: {new_version}\n" +"\n" +"Changelog:\n" +"{changelog}" +msgstr "" + +#: +msgctxt "#33738" +msgid "Max playlist size" +msgstr "" + +#: +msgctxt "#33739" +msgid "How many items to load into a Kodi playlist before chunking them remotely using Plex PlayQueues (default: 500); Reduce on memory issues/crashes." +msgstr "" + +#: +msgctxt "#33740" +msgid "Home: Episodes season thumbnails" +msgstr "" + +#: +msgctxt "#33741" +msgid "Use season thumbnails/posters when displaying episodes in hubs instead of the TV show's." +msgstr "" + +#: +msgctxt "#34000" +msgid "Watchlist" +msgstr "" + +#: +msgctxt "#34001" +msgid "Released" +msgstr "" + +#: +msgctxt "#34002" +msgid "Movies & Shows" +msgstr "" + +#: e.g.: 8 seasons +msgctxt "#34003" +msgid "{} seasons" +msgstr "" + +#: +msgctxt "#34004" +msgid "Choose server" +msgstr "" + +#: +msgctxt "#34005" +msgid "Availability" +msgstr "" + +#: e.g.: 1 season +msgctxt "#34006" +msgid "{} season" +msgstr "" + +#: +msgctxt "#34007" +msgid "Use Watchlist" +msgstr "" + +#: +msgctxt "#34008" +msgid "Activates the current user's Plex watchlist as a section item. Adds watchlist functionality to certain media screens. Per-user setting. Default: On" +msgstr "" + +#: +msgctxt "#34009" +msgid "Watchlist auto-remove" +msgstr "" + +#: +msgctxt "#34010" +msgid "Automatically remove fully watched items from watchlist. Default: On" +msgstr "" + +#: +msgctxt "#34011" +msgid "Remove from watchlist" +msgstr "" + +#: +msgctxt "#34012" +msgid "Fast pause/resume" +msgstr "" + +#: +msgctxt "#34013" +msgid "when paused" +msgstr "" + +#: +msgctxt "#34014" +msgid "when playing" +msgstr "" + +#: +msgctxt "#34015" +msgid "User-specific. Use OK/ENTER button to pause instead of showing the OSD (which can then only be accessed using DOWN), or resume when paused. Only works with 'Behave like official Plex clients' enabled." +msgstr "" + +#: +msgctxt "#34016" +msgid "Max shutdown wait" +msgstr "" + +#: +msgctxt "#34017" +msgid "The plugin will try to hard exit once this time limit is reached (default: 5 seconds)" +msgstr "" + +#: +msgctxt "#34018" +msgid "Related Media" +msgstr "" + +#: watchlist discover hub title prefix +msgctxt "#34019" +msgid "Discover: {}" +msgstr "" + +#: +msgctxt "#34020" +msgid "Loading external hubs" +msgstr "" + +#: +msgctxt "#34021" +msgid "Please wait ..." +msgstr "" + +#: +msgctxt "#34022" +msgid "Video play completion behaviour" +msgstr "" + +#: +msgctxt "#34023" +msgid "Decide whether to use end credits markers to determine the 'watched' state of video items. When markers are not available the selected threshold percentage will be used." +msgstr "" + +#: +msgctxt "#34024" +msgid "at selected threshold percentage" +msgstr "" + +#: +msgctxt "#34025" +msgid "at final credits marker position" +msgstr "" + +#: +msgctxt "#34026" +msgid "at first credits marker position" +msgstr "" + +#: +msgctxt "#34027" +msgid "earliest between threshold percent and first credits marker" +msgstr "" + +#: +msgctxt "#32973" +msgid "Episodes: Continuous playback" +msgstr "" + +#: +msgctxt "#34028" +msgid "\"Use alternate seek\" valid seek window" +msgstr "" + +#: +msgctxt "#34029" +msgid "When \"Use alternate seek\" is enabled, any seek amount outside of this threshold window will be applied, otherwise the seek attempt will be ignored. If you encounter any seek issues, play with this value (ms, default: 2000)" +msgstr "" + +#: +msgctxt "#34030" +msgid "Unlock Direct Play for above 4K" +msgstr "" + +#: +msgctxt "#34031" +msgid "Producer" +msgstr "" + +#: +msgctxt "#34032" +msgid "Audio Language" +msgstr "" + +#: +msgctxt "#34033" +msgid "Subtitle Language" +msgstr "" + +#: +msgctxt "#34034" +msgid "Folder Location" +msgstr "" + +#: +msgctxt "#34035" +msgid "Editions" +msgstr "" + +#: +msgctxt "#34036" +msgid "DOVI" +msgstr "" + +#: +msgctxt "#34037" +msgid "HDR" +msgstr "" + +#: +msgctxt "#34038" +msgid "Force plex.direct mapping" +msgstr "" + +#: +msgctxt "#34039" +msgid "If you (still) don't see any posters: Under certain circumstances when Kodi still can't resolve hostnames when loading assets, even though the host resolves natively, enable this to force plex.direct mapping via advancedsettings.xml." +msgstr "" + +#: +msgctxt "#34040" +msgid "By Progress" +msgstr "" + +#: +msgctxt "#34041" +msgid "Progress" +msgstr "" + +#: +msgctxt "#34042" +msgid "By Album" +msgstr "" + +#: +msgctxt "#34043" +msgid "Album" +msgstr "" + diff --git a/script.plexmod/resources/settings.xml b/script.plexmod/resources/settings.xml index 9e9cd8e3ec..b9dfa1fca3 100644 --- a/script.plexmod/resources/settings.xml +++ b/script.plexmod/resources/settings.xml @@ -13,11 +13,26 @@ false + + 0 + false + + + + 0 + true + + 0 true + + 0 + false + + 0 100 @@ -30,6 +45,18 @@ false + + 0 + 100 + + 25 + 25 + 750 + + + false + + 0 false @@ -54,6 +81,18 @@ + + 0 + 500 + + 100 + 50 + 10000 + + + false + + 0 false @@ -84,6 +123,21 @@ + + 0 + 4 + + 1 + 1 + 15 + + + false + + + true + + 0 true @@ -116,11 +170,23 @@ 0 - 350 + 850 - 100 + 500 50 - 1000 + 5000 + + + false + + + + 0 + 2000 + + 500 + 100 + 15000 false @@ -187,7 +253,7 @@ 0 - 2.5 + 1 -10 0.5 @@ -224,9 +290,9 @@ false - + 0 - 70 + 120 10 10 @@ -239,11 +305,11 @@ 0 - 600 + 1400 1 10 - 1800 + 3600 33510 @@ -359,8 +425,13 @@ 0 16 - - 32501 + + 2 + 1 + 60 + + + false @@ -401,6 +472,18 @@ true + + 0 + 5 + + 1 + 1 + 360 + + + false + + 0 true @@ -438,7 +521,7 @@ 0 - 10 + 5 0.5 0.5 @@ -532,6 +615,18 @@ false + + 0 + 168 + + 0 + 1 + 8760 + + + false + + diff --git a/script.plexmod/resources/skins/Main/1080i/templates/includes/watched_indicator.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/includes/watched_indicator.xml.tpl index 4d2d0d24ba..ba0a338f57 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/includes/watched_indicator.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/includes/watched_indicator.xml.tpl @@ -56,6 +56,7 @@ {% else %} + String.IsEmpty({{ itemref|default("ListItem") }}.Property(unwatched.count.large)) {{ xoff - wbg_w }} {{ yoff|vscale }} {{ wbg_w }} @@ -63,12 +64,35 @@ {{ wbg|default("script.plex/white-square-bl-rounded_w.png") }} {{ indicators.unwatched_count_bg|default("FFCC7B19") }} + + !String.IsEmpty({{ itemref|default("ListItem") }}.Property(unwatched.count.large)) + {{ xoff - wbg_w - 16 }} + {{ yoff|vscale }} + {{ wbg_w + 16 }} + {{ wbg_h|vscale }} + {{ wbg|default("script.plex/white-square-bl-rounded_w.png") }} + {{ indicators.unwatched_count_bg|default("FFCC7B19") }} + {% endif %} {# this label uses a nasty hack to get a smaller fitting font size: use a larger font, increase the label size, then zoom it down #} Conditional - {{ xoff - wbg_w - 16 }} + String.IsEmpty({{ itemref|default("ListItem") }}.Property(unwatched.count.large)) + {{ xoff - wbg_w - 20 }} + {{ (yoff - 8)|vscale }} + {{ wbg_w + 40 }} + {{ (wbg_h + 16)|vscale }} + font32_title + center + center + {{ indicators.textcolor|default("FF000000") }} + + + {# this label uses a nasty hack to get a smaller fitting font size: use a larger font, increase the label size, then zoom it down #} + Conditional + !String.IsEmpty({{ itemref|default("ListItem") }}.Property(unwatched.count.large)) + {{ xoff - wbg_w - 36 }} {{ (yoff - 8)|vscale }} - {{ wbg_w + 32 }} + {{ wbg_w + 56 }} {{ (wbg_h + 16)|vscale }} font32_title center diff --git a/script.plexmod/resources/skins/Main/1080i/templates/library.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/library.xml.tpl index f09ed452e1..18a280cb09 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/library.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/library.xml.tpl @@ -3,7 +3,7 @@ {% block header %} - {% block header_animation %}Conditional{% endblock %} + {% block header_animation %}Conditional{% endblock %} 201 0 0 @@ -30,7 +30,8 @@ left 60 horizontal - 50 + 50 + 600 40 {{ vscale(40) }} @@ -40,7 +41,8 @@ 40 {{ vscale(40) }} 202 - 50 + 50 + 600 font12 FF000000 script.plex/buttons/home-focus.png @@ -49,13 +51,14 @@ - auto + auto {{ vscale(40) }} font12 left center FFFFFFFF + true 40 @@ -65,9 +68,11 @@ UnFocus 40 {{ vscale(40) }} - 204 + 204 + 600 201 - 50 + 50 + 600 font12 FF000000 script.plex/buttons/search-focus.png @@ -78,7 +83,7 @@ Player.HasAudio + String.IsEmpty(Window(10000).Property(script.plex.theme_playing)) - 438 + 620 0 Player.HasAudio + String.IsEmpty(Window(10000).Property(script.plex.theme_playing)) @@ -87,7 +92,6 @@ 260 {{ vscale(75) }} 202 - 211 50 font12 FFFFFFFF @@ -174,18 +178,25 @@ {% block filteropts_grouplist %} - + String.IsEmpty(Window.Property(hide.filteroptions)) - 340 - {{ vscale(35) }} + !Integer.IsGreater(Container(101).ListItem.Property(index),{% block hide_filter_from_index %}5{% endblock %}) + String.IsEmpty(Window.Property(no.content)) + !String.IsEmpty(Window.Property(initialized)) + Conditional + {% block filteropts_animation %} + VisibleChange + {% endblock %} + 170 + {{ vscale(135) }} 1000 {{ vscale(65) }} right 30 horizontal - 204 - 210 - 50 + 304 + 200 + 151 + 101 + 200 false auto diff --git a/script.plexmod/resources/skins/Main/1080i/templates/library_posters.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/library_posters.xml.tpl index 9aa14dda78..dbcb0f60d7 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/library_posters.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/library_posters.xml.tpl @@ -1,17 +1,24 @@ {% extends "library.xml.tpl" %} {% block filteropts_grouplist %} - + String.IsEmpty(Window.Property(hide.filteroptions)) - 340 - {{ vscale(35) }} + !Integer.IsGreater(Container(101).ListItem.Property(index),{% block hide_filter_from_index %}5{% endblock %}) + String.IsEmpty(Window.Property(no.content)) + !String.IsEmpty(Window.Property(initialized)) + Conditional + {% block filteropts_animation %} + VisibleChange + {% endblock %} + 170 + {{ vscale(135) }} 870 {{ vscale(65) }} right 30 horizontal - 204 - 210 - 50 + 304 + 200 + 151 + 101 + 200 !String.IsEqual(Window.Property(media.itemType),folder) false @@ -45,7 +52,7 @@ - String.IsEqual(Window.Property(subDir),1) | ![String.IsEqual(Window.Property(media),show) | String.IsEqual(Window.Property(media),movie)] + String.IsEqual(Window.Property(subDir),1) | ![String.IsEqual(Window.Property(media),show) | String.IsEqual(Window.Property(media),movie) | String.IsEqual(Window.Property(media),movies_shows)] false auto {{ vscale(65) }} @@ -62,7 +69,7 @@ - !String.IsEqual(Window.Property(subDir),1) + [String.IsEqual(Window.Property(media),show) | String.IsEqual(Window.Property(media),movie)] + !String.IsEqual(Window.Property(subDir),1) + [String.IsEqual(Window.Property(media),show) | String.IsEqual(Window.Property(media),movie) | String.IsEqual(Window.Property(media),movies_shows)] auto {{ vscale(65) }} font12 diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-artist.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-artist.xml.tpl index 8dc370d54b..1e7988e5a2 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-artist.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-artist.xml.tpl @@ -244,15 +244,6 @@ {{ vscale(520) }} 0 {{ vscale(945) }} - - !String.IsEmpty(Window.Property(divider.401)) - 60 - 0 - 1800 - {{ vscale(2) }} - script.plex/white-square.png - A0000000 - 60 0 @@ -271,8 +262,8 @@ {{ vscale(520) }} 400 false - false - false + noop + noop 200 horizontal 4 diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-dropdown.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-dropdown.xml.tpl index 3342519d96..09c6573be9 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-dropdown.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-dropdown.xml.tpl @@ -35,11 +35,13 @@ Close noop Close + 1152 Close Close noop 200 vertical + 1152 @@ -48,7 +50,7 @@ 0 300 {{ vscale(66) }} - script.plex/white-square-top-rounded.png + script.plex/white-square-top-rounded.png String.IsEmpty(ListItem.Property(first)) + String.IsEmpty(ListItem.Property(last)) + String.IsEmpty(ListItem.Property(only)) @@ -56,7 +58,7 @@ 0 300 {{ vscale(66) }} - script.plex/white-square.png + script.plex/white-square.png !String.IsEmpty(ListItem.Property(last)) @@ -64,7 +66,7 @@ 0 300 {{ vscale(66) }} - script.plex/white-square-top-rounded.png + script.plex/white-square-top-rounded.png !String.IsEmpty(ListItem.Property(only)) @@ -72,7 +74,7 @@ 0 300 {{ vscale(66) }} - script.plex/white-square-rounded.png + script.plex/white-square-rounded.png String.IsEmpty(ListItem.Property(with.indicator)) @@ -202,5 +204,22 @@ + + !String.IsEmpty(Window.Property(scroll)) + 300 + 0 + 6 + {{ vscale(924) }} + true + script.plex/white-square.png + script.plex/white-square.png + script.plex/white-square.png + script.plex/white-square.png + script.plex/white-square.png + true + vertical + false + 250 + {% endblock controls %} \ No newline at end of file diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-dropdown_header.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-dropdown_header.xml.tpl index 6f3cb0796c..fdf76ecc7e 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-dropdown_header.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-dropdown_header.xml.tpl @@ -35,7 +35,7 @@ 0 640 {{ vscale(132) }} - script.plex/white-square-rounded.png + script.plex/white-square-rounded.png 20 @@ -45,7 +45,9 @@ font12 center center - FFFFFFFF + FFEEEEEE + true + 15 @@ -70,7 +72,7 @@ 0 600 {{ vscale(66) }} - script.plex/white-square-top-rounded.png + script.plex/white-square-top-rounded.png String.IsEmpty(ListItem.Property(first)) + String.IsEmpty(ListItem.Property(last)) + String.IsEmpty(ListItem.Property(only)) @@ -78,7 +80,7 @@ 0 600 {{ vscale(66) }} - script.plex/white-square.png + script.plex/white-square.png !String.IsEmpty(ListItem.Property(last)) @@ -86,7 +88,7 @@ 0 600 {{ vscale(66) }} - script.plex/white-square-top-rounded.png + script.plex/white-square-top-rounded.png !String.IsEmpty(ListItem.Property(only)) @@ -94,34 +96,34 @@ 0 600 {{ vscale(66) }} - script.plex/white-square-rounded.png + script.plex/white-square-rounded.png String.IsEmpty(ListItem.Property(with.indicator)) + String.IsEqual(ListItem.Property(align),center) 0 0 - 600 + 580 {{ vscale(66) }} font12 center center FFFFFFFF true - 60 + 20 String.IsEmpty(ListItem.Property(with.indicator)) + String.IsEqual(ListItem.Property(align),left) 20 0 - 600 + 580 {{ vscale(66) }} font12 left center FFFFFFFF true - 60 + 20 @@ -136,7 +138,7 @@ center FFFFFFFF true - 60 + 20 @@ -164,7 +166,7 @@ 0 600 {{ vscale(66) }} - script.plex/white-square-top-rounded.png + script.plex/white-square-top-rounded.png String.IsEmpty(ListItem.Property(first)) + String.IsEmpty(ListItem.Property(last)) + String.IsEmpty(ListItem.Property(only)) @@ -172,7 +174,7 @@ 0 600 {{ vscale(66) }} - script.plex/white-square.png + script.plex/white-square.png !String.IsEmpty(ListItem.Property(last)) @@ -194,28 +196,28 @@ String.IsEmpty(ListItem.Property(with.indicator)) + String.IsEqual(ListItem.Property(align),center) 0 0 - 600 + 580 {{ vscale(66) }} font12 center center FF000000 true - 60 + 20 String.IsEmpty(ListItem.Property(with.indicator)) + String.IsEqual(ListItem.Property(align),left) 20 0 - 600 + 580 {{ vscale(66) }} font12 left center FF000000 true - 60 + 20 @@ -230,7 +232,7 @@ center FF000000 true - 60 + 20 diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-episodes.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-episodes.xml.tpl index 226cc7792a..8f94be22aa 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-episodes.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-episodes.xml.tpl @@ -19,13 +19,16 @@ + 400 + 0 {{ vscale(155) }} - 101 + {% block buttons %} {% if theme.episodes.use_button_bg %} + String.IsEmpty(Window.Property(disable_playback)) 60 {{ vscale(369) }} 657 @@ -35,7 +38,7 @@ {% endif %} - String.IsEmpty(Container(400).ListItem.Property(media.multiple)) + !String.IsEmpty(Window.Property(initialized)) + String.IsEmpty(Container(400).ListItem.Property(media.multiple)) + !String.IsEmpty(Window.Property(initialized)) + String.IsEmpty(Window.Property(disable_playback)) 301 30 {{ theme.episodes.buttongroup.posy|vscale }} @@ -64,7 +67,7 @@ {% endwith %} - !String.IsEmpty(Container(400).ListItem.Property(media.multiple)) + !String.IsEmpty(Window.Property(initialized)) + !String.IsEmpty(Container(400).ListItem.Property(media.multiple)) + !String.IsEmpty(Window.Property(initialized)) + String.IsEmpty(Window.Property(disable_playback)) 1301 30 {{ theme.episodes.buttongroup_1300.posy|vscale }} @@ -284,7 +287,7 @@ {{ vscale(34) }} font12 center - center + top FFFFFFFF FFFFFFFF 15 @@ -321,7 +324,7 @@ {{ vscale(34) }} font12 center - center + top FFFFFFFF FFFFFFFF 15 @@ -334,7 +337,7 @@ {{ vscale(34) }} font12 left - center + top FFFFFFFF @@ -345,7 +348,7 @@ {{ vscale(34) }} font12 center - center + top FFFFFFFF FFFFFFFF 15 @@ -355,12 +358,14 @@ !String.IsEmpty(Container(400).ListItem.Property(subtitles)) - auto + auto {{ vscale(34) }} font12 left - center + top FFFFFFFF + true + 15 @@ -386,15 +391,6 @@ script.plex/white-square.png FFCC7B19 - - !Control.IsVisible(500) - 0 - {{ vscale(565) }} - 1920 - {{ vscale(2) }} - script.plex/white-square.png - A0000000 - @@ -407,6 +403,7 @@ 300 1300 + 200 0 @@ -417,7 +414,7 @@ 60 0 - 800 + 1600 {{ vscale(80) }} font12 left diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-home.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-home.xml.tpl index c9f6cda368..da800c3036 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-home.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-home.xml.tpl @@ -133,7 +133,7 @@ {{ vscale(200) }} Conditional - Conditional--> + Conditional -300 0 2430 @@ -6926,7 +6926,6 @@ 0 500 {{ vscale(900) }} - 201 203 202 SetProperty(show.servers,) @@ -7260,6 +7259,15 @@ FFFFFFFF + + !String.IsEmpty(Window(10000).Property(script.plex.update_available)) + -8 + {{ vscale(38) }} + 16 + {{ vscale(14) }} + script.plex/home/device/update_small.png + FF00CC00 + !Control.HasFocus(202) 53 @@ -7278,7 +7286,7 @@ script.plex/indicators/dropdown-triangle.png FF222222 - + Control.HasFocus(250) | !String.IsEmpty(Window.Property(show.options)) -213 {{ vscale(70) }} @@ -7298,13 +7306,11 @@ FF1F1F1F - + 0 0 300 - {{ vscale(198) }} - 202 - noop + {{ vscale(422) }} 201 SetProperty(show.options,) 200 @@ -7472,9 +7478,37 @@ + + String.IsEmpty(Window.Property(busy)) + !String.IsEmpty(Window.Property(loading.content)) + 0 + {{ vscale(465) }} + + false + 60 + 0 + 1800 + {{ vscale(35) }} + font13 + center + FFFFFFFF + + + + false + 60 + {{ vscale(60) }} + 1800 + {{ vscale(35) }} + font13 + center + FFCCCCCC + + + + !String.IsEmpty(Window.Property(busy)) - Visible + Visible 840 {{ vscale(465) }} diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-listview-16x9.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-listview-16x9.xml.tpl index 623dd62354..b1a6d0216f 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-listview-16x9.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-listview-16x9.xml.tpl @@ -1,9 +1,106 @@ {% extends "library.xml.tpl" %} {% block header_bg %}{% endblock %} {% block header_animation %}{% endblock %} -{% block filteropts_grouplist_attrs %} id="600"{% endblock %} {% block no_content %}{% endblock %} +{% block filteropts_grouplist %} + + String.IsEmpty(Window.Property(hide.filteroptions)) + 120 + {{ vscale(135) }} + 870 + {{ vscale(65) }} + right + 30 + horizontal + 304 + 200 + 151 + 101 + 200 + + !String.IsEqual(Window.Property(media.itemType),folder) + false + auto + {{ vscale(65) }} + font10 + A0FFFFFF + A0FFFFFF + A0FFFFFF + center + center + - + - + 0 + 0 + + + + !String.IsEqual(Window.Property(media.itemType),folder) + auto + {{ vscale(65) }} + font10 + A0FFFFFF + FF000000 + center + center + script.plex/white-square-rounded.png + - + 20 + 0 + + + + String.IsEqual(Window.Property(subDir),1) | ![String.IsEqual(Window.Property(media),show) | String.IsEqual(Window.Property(media),movie) | String.IsEqual(Window.Property(media),movies_shows)] + false + auto + {{ vscale(65) }} + font12 + FFFFFFFF + FFFFFFFF + FFFFFFFF + center + center + - + - + 20 + 0 + + + + !String.IsEqual(Window.Property(subDir),1) + [String.IsEqual(Window.Property(media),show) | String.IsEqual(Window.Property(media),movie) | String.IsEqual(Window.Property(media),movies_shows)] + auto + {{ vscale(65) }} + font12 + FFFFFFFF + FF000000 + FFFFFFFF + center + center + script.plex/white-square-rounded.png + - + 20 + 0 + + + + !String.IsEqual(Window.Property(media.itemType),folder) + auto + {{ vscale(65) }} + font10 + A0FFFFFF + FF000000 + center + center + script.plex/white-square-rounded.png + - + 20 + 0 + + + +{% endblock filteropts_grouplist %} + {% block content %} 60 @@ -67,6 +164,7 @@ left FFDDDDDD + @@ -85,7 +183,8 @@ {{ vscale(145) }} 200 101 - 101 + 210 + 600 -20 horizontal 200 @@ -93,9 +192,9 @@ !String.IsEmpty(Window.Property(initialized)) {% with attr = {"width": 126, "height": 100} & template = "includes/themed_button.xml.tpl" & hitrect = {"x": 20, "y": 20, "w": 86, "h": 60} %} - {% include template with name="play" & id=301 & visible="!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)" %} - {% include template with name="shuffle" & id=302 & visible="!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)" %} - {% include template with name="more" & id=303 & visible="String.IsEmpty(Window.Property(no.options)) | Player.HasAudio" %} + {% include template with name="play" & id=301 & visible="String.IsEmpty(Window.Property(disable_playback)) + [!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)]" %} + {% include template with name="shuffle" & id=302 & visible="String.IsEmpty(Window.Property(disable_playback)) + [!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)]" %} + {% include template with name="more" & id=303 & visible="String.IsEmpty(Window.Property(disable_playback)) + [String.IsEmpty(Window.Property(no.options)) | Player.HasAudio]" %} {% include template with name="chapters" & id=304 %} {% endwith %} @@ -106,7 +205,7 @@ Integer.IsGreater(Container(101).NumItems,0) + String.IsEmpty(Window.Property(drawing)) 101 750 - 0 + {{ vscale(100) }} 1170 1080 @@ -118,11 +217,11 @@ 20000000 - + 0 0 1170 - 945 + 845 600 151 304 @@ -275,25 +374,23 @@ - - - - 1128 - 33 - 12 - 910 - 101 - true - script.plex/white-square-rounded.png - script.plex/white-square-rounded.png - script.plex/white-square-rounded.png - - - - - false - vertical - false - 151 - + + + + 1875 + {{ vscale(15) }} + 12 + 910 + 151 + true + script.plex/white-square-rounded.png + script.plex/white-square-rounded.png + script.plex/white-square-rounded.png + - + - + false + vertical + false @@ -301,7 +398,7 @@ String.IsEqual(Window(10000).Property(script.plex.sort),titleSort) + Integer.IsGreater(Container(101).NumItems,0) + String.IsEmpty(Window.Property(drawing)) 151 1830 - {{ vscale(135) + 33 }} + {{ vscale(150) }} 20 920 @@ -309,7 +406,7 @@ 0 34 1050 - 100 + 600 152 200 vertical diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-listview-square.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-listview-square.xml.tpl index bb0417036a..d22a529437 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-listview-square.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-listview-square.xml.tpl @@ -1,7 +1,7 @@ {% extends "library.xml.tpl" %} {% block header_bg %}{% endblock %} {% block header_animation %}{% endblock %} -{% block filteropts_grouplist_attrs %} id="600"{% endblock %} +{% block filteropts_animation %}{% endblock %} {% block no_content %}{% endblock %} {% block content %} @@ -93,6 +93,7 @@ left FFDDDDDD + @@ -110,7 +111,8 @@ {{ vscale(145) }} 200 101 - 101 + 210 + 600 -20 horizontal 200 @@ -118,9 +120,9 @@ !String.IsEmpty(Window.Property(initialized)) {% with attr = {"width": 126, "height": 100} & template = "includes/themed_button.xml.tpl" & hitrect = {"x": 20, "y": 20, "w": 86, "h": 60} %} - {% include template with name="play" & id=301 & visible="!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)" %} - {% include template with name="shuffle" & id=302 & visible="!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)" %} - {% include template with name="more" & id=303 & visible="String.IsEmpty(Window.Property(no.options)) | Player.HasAudio" %} + {% include template with name="play" & id=301 & visible="String.IsEmpty(Window.Property(disable_playback)) + [!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)]" %} + {% include template with name="shuffle" & id=302 & visible="String.IsEmpty(Window.Property(disable_playback)) + [!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)]" %} + {% include template with name="more" & id=303 & visible="String.IsEmpty(Window.Property(disable_playback)) + [String.IsEmpty(Window.Property(no.options)) | Player.HasAudio]" %} {% include template with name="chapters" & id=304 & visible="String.IsEmpty(Window.Property(hide.filteroptions))" %} {% endwith %} @@ -131,7 +133,7 @@ Integer.IsGreater(Container(101).NumItems,0) + String.IsEmpty(Window.Property(drawing)) 101 750 - 0 + {{ vscale(100) }} 1170 1080 @@ -143,11 +145,11 @@ 20000000 - + 0 0 1170 - 945 + 845 600 151 304 @@ -312,25 +314,23 @@ - - - - 1128 - 33 - 12 - 879 - 101 - true - script.plex/white-square-rounded.png - script.plex/white-square-rounded.png - script.plex/white-square-rounded.png - - - - - false - vertical - false - 151 - + + + + 1875 + {{ vscale(15) }} + 12 + 910 + 151 + true + script.plex/white-square-rounded.png + script.plex/white-square-rounded.png + script.plex/white-square-rounded.png + - + - + false + vertical + false @@ -338,7 +338,7 @@ String.IsEqual(Window(10000).Property(script.plex.sort),titleSort) + Integer.IsGreater(Container(101).NumItems,0) + String.IsEmpty(Window.Property(drawing)) 151 1830 - {{ vscale(135) + 33 }} + {{ vscale(150) }} 20 920 @@ -346,7 +346,7 @@ 0 34 1050 - 100 + 600 152 200 vertical diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-music_current_playlist.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-music_current_playlist.xml.tpl index d23828a9fd..e5dd44c767 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-music_current_playlist.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-music_current_playlist.xml.tpl @@ -477,7 +477,7 @@ --> - Control.HasFocus(500) + Control.HasFocus(500) + !String.IsEmpty(Window.Property(time.selection)) 0 {{ vscale(184) }}r diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-music_player.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-music_player.xml.tpl index a5ba4bf20a..00e3c77287 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-music_player.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-music_player.xml.tpl @@ -164,7 +164,7 @@ {{ vscale(124) }} center - 100 + 500 -40 horizontal 200 @@ -177,7 +177,7 @@ 0 {{ vscale(140) }}r - + Player.HasAudio 0 @@ -190,7 +190,7 @@ A0000000 - Control.HasFocus(100) + Control.HasFocus(500) Visible 0 1 @@ -200,7 +200,7 @@ FFE5A00D - !Control.HasFocus(100) + !Control.HasFocus(500) Progressbar 0 2 @@ -214,7 +214,7 @@ Player.Progress - Control.HasFocus(100) + Control.HasFocus(500) Progressbar 0 2 @@ -253,7 +253,7 @@ --> - Control.HasFocus(100) + Control.HasFocus(500) + !String.IsEmpty(Window.Property(time.selection)) 0 {{ vscale(184) }}r diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-options_dialog.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-options_dialog.xml.tpl index 09abb3090c..97b7d3566e 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-options_dialog.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-options_dialog.xml.tpl @@ -81,6 +81,7 @@ true !String.IsEmpty(Window.Property(button.0)) + String.IsEmpty(Window.Property(delay_buttons)) | !String.IsEmpty(Window.Property(enable_buttons)) Focus UnFocus 0 @@ -97,6 +98,7 @@ !String.IsEmpty(Window.Property(button.1)) + String.IsEmpty(Window.Property(delay_buttons)) | !String.IsEmpty(Window.Property(enable_buttons)) Focus UnFocus 0 @@ -113,6 +115,7 @@ !String.IsEmpty(Window.Property(button.2)) + String.IsEmpty(Window.Property(delay_buttons)) | !String.IsEmpty(Window.Property(enable_buttons)) Focus UnFocus 0 diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-playlist.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-playlist.xml.tpl index 716e16dcb1..22e5b271c4 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-playlist.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-playlist.xml.tpl @@ -207,23 +207,22 @@ - - False + !String.IsEmpty(ListItem.Property(progress)) 63 - {{ vscale(85) }} + {{ vscale(79) }} 0 0 - 870 - {{ vscale(8) }} + 74 + {{ vscale(6) }} script.plex/white-square.png C0000000 0 1 - 870 - {{ vscale(6) }} + 74 + {{ vscale(4) }} $INFO[ListItem.Property(progress)] FFCC7B19 @@ -357,20 +356,20 @@ !String.IsEmpty(ListItem.Property(progress)) 63 - {{ vscale(88) }} + {{ vscale(79) }} 0 0 - 870 - {{ vscale(10) }} + 74 + {{ vscale(6) }} script.plex/white-square.png C0000000 0 1 - 870 - {{ vscale(8) }} + 74 + {{ vscale(4) }} $INFO[ListItem.Property(progress)] FFCC7B19 @@ -534,20 +533,20 @@ !String.IsEmpty(ListItem.Property(progress)) 103 - {{ vscale(91) }} + {{ vscale(94) }} 0 0 - 899 - {{ vscale(10) }} + 178 + {{ vscale(6) }} script.plex/white-square.png C0000000 0 1 - 899 - {{ vscale(8) }} + 178 + {{ vscale(4) }} $INFO[ListItem.Property(progress)] FFCC7B19 diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-posters-small.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-posters-small.xml.tpl index fcab6d7c88..b37581e4e0 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-posters-small.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-posters-small.xml.tpl @@ -1,5 +1,6 @@ {% extends "library_posters.xml.tpl" %} -{% block header_animation %}Conditional{% endblock %} +{% block header_animation %}Conditional{% endblock %} +{% block hide_filter_from_index %}9{% endblock %} {% block header_bg %} VisibleChange @@ -14,7 +15,7 @@ {% endblock %} {% block content %} - Conditional + Conditional 0 {{ vscale(135) }} 101 @@ -30,15 +31,17 @@ {{ vscale(145) }} 200 101 + 210 + 600 -20 horizontal 200 true {% with attr = {"width": 126, "height": 100} & template = "includes/themed_button.xml.tpl" & hitrect = {"x": 20, "y": 20, "w": 86, "h": 60} %} - {% include template with name="play" & id=301 & visible="!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)" %} - {% include template with name="shuffle" & id=302 & visible="!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)" %} - {% include template with name="more" & id=303 & visible="String.IsEmpty(Window.Property(no.options)) | Player.HasAudio" %} + {% include template with name="play" & id=301 & visible="String.IsEmpty(Window.Property(disable_playback)) + [!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)]" %} + {% include template with name="shuffle" & id=302 & visible="String.IsEmpty(Window.Property(disable_playback)) + [!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)]" %} + {% include template with name="more" & id=303 & visible="String.IsEmpty(Window.Property(disable_playback)) + [String.IsEmpty(Window.Property(no.options)) | Player.HasAudio]" %} {% include template with name="chapters" & id=304 %} {% endwith %} @@ -57,8 +60,9 @@ 0 0 1800 - 1190 - 300 + 1198 + 300 + 600 151 200 vertical @@ -119,7 +123,7 @@ font10 center FFFFFFFF - + String.IsEmpty(ListItem.Property(subtitle)) + String.IsEmpty(ListItem.Property(year)) @@ -155,8 +159,8 @@ 55 {{ vscale(137) }} - Focus - UnFocus + Focus + UnFocus 0 0 @@ -210,6 +214,7 @@ String.IsEmpty(ListItem.Property(subtitle)) + !String.IsEmpty(ListItem.Property(year)) true + 15 0 {{ vscale(218) }} 144 @@ -217,11 +222,12 @@ font10 center FFFFFFFF - + String.IsEmpty(ListItem.Property(subtitle)) + String.IsEmpty(ListItem.Property(year)) true + 15 0 {{ vscale(218) }} 144 @@ -234,6 +240,7 @@ !String.IsEmpty(ListItem.Property(subtitle)) true + 15 0 {{ vscale(218) }} 144 @@ -272,7 +279,8 @@ 0 34 1050 - 100 + 100 + 600 152 200 vertical @@ -372,6 +380,7 @@ 12 910 true + Conditional script.plex/white-square-rounded.png script.plex/white-square-rounded.png script.plex/white-square-rounded.png diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-posters.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-posters.xml.tpl index 224643af14..c7c484936f 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-posters.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-posters.xml.tpl @@ -1,7 +1,7 @@ {% extends "library_posters.xml.tpl" %} {% block content %} - Conditional + Conditional 0 {{ vscale(135) }} 101 @@ -17,15 +17,17 @@ {{ vscale(145) }} 200 101 + 210 + 600 -20 horizontal 200 true {% with attr = {"width": 126, "height": 100} & template = "includes/themed_button.xml.tpl" & hitrect = {"x": 20, "y": 20, "w": 86, "h": 60} %} - {% include template with name="play" & id=301 & visible="!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)" %} - {% include template with name="shuffle" & id=302 & visible="!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)" %} - {% include template with name="more" & id=303 & visible="String.IsEmpty(Window.Property(no.options)) | Player.HasAudio" %} + {% include template with name="play" & id=301 & visible="String.IsEmpty(Window.Property(disable_playback)) + [!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)]" %} + {% include template with name="shuffle" & id=302 & visible="String.IsEmpty(Window.Property(disable_playback)) + [!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)]" %} + {% include template with name="more" & id=303 & visible="String.IsEmpty(Window.Property(disable_playback)) + [String.IsEmpty(Window.Property(no.options)) | Player.HasAudio]" %} {% include template with name="chapters" & id=304 %} {% endwith %} @@ -45,7 +47,8 @@ 0 1800 1190 - 300 + 300 + 600 151 200 vertical @@ -108,7 +111,7 @@ - !String.IsEmpty(ListItem.Property(year)) + !String.IsEmpty(ListItem.Property(subtitle)) false 0 {{ vscale(396) }} @@ -116,7 +119,19 @@ {{ vscale(72) }} font10 center - FFFFFFFF + A0FFFFFF + + + + !String.IsEmpty(ListItem.Property(year)) + String.IsEmpty(ListItem.Property(subtitle)) + false + 0 + {{ vscale(396) }} + 244 + {{ vscale(72) }} + font10 + center + A0FFFFFF @@ -193,7 +208,7 @@ - !String.IsEmpty(ListItem.Property(year)) + !String.IsEmpty(ListItem.Property(subtitle)) false 0 {{ vscale(396) }} @@ -201,7 +216,19 @@ {{ vscale(72) }} font10 center - FFFFFFFF + A0FFFFFF + + + + !String.IsEmpty(ListItem.Property(year)) + String.IsEmpty(ListItem.Property(subtitle)) + false + 0 + {{ vscale(396) }} + 244 + {{ vscale(72) }} + font10 + center + A0FFFFFF @@ -233,7 +260,8 @@ 0 34 1050 - 100 + 100 + 600 152 200 vertical @@ -333,6 +361,7 @@ 12 910 true + Conditional script.plex/white-square-rounded.png script.plex/white-square-rounded.png script.plex/white-square-rounded.png diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-pre_play.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-pre_play.xml.tpl index 504e85b3d9..104ded6852 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-pre_play.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-pre_play.xml.tpl @@ -37,332 +37,332 @@ {% with attr = theme.pre_play.buttons & template = "includes/themed_button.xml.tpl" %} {% include template with name="info" & id=304 %} - {% include template with name="play" & id=302 & visible="String.IsEmpty(Window.Property(unavailable))" %} + {% include template with name="play" & id=302 & visible="String.IsEmpty(Window.Property(unavailable)) + String.IsEmpty(Window.Property(disable_playback))" %} + {% include "includes/wl_dynamic_buttons.xml.tpl" %} {% include template with name="trailer" & id=303 & visible="!String.IsEmpty(Window.Property(trailer.button))" %} + {% include "includes/wl_add_remove_buttons.xml.tpl" %} {% include template with name="media" & id=307 & visible="!String.IsEmpty(Window.Property(media.multiple))" %} - {% include template with name="settings" & id=305 %} - {% include template with name="more" & id=306 %} + {% include template with name="settings" & id=305 & visible="String.IsEmpty(Window.Property(disable_playback))" %} + {% include template with name="more" & id=306 & visible="String.IsEmpty(Window.Property(disable_playback))" %} {% endwith %} {% endblock %} - - 0 - 0 - 1920 - {{ vscale(600) }} + {% block details %} - !String.IsEmpty(Window.Property(preview.no)) - - 60 - 0 - 347 - {{ vscale(518) }} - script.plex/thumb_fallbacks/movie.png - WindowOpen - scale + 0 + 0 + 1920 + {{ vscale(600) }} + + !String.IsEmpty(Window.Property(preview.no)) + + 60 + 0 + 347 + {{ vscale(518) }} + script.plex/thumb_fallbacks/movie.png + WindowOpen + scale + + + 60 + 0 + 347 + {{ vscale(518) }} + $INFO[Window.Property(thumb)] + scale + + {% include "includes/watched_indicator.xml.tpl" with itemref="Window" & xoff=347+60 & uw_size=48 & scale="medium" %} + - + + + !String.IsEmpty(Window.Property(preview.yes)) 60 0 - 347 - {{ vscale(518) }} - $INFO[Window.Property(thumb)] - scale - - {% include "includes/watched_indicator.xml.tpl" with itemref="Window" & xoff=347+60 & uw_size=48 & scale="medium" %} + + 0 + 0 + 347 + {{ vscale(315) }} + script.plex/thumb_fallbacks/show.png + WindowOpen + scale + + + 0 + {{ vscale(323) }} + 347 + {{ vscale(195) }} + script.plex/white-square.png + scale + - - - - !String.IsEmpty(Window.Property(preview.yes)) - 60 - 0 - - 0 - 0 - 347 - {{ vscale(315) }} - script.plex/thumb_fallbacks/show.png - WindowOpen - scale + + 0 + 0 + 347 + {{ vscale(315) }} + $INFO[Window.Property(thumb)] + scale + + + 0 + {{ vscale(323) }} + 347 + {{ vscale(195) }} + $INFO[Window.Property(preview)] + scale + - - 0 - {{ vscale(323) }} - 347 - {{ vscale(195) }} - script.plex/white-square.png - scale - - - - 0 + + 466 0 - 347 - {{ vscale(315) }} - $INFO[Window.Property(thumb)] - scale - - - 0 - {{ vscale(323) }} - 347 - {{ vscale(195) }} - $INFO[Window.Property(preview)] - scale - - - - 466 - 0 - 1226 - {{ vscale(60) }} - left - 0 - true - 5 - horizontal - true - - auto + 1226 {{ vscale(60) }} - font13 left - FFFFFFFF - - - - !String.IsEmpty(Window.Property(remainingTime)) - 10 - 6 - auto - {{ vscale(34) }} - font12 - center - center - FFE5A00D - FFE5A00D - 15 - script.plex/white-square-rounded-top-padded.png - script.plex/white-square-rounded-top-padded.png - - - - - 466 - {{ vscale(68) }} - 1360 - {{ vscale(34) }} - left - 0 - horizontal - true - - auto - {{ vscale(34) }} - font12 - left - FFFFFFFF - - - - !String.IsEmpty(Window.Property(video.res)) - 10 - auto - {{ vscale(34) }} - font12 - center - center - FFFFFFFF - FFFFFFFF - 15 - script.plex/white-square-rounded-top-padded.png - script.plex/white-square-rounded-top-padded.png - + 0 + true + 5 + horizontal + true + + auto + {{ vscale(60) }} + font13 + left + FFFFFFFF + + + + !String.IsEmpty(Window.Property(remainingTime)) + 10 + 6 + auto + {{ vscale(34) }} + font12 + center + center + FFE5A00D + FFE5A00D + 15 + script.plex/white-square-rounded-top-padded.png + script.plex/white-square-rounded-top-padded.png + + - - !String.IsEmpty(Window.Property(unavailable)) - 10 - auto + + 466 + {{ vscale(68) }} + 1360 {{ vscale(34) }} - font12 - center - center - FFFFFFFF - FFFFFFFF - 15 - script.plex/white-square-rounded-top-padded.png - script.plex/white-square-rounded-top-padded.png - - - - - - !String.IsEmpty(Window.Property(rating)) | !String.IsEmpty(Window.Property(rating2)) - 1426 - 4 - 434 - {{ vscale(32) }} - right - 15 - horizontal - true - - !String.IsEmpty(Window.Property(rating)) - 2 - 63 - {{ vscale(30) }} - $INFO[Window.Property(rating.image)] - keep - - - !String.IsEmpty(Window.Property(rating)) - auto - {{ vscale(30) }} - font12 - left - FFFFFFFF - - - - !String.IsEmpty(Window.Property(rating2)) - 2 - 40 - {{ vscale(30) }} - $INFO[Window.Property(rating2.image)] - keep - - - !String.IsEmpty(Window.Property(rating2)) - auto - {{ vscale(30) }} - font12 left - FFFFFFFF - - - - !String.IsEmpty(Window.Property(rating.stars)) - 6 - 134 - {{ vscale(22) }} - script.plex/stars/$INFO[Window.Property(rating.stars)].png + 0 + horizontal + true + + auto + {{ vscale(34) }} + font12 + left + FFFFFFFF + + + + !String.IsEmpty(Window.Property(video.res)) + 10 + auto + {{ vscale(34) }} + font12 + center + top + FFFFFFFF + FFFFFFFF + 15 + script.plex/white-square-rounded-top-padded.png + script.plex/white-square-rounded-top-padded.png + + + + !String.IsEmpty(Window.Property(unavailable)) + 10 + auto + {{ vscale(34) }} + font12 + center + top + FFFFFFFF + FFFFFFFF + 15 + script.plex/white-square-rounded-top-padded.png + script.plex/white-square-rounded-top-padded.png + + - - - !String.IsEmpty(Window.Property(directors)) | !String.IsEmpty(Window.Property(writers)) - 466 - {{ vscale(130) }} - 1360 - {{ vscale(30) }} - font12 - left - 99FFFFFF - - - - !String.IsEmpty(Window.Property(cast)) - 466 - {{ vscale(165) }} - 1360 - {{ vscale(30) }} - font12 - left - 99FFFFFF - - - - 466 - {{ vscale(223) }} - 1360 - {{ vscale(34) }} - left - 15 - horizontal - true - - !String.IsEmpty(Window.Property(audio)) - auto - {{ vscale(34) }} - font12 - center - center - FFFFFFFF - FFFFFFFF - 15 - script.plex/white-square-rounded-top-padded.png - script.plex/white-square-rounded-top-padded.png - - - - auto - {{ vscale(34) }} - font12 - left - center - FFFFFFFF - - - - !String.IsEmpty(Window.Property(subtitles)) - 30 - auto - {{ vscale(34) }} - font12 - center - center - FFFFFFFF - FFFFFFFF - 15 - script.plex/white-square-rounded-top-padded.png - script.plex/white-square-rounded-top-padded.png - + + !String.IsEmpty(Window.Property(rating)) | !String.IsEmpty(Window.Property(rating2)) + 1426 + 4 + 434 + {{ vscale(32) }} + right + 15 + horizontal + true + + !String.IsEmpty(Window.Property(rating)) + 2 + 63 + {{ vscale(30) }} + $INFO[Window.Property(rating.image)] + keep + + + !String.IsEmpty(Window.Property(rating)) + auto + {{ vscale(30) }} + font12 + left + FFFFFFFF + + + + !String.IsEmpty(Window.Property(rating2)) + 2 + 40 + {{ vscale(30) }} + $INFO[Window.Property(rating2.image)] + keep + + + !String.IsEmpty(Window.Property(rating2)) + auto + {{ vscale(30) }} + font12 + left + FFFFFFFF + + + + !String.IsEmpty(Window.Property(rating.stars)) + 6 + 134 + {{ vscale(22) }} + script.plex/stars/$INFO[Window.Property(rating.stars)].png + - - !String.IsEmpty(Window.Property(subtitles)) - auto - {{ vscale(34) }} - font12 - left - center - FFFFFFFF - + {% block cast_detail_and_streams %} + + !String.IsEmpty(Window.Property(directors)) | !String.IsEmpty(Window.Property(writers)) + 466 + {{ vscale(130) }} + 1360 + {{ vscale(30) }} + font12 + left + 99FFFFFF + + + + !String.IsEmpty(Window.Property(cast)) + 466 + {{ vscale(165) }} + 1360 + {{ vscale(30) }} + font12 + left + 99FFFFFF + + + {% block streams %} + + 466 + {{ vscale(223) }} + 1360 + {{ vscale(34) }} + left + 15 + horizontal + true + + !String.IsEmpty(Window.Property(audio)) + auto + {{ vscale(34) }} + font12 + center + top + FFFFFFFF + FFFFFFFF + 15 + script.plex/white-square-rounded-top-padded.png + script.plex/white-square-rounded-top-padded.png + + + + auto + {{ vscale(34) }} + font12 + left + top + FFFFFFFF + + + + !String.IsEmpty(Window.Property(subtitles)) + 30 + auto + {{ vscale(34) }} + font12 + center + top + FFFFFFFF + FFFFFFFF + 15 + script.plex/white-square-rounded-top-padded.png + script.plex/white-square-rounded-top-padded.png + + + + !String.IsEmpty(Window.Property(subtitles)) + auto + {{ vscale(34) }} + font12 + left + top + FFFFFFFF + + + + {% endblock %} + {% endblock %} + {% block summary %} + + 466 + {{ vscale(290) }} + 1360 + {{ vscale(102) }} + font12 + left + FFFFFFFF + 200 + !Control.HasFocus(13) + + + {% endblock %} + + WindowOpen + -1 + {{ vscale(557) }} + 1 + {{ vscale(8) }} + script.plex/white-square.png + FFCC7B19 - - 466 - {{ vscale(290) }} - 1360 - {{ vscale(102) }} - font12 - left - FFFFFFFF - 200 - !Control.HasFocus(13) - - - - WindowOpen - -1 - {{ vscale(557) }} - 1 - {{ vscale(8) }} - script.plex/white-square.png - FFCC7B19 - - - !Control.IsVisible(500) - 0 - {{ vscale(565) }} - 1920 - {{ vscale(2) }} - script.plex/white-square.png - A0000000 - - + {% endblock %} 0 @@ -743,6 +743,32 @@ $INFO[ListItem.Thumb] scale + + 0 + 0 + 299 + {{ vscale(168) }} + + 10 + 10 + 64 + 26 + script.plex/white-square-rounded.png + 99000000 + + + Conditional + 42 + 10 + auto + 26 + font32_title + center + center + FFEEEEEE + + + 0 {{ vscale(180) }} @@ -794,6 +820,32 @@ $INFO[ListItem.Thumb] scale + + 0 + 0 + 299 + {{ vscale(168) }} + + 10 + 10 + 64 + 26 + script.plex/white-square-rounded.png + 99000000 + + + Conditional + 42 + 10 + auto + 26 + font32_title + center + center + FFEEEEEE + + + 0 {{ vscale(180) }} @@ -826,15 +878,6 @@ 403 1920 {{ vscale(520) }} - - !String.IsEmpty(Window.Property(divider.403)) - 60 - 0 - 1800 - {{ vscale(2) }} - script.plex/white-square.png - A0000000 - 60 0 @@ -852,8 +895,8 @@ 1920 {{ vscale(520) }} 402 - false - false + noop + noop 200 horizontal 4 diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-pre_signin.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-pre_signin.xml.tpl index c597a23600..2c018e9cef 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-pre_signin.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-pre_signin.xml.tpl @@ -18,7 +18,7 @@ 1437 - {{ vscale(801) }} + {{ vperc(vscale(1080)) + vscale(1080) - vscale(279) }} 275 {{ vscale(104) }} 200 diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-seasons.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-seasons.xml.tpl index e15c1f18c8..7bce596d11 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-seasons.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-seasons.xml.tpl @@ -37,9 +37,11 @@ {% with attr = theme.seasons.buttons & template = "includes/themed_button.xml.tpl" & hitrect = None %} {# fixme: should hitrect be None? #} {% include template with name="info" & id=301 %} - {% include template with name="play" & id=302 %} - {% include template with name="shuffle" & id=303 %} - {% include template with name="more" & id=304 %} + {% include template with name="play" & id=302 & visible="String.IsEmpty(Window.Property(disable_playback))" %} + {% include "includes/wl_dynamic_buttons.xml.tpl" %} + {% include "includes/wl_add_remove_buttons.xml.tpl" %} + {% include template with name="shuffle" & id=303 & visible="String.IsEmpty(Window.Property(disable_playback))" %} + {% include template with name="more" & id=304 & visible="String.IsEmpty(Window.Property(disable_playback))" %} {% endwith %} @@ -116,9 +118,9 @@ !String.IsEmpty(Window.Property(rating)) | !String.IsEmpty(Window.Property(rating2)) - 1660 + 1560 {{ vscale(70) }} - 200 + 300 {{ vscale(32) }} right 15 @@ -144,7 +146,7 @@ !String.IsEmpty(Window.Property(rating2)) 2 - 63 + 40 {{ vscale(30) }} $INFO[Window.Property(rating2.image)] keep @@ -250,9 +252,10 @@ FFFFFFFF --> + {% include "includes/wl_availability.xml.tpl" %} 466 - {{ vscale(234) }} + {{ vscale(274) }} 1360 {{ vscale(179) }} font12 @@ -269,15 +272,6 @@ script.plex/white-square.png FFCC7B19 - - !Control.IsVisible(500) - 0 - {{ vscale(565) }} - 1920 - {{ vscale(2) }} - script.plex/white-square.png - A0000000 - @@ -766,15 +760,6 @@ 403 1920 {{ vscale(520) }} - - !String.IsEmpty(Window.Property(divider.403)) - 60 - 0 - 1800 - {{ vscale(2) }} - script.plex/white-square.png - A0000000 - 60 0 @@ -793,8 +778,8 @@ {{ vscale(520) }} 402 403 - false - false + noop + noop 200 horizontal 4 diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-seek_dialog.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-seek_dialog.xml.tpl index af9ad17bd9..9ba6714e3e 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-seek_dialog.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-seek_dialog.xml.tpl @@ -256,7 +256,7 @@ - - - SetProperty(show.OSD,1) + SetProperty(show.OSD,1) @@ -344,7 +344,7 @@ {{ vscale(350) }} 993 - 893 + 963 {{ vscale(50) }} bottom @@ -353,7 +353,7 @@ Player.HasVideo + !String.IsEmpty(Window.Property(ppi.Status)) - 893 + 963 {{ vscale(50) }} bottom @@ -362,7 +362,7 @@ Player.HasVideo + !String.IsEmpty(Window.Property(ppi.Mode)) - 893 + 963 {{ vscale(50) }} bottom @@ -370,8 +370,9 @@ black Player.HasVideo + !String.IsEmpty(Window.Property(ppi.Container)) - - 893 + + 963 + {{ vscale(50) }} bottom @@ -379,8 +380,9 @@ black Player.HasVideo + !String.IsEmpty(Window.Property(ppi.Video)) - - 893 + + 963 + {{ vscale(50) }} bottom @@ -388,8 +390,9 @@ black Player.HasVideo + [!String.IsEmpty(Window.Property(ppi.Audio)) | !String.IsEmpty(Window.Property(ppi.Subtitles))] - - 893 + + 963 + {{ vscale(50) }} bottom @@ -398,19 +401,19 @@ Player.HasVideo + !String.IsEmpty(Window.Property(ppi.User)) - 893 + 963 {{ vscale(50) }} bottom - + font14 black Player.HasVideo + String.IsEmpty(Window.Property(ppi.Buffered)) - 893 + 963 {{ vscale(50) }} bottom - + font14 black Player.HasVideo + !String.IsEmpty(Window.Property(ppi.Buffered)) @@ -428,31 +431,31 @@ black + + !String.IsEmpty(Window.Property(has.bif)) + !String.IsEmpty(Window.Property(bif.image)) + String.IsEmpty(Window.Property(show.chapters)) + [Control.HasFocus(100) | Control.HasFocus(501) | !String.IsEmpty(Window.Property(button.seek))] + [String.IsEmpty(Window.Property(no.osd.hide_info)) | !String.IsEmpty(Window.Property(show.OSD))] + Visible + 0 + 752 + + 0 + 0 + 324 + {{ vscale(184) }} + script.plex/white-square.png + FF000000 + + + 2 + 2 + 320 + {{ vscale(180) }} + 10 + $INFO[Window.Property(bif.image)] + + !String.IsEmpty(Window.Property(show.OSD)) + !Window.IsVisible(osdvideosettings) + !Window.IsVisible(osdaudiosettings) + !Window.IsVisible(osdsubtitlesettings) + !Window.IsVisible(subtitlesearch) + !Window.IsActive(playerprocessinfo) + !Window.IsActive(selectdialog) + !Window.IsVisible(osdcmssettings) Hidden - - !String.IsEmpty(Window.Property(has.bif)) + [Control.HasFocus(100) | Control.HasFocus(501) | !String.IsEmpty(Window.Property(button.seek))] - Visible - 0 - 752 - - 0 - 0 - 324 - {{ vscale(184) }} - script.plex/white-square.png - FF000000 - - - 2 - 2 - 320 - {{ vscale(180) }} - 10 - $INFO[Window.Property(bif.image)] - - 406 @@ -488,6 +491,7 @@ !Control.HasFocus(401) + 501 !Playlist.IsRepeatOne + !Playlist.IsRepeat + String.IsEmpty(Window.Property(pq.repeat)) 0 @@ -515,6 +519,7 @@ Control.HasFocus(401) + 501 !Playlist.IsRepeatOne + !Playlist.IsRepeat + String.IsEmpty(Window.Property(pq.repeat)) 0 @@ -550,6 +555,7 @@ 125 {{ vscale(101) }} font12 + 501 {{ theme.assets.buttons.base }}shuffle{{ theme.assets.buttons.focusSuffix }}.png {{ theme.assets.buttons.base }}shuffle.png !String.IsEmpty(Window.Property(pq.shuffled)) @@ -565,6 +571,7 @@ 125 {{ vscale(101) }} font12 + 501 {{ theme.assets.buttons.base }}shuffle{{ theme.assets.buttons.focusSuffix }}.png {{ theme.assets.buttons.base }}shuffle.png @@ -577,6 +584,7 @@ 125 {{ vscale(101) }} font12 + 501 {{ theme.assets.buttons.base }}settings{{ theme.assets.buttons.focusSuffix }}.png {{ theme.assets.buttons.base }}settings.png @@ -591,6 +599,7 @@ 125 {{ vscale(101) }} font12 + 501 {{ theme.assets.buttons.base }}next{{ theme.assets.buttons.focusSuffix }}.png {{ theme.assets.buttons.base }}next.png @@ -603,6 +612,7 @@ 125 {{ vscale(101) }} font12 + 501 {{ theme.assets.buttons.base }}next{{ theme.assets.buttons.focusSuffix }}.png {{ theme.assets.buttons.base }}next.png @@ -615,6 +625,7 @@ 125 {{ vscale(101) }} font12 + 501 {{ theme.assets.buttons.base }}skip-forward{{ theme.assets.buttons.focusSuffix }}.png {{ theme.assets.buttons.base }}skip-forward.png @@ -644,6 +655,7 @@ PlayerControl(Play) + 501 !Control.HasFocus(406) !Player.Paused + !Player.Forwarding + !Player.Rewinding @@ -663,6 +675,7 @@ + 501 Control.HasFocus(406) !Player.Paused + !Player.Forwarding + !Player.Rewinding @@ -690,6 +703,7 @@ 125 {{ vscale(101) }} font12 + 501 {{ theme.assets.buttons.base }}stop{{ theme.assets.buttons.focusSuffix }}.png {{ theme.assets.buttons.base }}stop.png @@ -702,6 +716,7 @@ 125 {{ vscale(101) }} font12 + 501 {{ theme.assets.buttons.base }}skip-forward{{ theme.assets.buttons.focusSuffix }}.png {{ theme.assets.buttons.base }}skip-forward.png @@ -714,6 +729,7 @@ 125 {{ vscale(101) }} font12 + 501 {{ theme.assets.buttons.base }}next{{ theme.assets.buttons.focusSuffix }}.png {{ theme.assets.buttons.base }}next.png @@ -725,6 +741,7 @@ 0 125 {{ vscale(101) }} + 501 {{ theme.assets.buttons.base }}next{{ theme.assets.buttons.focusSuffix }}.png {{ theme.assets.buttons.base }}next.png @@ -739,6 +756,7 @@ 125 {{ vscale(101) }} font12 + 501 {{ theme.assets.buttons.base }}pqueue{{ theme.assets.buttons.focusSuffix }}.png {{ theme.assets.buttons.base }}pqueue.png @@ -752,6 +770,7 @@ 125 {{ vscale(101) }} font12 + 501 {{ theme.assets.buttons.base }}pqueue{{ theme.assets.buttons.focusSuffix }}.png {{ theme.assets.buttons.base }}pqueue.png @@ -764,6 +783,7 @@ 125 {{ vscale(101) }} font12 + 501 {{ theme.assets.buttons.base }}subtitle{{ theme.assets.buttons.focusSuffix }}.png {{ theme.assets.buttons.base }}subtitle.png diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-settings.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-settings.xml.tpl index 6f4d60def0..2f36435e2c 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-settings.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-settings.xml.tpl @@ -78,7 +78,7 @@ 590 741 201 - 100 + noop 200 vertical @@ -178,7 +178,7 @@ 776 715 75 - 150 + noop 200 vertical 101 @@ -384,20 +384,6 @@ 151 - - Integer.IsGreater(Container(100).NumItems,0) + String.IsEmpty(Window.Property(section.about)) - 10 - {{ vscale(10) }} - 125 - 100 - font12 - FF000000 - - - - - - SetFocus(100) - - Control.HasFocus(125) + Integer.IsGreater(Container(100).NumItems,0) Integer.IsGreater(Container(100).NumItems,0) + String.IsEmpty(Window.Property(section.about)) diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-settings_select_dialog.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-settings_select_dialog.xml.tpl index 7a3fa27406..f409f525ae 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-settings_select_dialog.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-settings_select_dialog.xml.tpl @@ -25,7 +25,7 @@ 600 {{ vscale(710) }} script.plex/white-square-top-rounded.png - F2606060 + D3111111 0 @@ -59,29 +59,35 @@ left center FFFFFFFF + true + 15 !String.IsEmpty(ListItem.Label2) 20 {{ vscale(15) }} - 560 + 600 {{ vscale(40) }} font12 left center FFFFFFFF + true + 15 !String.IsEmpty(ListItem.Label2) 20 {{ vscale(40) }} - 560 + 600 font10 left center FFBBBBBB + true + 15 @@ -103,18 +109,22 @@ left center FF000000 + true + 15 !String.IsEmpty(ListItem.Label2) 20 {{ vscale(15) }} - 560 + 600 {{ vscale(40) }} font12 left center FF000000 + true + 15 @@ -126,6 +136,8 @@ left center FF222222 + true + 15 diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-squares.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-squares.xml.tpl index d3478f17d3..838e9c5672 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-squares.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-squares.xml.tpl @@ -1,94 +1,101 @@ {% extends "library_posters.xml.tpl" %} {% block filteropts_grouplist %} - -String.IsEmpty(Window.Property(hide.filteroptions)) -340 -35 -1000 -65 -right -30 -horizontal -204 -210 -50 - - false - auto - 65 - font12 - FFFFFFFF - FFFFFFFF - FFFFFFFF - center - center - - - - - 0 - 0 - - - - auto - 65 - font12 - FFFFFFFF - FF000000 - center - center - script.plex/white-square-rounded.png - - - 20 - 0 - - - - !String.IsEqual(Window.Property(media),artist) - false - auto - 65 - font12 - FFFFFFFF - FFFFFFFF - FFFFFFFF - center - center - - - - - 20 - 0 - - - - String.IsEqual(Window.Property(media),artist) - auto - 65 - font12 - FFFFFFFF - FF000000 - FFFFFFFF - center - center - script.plex/white-square-rounded.png - - - 20 - 0 - - - - auto + + String.IsEmpty(Window.Property(hide.filteroptions)) + !Integer.IsGreater(Container(101).ListItem.Property(index),{% block hide_filter_from_index %}5{% endblock %}) + String.IsEmpty(Window.Property(no.content)) + !String.IsEmpty(Window.Property(initialized)) + Conditional + {% block filteropts_animation %} + VisibleChange + {% endblock %} + 170 + {{ vscale(135) }} + 1000 65 - font12 - FFFFFFFF - FF000000 - center - center - script.plex/white-square-rounded.png - - - 20 - 0 - - + right + 30 + horizontal + 304 + 200 + 151 + 101 + 200 + + false + auto + 65 + font12 + FFFFFFFF + FFFFFFFF + FFFFFFFF + center + center + - + - + 0 + 0 + + + + auto + 65 + font12 + FFFFFFFF + FF000000 + center + center + script.plex/white-square-rounded.png + - + 20 + 0 + + + + !String.IsEqual(Window.Property(media),artist) + false + auto + 65 + font12 + FFFFFFFF + FFFFFFFF + FFFFFFFF + center + center + - + - + 20 + 0 + + + + String.IsEqual(Window.Property(media),artist) + auto + 65 + font12 + FFFFFFFF + FF000000 + FFFFFFFF + center + center + script.plex/white-square-rounded.png + - + 20 + 0 + + + + auto + 65 + font12 + FFFFFFFF + FF000000 + center + center + script.plex/white-square-rounded.png + - + 20 + 0 + + {% endblock filteropts_grouplist %} {% block content %} @@ -110,15 +117,17 @@ {{ vscale(145) }} 200 101 + 210 + 600 -20 horizontal 200 true {% with attr = {"width": 126, "height": 100} & template = "includes/themed_button.xml.tpl" & hitrect = {"x": 20, "y": 20, "w": 86, "h": 60} %} - {% include template with name="play" & id=301 & visible="!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)" %} - {% include template with name="shuffle" & id=302 & visible="!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)" %} - {% include template with name="more" & id=303 & visible="String.IsEmpty(Window.Property(no.options)) | Player.HasAudio" %} + {% include template with name="play" & id=301 & visible="String.IsEmpty(Window.Property(disable_playback)) + [!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)]" %} + {% include template with name="shuffle" & id=302 & visible="String.IsEmpty(Window.Property(disable_playback)) + [!String.IsEqual(Window(10000).Property(script.plex.item.type),collection) | String.IsEqual(Window.Property(media),collection)]" %} + {% include template with name="more" & id=303 & visible="String.IsEmpty(Window.Property(disable_playback)) + [String.IsEmpty(Window.Property(no.options)) | Player.HasAudio]" %} {% include template with name="chapters" & id=304 & visible="String.IsEmpty(Window.Property(hide.filteroptions))" %} {% endwith %} @@ -138,7 +147,8 @@ 0 1800 1280 - 300 + 300 + 600 151 200 vertical @@ -299,7 +309,8 @@ 0 34 1050 - 100 + 100 + 600 152 200 vertical @@ -399,6 +410,7 @@ 12 910 true + Conditional script.plex/white-square-rounded.png script.plex/white-square-rounded.png script.plex/white-square-rounded.png diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-video_current_playlist.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-video_current_playlist.xml.tpl index 1d1eeb7031..6f1e48f8c6 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-video_current_playlist.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-video_current_playlist.xml.tpl @@ -22,9 +22,7 @@ 0 1170 {{ vscale(800) }} - 200 152 - 300 200 vertical 4 @@ -78,6 +76,7 @@ center FFFFFFFF + true 0 @@ -89,6 +88,7 @@ center B8FFFFFF + true @@ -102,6 +102,27 @@ $INFO[ListItem.Thumb] scale + + !String.IsEmpty(ListItem.Property(progress)) + 63 + {{ vscale(79) }} + + 0 + 0 + 132 + {{ vscale(6) }} + script.plex/white-square.png + C0000000 + + + 0 + 1 + 132 + {{ vscale(4) }} + $INFO[ListItem.Property(progress)] + FFCC7B19 + + {% include "includes/watched_indicator.xml.tpl" with xoff=132+63 & yoff=11 & uw_posy=11 & uw_size=24 & scale="tiny" %} 226 @@ -116,6 +137,7 @@ center FFFFFFFF + true 0 @@ -127,6 +149,7 @@ center B8FFFFFF + true @@ -204,6 +227,7 @@ center FFFFFFFF + true 0 @@ -215,6 +239,7 @@ center B8FFFFFF + true @@ -242,6 +267,7 @@ center FFFFFFFF + true 0 @@ -253,6 +279,7 @@ center B8FFFFFF + true @@ -359,6 +386,7 @@ center DF000000 + true 0 @@ -370,6 +398,7 @@ center 98000000 + true @@ -383,6 +412,27 @@ $INFO[ListItem.Thumb] scale + + !String.IsEmpty(ListItem.Property(progress)) + 103 + {{ vscale(94) }} + + 0 + 0 + 178 + {{ vscale(6) }} + script.plex/white-square.png + C0000000 + + + 0 + 1 + 178 + {{ vscale(4) }} + $INFO[ListItem.Property(progress)] + FFCC7B19 + + {% include "includes/watched_indicator.xml.tpl" with xoff=178+103 %} 313 @@ -397,6 +447,7 @@ center DF000000 + true 0 @@ -408,6 +459,7 @@ center 98000000 + true @@ -434,7 +486,6 @@ 33 10 {{ vscale(734) }} - 101 true script.plex/white-square-rounded.png script.plex/white-square-rounded.png @@ -444,7 +495,6 @@ false vertical false - 151 diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-video_player.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-video_player.xml.tpl index a9224f9adc..938744b4ec 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-video_player.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-video_player.xml.tpl @@ -35,15 +35,6 @@ {{ vscale(135) }} 102 - - 60 - 0 - 1800 - {{ vscale(2) }} - script.plex/white-square.png - A0000000 - - false 60 @@ -117,7 +108,7 @@ - false + true 0 {{ vscale(269) }} 462 @@ -128,7 +119,7 @@ - false + true 0 {{ vscale(301) }} 462 @@ -228,7 +219,7 @@ - false + true 0 {{ vscale(313) }} 537 @@ -239,7 +230,7 @@ - false + true 0 {{ vscale(345) }} 537 @@ -269,7 +260,7 @@ !String.IsEmpty(Window.Property(has.next)) - false + true 1177 {{ vscale(131) }} 683 @@ -293,9 +284,9 @@ - false + 1177 - {{ vscale(300) }} + {{ vscale(240) }} 683 {{ vscale(215) }} font12 @@ -308,7 +299,7 @@ String.IsEmpty(Window.Property(has.next)) - false + true 580 {{ vscale(131) }} 1280 @@ -332,9 +323,9 @@ - false + 580 - {{ vscale(300) }} + {{ vscale(240) }} 1280 {{ vscale(225) }} font12 @@ -384,8 +375,8 @@ {{ vscale(430) }} 100 401 - false - false + noop + noop 200 horizontal 4 @@ -631,15 +622,6 @@ 401 1920 {{ vscale(520) }} - - !String.IsEmpty(Window.Property(divider.401)) - 60 - 0 - 1800 - {{ vscale(2) }} - script.plex/white-square.png - A0000000 - 60 0 @@ -658,8 +640,8 @@ {{ vscale(520) }} 400 403 - false - false + noop + noop 200 horizontal 4 @@ -881,15 +863,6 @@ 403 1920 {{ vscale(410) }} - - !String.IsEmpty(Window.Property(divider.403)) - 60 - {{ vscale(20) }} - 1800 - {{ vscale(2) }} - script.plex/white-square.png - A0000000 - 60 {{ vscale(20) }} diff --git a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-video_settings_dialog.xml.tpl b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-video_settings_dialog.xml.tpl index cd387171b0..c7eea39aba 100644 --- a/script.plexmod/resources/skins/Main/1080i/templates/script-plex-video_settings_dialog.xml.tpl +++ b/script.plexmod/resources/skins/Main/1080i/templates/script-plex-video_settings_dialog.xml.tpl @@ -40,7 +40,7 @@ 1000 {{ vscale(610) }} script.plex/white-square-top-rounded.png - F2606060 + D3111111 0 @@ -83,6 +83,8 @@ left center FFFFFFFF + true + 15 @@ -94,6 +96,8 @@ right center FFFFFFFF + true + 15 @@ -114,6 +118,8 @@ left center FF000000 + true + 15 @@ -125,6 +131,8 @@ right center FF000000 + true + 15 @@ -139,7 +147,7 @@ 100 !Window.IsVisible(sliderdialog) + Control.IsVisible(100) + !Window.IsVisible(osdvideosettings) + !Window.IsVisible(osdaudiosettings) + !Window.IsVisible(osdsubtitlesettings) + !Window.IsVisible(subtitlesearch) + !Window.IsVisible(osdcmssettings) script.plex/white-square.png - script.plex/white-square.png + script.plex/white-square.png script.plex/white-square.png - - diff --git a/script.plexmod/resources/skins/Main/media/script.plex/home/type/show.png b/script.plexmod/resources/skins/Main/media/script.plex/home/type/show.png index d94060b71c..0369981e14 100644 Binary files a/script.plexmod/resources/skins/Main/media/script.plex/home/type/show.png and b/script.plexmod/resources/skins/Main/media/script.plex/home/type/show.png differ diff --git a/script.plexmod/service.py b/script.plexmod/service.py index c5f10e8245..73eb72b05a 100644 --- a/script.plexmod/service.py +++ b/script.plexmod/service.py @@ -1,22 +1,22 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import -from kodi_six import xbmc -from kodi_six import xbmcgui -from kodi_six import xbmcaddon +try: + from importlib import reload +except ImportError: + try: + from imp import reload + except ImportError: + pass + # python 2.7 has "reload" natively - -def main(): - if xbmc.getInfoLabel('Window(10000).Property(script.plex.service.started)'): - # Prevent add-on updates from starting a new version of the addon - return - - xbmcgui.Window(10000).setProperty('script.plex.service.started', '1') - - if xbmcaddon.Addon().getSetting('kiosk.mode') == 'true': - xbmc.log('script.plex: Starting from service (Kiosk Mode)', xbmc.LOGINFO) - delay = xbmcaddon.Addon().getSetting('kiosk.delay') or "0" - xbmc.executebuiltin('RunScript(script.plexmod{})'.format(",{}".format(delay) if delay != "0" else "")) +import lib.service_runner as runner if __name__ == '__main__': - main() + restarting_service = False + while 1: + if runner.main(restarting_service=restarting_service): + reload(runner) + restarting_service = True + else: + break