diff options
author | Christian Kölpin <raptor2101@mykolab.com> | 2016-12-16 19:22:24 +0100 |
---|---|---|
committer | Philipp Temminghoff <phil65@kodi.tv> | 2016-12-16 19:22:24 +0100 |
commit | 7bafd9290195ce39adc83531d103a4a2a97d5b7b (patch) | |
tree | 67ec57f7d5834fe94f25bcf7a57d0674f4230f8e /plugin.video.mediathek | |
parent | 82884ecc755da2186f1724e54501b4ac73e573b4 (diff) |
[plugin.video.mediathek] 0.7.5 (#756)
[plugin.video.mediathek] 0.7.5
Diffstat (limited to 'plugin.video.mediathek')
38 files changed, 3004 insertions, 0 deletions
diff --git a/plugin.video.mediathek/.gitignore b/plugin.video.mediathek/.gitignore new file mode 100755 index 0000000..539da74 --- /dev/null +++ b/plugin.video.mediathek/.gitignore @@ -0,0 +1 @@ +*.py[co] diff --git a/plugin.video.mediathek/LICENSE.txt b/plugin.video.mediathek/LICENSE.txt new file mode 100644 index 0000000..48c6196 --- /dev/null +++ b/plugin.video.mediathek/LICENSE.txt @@ -0,0 +1,533 @@ + GNU GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. <http://fsf.org/> + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. +Preamble + +The GNU General Public License is a free, copyleft license for software and +other kinds of works. + +The licenses for most software and other practical works are designed to take +away your freedom to share and change the works. By contrast, the GNU General +Public License is intended to guarantee your freedom to share and change all +versions of a program--to make sure it remains free software for all its users. +We, the Free Software Foundation, use the GNU General Public License for most of +our software; it applies also to any other work released this way by its +authors. You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom to +distribute copies of free software (and charge for them if you wish), that you +receive source code or can get it if you want it, that you can change the +software or use pieces of it in new free programs, and that you know you can do +these things. + +To protect your rights, we need to prevent others from denying you these rights +or asking you to surrender the rights. Therefore, you have certain +responsibilities if you distribute copies of the software, or if you modify it: +responsibilities to respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for a +fee, you must pass on to the recipients the same freedoms that you received. You +must make sure that they, too, receive or can get the source code. And you must +show them these terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: (1) assert +copyright on the software, and (2) offer you this License giving you legal +permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that there +is no warranty for this free software. For both users' and authors' sake, the +GPL requires that modified versions be marked as changed, so that their problems +will not be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified +versions of the software inside them, although the manufacturer can do so. This +is fundamentally incompatible with the aim of protecting users' freedom to +change the software. The systematic pattern of such abuse occurs in the area of +products for individuals to use, which is precisely where it is most +unacceptable. Therefore, we have designed this version of the GPL to prohibit +the practice for those products. If such problems arise substantially in other +domains, we stand ready to extend this provision to those domains in future +versions of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. States +should not allow patents to restrict development and use of software on +general-purpose computers, but in those that do, we wish to avoid the special +danger that patents applied to a free program could make it effectively +proprietary. To prevent this, the GPL assures that patents cannot be used to +render the program non-free. + +The precise terms and conditions for copying, distribution and modification +follow. +TERMS AND CONDITIONS +0. Definitions. + +“This License” refers to version 3 of the GNU General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of works, +such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this License. Each +licensee is addressed as “you”. “Licensees” and “recipients” may be individuals +or organizations. + +To “modify” a work means to copy from or adapt all or part of the work in a +fashion requiring copyright permission, other than the making of an exact copy. +The resulting work is called a “modified version” of the earlier work or a work +“based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based on the +Program. + +To “propagate” a work means to do anything with it that, without permission, +would make you directly or secondarily liable for infringement under applicable +copyright law, except executing it on a computer or modifying a private copy. +Propagation includes copying, distribution (with or without modification), +making available to the public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other parties to +make or receive copies. Mere interaction with a user through a computer network, +with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” to the extent +that it includes a convenient and prominently visible feature that (1) displays +an appropriate copyright notice, and (2) tells the user that there is no +warranty for the work (except to the extent that warranties are provided), that +licensees may convey the work under this License, and how to view a copy of this +License. If the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. +1. Source Code. + +The “source code” for a work means the preferred form of the work for making +modifications to it. “Object code” means any non-source form of a work. + +A “Standard Interface” means an interface that either is an official standard +defined by a recognized standards body, or, in the case of interfaces specified +for a particular programming language, one that is widely used among developers +working in that language. + +The “System Libraries” of an executable work include anything, other than the +work as a whole, that (a) is included in the normal form of packaging a Major +Component, but which is not part of that Major Component, and (b) serves only to +enable use of the work with that Major Component, or to implement a Standard +Interface for which an implementation is available to the public in source code +form. A “Major Component”, in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system (if any) on +which the executable work runs, or a compiler used to produce the work, or an +object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all the source +code needed to generate, install, and (for an executable work) run the object +code and to modify the work, including scripts to control those activities. +However, it does not include the work's System Libraries, or general-purpose +tools or generally available free programs which are used unmodified in +performing those activities but which are not part of the work. For example, +Corresponding Source includes interface definition files associated with source +files for the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, such as by +intimate data communication or control flow between those subprograms and other +parts of the work. + +The Corresponding Source need not include anything that users can regenerate +automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. +2. Basic Permissions. + +All rights granted under this License are granted for the term of copyright on +the Program, and are irrevocable provided the stated conditions are met. This +License explicitly affirms your unlimited permission to run the unmodified +Program. The output from running a covered work is covered by this License only +if the output, given its content, constitutes a covered work. This License +acknowledges your rights of fair use or other equivalent, as provided by +copyright law. + +You may make, run and propagate covered works that you do not convey, without +conditions so long as your license otherwise remains in force. You may convey +covered works to others for the sole purpose of having them make modifications +exclusively for you, or provide you with facilities for running those works, +provided that you comply with the terms of this License in conveying all +material for which you do not control copyright. Those thus making or running +the covered works for you must do so exclusively on your behalf, under your +direction and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions +stated below. Sublicensing is not allowed; section 10 makes it unnecessary. +3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological measure under +any applicable law fulfilling obligations under article 11 of the WIPO copyright +treaty adopted on 20 December 1996, or similar laws prohibiting or restricting +circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention is +effected by exercising rights under this License with respect to the covered +work, and you disclaim any intention to limit operation or modification of the +work as a means of enforcing, against the work's users, your or third parties' +legal rights to forbid circumvention of technological measures. +4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you receive it, +in any medium, provided that you conspicuously and appropriately publish on each +copy an appropriate copyright notice; keep intact all notices stating that this +License and any non-permissive terms added in accord with section 7 apply to the +code; keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may +offer support or warranty protection for a fee. +5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to produce it +from the Program, in the form of source code under the terms of section 4, +provided that you also meet all of these conditions: +a) The work must carry prominent notices stating that you modified it, and +giving a relevant date. +b) The work must carry prominent notices stating that it is released under this +License and any conditions added under section 7. This requirement modifies the +requirement in section 4 to “keep intact all notices”. +c) You must license the entire work, as a whole, under this License to anyone +who comes into possession of a copy. This License will therefore apply, along +with any applicable section 7 additional terms, to the whole of the work, and +all its parts, regardless of how they are packaged. This License gives no +permission to license the work in any other way, but it does not invalidate such +permission if you have separately received it. +d) If the work has interactive user interfaces, each must display Appropriate +Legal Notices; however, if the Program has interactive interfaces that do not +display Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which +are not by their nature extensions of the covered work, and which are not +combined with it such as to form a larger program, in or on a volume of a +storage or distribution medium, is called an “aggregate” if the compilation and +its resulting copyright are not used to limit the access or legal rights of the +compilation's users beyond what the individual works permit. Inclusion of a +covered work in an aggregate does not cause this License to apply to the other +parts of the aggregate. +6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of sections 4 +and 5, provided that you also convey the machine-readable Corresponding Source +under the terms of this License, in one of these ways: +a) Convey the object code in, or embodied in, a physical product (including a +physical distribution medium), accompanied by the Corresponding Source fixed on +a durable physical medium customarily used for software interchange. +b) Convey the object code in, or embodied in, a physical product (including a +physical distribution medium), accompanied by a written offer, valid for at +least three years and valid for as long as you offer spare parts or customer +support for that product model, to give anyone who possesses the object code +either (1) a copy of the Corresponding Source for all the software in the +product that is covered by this License, on a durable physical medium +customarily used for software interchange, for a price no more than your +reasonable cost of physically performing this conveying of source, or (2) access +to copy the Corresponding Source from a network server at no charge. +c) Convey individual copies of the object code with a copy of the written offer +to provide the Corresponding Source. This alternative is allowed only +occasionally and noncommercially, and only if you received the object code with +such an offer, in accord with subsection 6b. +d) Convey the object code by offering access from a designated place (gratis or +for a charge), and offer equivalent access to the Corresponding Source in the +same way through the same place at no further charge. You need not require +recipients to copy the Corresponding Source along with the object code. If the +place to copy the object code is a network server, the Corresponding Source may +be on a different server (operated by you or a third party) that supports +equivalent copying facilities, provided you maintain clear directions next to +the object code saying where to find the Corresponding Source. Regardless of +what server hosts the Corresponding Source, you remain obligated to ensure that +it is available for as long as needed to satisfy these requirements. +e) Convey the object code using peer-to-peer transmission, provided you inform +other peers where the object code and Corresponding Source of the work are being +offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the +Corresponding Source as a System Library, need not be included in conveying the +object code work. + +A “User Product” is either (1) a “consumer product”, which means any tangible +personal property which is normally used for personal, family, or household +purposes, or (2) anything designed or sold for incorporation into a dwelling. In +determining whether a product is a consumer product, doubtful cases shall be +resolved in favor of coverage. For a particular product received by a particular +user, “normally used” refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way in which +the particular user actually uses, or expects or is expected to use, the +product. A product is a consumer product regardless of whether the product has +substantial commercial, industrial or non-consumer uses, unless such uses +represent the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, procedures, +authorization keys, or other information required to install and execute +modified versions of a covered work in that User Product from a modified version +of its Corresponding Source. The information must suffice to ensure that the +continued functioning of the modified object code is in no case prevented or +interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as part of a +transaction in which the right of possession and use of the User Product is +transferred to the recipient in perpetuity or for a fixed term (regardless of +how the transaction is characterized), the Corresponding Source conveyed under +this section must be accompanied by the Installation Information. But this +requirement does not apply if neither you nor any third party retains the +ability to install modified object code on the User Product (for example, the +work has been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates for a +work that has been modified or installed by the recipient, or for the User +Product in which it has been modified or installed. Access to a network may be +denied when the modification itself materially and adversely affects the +operation of the network or violates the rules and protocols for communication +across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord +with this section must be in a format that is publicly documented (and with an +implementation available to the public in source code form), and must require no +special password or key for unpacking, reading or copying. +7. Additional Terms. + +“Additional permissions” are terms that supplement the terms of this License by +making exceptions from one or more of its conditions. Additional permissions +that are applicable to the entire Program shall be treated as though they were +included in this License, to the extent that they are valid under applicable +law. If additional permissions apply only to part of the Program, that part may +be used separately under those permissions, but the entire Program remains +governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any +additional permissions from that copy, or from any part of it. (Additional +permissions may be written to require their own removal in certain cases when +you modify the work.) You may place additional permissions on material, added by +you to a covered work, for which you have or can give appropriate copyright +permission. + +Notwithstanding any other provision of this License, for material you add to a +covered work, you may (if authorized by the copyright holders of that material) +supplement the terms of this License with terms: +a) Disclaiming warranty or limiting liability differently from the terms of +sections 15 and 16 of this License; or +b) Requiring preservation of specified reasonable legal notices or author +attributions in that material or in the Appropriate Legal Notices displayed by +works containing it; or +c) Prohibiting misrepresentation of the origin of that material, or requiring +that modified versions of such material be marked in reasonable ways as +different from the original version; or +d) Limiting the use for publicity purposes of names of licensors or authors of +the material; or +e) Declining to grant rights under trademark law for use of some trade names, +trademarks, or service marks; or +f) Requiring indemnification of licensors and authors of that material by anyone +who conveys the material (or modified versions of it) with contractual +assumptions of liability to the recipient, for any liability that these +contractual assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered “further restrictions” +within the meaning of section 10. If the Program as you received it, or any part +of it, contains a notice stating that it is governed by this License along with +a term that is a further restriction, you may remove that term. If a license +document contains a further restriction but permits relicensing or conveying +under this License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does not survive +such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, +in the relevant source files, a statement of the additional terms that apply to +those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a +separately written license, or stated as exceptions; the above requirements +apply either way. +8. Termination. + +You may not propagate or modify a covered work except as expressly provided +under this License. Any attempt otherwise to propagate or modify it is void, and +will automatically terminate your rights under this License (including any +patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a +particular copyright holder is reinstated (a) provisionally, unless and until +the copyright holder explicitly and finally terminates your license, and (b) +permanently, if the copyright holder fails to notify you of the violation by +some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated +permanently if the copyright holder notifies you of the violation by some +reasonable means, this is the first time you have received notice of violation +of this License (for any work) from that copyright holder, and you cure the +violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of +parties who have received copies or rights from you under this License. If your +rights have been terminated and not permanently reinstated, you do not qualify +to receive new licenses for the same material under section 10. +9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run a copy of +the Program. Ancillary propagation of a covered work occurring solely as a +consequence of using peer-to-peer transmission to receive a copy likewise does +not require acceptance. However, nothing other than this License grants you +permission to propagate or modify any covered work. These actions infringe +copyright if you do not accept this License. Therefore, by modifying or +propagating a covered work, you indicate your acceptance of this License to do +so. +10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically receives a +license from the original licensors, to run, modify and propagate that work, +subject to this License. You are not responsible for enforcing compliance by +third parties with this License. + +An “entity transaction” is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered work results +from an entity transaction, each party to that transaction who receives a copy +of the work also receives whatever licenses to the work the party's predecessor +in interest had or could give under the previous paragraph, plus a right to +possession of the Corresponding Source of the work from the predecessor in +interest, if the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights +granted or affirmed under this License. For example, you may not impose a +license fee, royalty, or other charge for exercise of rights granted under this +License, and you may not initiate litigation (including a cross-claim or +counterclaim in a lawsuit) alleging that any patent claim is infringed by +making, using, selling, offering for sale, or importing the Program or any +portion of it. +11. Patents. + +A “contributor” is a copyright holder who authorizes use under this License of +the Program or a work on which the Program is based. The work thus licensed is +called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims owned or +controlled by the contributor, whether already acquired or hereafter acquired, +that would be infringed by some manner, permitted by this License, of making, +using, or selling its contributor version, but do not include claims that would +be infringed only as a consequence of further modification of the contributor +version. For purposes of this definition, “control” includes the right to grant +patent sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent +license under the contributor's essential patent claims, to make, use, sell, +offer for sale, import and otherwise run, modify and propagate the contents of +its contributor version. + +In the following three paragraphs, a “patent license” is any express agreement +or commitment, however denominated, not to enforce a patent (such as an express +permission to practice a patent or covenant not to sue for patent infringement). +To “grant” such a patent license to a party means to make such an agreement or +commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the +Corresponding Source of the work is not available for anyone to copy, free of +charge and under the terms of this License, through a publicly available network +server or other readily accessible means, then you must either (1) cause the +Corresponding Source to be so available, or (2) arrange to deprive yourself of +the benefit of the patent license for this particular work, or (3) arrange, in a +manner consistent with the requirements of this License, to extend the patent +license to downstream recipients. “Knowingly relying” means you have actual +knowledge that, but for the patent license, your conveying the covered work in a +country, or your recipient's use of the covered work in a country, would +infringe one or more identifiable patents in that country that you have reason +to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you +convey, or propagate by procuring conveyance of, a covered work, and grant a +patent license to some of the parties receiving the covered work authorizing +them to use, propagate, modify or convey a specific copy of the covered work, +then the patent license you grant is automatically extended to all recipients of +the covered work and works based on it. + +A patent license is “discriminatory” if it does not include within the scope of +its coverage, prohibits the exercise of, or is conditioned on the non-exercise +of one or more of the rights that are specifically granted under this License. +You may not convey a covered work if you are a party to an arrangement with a +third party that is in the business of distributing software, under which you +make payment to the third party based on the extent of your activity of +conveying the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by you (or +copies made from those copies), or (b) primarily for and in connection with +specific products or compilations that contain the covered work, unless you +entered into that arrangement, or that patent license was granted, prior to 28 +March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied +license or other defenses to infringement that may otherwise be available to you +under applicable patent law. +12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not excuse +you from the conditions of this License. If you cannot convey a covered work so +as to satisfy simultaneously your obligations under this License and any other +pertinent obligations, then as a consequence you may not convey it at all. For +example, if you agree to terms that obligate you to collect a royalty for +further conveying from those to whom you convey the Program, the only way you +could satisfy both those terms and this License would be to refrain entirely +from conveying the Program. +13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have permission to link +or combine any covered work with a work licensed under version 3 of the GNU +Affero General Public License into a single combined work, and to convey the +resulting work. The terms of this License will continue to apply to the part +which is the covered work, but the special requirements of the GNU Affero +General Public License, section 13, concerning interaction through a network +will apply to the combination as such. +14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of the GNU +General Public License from time to time. Such new versions will be similar in +spirit to the present version, but may differ in detail to address new problems +or concerns. + +Each version is given a distinguishing version number. If the Program specifies +that a certain numbered version of the GNU General Public License “or any later +version” applies to it, you have the option of following the terms and +conditions either of that numbered version or of any later version published by +the Free Software Foundation. If the Program does not specify a version number +of the GNU General Public License, you may choose any version ever published by +the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the +GNU General Public License can be used, that proxy's public statement of +acceptance of a version permanently authorizes you to choose that version for +the Program. + +Later license versions may give you additional or different permissions. +However, no additional obligations are imposed on any author or copyright holder +as a result of your choosing to follow a later version. +15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER +PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE +QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. +16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY +COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS +PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, +INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE +THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED +INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE +PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY +HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided above cannot +be given local legal effect according to their terms, reviewing courts shall +apply local law that most closely approximates an absolute waiver of all civil +liability in connection with the Program, unless a warranty or assumption of +liability accompanies a copy of the Program in return for a fee.
\ No newline at end of file diff --git a/plugin.video.mediathek/addon.xml b/plugin.video.mediathek/addon.xml new file mode 100644 index 0000000..975df8a --- /dev/null +++ b/plugin.video.mediathek/addon.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<addon + id="plugin.video.mediathek" + name="Mediathek" + version="0.7.5" + provider-name="Raptor 2101"> + <requires> + <import addon="xbmc.python" version="2.25.0"/> + <import addon="script.module.beautifulsoup4" version="4.3.2"/> + </requires> + <extension + point="xbmc.python.pluginsource" + library="default.py"> + <provides>video</provides> + </extension> + <extension point="xbmc.addon.metadata"> + <summary lang="de">Ermöglicht den Zugriff auf alle deutschen Mediatheken der öffentlich Rechtlichen</summary> + <summary lang="en">Gives access to most video-platforms from German public service broadcasters</summary> + <summary lang="it">Fornisce l'accesso a gran parte delle piattaforme video operate dalle emittenti pubbliche tedesche</summary> + <description lang="de">Ermöglicht den Zugriff auf alle deutschen Mediatheken der öffentlich Rechtlichen. +Aktuell implementiert sind: + 3Sat, ARD, ZDF, ARTE, ORF, NDR, KiKa (ohne Kikaninchen) +</description> + <description lang="en">Gives access to most video-platforms from German public service broadcasters. +Currently implemented: + 3Sat, ARD, ZDF, ARTE, ORF, NDR, KiKa (without Kikaninchen) +</description> + <description lang="it">Fornisce l'accesso a gran parte delle piattaforme video operate dalle emittenti pubbliche tedesche. +Al momento sono supportate le seguenti emittenti: + 3Sat, ARD, ZDF, ARTE, ORF, NDR, KiKa (senza Kikaninchen) + </description> + <language>de</language> + <platform>all</platform> + <license>GNU GENERAL PUBLIC LICENSE. Version 3, 29 June 2007</license> + <forum>https://github.com/raptor2101/Mediathek/issues</forum> + <website>https://github.com/raptor2101/Mediathek</website> + <source>https://github.com/raptor2101/Mediathek</source> + <email>raptor2101@gmx.de</email> + </extension> +</addon> diff --git a/plugin.video.mediathek/changelog.txt b/plugin.video.mediathek/changelog.txt new file mode 100644 index 0000000..f0ea03b --- /dev/null +++ b/plugin.video.mediathek/changelog.txt @@ -0,0 +1,70 @@ +0.7.5 - CHG: reimplement ZDF Mediathak (important: ZDF enforces TLSv1 with SNI enabled, what is only supported by python 2.7.9.) + Temporaily fix ARTE +0.7.4 - CHG: Repearing NDR und 3SAT +0.7.3 - CHG: Change to BeautifulSoup instead of lxml +0.7.2 - FIX: Multiple bugfixes for ard (by p-friedrich) +0.7.1 - FIX: Minor bugfixes for ard +0.7.0 - FIX: Minor bugfixes for arte,Kika,ZDF +0.6.9 - FIX: Fixes for NDR (by +0.6.8 - FIX: Fixes for KIKA +0.6.7 - FIX: Fixes for ARTE and NDR +0.6.6 - FIX: Minor Fixes for ARTE and ARD +0.6.5 - FIX: Minor Fixes for ARTE and NDR +0.6.4 - FIX: ARTE and NDR +0.6.3 - FIX: extend ARD plugin +0.6.2 - FIX: adopt ARD infrastructure change +0.6.1 - FIX: adopt NDR infrastructure change +0.6.0 - FIX: Regexp for crawling the ARD-Mediathek +0.5.9 - FIX: adopt NDR infrastructure change (by dethfee) +0.5.8 - FIX: adopt NDR link change (by Toerless Eckert) +0.5.7 - CHG: Temporaily disable BR, BR Alpha + FIX: 3Sat offers webem, will used as fallback + REM: WDR Support - they moves to a creepy format and i'am unable to handle this in xbmc correctly +0.5.6 - FIX: NDR Update by dethfeet +0.5.5 - FIX: More NDR fixes +0.5.4 - FIX: NDR Fixed by dethfeet +0.5.2 - FIX: adopt some specialities in the ARD Format +0.5.2 - FIX: Get all ARD features back working +0.5.1 - FIX: Get ARD working again (without "Sendungen A-Z") +0.5.0 - FIX: ORF-Program listing is parsed from Webseite instead of being hardcoded +0.4.9 - FIX: KiKa Plus naming +0.4.8 - ADD: show http://www.3sat.de/mediathek/rss/mediathek_makro.xml to 3sat show list. + FIX: BR Alpha +0.4.7 - Fix syntax error +0.4.6 - Minor fix for rasp-xbmc + added new channels for kika +0.4.5 - FIX: ARTE and KIKA - adopt format changes +0.4.4 - FIX: Get ARD working again (base code from soern) +0.4.3 - FIX: NDR streams / NDR changed their streaming adresses by soern +0.4.2 - FIX: WDR add search function, add stream duration, add "pageSize" optioni (Soern) + FIX: bayrisches FS podcasts eingefügt, Auswahl nach Sendungen, Suche (Soern) +0.4.1 - FIX: Soern completed the NDR implementation +0.4.0 - FIX: Commit FIX from soern +0.3.9 - ADD: KIKA (Without Kikaninchen) +0.3.8 - FIX: 3SAT Broken Link + ADD: NDR +0.3.7 - FIX: ORF +0.3.6 - FIX: WDR +0.3.5 - FIX: BayernFS +0.3.4 - FIX: ARTE +0.3.3 - ADD: BR-Alpha +0.3.2 - Version bump for eden pre +0.2.2 - ADD: WDR Mediathek + FIX: ZDF Mediathek will now ignore mp3 files + FIX: OfflineCache removed +0.2.1 - ADD: PageNavigation for ZDF + FIX: ChannelLogos fixed +0.2.0 - FIX: Minor Bugfixes +0.1.9 - FIX: ORF Playback + FIX: Progressbar is displayed corectly +0.1.8 - ZDF Changes their Interface, reimplement it +0.1.7 - Adding ORF +0.1.6 - FIXES: Arty playback now functional, RTMP set as default, Sorting of Items in Bayrisches Fernsehen + ADD: Searching for ARTE, ARD, ZDF, 3Sat +0.1.5 - Bypassing Arte-FalshVersion check + - Adding Bayrisches Fernsehen (by Benedikt Elser) +0.1.4 - Some improvements for ARD +0.1.3 - Adjust ARD Parsing - ard now works fine +0.1.2 - Now RTMP playback is avaible under Windows !!! +0.1.1 - added script to extract Media-Information, Added timeout while downloading something. +0.1.0 - initial release diff --git a/plugin.video.mediathek/default.py b/plugin.video.mediathek/default.py new file mode 100644 index 0000000..c09863f --- /dev/null +++ b/plugin.video.mediathek/default.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +#-------------LicenseHeader-------------- +# plugin.video.mediathek - Gives access to most video-platforms from German public service broadcasters +# Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import urllib,xbmc,os,xbmcaddon +from simplexbmc import SimpleXbmcGui +from mediathek.factory import MediathekFactory +__plugin__ = "mediathek" + +settings = xbmcaddon.Addon(id='plugin.video.mediathek'); + +gui = SimpleXbmcGui(settings); + +def get_params(): + paramDict = {} + try: + print "get_params() argv=", sys.argv + if sys.argv[2]: + paramPairs=sys.argv[2][1:].split( "&" ) + for paramsPair in paramPairs: + paramSplits = paramsPair.split('=') + if (len(paramSplits))==2: + paramDict[paramSplits[0]] = paramSplits[1] + except: + errorOK() + return paramDict + + +params = get_params(); +mediathekName = params.get("type", "") +action=params.get("action", "") + +DIR_HOME = xbmc.translatePath(settings.getAddonInfo("profile")) +if not os.path.exists(DIR_HOME): + os.mkdir(DIR_HOME); + +gui.log("Quality: %s"%gui.quality); +gui.log("argv[0]: %s"%sys.argv[0]); +gui.log("argv[1]: %s"%sys.argv[1]); +gui.openMenuContext(); +factory = MediathekFactory(); + + + + +if(mediathekName == ""): + if(action == ""): + gui.addSearchButton(None); + gui.listAvailableMediathekes(factory.getAvaibleMediathekTypes()); + else: + result = gui.keyboardInput(); + if (result.isConfirmed()): + searchText = unicode(result.getText().decode('UTF-8')); + for name in factory.getAvaibleMediathekTypes(): + mediathek = factory.getMediathek(name, gui); + if(mediathek.isSearchable()): + mediathek.searchVideo(searchText); + else: + gui.back(); + +else: + cat=int(params.get("cat", 0)) + mediathek = factory.getMediathek(mediathekName,gui); + + if(action == "openTopicPage"): + link = urllib.unquote_plus(params.get("link", "")); + gui.log(link); + mediathek.buildPageMenu(link, 0); + elif(action == "openPlayList"): + link = urllib.unquote_plus(params.get("link", "")); + gui.log(link); + remotePlaylist = mediathek.loadPage(link); + gui.playPlaylist(remotePlaylist); + elif(action == "openMenu"): + path = params.get("path", "0"); + mediathek.buildMenu(path) + elif(action == "search"): + result = gui.keyboardInput(); + if (result.isConfirmed()): + searchText = unicode(result.getText().decode('UTF-8')); + mediathek.searchVideo(searchText); + else: + gui.back(); + elif(action == "openJsonPath"): + path = params.get("path", "0"); + callhash = params.get("callhash", "0"); + mediathek.buildJsonMenu(path,callhash,0) + elif(action == "openJsonLink"): + link = urllib.unquote_plus(params.get("link", "")); + mediathek.playVideoFromJsonLink(link); + else: + if(mediathek.isSearchable()): + gui.addSearchButton(mediathek); + mediathek.displayCategories(); +gui.closeMenuContext(); + diff --git a/plugin.video.mediathek/icon.png b/plugin.video.mediathek/icon.png Binary files differnew file mode 100644 index 0000000..4f91188 --- /dev/null +++ b/plugin.video.mediathek/icon.png diff --git a/plugin.video.mediathek/mediathek/__init__.py b/plugin.video.mediathek/mediathek/__init__.py new file mode 100644 index 0000000..b3aedef --- /dev/null +++ b/plugin.video.mediathek/mediathek/__init__.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +#-------------LicenseHeader-------------- +# plugin.video.Mediathek - Gives access to most video-platforms from German public service broadcasters +# Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import sys, urllib2,urllib, time; +import socket +socket.setdefaulttimeout(1); + +class SimpleLink(object): + def __init__(self, basePath, size): + self.basePath = basePath; + self.size = size; + +class ComplexLink(object): + def __init__(self, basePath, playPath, size): + self.basePath = basePath; + self.playPath = playPath; + self.size = size; + +class TreeNode(object): + def __init__(self,path,name,link,displayElements,childNodes = []): + self.name = name; + self.path = path; + self.link = link; + self.displayElements = displayElements; + self.childNodes = childNodes; + +class DisplayObject(object): + def __init__(self,title,subTitle,picture,description,link=[],isPlayable = True, date = None, duration = None): + self.title = title + self.subTitle = subTitle + self.link = link + self.picture = picture + self.isPlayable = isPlayable + self.description = description + self.date = date; + self.duration = duration; + +class Mediathek(object): + def loadPage(self,url, values = None, maxTimeout = None): + try: + safe_url = url.replace( " ", "%20" ).replace("&","&") + + if(values is not None): + data = urllib.urlencode(values) + req = urllib2.Request(safe_url, data) + else: + req = urllib2.Request(safe_url) + req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20100101 Firefox/15.0.1') + req.add_header('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8') + req.add_header('Accept-Language', 'de-de,de;q=0.8,en-us;q=0.5,en;q=0.3') + req.add_header('Accept-Charset', 'utf-8') + + if maxTimeout == None: + maxTimeout = 60; + + waittime = 0; + doc = False; + while not doc and waittime < maxTimeout: + try: + if waittime > 0: + time.sleep(waittime); + self.gui.log("download %s %d"%(safe_url,waittime)); + sock = urllib2.urlopen( req ) + doc = sock.read(); + sock.close() + except: + if(waittime == 0): + waittime = 1; + else: + waittime *= 2; + + if doc: + try: + return doc.encode('utf-8'); + except: + return doc; + else: + return '' + except: + return '' + + def buildMenu(self, path, treeNode = None): + if(isinstance(path, (str,unicode))): + path = path.split('.'); + if(len(path) > 0): + index = int(path.pop(0)); + + if(treeNode == None): + treeNode = self.menuTree[index]; + else: + treeNode = treeNode.childNodes[index]; + self.buildMenu(path,treeNode); + else: + if(treeNode == None): + treeNode = self.menuTree[0]; + self.gui.log(treeNode.name); + for childNode in treeNode.childNodes: + self.gui.buildMenuLink(childNode,self, len(treeNode.childNodes)); + if(treeNode.displayElements): + self.buildPageMenu(treeNode.link,len(treeNode.childNodes)); + + def displayCategories(self): + if(len(self.menuTree)>1 or not self.menuTree[0].displayElements): + for treeNode in self.menuTree: + self.gui.buildMenuLink(treeNode,self,len(self.menuTree)) + else: + self.buildPageMenu(self.menuTree[0].link, 0); + + def walkJson(self, path, jsonObject): + path = path.split('.'); + i = 0 + while(i < len(path)): + if(isinstance(jsonObject,list)): + index = int(path.pop(0)); + else: + index = path.pop(0); + jsonObject = jsonObject[index]; + + return jsonObject; + diff --git a/plugin.video.mediathek/mediathek/ard.py b/plugin.video.mediathek/mediathek/ard.py new file mode 100644 index 0000000..6c66ce2 --- /dev/null +++ b/plugin.video.mediathek/mediathek/ard.py @@ -0,0 +1,168 @@ +# -*- coding: utf-8 -*- +#-------------LicenseHeader-------------- +# plugin.video.Mediathek - Gives access to most video-platforms from German public service broadcasters +# Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import re, time, datetime; +from mediathek import * + +class ARDMediathek(Mediathek): + def __init__(self, simpleXbmcGui): + self.gui = simpleXbmcGui; + self.rootLink = "http://www.ardmediathek.de" + self.menuTree = ( + TreeNode("0","Neuste Videos",self.rootLink+"/tv/Neueste-Videos/mehr?documentId=21282466",True), + TreeNode("1","Sendungen von A-Z","",False,( + TreeNode("1.0","0-9",self.rootLink+"/tv/sendungen-a-z?buchstabe=0-9",True), + TreeNode("1.1","A",self.rootLink+"/tv/sendungen-a-z?buchstabe=A",True), + TreeNode("1.2","B",self.rootLink+"/tv/sendungen-a-z?buchstabe=B",True), + TreeNode("1.3","C",self.rootLink+"/tv/sendungen-a-z?buchstabe=C",True), + TreeNode("1.4","D",self.rootLink+"/tv/sendungen-a-z?buchstabe=D",True), + TreeNode("1.5","E",self.rootLink+"/tv/sendungen-a-z?buchstabe=E",True), + TreeNode("1.6","F",self.rootLink+"/tv/sendungen-a-z?buchstabe=F",True), + TreeNode("1.7","G",self.rootLink+"/tv/sendungen-a-z?buchstabe=G",True), + TreeNode("1.8","H",self.rootLink+"/tv/sendungen-a-z?buchstabe=H",True), + TreeNode("1.9","I",self.rootLink+"/tv/sendungen-a-z?buchstabe=I",True), + TreeNode("1.10","J",self.rootLink+"/tv/sendungen-a-z?buchstabe=J",True), + TreeNode("1.11","K",self.rootLink+"/tv/sendungen-a-z?buchstabe=K",True), + TreeNode("1.12","L",self.rootLink+"/tv/sendungen-a-z?buchstabe=L",True), + TreeNode("1.13","M",self.rootLink+"/tv/sendungen-a-z?buchstabe=M",True), + TreeNode("1.14","N",self.rootLink+"/tv/sendungen-a-z?buchstabe=N",True), + TreeNode("1.15","O",self.rootLink+"/tv/sendungen-a-z?buchstabe=O",True), + TreeNode("1.16","P",self.rootLink+"/tv/sendungen-a-z?buchstabe=P",True), + TreeNode("1.17","Q",self.rootLink+"/tv/sendungen-a-z?buchstabe=Q",True), + TreeNode("1.18","R",self.rootLink+"/tv/sendungen-a-z?buchstabe=R",True), + TreeNode("1.19","S",self.rootLink+"/tv/sendungen-a-z?buchstabe=S",True), + TreeNode("1.20","T",self.rootLink+"/tv/sendungen-a-z?buchstabe=T",True), + TreeNode("1.21","U",self.rootLink+"/tv/sendungen-a-z?buchstabe=U",True), + TreeNode("1.22","V",self.rootLink+"/tv/sendungen-a-z?buchstabe=V",True), + TreeNode("1.23","W",self.rootLink+"/tv/sendungen-a-z?buchstabe=W",True), + TreeNode("1.24","X",self.rootLink+"/tv/sendungen-a-z?buchstabe=X",True), + TreeNode("1.25","Y",self.rootLink+"/tv/sendungen-a-z?buchstabe=Y",True), + TreeNode("1.26","Z",self.rootLink+"/tv/sendungen-a-z?buchstabe=Z",True), + )), + TreeNode("2","Ausgewählte Dokus".decode("utf-8"),self.rootLink+"/tv/Ausgew%C3%A4hlte-Dokus/mehr?documentId=33649086",True), + TreeNode("3","Ausgewählte Filme".decode("utf-8"),self.rootLink+"/tv/Ausgew%C3%A4hlte-Filme/mehr?documentId=33649088",True), + TreeNode("4","Alle Reportagen und Dokus",self.rootLink+"/tv/Alle-Dokus-Reportagen/mehr?documentId=29897596",True), + TreeNode("5","Alle Filme",self.rootLink+"/tv/Alle-Filme/mehr?documentId=33594630",True), + TreeNode("6","Alle Serien",self.rootLink+"/tv/Serien/mehr?documentId=26402940",True), + TreeNode("7","Themen",self.rootLink+"/tv/Themen/mehr?documentId=21301810",True), + TreeNode("8","Rubriken","",False,( + TreeNode("8.0","Kinder",self.rootLink+"/tv/Kinder/Tipps?documentId=21282542",True), + TreeNode("8.1","Unterhaltung & Comedy",self.rootLink+"/tv/Unterhaltung-Comedy/mehr?documentId=21282544",True), + TreeNode("8.2","Kultur",self.rootLink+"/tv/Kultur/mehr?documentId=21282546",True), + TreeNode("8.3","Wissen",self.rootLink+"/tv/Wissen/mehr?documentId=21282530",True), + TreeNode("8.4","Politik",self.rootLink+"/tv/Politik/mehr?documentId=29684598",True), + TreeNode("8.5","Ratgeber",self.rootLink+"/tv/Ratgeber/mehr?documentId=27112994",True), + TreeNode("8.6","Krimi",self.rootLink+"/tv/Krimi/mehr?documentId=27258656",True), + TreeNode("8.7","Reise",self.rootLink+"/tv/Reise/mehr?documentId=29769608",True), + )), + ) + self.configLink = self.rootLink+"/play/media/%s?devicetype=pc&feature=flash" + #.*Video\?bcastId=\d+&documentId=(\d+)\" class=\"textLink\">\s+?<p class=\"dachzeile\">(.*?)</p>\s+?<h4 class=\"headline\">(.*?)</h4> + self.regex_VideoPageLink = re.compile("<a href=\".*Video\?.*?documentId=(\d+).*?\" class=\"textLink\">\s+?<p class=\"dachzeile\">(.*?)<\/p>\s+?<h4 class=\"headline\">(.*?)<\/h4>\s+?<p class=\"subtitle\">(?:(\d+.\d+.\d+) \| )?(\d*) Min.") + self.regex_CategoryPageLink = re.compile("<a href=\"(.*(?:Sendung|Thema)\?.*?documentId=\d+.*?)\" class=\"textLink\">(?:.|\n)+?<h4 class=\"headline\">(.*?)<\/h4>") + self.pageSelectString = "&mcontent%s=page.%s" + self.regex_DetermineSelectedPage = re.compile("&mcontents{0,1}=page.(\d+)"); + + self.regex_videoLinks = re.compile("\"_quality\":(\d).*?\"_stream\":\[?\"(.*?)\""); + self.regex_pictureLink = re.compile("_previewImage\":\"(.*?)\""); + + + self.regex_Date = re.compile("\\d{2}\\.\\d{2}\\.\\d{2}"); + + + self.replace_html = re.compile("<.*?>"); + + @classmethod + def name(self): + return "ARD"; + def isSearchable(self): + return False; + + def buildPageMenu(self, link, initCount, subLink = False): + self.gui.log("Build Page Menu: %s SubLink: %d"%(link,subLink)); + mainPage = self.loadPage(link); + + elementCount = 0; + + elementCount = self.extractElements(mainPage); + + + self.generateNextPageElement(link, elementCount); + return elementCount; + def generateNextPageElement(self, link, elementCount): + marker = ""; + if("Sendung?documentId" in link): + marker = "s"; + + numberElement = self.regex_DetermineSelectedPage.search(link); + if(numberElement is not None): + oldNumber = int(numberElement.group(1)); + newNumber = oldNumber + 1; + link = link.replace(self.pageSelectString%(marker,oldNumber),self.pageSelectString%(marker,newNumber)); + + self.gui.buildVideoLink(DisplayObject("Weiter","","","",link,False),self,elementCount); + else: + link += self.pageSelectString%(marker,2) + + self.gui.buildVideoLink(DisplayObject("Weiter","","","",link,False),self,elementCount); + + def extractElements(self,mainPage): + videoElements = list(self.regex_VideoPageLink.finditer(mainPage)); + if len(videoElements) == 0: + linkElements = list(self.regex_CategoryPageLink.finditer(mainPage)); + else: + linkElements = [] + + counter = len(videoElements) + len(linkElements); + for element in linkElements: + link = self.rootLink+element.group(1); + title = element.group(2).decode('utf-8'); + # subTitle = element.group(3).decode('utf-8'); + subTitle = "" + self.gui.buildVideoLink(DisplayObject(title,subTitle,"","",link,False),self,counter); + for element in videoElements: + videoId = element.group(1); + title = element.group(2).decode('utf-8'); + subTitle = element.group(3).decode('utf-8'); + if element.group(4): + datestring = element.group(4).decode('utf-8'); + date = datetime.date(*[int(x) for x in datestring.split('.')[::-1]]).timetuple() + else: + date = None + durationstring = element.group(5).decode('utf-8'); + duration = int(durationstring) * 60; + self.decodeVideoInformation(videoId, title, subTitle, counter, date, duration); + return counter; + + def decodeVideoInformation(self, videoId, title, subTitle, nodeCount, date, duration): + link = self.configLink%videoId; + self.gui.log("VideoLink: "+link); + videoPage = self.loadPage(link); + videoLinks = {} + for match in self.regex_videoLinks.finditer(videoPage): + quality = int(match.group(1)); + link = SimpleLink(match.group(2),0); + + if(quality > 0): + quality -= 1 + videoLinks[quality] = link + match = self.regex_pictureLink.search(videoPage) + picture = None + if(match is not None): + picture = match.group(1); + if(len(videoLinks)>0): + self.gui.buildVideoLink(DisplayObject(title, subTitle,picture,"",videoLinks,True,date,duration),self,nodeCount); diff --git a/plugin.video.mediathek/mediathek/arte.py b/plugin.video.mediathek/mediathek/arte.py new file mode 100644 index 0000000..e0a158d --- /dev/null +++ b/plugin.video.mediathek/mediathek/arte.py @@ -0,0 +1,262 @@ +## -*- coding: utf-8 -*- +#-------------LicenseHeader-------------- +# plugin.video.Mediathek - Gives access to most video-platforms from German public service broadcasters +# Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import re, traceback, urllib,json; +from mediathek import * +from xml.dom import minidom +from xml.dom import Node; +from bs4 import BeautifulSoup; + +regex_dateString = re.compile("\\d{1,2} ((\\w{3})|(\\d{2})) \\d{4}"); +month_replacements = { + "Jan":"01", + "Feb":"02", + "Mar":"03", + "Apr":"04", + "May":"05", + "Jun":"06", + "Jul":"07", + "Aug":"08", + "Sep":"09", + "Oct":"10", + "Nov":"11", + "Dec":"12", + }; + +class ARTEMediathek(Mediathek): + @classmethod + def name(self): + return "ARTE"; + @classmethod + def isSearchable(self): + return False; + + def __init__(self, simpleXbmcGui): + self.gui = simpleXbmcGui; + self.rootLink = "http://www.arte.tv"; + self.basePage = self.rootLink+"/guide/de/plus7"; + self.jsonLink = "https://api.arte.tv/api/player/v1/config/de/%s" + + self.menuTree = ( + TreeNode("0","Arte+7","mainPage",True), + TreeNode("1","Sendungen von A-Z","showCluster",True), + TreeNode("2","Kategorien","showCategories",True), + ); + + self.selector_videoPages = "li.video > a"; + + self.regex_VideoPageLinksHTML = re.compile("href=[\"'](http:\\\\/\\\\/www\\.arte\\.tv\\\\/guide\\\\/de\\\\/\d{6}-\d{3}/.+?)[\"']"); + self.regex_VideoPageLinksJSON = re.compile("\"url\":\"((http:\\\\/\\\\/www\\.arte\\.tv){0,1}\\\\/guide\\\\/de\\\\/\d{6}-\d{3}\\\\/.+?)\""); + self.regex_findVideoIds = re.compile("(\d{6}-\d{3})(-A)"); + self.regex_JSONPageLink = re.compile("http://arte.tv/papi/tvguide/videos/stream/player/D/\d{6}-\d{3}.+?/ALL/ALL.json"); + self.regex_JSON_VideoLink = re.compile("\"HTTP_MP4_.+?\":{.*?\"bitrate\":(\d+),.*?\"url\":\"(http://.*?.mp4)\".*?\"versionShortLibelle\":\"([a-zA-Z]{2})\".*?}"); + self.regex_JSON_ImageLink = re.compile("\"IUR\":\"(http://.*?\\.arte\\.tv/papi/tvguide/images/.*?\\..{3})\""); + self.regex_JSON_Detail = re.compile("\"VDE\":\"(.*?)\""); + self.regex_JSON_Titel = re.compile("\"VTI\":\"(.*?)\""); + + regexSourceString="%s=\"([\[{].*?[}\]])\""; + self.regex_cluster=re.compile(regexSourceString%"data-clusters"); + self.regex_categories = re.compile(regexSourceString%"data-categoriesVideos"); + self.regex_playlists = re.compile(regexSourceString%"data-highlightedPlaylists"); + + self.categories = { + "DailyMostViewed":re.compile(regexSourceString%"data-dailyMostViewedVideos"), + "Most Viewed": re.compile(regexSourceString%"data-mostViewedVideos"), + "ExpiringVideos":re.compile(regexSourceString%"data-nextExpiringVideos"), + } + + self.regex_extractVideoSources = ( + re.compile(regexSourceString%"data-highlightedVideos"), + re.compile(regexSourceString%"data-latestVideos"), + re.compile(regexSourceString%"data-categoryVideoSet"), + ); + + + + + def buildPageMenu(self, link, initCount): + if(link == "showCluster"): + self.showCluster(); + elif (link == "mainPage"): + self.showMainPage(); + elif (link == "showCategories"): + self.showCategories(); + else: + if(not link.startswith("http")): + link = self.rootLink+link; + pageContent = self.loadPage(link).decode('UTF-8'); + linkFound = self.extractVideoLinksFromHtml(pageContent) + if(not linkFound): + self.extractVideoLinks(pageContent,0); + + def buildJsonMenu(self, path,callhash, initCount): + jsonContent=self.gui.loadJsonFile(callhash); + if(path == "init"): + jsonObject = jsonContent; + else: + jsonObject=self.walkJson(path,jsonContent); + self.gui.log(json.dumps(jsonObject)); + if("videos" in jsonObject): + self.extractVideoLinksFromJson(jsonObject); + if(isinstance(jsonObject,list)): + for counter,jsonObject in enumerate(jsonObject): + if("day" in jsonObject): + self.gui.buildJsonLink(self,jsonObject["day"],"%d"%counter,callhash,0) + + def buildJsonLink(self,name,content): + content = BeautifulSoup(content); + jsonContent = json.loads(content.prettify(formatter=None)) + callhash = self.gui.storeJsonFile(jsonContent,name); + self.gui.buildJsonLink(self,name,"init",callhash,0) + + def showMainPage(self): + self.gui.log("buildPageMenu: "+self.basePage); + pageContent = self.loadPage(self.basePage).decode('UTF-8'); + + for name,regex in self.categories.iteritems(): + match = regex.search(pageContent); + if(match is not None): + self.buildJsonLink(name,match.group(1)); + + self.extractVideoLinksFromHtml(pageContent) + + def extractVideoLinksFromHtml(self, htmlPage): + someMatch = False; + for regex in self.regex_extractVideoSources: + match = regex.search(htmlPage); + if(match is not None): + someMatch = True; + content = BeautifulSoup(match.group(1)); + jsonContent = json.loads(content.prettify(formatter=None)) + self.extractVideoLinksFromJson(jsonContent) + return someMatch; + + + def extractVideoLinksFromJson(self,jsonContent): + for jsonObject in jsonContent["videos"]: + self.buildVideoEntry(jsonObject); + + def showCluster(self): + pageContent = self.loadPage(self.basePage).decode('UTF-8'); + content = BeautifulSoup(self.regex_cluster.search(pageContent).group(1)); + jsonContent = json.loads(content.prettify(formatter=None)) + for menuItem in jsonContent: + self.buildMenuEntry(menuItem); + + def showCategories(self): + pageContent = self.loadPage(self.basePage).decode('UTF-8'); + content = BeautifulSoup(self.regex_categories.search(pageContent).group(1)); + jsonContent = json.loads(content.prettify(formatter=None)) + for jsonObject in jsonContent: + jsonCategorie = jsonObject["category"] + title = unicode(jsonCategorie["title"]); + link=jsonCategorie["url"]; + self.gui.buildVideoLink(DisplayObject(title,"","","",link,False,None),self,0); + + def buildMenuEntry(self, menuItem): + title = menuItem["title"]; + subTitle = menuItem["subtitle"]; + link=menuItem["permalink"]; + self.gui.buildVideoLink(DisplayObject(title,subTitle,"","",link,False,None),self,0); + + def buildVideoEntry(self, jsonObject): + title = jsonObject["title"]; + subTitle = jsonObject["subtitle"]; + detail = jsonObject["teaser"]; + picture = None; + for pictureItem in jsonObject["thumbnails"]: + if(picture is None or picture["width"]<pictureItem["width"]): + picture = pictureItem; + link=self.jsonLink%jsonObject["id"]; + self.gui.buildVideoLink(DisplayObject(title,"",picture["url"],detail,link,"JsonLink", None),self,0); + + + def playVideoFromJsonLink(self,link): + jsonObject = json.loads(self.loadPage(link)); + links = self.extractLinks(jsonObject["videoJsonPlayer"]); + self.gui.play(links); + + + def extractLinks(self,jsonObject): + links={}; + + vsrObjects = jsonObject["VSR"]; + if(not isinstance(vsrObjects,dict)): + return links; + + for videoObject in jsonObject["VSR"].itervalues(): + if videoObject["mediaType"] == "mp4": + url = videoObject["url"]; + quality = videoObject["quality"]; + self.gui.log("%s %s"%(quality,url)) + if quality == "MQ": + links[0] = SimpleLink(url, -1); + if quality == "HQ": + links[1] = SimpleLink(url, -1); + if quality == "EQ": + links[2] = SimpleLink(url, -1); + if quality == "SQ": + links[3] = SimpleLink(url, -1); + return links; + + def extractVideoLinks(self, htmlPage, initCount): + links = set(); + jsonLinks = set(); + for videoPageLink in self.regex_findVideoIds.finditer(htmlPage): + link = self.jsonLink%videoPageLink.group(1) + self.gui.log(link); + if(link not in jsonLinks): + jsonLinks.add(link); + + for videoPageLink in self.regex_VideoPageLinksJSON.finditer(htmlPage): + link = videoPageLink.group(1).replace("\\/","/"); + if(link not in links): + links.add(link); + for link in self.regex_JSONPageLink.finditer(htmlPage): + jsonLinks.add(link.group(0)); + linkCount = initCount + len(links); + for link in links: + if(not link.startswith(self.rootLink)): + videoPage = self.loadPage(self.rootLink+link); + else: + videoPage = self.loadPage(link); + match = self.regex_JSONPageLink.search(videoPage); + if(match is not None): + jsonLinks.add(match.group(0)); + + linkCount = linkCount + len(jsonLinks); + + self.gui.log("Found %s unique links"%len(jsonLinks)); + for link in jsonLinks: + jsonPage = json.loads(self.loadPage(link)); + self.extractVideoLinksFromJSONPage(jsonPage["videoJsonPlayer"],linkCount) + + def extractVideoLinksFromJSONPage(self, jsonPage, linkCount): + + videoLinks = self.extractLinks (jsonPage); + if(len(videoLinks) == 0): + return; + + picture = jsonPage["VTU"]["IUR"]; + title = jsonPage["VTI"]; + + subTitle = "" + if("subtitle" in jsonPage): + subTitle = jsonPage["subtitle"]; + + + self.gui.buildVideoLink(DisplayObject(title,subTitle,picture,"",videoLinks,True, None),self,linkCount); diff --git a/plugin.video.mediathek/mediathek/dreisat.py b/plugin.video.mediathek/mediathek/dreisat.py new file mode 100644 index 0000000..615f88d --- /dev/null +++ b/plugin.video.mediathek/mediathek/dreisat.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +#-------------LicenseHeader-------------- +# plugin.video.Mediathek - Gives access to most video-platforms from German public service broadcasters +# Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import re,time +from mediathek import * +from xml.dom import minidom; + + +regex_dateString = re.compile("\\d{2} ((\\w{3})|(\\d{2})) \\d{4}"); +month_replacements = { + "Jan":"01", + "Feb":"02", + "Mar":"03", + "Apr":"04", + "May":"05", + "Jun":"06", + "Jul":"07", + "Aug":"08", + "Sep":"09", + "Oct":"10", + "Nov":"11", + "Dec":"12", + }; + +class DreiSatMediathek(Mediathek): + @classmethod + def name(self): + return "3Sat"; + def isSearchable(self): + return True; + def __init__(self, simpleXbmcGui): + self.gui = simpleXbmcGui; + if(self.gui.preferedStreamTyp == 0): + self.baseType = "video/x-ms-asf"; + elif (self.gui.preferedStreamTyp == 1): + self.baseType = "video/x-ms-asf" + elif (self.gui.preferedStreamTyp == 2): + self.baseType ="video/x-ms-asf"; + else: + self.baseType ="video/quicktime"; + self.webEmType = "video/webm"; + self.menuTree = ( + TreeNode("0","Bauerfeind","http://www.3sat.de/mediathek/rss/mediathek_bauerfeind.xml",True), + TreeNode("1","Bookmark","http://www.3sat.de/mediathek/rss/mediathek_bookmark.xml",True), + TreeNode("2",u"Börse","http://www.3sat.de/mediathek/rss/mediathek_boerse.xml",True), + TreeNode("3","Buchzeit","http://www.3sat.de/mediathek/rss/mediathek_buchzeit.xml",True), + TreeNode("4","daVinci","http://www.3sat.de/mediathek/rss/mediathek_davinci.xml",True), + TreeNode("5","delta","http://www.3sat.de/mediathek/rss/mediathek_delta.xml",True), + TreeNode("6","Film","http://www.3sat.de/mediathek/rss/mediathek_film.xml",True), + TreeNode("7","Gero von Boehm","http://www.3sat.de/mediathek/rss/mediathek_gero.xml",True), + TreeNode("8","hessenreporter","http://www.3sat.de/mediathek/rss/mediathek_hessenreporter.xml",True), + TreeNode("9","hitec","http://www.3sat.de/mediathek/rss/mediathek_hitec.xml",True), + TreeNode("10","Kabarett","http://www.3sat.de/mediathek/rss/mediathek_kabarett.xml",True), + TreeNode("11","Kinomagazin","http://www.3sat.de/mediathek/rss/mediathek_kinomag.xml",True), + TreeNode("12","Kulturzeit","http://www.3sat.de/mediathek/rss/mediathek_Kulturzeit.xml",True), + TreeNode("13","makro","http://www.3sat.de/mediathek/rss/mediathek_makro.xml",True), + TreeNode("14","Musik","http://www.3sat.de/mediathek/rss/mediathek_musik.xml",True), + TreeNode("15","nano","http://www.3sat.de/mediathek/rss/mediathek_nano.xml",True), + TreeNode("16","neues","http://www.3sat.de/mediathek/rss/mediathek_neues.xml",True), + TreeNode("17",u"Peter Voß fragt","http://www.3sat.de/mediathek/rss/mediathek_begegnungen.xml",True), + TreeNode("18","Recht brisant","http://www.3sat.de/mediathek/rss/mediathek_Recht%20brisant.xml",True), + TreeNode("19","scobel","http://www.3sat.de/mediathek/rss/mediathek_scobel.xml",True), + TreeNode("20","SCHWEIZWEIT","http://www.3sat.de/mediathek/rss/mediathek_schweizweit.xml",True), + TreeNode("21","Theater","http://www.3sat.de/mediathek/rss/mediathek_theater.xml",True), + TreeNode("22","vivo","http://www.3sat.de/mediathek/rss/mediathek_vivo.xml",True), + ); + + self.rootLink = "http://www.3sat.de" + self.searchLink = 'http://www.3sat.de/mediathek/mediathek'; + link = "/mediathek/mediathek.php\\?obj=\\d+"; + self.regex_searchResult = re.compile("href=\""+link+"\" class=\"media_result_thumb\""); + self.regex_searchResultLink = re.compile(link) + self.regex_searchLink = re.compile("http://(w|f)streaming.zdf.de/.*?(\\.asx|\\.smil)") + self.regex_searchTitle = re.compile("<h2>.*</h2>"); + self.regex_searchDetail = re.compile("<span class=\"text\">.*"); + self.regex_searchDate = re.compile("\\d{2}.\\d{2}.\\d{4}"); + self.regex_searchImage = re.compile("(/dynamic/mediathek/stills/|/mediaplayer/stills/)\\d*_big\\.jpg"); + self.replace_html = re.compile("<.*?>"); + + def buildPageMenu(self, link, initCount): + self.gui.log("buildPageMenu: "+link); + rssFeed = self.loadConfigXml(link); + self.extractVideoObjects(rssFeed, initCount); + def searchVideo(self, searchText): + values ={'mode':'search', + 'query':searchText, + 'red': '', + 'query_time': '', + 'query_sort': '', + 'query_order':'' + } + mainPage = self.loadPage(self.searchLink,values); + results = self.regex_searchResult.findall(mainPage); + for result in results: + objectLink = self.regex_searchResultLink.search(result).group(); + infoLink = self.rootLink+objectLink + infoPage = self.loadPage(infoLink); + title = self.regex_searchTitle.search(infoPage).group(); + detail = self.regex_searchDetail.search(infoPage).group(); + image = self.regex_searchImage.search(infoPage).group(); + title = self.replace_html.sub("", title); + detail = self.replace_html.sub("", detail); + try: + dateString = self.regex_searchDate.search(infoPage).group(); + pubDate = time.strptime(dateString,"%d.%m.%Y"); + except: + pubDate = time.gmtime(); + videoLink = self.rootLink+objectLink+"&mode=play"; + videoPage = self.loadPage(videoLink); + video = self.regex_searchLink.search(videoPage).group(); + video = video.replace("fstreaming","wstreaming").replace(".smil",".asx"); + links = {} + links[2] = SimpleLink(video,0) + self.gui.buildVideoLink(DisplayObject(title,"",self.rootLink + image,detail,links,True, pubDate),self,len(results)); + def readText(self,node,textNode): + try: + node = node.getElementsByTagName(textNode)[0].firstChild; + return unicode(node.data); + except: + return ""; + def loadConfigXml(self, link): + self.gui.log("load:"+link) + xmlPage = self.loadPage(link); + return minidom.parseString(xmlPage); + def extractVideoObjects(self, rssFeed, initCount): + nodes = rssFeed.getElementsByTagName("item"); + nodeCount = initCount + len(nodes) + for itemNode in nodes: + try: + self.extractVideoInformation(itemNode,nodeCount); + except: + pass + + def parseDate(self,dateString): + dateString = regex_dateString.search(dateString).group(); + for month in month_replacements.keys(): + dateString = dateString.replace(month,month_replacements[month]); + return time.strptime(dateString,"%d %m %Y"); + + def extractVideoInformation(self, itemNode, nodeCount): + title = self.readText(itemNode,"title"); + self.gui.log(title) + dateString = self.readText(itemNode,"pubDate"); + pubDate = self.parseDate(dateString); + descriptionNode = itemNode.getElementsByTagName("description")[0].firstChild.data; + description = unicode(descriptionNode); + picture = ""; + pictureNodes = itemNode.getElementsByTagName("media:thumbnail"); + if(len(pictureNodes) > 0): + picture = pictureNodes[0].getAttribute("url"); + links = {}; + for contentNode in itemNode.getElementsByTagName("media:content"): + height = int(contentNode.getAttribute("height")); + url = contentNode.getAttribute("url"); + size = int(contentNode.getAttribute("fileSize")); + if(height < 300): + links[0] = SimpleLink(url, size); + elif (height < 480): + links[1] = SimpleLink(url, size); + else: + links[2] = SimpleLink(url, size); + if links: + self.gui.buildVideoLink(DisplayObject(title,"",picture,description,links,True, pubDate),self,nodeCount); diff --git a/plugin.video.mediathek/mediathek/factory.py b/plugin.video.mediathek/mediathek/factory.py new file mode 100644 index 0000000..7bcdb96 --- /dev/null +++ b/plugin.video.mediathek/mediathek/factory.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +#-------------LicenseHeader-------------- +# plugin.video.Mediathek - Gives access to most video-platforms from German public service broadcasters +# Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +from mediathek.wdr import * +from mediathek.ard import * +from mediathek.zdf import * +from mediathek.arte import * +from mediathek.dreisat import * +from mediathek.orf import * +from mediathek.ndr import * +from mediathek.kika import * + +class MediathekFactory(object): + def __init__(self): + self.avaibleMediathekes = { + ARDMediathek.name():ARDMediathek, + ZDFMediathek.name():ZDFMediathek, + ARTEMediathek.name():ARTEMediathek, + DreiSatMediathek.name():DreiSatMediathek, + ORFMediathek.name():ORFMediathek, + NDRMediathek.name():NDRMediathek, + KIKA.name():KIKA + } + def getAvaibleMediathekTypes(self): + return sorted(self.avaibleMediathekes.keys()) + + def getMediathek(self,mediathekName, gui): + return self.avaibleMediathekes[mediathekName](gui); diff --git a/plugin.video.mediathek/mediathek/kika.py b/plugin.video.mediathek/mediathek/kika.py new file mode 100644 index 0000000..238ba9d --- /dev/null +++ b/plugin.video.mediathek/mediathek/kika.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +#-------------LicenseHeader-------------- +# plugin.video.Mediathek - Gives access to most video-platforms from German public service broadcasters +# Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import re, time; +from bs4 import BeautifulSoup; +from mediathek import * + +class KIKA(Mediathek): + def __init__(self, simpleXbmcGui): + self.gui = simpleXbmcGui; + self.rootLink = "http://www.kika.de"; + self.menuTree = ( + TreeNode("0","Videos",self.rootLink+"/videos/index.html",True), + TreeNode("1","Sendungen von A-Z","",False, + ( + TreeNode("1.0","A",self.rootLink+"/sendungen/sendungenabisz100_page-A_zc-05fb1331.html",True), + TreeNode("1.1","B",self.rootLink+"/sendungen/sendungenabisz100_page-B_zc-1775e6d8.html",True), + TreeNode("1.2","C",self.rootLink+"/sendungen/sendungenabisz100_page-C_zc-6248eba0.html",True), + TreeNode("1.3","D",self.rootLink+"/sendungen/sendungenabisz100_page-D_zc-e090a8fb.html",True), + TreeNode("1.4","E",self.rootLink+"/sendungen/sendungenabisz100_page-E_zc-ec2376ed.html",True), + TreeNode("1.5","F",self.rootLink+"/sendungen/sendungenabisz100_page-F_zc-f76734a0.html",True), + TreeNode("1.6","G",self.rootLink+"/sendungen/sendungenabisz100_page-G_zc-34bda7c3.html",True), + TreeNode("1.7","H",self.rootLink+"/sendungen/sendungenabisz100_page-H_zc-7e25e70a.html",True), + TreeNode("1.8","I",self.rootLink+"/sendungen/sendungenabisz100_page-I_zc-b7f774f5.html",True), + TreeNode("1.9","J",self.rootLink+"/sendungen/sendungenabisz100_page-J_zc-3130680a.html",True), + TreeNode("1.10","K",self.rootLink+"/sendungen/sendungenabisz100_page-K_zc-c8f76ba1.html",True), + TreeNode("1.11","L",self.rootLink+"/sendungen/sendungenabisz100_page-L_zc-bbebc1a7.html",True), + TreeNode("1.12","M",self.rootLink+"/sendungen/sendungenabisz100_page-M_zc-00574a43.html",True), + TreeNode("1.13","N",self.rootLink+"/sendungen/sendungenabisz100_page-N_zc-b079366f.html",True), + TreeNode("1.14","O",self.rootLink+"/sendungen/sendungenabisz100_page-O_zc-febc55f5.html",True), + TreeNode("1.15","P",self.rootLink+"/sendungen/sendungenabisz100_page-P_zc-2c1a492f.html",True), + TreeNode("1.16","Q",self.rootLink+"/sendungen/sendungenabisz100_page-Q_zc-2cb019d6.html",True), + TreeNode("1.17","R",self.rootLink+"/sendungen/sendungenabisz100_page-R_zc-cab3e22b.html",True), + TreeNode("1.18","S",self.rootLink+"/sendungen/sendungenabisz100_page-S_zc-e7f420d0.html",True), + TreeNode("1.19","T",self.rootLink+"/sendungen/sendungenabisz100_page-T_zc-84a2709f.html",True), + TreeNode("1.20","U",self.rootLink+"/sendungen/sendungenabisz100_page-U_zc-a26c1157.html",True), + TreeNode("1.21","V",self.rootLink+"/sendungen/sendungenabisz100_page-V_zc-1fc26dc3.html",True), + TreeNode("1.22","W",self.rootLink+"/sendungen/sendungenabisz100_page-W_zc-25c5c777.html",True), + TreeNode("1.23","Y",self.rootLink+"/sendungen/sendungenabisz100_page-Y_zc-388beba7.html",True), + TreeNode("1.24","Z",self.rootLink+"/sendungen/sendungenabisz100_page-Z_zc-e744950d.html",True), + TreeNode("1.25","...",self.rootLink+"/sendungen/sendungenabisz100_page-1_zc-43c28d56.html",True) + ) + ) + ) + + self.regex_videoLinks=re.compile("<a href=\"(.*?/videos/video\\d+?)\\.html\""); + self.regex_configLinks=re.compile("{dataURL:'http://www.kika.de(/.*?-avCustom.xml)'}"); + self.selector_videoPages = "div.mod > div.box > div.teaser > a.linkAll"; + self.selector_seriesPages = "div.modCon > div.mod > div.boxCon > div.boxBroadcastSeries > div.teaser > a.linkAll"; + self.regex_xml_channel=re.compile("<channelName>(.*?)</channelName>"); + self.regex_xml_title=re.compile("<title>(.*?)</title>"); + self.regex_xml_image=re.compile("<teaserimage>\\s*?<url>(.*?)</url>"); + self.regex_xml_videoLink=re.compile("<asset>\\s*?<profileName>(.*?)</profileName>.*?<progressiveDownloadUrl>(.*?)</progressiveDownloadUrl>\\s*?</asset>",re.DOTALL) + self.regex_videoLink=re.compile("rtmp://.*?\.mp4"); + @classmethod + def name(self): + return "KI.KA"; + + def isSearchable(self): + return False; + + def searchVideo(self, searchText): + return; + + def buildVideoLink(self,pageLink): + xmlPage = self.loadPage(self.rootLink+pageLink); + channel = self.regex_xml_channel.search(xmlPage); + if(channel is not None): + channel = unicode(channel.group(1),"UTF-8"); + title = unicode(self.regex_xml_title.search(xmlPage).group(1),"UTF-8"); + image = self.regex_xml_image.search(xmlPage).group(1).replace("**aspectRatio**","tlarge169").replace("**width**","1472"); + + self.gui.log("%s %s"%(title,image)); + links = {}; + for match in self.regex_xml_videoLink.finditer(xmlPage): + profile = match.group(1); + directLink = match.group(2); + self.gui.log("%s %s"%(profile,directLink)); + if("MP4 Web S" in profile): + links[0] = SimpleLink(directLink, 0); + if("MP4 Web L" in profile): + links[1] = SimpleLink(directLink, 0); + if("MP4 Web L+" in profile): + links[2] = SimpleLink(directLink, 0); + if("MP4 Web XL" in profile): + links[3] = SimpleLink(directLink, 0); + + if(channel is not None): + return DisplayObject(channel,title,image,"",links,True, None); + else: + return DisplayObject(title,"",image,"",links,True, None); + + def buildPageMenu(self, link, initCount): + pageContent = self.loadPage(link); + htmlPage = BeautifulSoup(pageContent, 'html.parser') + htmlElements = htmlPage.select(self.selector_videoPages) + videoLinks = set() + + for item in htmlElements: + link = self.rootLink+item['href']; + videoPage = self.loadPage(link); + for match in self.regex_videoLinks.finditer(videoPage): + link=match.group(1)+"-avCustom.xml"; + if(link not in videoLinks): + videoLinks.add(link) + directLinks = list(self.regex_configLinks.finditer(pageContent)); + for match in directLinks: + link = match.group(1); + if(link not in videoLinks): + videoLinks.add(link) + self.gui.log("found %d video links"%len(videoLinks)) + count = initCount + len(videoLinks) + for link in videoLinks: + displayObject = self.buildVideoLink(link); + self.gui.buildVideoLink(displayObject,self, count); + if(len(videoLinks) > 0): + return; + htmlElements = htmlPage.select(self.selector_seriesPages); + count = count + len(htmlElements) + self.gui.log("found %d page links"%len(htmlElements)) + for item in htmlElements: + self.gui.log(item.prettify()); + link = self.rootLink+item['href']; + title = item['title']; + displayObject = DisplayObject(title,"",None,"",link,False, None); + self.gui.buildVideoLink(displayObject,self, count); diff --git a/plugin.video.mediathek/mediathek/ndr.py b/plugin.video.mediathek/mediathek/ndr.py new file mode 100644 index 0000000..cbe57df --- /dev/null +++ b/plugin.video.mediathek/mediathek/ndr.py @@ -0,0 +1,274 @@ +# -*- coding: utf-8 -*- +# -------------LicenseHeader-------------- +# plugin.video.Mediathek - Gives access to most video-platforms from German public service broadcasters +# Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import re +import datetime +import time +import calendar +from mediathek import * +from xml.dom import minidom + + +class NDRMediathek(Mediathek): + @classmethod + def name(self): + return "NDR" + + def isSearchable(self): + return True + + def __init__(self, simpleXbmcGui): + self.gui = simpleXbmcGui + + self.rootLink = "http://www.ndr.de" + + self.searchLink = self.rootLink+"/suche10.html?search_mediathek=1&" + + # Hauptmenue + tmp_menu = [] + extractBroadcasts = re.compile("<a href=\"/mediathek/mediatheksuche105_broadcast-(.*?).html\" class=\"link_arrow\">(.*?)</a>") + htmlPage = self.loadPage("http://www.ndr.de/mediathek/sendungen_a-z/index.html").decode('utf-8') + + x = 0 + for menuNode in extractBroadcasts.finditer(htmlPage): + menuId = menuNode.group(1) + menuItem = menuNode.group(2) + menuLink = self.rootLink+"/mediatheksuche105_broadcast-"+menuId+"_format-video_page-1.html" + tmp_menu.append(TreeNode("0."+str(x), menuItem, menuLink, True)) + x = x+1 + + self.menuTree = [ + TreeNode("0", "Sendungen von A-Z", "", False, tmp_menu), + TreeNode("1", "Sendung verpasst?", "sendungverpasst", True), + TreeNode("2","Live","livestream",True),#Livestream ruckelt zu stark :-( + ] + + def buildPageMenuSendungVerpasst(self, action): + htmlPage = self.loadPage("http://www.ndr.de/mediathek/sendung_verpasst/epg1490_display-onlyvideo.html") + + regex_verpasstNow = re.compile( + '<h1 class="viewdate">\n.*?(\\d{2})\.(\\d{2})<span class="notbelow30em">\.(\\d{4})</span>' + ) + verpasstNow = ".".join(regex_verpasstNow.search(htmlPage).groups()) + try: + dateTimeTmp = datetime.datetime.strptime(verpasstNow, "%d.%m.%Y") + except TypeError: + dateTimeTmp = datetime.datetime(*(time.strptime(verpasstNow, "%d.%m.%Y")[0:6])) + + nodeCount = 0 + + if action == "": + verpasstHeute = dateTimeTmp.strftime("%Y-%m-%d") + dateTimeTmp = dateTimeTmp-datetime.timedelta(1) + verpasstGestern = dateTimeTmp.strftime("%Y-%m-%d") + dateTimeTmp = dateTimeTmp-datetime.timedelta(1) + verpasstVorGestern = dateTimeTmp.strftime("%Y-%m-%d") + + self.gui.buildVideoLink(DisplayObject("Heute", "", "", "description", self.rootLink + "/mediathek/sendung_verpasst/epg1490_date-" + verpasstHeute + "_display-onlyvideo.html", False), self, 1) + self.gui.buildVideoLink(DisplayObject("Gestern", "", "", "description", self.rootLink + "/mediathek/sendung_verpasst/epg1490_date-" + verpasstGestern + "_display-onlyvideo.html", False), self, 2) + self.gui.buildVideoLink(DisplayObject("Vorgestern", "", "", "description", self.rootLink + "/mediathek/sendung_verpasst/epg1490_date-" + verpasstVorGestern + "_display-onlyvideo.html", False), self, 3) + self.gui.buildVideoLink(DisplayObject("Datum waehlen", "", "", "description", "sendungverpasstselect", False), self, 4) + elif action == "select": + dateTimeTmp = dateTimeTmp-datetime.timedelta(3) + verpasstStartYear = int(dateTimeTmp.strftime("%Y")) + for verpasstStart in reversed(range(1, int(dateTimeTmp.strftime("%m")))): + menu_title = str(verpasstStart)+"."+str(verpasstStartYear) + menu_action = "sendungverpasstselectmonth" + str(verpasstStartYear) + str(verpasstStart) + self.gui.buildVideoLink(DisplayObject(menu_title, "", "", "description", menu_action, False), self, nodeCount) + verpasstStart = verpasstStart - 1 + nodeCount = nodeCount + 1 + + while verpasstStartYear > 2008: + verpasstStartYear = verpasstStartYear - 1 + menu_title = str(verpasstStartYear) + self.gui.buildVideoLink(DisplayObject(menu_title, "", "", "description", "sendungverpasstselectyear" + str(verpasstStartYear), False), self, nodeCount) + elif action[0:11] == "selectmonth": + action = action[11:] + action_year = action[0:4] + action_month = action[4:] + + try: + dateTimeTmp2 = datetime.datetime.strptime(action_year+action_month, "%Y%m") + except TypeError: + dateTimeTmp2 = datetime.datetime(*(time.strptime(action_year+action_month, "%Y%m")[0:6])) + + if dateTimeTmp.strftime("%Y%m") == dateTimeTmp2.strftime("%Y%m"): + startDay = int(dateTimeTmp2.strftime("%d")) + else: + startDay = calendar.monthrange(int(action_year), int(action_month))[1] + + try: + dateTimeTmp2 = datetime.datetime.strptime(action_year + action_month + str(startDay), "%Y%m%d") + except TypeError: + dateTimeTmp2 = datetime.datetime(*(time.strptime(action_year + action_month + str(startDay), "%Y%m%d")[0:6])) + + for i in reversed(range(1, startDay)): + verpasstDatum = dateTimeTmp2.strftime("%Y-%m-%d") + menu_title = dateTimeTmp2.strftime("%d.%m.%Y") + menu_action = self.rootLink + "/mediathek/sendung_verpasst/epg1490_date-" + verpasstDatum + "_display-onlyvideo.html" + self.gui.buildVideoLink(DisplayObject(menu_title, "", "", "description", menu_action, False), self, nodeCount) + nodeCount = nodeCount + 1 + dateTimeTmp2 = dateTimeTmp2-datetime.timedelta(1) + elif action[0:10] == "selectyear": + action = action[10:] + action_year = action[0:4] + for startMonth in reversed(range(1, 12)): + menu_title = str(startMonth) + "." + action_year + menu_action = "sendungverpasstselectmonth" + action_year + str(startMonth) + self.gui.buildVideoLink(DisplayObject(menu_title, "", "", "description", menu_action, False), self, nodeCount) + nodeCount = nodeCount + 1 + + def buildPageMenuLivestream(self): + nodeCount = 0 + + # Hamburg + nodeCount = nodeCount+1 + links = {} + links[0] = SimpleLink("http://ndr_fs-lh.akamaihd.net/i/ndrfs_hh@119223/master.m3u8", 0) + self.gui.buildVideoLink(DisplayObject("Hamburg", "", "", "", links, True), self, nodeCount) + + # Mecklenburg-Vorpommern + nodeCount = nodeCount+1 + links = {} + links[0] = SimpleLink("http://ndr_fs-lh.akamaihd.net/i/ndrfs_mv@119226/master.m3u8", 0) + self.gui.buildVideoLink(DisplayObject("Mecklenburg-Vorpommern", "", "", "", links, True), self, nodeCount) + + # Niedersachsen + nodeCount = nodeCount+1 + links = {} + links[0] = SimpleLink("http://ndr_fs-lh.akamaihd.net/i/ndrfs_nds@119224/master.m3u8", 0) + self.gui.buildVideoLink(DisplayObject("Niedersachsen", "", "", "", links, True), self, nodeCount) + + # Schleswig-Holstein + nodeCount = nodeCount+1 + links = {} + links[0] = SimpleLink("http://ndr_fs-lh.akamaihd.net/i/ndrfs_sh@119225/master.m3u8", 0) + self.gui.buildVideoLink(DisplayObject("Schleswig-Holstein", "", "", "", links, True), self, nodeCount) + + def buildPageMenuVideoListVerpasst(self, link, initCount): + self.gui.log("buildPageMenuVerpasst: " + link) + + htmlPage = self.loadPage(link) + + re_video_item = re.compile( + '<div class="videolinks"><a href="(.*?)" title=".*?" class=\'button epgbutton\' >' + ) + # won't parse "Beiträge", they are only cutted parts of the main + # program + video_items = re.findall(re_video_item, htmlPage) + nodeCount = initCount + len(video_items) + for video_link in video_items: + if not re.compile("http://www.n-joy.de/.*").search(video_link): + print video_link + self.extractVideoInformation(video_link, None, nodeCount) + + + def buildPageMenuVideoList(self, link, initCount): + self.gui.log("buildPageMenu: " + link) + + htmlPage = self.loadPage(link) + + regex_extractVideoItems = re.compile( + "<div class=\"teaserpadding\">" + "(.*?)" + "(</p>\n</div>\n</div>|\n</div>\n</div>\n</li>)", re.DOTALL) + regex_extractVideoItemHref = re.compile("<a title=\".*?\" href=\"(.*?/[^/]*?\.html)\".*?>", re.DOTALL) + regex_extractVideoItemDate = re.compile("<div class=\"subline\" style=\"cursor: pointer;\">.*?(\\d{2}\.\\d{2}\.\\d{4} \\d{2}:\\d{2})</div>") + + videoItems = regex_extractVideoItems.findall(htmlPage) + nodeCount = initCount + len(videoItems) + + for videoItem in videoItems: + print "link: {0}".format(link) + print "videoItem: {0}".format(videoItem[0]) + if "<div class=\"subline\">" not in videoItem[0]: + continue + videoLink = regex_extractVideoItemHref.search(videoItem[0]).group(1) + try: + dateString = regex_extractVideoItemDate.search(videoItem[0]).group(1) + dateTime = time.strptime(dateString, "%d.%m.%Y %H:%M") + except: + dateTime = None + # TODO: Some videos from Extra 3 are located on http://www.n-joy.de/ + # which cannot be parsed by this script, yet. + if not re.compile("http://www.n-joy.de/.*").search(videoLink): + self.extractVideoInformation(videoLink, dateTime, nodeCount) + + # Pagination (weiter) + regex_extractNextPage = re.compile( + "<a class=\"square button\" href=\"(.*?)\" title=\"(.*?)\".*?>" + ) + for nextPageHref in regex_extractNextPage.finditer(htmlPage): + menuItemName = nextPageHref.group(2).decode("UTF-8") + link = self.rootLink+nextPageHref.group(1) + self.gui.buildVideoLink(DisplayObject(menuItemName, "", "", "description", link, False), self, nodeCount) + + def buildPageMenu(self, link, initCount): + + print link + if link[0:15] == "sendungverpasst": + self.buildPageMenuSendungVerpasst(link[15:]) + elif link == "livestream": + self.buildPageMenuLivestream() + elif "/sendung_verpasst/" in link: + self.buildPageMenuVideoListVerpasst(link, initCount) + else: + self.buildPageMenuVideoList(link, initCount) + + def searchVideo(self, searchText): + searchText = searchText.encode("UTF-8") + searchText = urllib.urlencode({"query": searchText}) + self.buildPageMenu(self.searchLink+searchText, 0) + + def extractVideoInformation(self, videoLink, pubDate, nodeCount): + + regexFindVideoLink = re.compile("http://.*(hq.mp4|hi.mp4|lo.flv)") + regexFindImageLink = re.compile("/.*v-ardgalerie.jpg") + regexFindMediaData = re.compile( + "<div class=\"padding group\">\\s*?<div class=\"textinfo\">\\s*?<h\\d.*?>" + "(.*?)" + "</h\\d>\\s*?.*?<div class=\"subline\">.*?</div>\\s*?<p.*?>" + "(.*?)" + "</p>", re.DOTALL + ) + if not videoLink.startswith(self.rootLink): + videoLink = self.rootLink+videoLink + videoPage = self.loadPage(videoLink) + + self.gui.log("videolink: {0}".format(videoLink)) + videoLink = {} + videoLink[0] = SimpleLink(regexFindVideoLink.search(videoPage).group(0), 0) + + try: + pictureLink = self.rootLink+regexFindImageLink.search(videoPage).group(0) + except: + pictureLink = None + if '<article class="w66 ">' not in videoPage: + searchResult = regexFindMediaData.search(videoPage) + title = searchResult.group(1).decode('utf-8') + description = searchResult.group(2).decode('utf-8') + else: + title = re.search( + 'var trackTitle = "(.*?)"', + videoPage + ).group(1).decode('utf-8') + description = re.search( + '<meta name="description" content="(.*?)"', + videoPage + ).group(1).decode('utf-8') + + self.gui.buildVideoLink(DisplayObject(title, "", pictureLink, description, videoLink, True, pubDate, 0), self, nodeCount) diff --git a/plugin.video.mediathek/mediathek/orf.py b/plugin.video.mediathek/mediathek/orf.py new file mode 100644 index 0000000..1146abf --- /dev/null +++ b/plugin.video.mediathek/mediathek/orf.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +# -------------LicenseHeader-------------- +# plugin.video.Mediathek - Gives access to most video-platforms from German public service broadcasters +# Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import re,time,urllib +from xml.dom import Node; +from xml.dom import minidom; +from mediathek import * + +class ORFMediathek(Mediathek): + def __init__(self, simpleXbmcGui): + + self.rootLink = "http://tvthek.orf.at" + self.gui = simpleXbmcGui; + self.menuTree = []; + self.menuTree.append(TreeNode("0","Startseite","http://tvthek.orf.at/",True)); + + menuPage = self.loadPage(self.rootLink+"/programs"); + findMenuLink = re.compile("<li><a href=\"(/programs/.*?)\" title=\".*?\">(.*?)</a></li>"); + findCategorie = re.compile("<h4>(.*?)</h4>\\s*?<ul>((\\s*?%s\\s*?)+)</ul>"%findMenuLink.pattern) + categories = []; + + for categorieMatch in findCategorie.finditer(menuPage): + title = categorieMatch.group(1); + items = []; + for menuMatch in findMenuLink.finditer(categorieMatch.group(2)): + items.append(TreeNode("1.%d.%d"%(len(categories),len(items)), menuMatch.group(2),"%s%s"%(self.rootLink,menuMatch.group(1)),True)); + categories.append(TreeNode("1.%d"%len(categories), title,"",False,items)); + + self.menuTree.append(TreeNode("1","Sendungen","",False,categories)); + + videoLinkPage = "/programs/.*" + imageLink = "http://tvthek.orf.at/assets/.*?.jpeg" + + + self.regex_extractVideoPageLink = re.compile(videoLinkPage+"?\""); + self.regex_extractImageLink = re.compile(imageLink); + self.regex_extractTitle = re.compile("<strong>.*<span"); + self.regex_extractVideoLink = re.compile("/programs/.*.asx"); + self.regex_extractVideoObject = re.compile("<a href=\""+videoLinkPage+"\" title=\".*\">\\s*<span class=\"spcr\">\\s*<img src=\""+imageLink+"\" title=\".*\" alt=\".*\" />\\s*<span class=\".*\"></span>\\s*<strong>.*<span class=\"nowrap duration\">.*</span></strong>\\s*<span class=\"desc\">.*</span>\\s*</span>\\s*</a>"); + + self.regex_extractSearchObject = re.compile("<li class=\"clearfix\">\\s*<a href=\".*\" title=\".*\" class=\".*\"><img src=\".*\" alt=\".*\" /><span class=\"btn_play\">.*</span></a>\\s*<p>.*</p>\\s*<h4><a href=\".*\" title=\".*\">.*</a></h4>\\s*<p><a href=\".*\" title=\".*\"></a></p>\\s*</li>"); + + self.regex_extractProgrammLink = re.compile("/programs/.*?\""); + self.regex_extractProgrammTitle = re.compile("title=\".*?\""); + self.regex_extractProgrammPicture = re.compile("/binaries/asset/segments/\\d*/image1"); + + self.regex_extractFlashVars = re.compile("ORF.flashXML = '.*?'"); + self.regex_extractHiddenDate = re.compile("\d{4}-\d{2}-\d{2}"); + self.regex_extractXML = re.compile("%3C.*%3E"); + self.regex_extractReferingSites = re.compile("<li><a href=\"/programs/\d+.*?/episodes/\d+.*?\""); + + self.replace_html = re.compile("<.*?>"); + self.searchLink = "http://tvthek.orf.at/search?q=" + @classmethod + def name(self): + return "ORF"; + + def isSearchable(self): + return True; + + def createVideoLink(self,title,image,videoPageLink,elementCount): + videoPage = self.loadPage(self.rootLink+videoPageLink); + + videoLink = self.regex_extractVideoLink.search(videoPage); + if(videoLink == None): + return; + + simpleLink = SimpleLink(self.rootLink+videoLink.group(), 0); + videoLink = {0:simpleLink}; + counter = 0 + playlist = self.loadPage(simpleLink.basePath); + for line in playlist: + counter+=1; + + if(counter == 1): + self.gui.buildVideoLink(DisplayObject(title,"",image,"",videoLink, True, time.gmtime()),self,elementCount); + else: + self.gui.buildVideoLink(DisplayObject(title,"",image,"",videoLink, "PlayList", time.gmtime()),self,elementCount); + + def searchVideo(self, searchText): + link = self.searchLink = "http://tvthek.orf.at/search?q="+searchText; + mainPage = self.loadPage(link); + result = self.regex_extractSearchObject.findall(mainPage); + for searchObject in result: + videoLink = self.regex_extractProgrammLink.search(searchObject).group().replace("\"",""); + title = self.regex_extractProgrammTitle.search(searchObject).group().replace("title=\"","").replace("\"",""); + title = title.decode("UTF-8"); + pictureLink = self.regex_extractProgrammPicture.search(searchObject).group(); + + print videoLink; + + self.createVideoLink(title,pictureLink,videoLink, len(result)); + + def extractLinksFromFlashXml(self, flashXml, date, elementCount): + print flashXml.toprettyxml().encode('UTF-8'); + playlistNode = flashXml.getElementsByTagName("Playlist")[0]; + linkNode=flashXml.getElementsByTagName("AsxUrl")[0]; + link=linkNode.firstChild.data; + asxLink = SimpleLink(self.rootLink+link,0); + videoLink = {0:asxLink}; + for videoItem in playlistNode.getElementsByTagName("Items")[0].childNodes: + if(videoItem.nodeType == Node.ELEMENT_NODE): + titleNode=videoItem.getElementsByTagName("Title")[0]; + + descriptionNode=videoItem.getElementsByTagName("Description")[0]; + title=titleNode.firstChild.data; + + stringArray = link.split("mp4:"); + + try: + description=descriptionNode.firstChild.data; + except: + description=""; + self.gui.buildVideoLink(DisplayObject(title,"","",description,videoLink, True, date),self,elementCount); + def extractFlashLinks(self, flashVars,videoPageLinks,elementCount): + for flashVar in flashVars: + encodedXML = self.regex_extractXML.search(flashVar).group(); + dateString = self.regex_extractHiddenDate.search(flashVar).group(); + date = time.strptime(dateString,"%Y-%m-%d"); + parsedXML = minidom.parseString(urllib.unquote(encodedXML)); + self.extractLinksFromFlashXml(parsedXML, date,elementCount); + for videoPageLink in videoPageLinks: + videoPageLink = self.rootLink+videoPageLink.replace("<li><a href=\"","").replace("\"",""); + print videoPageLink; + videoPage = self.loadPage(videoPageLink); + flashVars = self.regex_extractFlashVars.findall(videoPage); + for flashVar in flashVars: + encodedXML = self.regex_extractXML.search(flashVar).group(); + dateString = self.regex_extractHiddenDate.search(flashVar).group(); + date = time.strptime(dateString,"%Y-%m-%d"); + parsedXML = minidom.parseString(urllib.unquote(encodedXML)); + self.extractLinksFromFlashXml(parsedXML,date,elementCount); + def buildPageMenu(self, link, initCount): + mainPage = self.loadPage(link); + videoPageLinks = self.regex_extractReferingSites.findall(mainPage); + flashVars = self.regex_extractFlashVars.findall(mainPage); + links = self.regex_extractVideoObject.findall(mainPage); + elementCount = initCount + len(links)+len(flashVars)+len(videoPageLinks); + self.extractFlashLinks(flashVars,videoPageLinks,elementCount); + for linkObject in links: + + videoLink = self.regex_extractVideoPageLink.search(linkObject).group().replace("\"",""); + image = self.regex_extractImageLink.search(linkObject).group(); + title = self.regex_extractTitle.search(linkObject).group().decode('UTF8'); + title = self.replace_html.sub("", title); + title = title.replace(" <span",""); + self.createVideoLink(title,image,videoLink, elementCount); + diff --git a/plugin.video.mediathek/mediathek/wdr.py b/plugin.video.mediathek/mediathek/wdr.py new file mode 100644 index 0000000..5c784db --- /dev/null +++ b/plugin.video.mediathek/mediathek/wdr.py @@ -0,0 +1,191 @@ +# -*- coding: utf-8 -*- +#-------------LicenseHeader-------------- +# plugin.video.Mediathek - Gives access to most video-platforms from German public service broadcasters +# Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import re, time; +from mediathek import * +from xml.dom import minidom; +regex_dateString = re.compile("\\d{4}-\\d{2}-\\d{2}"); +class WDRMediathek(Mediathek): + def __init__(self, simpleXbmcGui): + self.gui = simpleXbmcGui; + self.pageSize = 20; #max 49; + self.rootLink = "http://www.wdr.de" + self.menuTree = ( + TreeNode("0","Neuste Videos",self.rootLink+"/mediathek/rdf/regional/index.xml",True), + TreeNode("1","Sendungen von A-Z","",False, + ( + TreeNode("1.0",u"A40","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=A40",True), + TreeNode("1.1",u"Aktuelle Stunde","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Aktuelle+Stunde",True), + TreeNode("1.2",u"Am Sonntag","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WDR+2+-+Der+Sonntag",True), + TreeNode("1.3",u"Cosmo","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Funkhaus+Europa+-+Cosmo",True), + TreeNode("1.4",u"daheim & unterwegs","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=daheim+%26+unterwegs",True), + TreeNode("1.5",u"die story","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=die+story",True), + TreeNode("1.6",u"Dittsche","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Dittsche",True), + TreeNode("1.7",u"eins zu eins","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=eins+zu+eins",True), + TreeNode("1.8",u"frauTV","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=frauTV",True), + TreeNode("1.9",u"hier und heute","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Hier+und+Heute",True), + TreeNode("1.10",u"Kabarett","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WDR+2+-+Kabarett",True), + TreeNode("1.11",u"Lokalzeit aus Aachen","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Lokalzeit+aus+Aachen",True), + TreeNode("1.12",u"Lokalzeit aus Düsseldorf","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Lokalzeit+aus+D%FCsseldorf",True), + TreeNode("1.13",u"Lokalzeit OWL","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Lokalzeit+OWL+aktuell",True), + TreeNode("1.14",u"Lokalzeit aus Bonn","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Lokalzeit+aus+Bonn",True), + TreeNode("1.15",u"Lokalzeit aus Köln","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Lokalzeit+aus+K%F6ln",True), + TreeNode("1.16",u"Lokalzeit Ruhr","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Lokalzeit+Ruhr",True), + TreeNode("1.17",u"Lokalzeit aus Dortmund","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Lokalzeit+aus+Dortmund",True), + TreeNode("1.18",u"Lokalzeit Bergisches Land","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Lokalzeit+Bergisches+Land",True), + TreeNode("1.19",u"Lokalzeit Südwestfalen","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Lokalzeit+S%FCdwestfalen",True), + TreeNode("1.20",u"Lokalzeit aus Duisburg","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Lokalzeit+aus+Duisburg",True), + TreeNode("1.21",u"Lokalzeit Münsterland","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Lokalzeit+M%FCnsterland",True), + TreeNode("1.22",u"markt","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=markt",True), + TreeNode("1.23",u"Menschen hautnah","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Menschen+hautnah",True), + TreeNode("1.24",u"Mittagsecho","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WDR+5+-+Mittagsecho",True), + TreeNode("1.25",u"Mittagsmagazin","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WDR+2+-+Mittagsmagazin",True), + TreeNode("1.26",u"mittwochs live","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=mittwochs+live",True), + TreeNode("1.27",u"Morgenecho","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WDR+5+-+Morgenecho",True), + TreeNode("1.28",u"Morgenmagazin","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WDR+2+-+Morgenmagazin",True), + TreeNode("1.29",u"Mosaik","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WDR+3+-+Mosaik",True), + TreeNode("1.30",u"Piazza","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Funkhaus+Europa+-+Piazza",True), + TreeNode("1.31",u"Platz der Republik","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WDR+5+-+Platz+der+Republik",True), + TreeNode("1.32",u"Quarks & Co","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Quarks+%26+Co",True), + TreeNode("1.33",u"Resonanzen","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WDR+3+-+Resonanzen",True), + TreeNode("1.34",u"Scala","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WDR+5+-+Scala",True), + TreeNode("1.35",u"schön hier","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=sch%F6n+hier",True), + TreeNode("1.36",u"Servicezeit","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Servicezeit",True), + TreeNode("1.37",u"sport inside","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=sport+inside",True), + TreeNode("1.38",u"Stichtag","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WDR+2+-+Stichtag",True), + TreeNode("1.39",u"Thema NRW","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WDR+5+-+Thema+NRW",True), + TreeNode("1.40",u"WDR aktuell","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WDR+aktuell",True), + TreeNode("1.41",u"WDR sport aktuell","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WDR+sport+aktuell",True), + TreeNode("1.42",u"west.art","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=West.art",True), + TreeNode("1.43",u"Westblick","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WDR+5+-+Westblick",True), + TreeNode("1.44",u"WESTPOL","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WESTPOL",True), + TreeNode("1.45",u"Westzeit","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WDR+2+-+Westzeit",True), + TreeNode("1.46",u"Zeiglers wunderbare Welt des Fußballs","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Zeiglers+wunderbare+Welt+des+Fu%DFballs",True), + TreeNode("1.47",u"ZeitZeichen","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WDR+3+%2F+WDR+5+-+ZeitZeichen",True), + TreeNode("1.48",u"Zimmer Frei!","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=Zimmer+Frei%21",True), + TreeNode("1.49",u"Zwischen Rhein und Weser","http://www.wdr.de/mediathek/html/regional/ergebnisse/sendung.xml?rankingtype=sendung&rankingvalue=WDR+2+-+Zwischen+Rhein+und+Weser",True), + ) + ), + TreeNode("2","Themen","",False, + ( + TreeNode("2.0","Politik","http://www.wdr.de/mediathek/html/regional/ergebnisse/schlagwort.xml?rankingvalue=Politik",True), + TreeNode("2.1","Wirtschaft","http://www.wdr.de/mediathek/html/regional/ergebnisse/schlagwort.xml?rankingvalue=Wirtschaft",True), + TreeNode("2.2","Kultur","http://www.wdr.de/mediathek/html/regional/ergebnisse/schlagwort.xml?rankingvalue=Kultur",True), + TreeNode("2.3","Panorama","http://www.wdr.de/mediathek/html/regional/ergebnisse/schlagwort.xml?rankingvalue=Panorama",True), + TreeNode("2.4","Service","http://www.wdr.de/mediathek/html/regional/ergebnisse/schlagwort.xml?rankingvalue=Service",True), + TreeNode("2.5","Freizeit","http://www.wdr.de/mediathek/html/regional/ergebnisse/schlagwort.xml?rankingvalue=Freizeit",True), + TreeNode("2.6","Sport","http://www.wdr.de/mediathek/html/regional/ergebnisse/schlagwort.xml?rankingvalue=Sport",True), + ) + ), + ) + self._regex_extractTitle = re.compile("<h1>.*?<span class=\"inv\">"); + self._regex_extractDescription = re.compile("<meta name=\"description\" content=\"(.|\\s)*?\" />"); + self._regex_extractPicture = re.compile("<link rel=\"image_src\" href=\".*?\" />"); + self._regex_extractDate = re.compile("<meta name=\"DC.Date\" content=\".*?\" />"); + self._regex_extractDuration = re.compile("\\((.*)\\)<span class=\"inv\">"); + + self._regex_extractVideoPage = re.compile("<a href=\"/mediathek/html/.*?\\.xml\" title=\".*?\".*?>"); + self._regex_extractLink = re.compile("/mediathek/html/.*?\\.xml"); + self._regex_extractAudioLink = re.compile(self.rootLink+"/mediathek/.*?\\.mp3"); + self._regex_extractVideoLink = re.compile("(dsl|isdn)Src=rtmp://.*?\\.(mp4|flv)"); + self.replace_html = re.compile("<.*?>"); + self.replace_tag = re.compile("(<meta name=\".*?\" content=\"|<link rel=\"image_src\" href=\"|\" />)"); + self.searchLink = "http://www.wdr.de/mediathek/html/regional/suche/index.xml?wsSucheAusgabe=liste&wsSucheSuchart=volltext&wsSucheMedium=av&suche_submit=Suche+starten&wsSucheBegriff=" + + @classmethod + def name(self): + return "WDR"; + def isSearchable(self): + return True; + + def searchVideo(self, searchText): + link = self.searchLink+searchText; + self.buildPageMenu(link, 0, False) + def buildPageMenu(self, link, initCount, subLink = False): + link = link+"&rankingcount="+str(self.pageSize); + self.gui.log("MenuLink: %s"%link); + mainPage = self.loadPage(link); + if(mainPage.startswith("<?xml version=\"1.0\"")): + self.parseXml(mainPage); + else: + self.parseHtml(mainPage); + def parseHtml(self, htmlPage): + videoPageLinks = list(self._regex_extractVideoPage.finditer(htmlPage)); + + for videoPageLink in videoPageLinks: + link = self._regex_extractLink.search(videoPageLink.group()).group(); + print link; + displayObject = self.generateDisplayObject(self.rootLink+link); + self.gui.buildVideoLink(displayObject,self,len(videoPageLinks)); + + def readText(self,node,textNode): + try: + node = node.getElementsByTagName(textNode)[0].firstChild; + return unicode(node.data); + except: + return ""; + + def parseDate(self,dateString): + dateString = regex_dateString.search(dateString).group(); + return time.strptime(dateString,"%Y-%m-%d"); + + def parseXml(self, xmlPage): + xmlPage = minidom.parseString(xmlPage); + items = xmlPage.getElementsByTagName("item"); + for itemNode in items: + link = self.readText(itemNode,"link"); + displayObject = self.generateDisplayObject(link); + self.gui.buildVideoLink(displayObject,self,len(items)); + + def generateDisplayObject(self,videoPageLink): + mainPage = self.loadPage(videoPageLink); + title = unicode(self._regex_extractTitle.search(mainPage).group(),'ISO-8859-1'); + description = unicode(self._regex_extractDescription.search(mainPage).group(),'ISO-8859-1'); + picture = unicode(self._regex_extractPicture.search(mainPage).group(),'ISO-8859-1'); + date = self._regex_extractDate.search(mainPage).group(); + duration = self._regex_extractDuration.search(mainPage).group(1); + + title = self.replace_html.sub("", title); + description = self.replace_tag.sub("",description); + picture = self.replace_tag.sub("",picture); + date = self.parseDate(self.replace_tag.sub("",date)); + + links = {}; + for linkString in self._regex_extractVideoLink.finditer(mainPage): + linkString = linkString.group(); + if linkString.startswith("dslSrc="): + linkString = linkString.replace("dslSrc=",""); + links[1] = self.extractLink(linkString); + else: + linkString = linkString.replace("isdnSrc=",""); + links[0] = self.extractLink(linkString); + + if len(links) == 0: + linkString = self._regex_extractAudioLink.search(mainPage).group(); + links[0] = self.extractLink(linkString); + + return DisplayObject(title,"",picture,description,links,True, date, duration) + + def extractLink(self, linkString): + if(linkString.find("mediartmp://")>-1): + linkString = linkString.split("mediartmp://") + return SimpleLink("rtmp://%s"%linkString[1], 0); + elif(linkString.find("mediahttp://")>-1): + linkString = linkString.split("mediahttp://") + return SimpleLink("http://%s"%linkString[1], 0); + else: + return SimpleLink(linkString, 0); diff --git a/plugin.video.mediathek/mediathek/zdf.py b/plugin.video.mediathek/mediathek/zdf.py new file mode 100644 index 0000000..3ece7a9 --- /dev/null +++ b/plugin.video.mediathek/mediathek/zdf.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- +#-------------LicenseHeader-------------- +# plugin.video.Mediathek - Gives access to most video-platforms from German public service broadcasters +# Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import re,math,traceback,time +from mediathek import * +from datetime import datetime,timedelta +import json + +class ZDFMediathek(Mediathek): + def __init__(self, simpleXbmcGui): + self.gui = simpleXbmcGui; + + today = datetime.today(); + + self.menuTree = ( + TreeNode("0","Startseite","https://zdf-cdn.live.cellular.de/mediathekV2/start-page",True), + TreeNode("1","Kategorien","https://zdf-cdn.live.cellular.de/mediathekV2/categories",True), + TreeNode("2","Sendungen von A-Z","https://zdf-cdn.live.cellular.de/mediathekV2/brands-alphabetical",True), + TreeNode("3","Sendung verpasst?","",False,( + TreeNode("3.0","Heute","https://zdf-cdn.live.cellular.de/mediathekV2/broadcast-missed/%s"%(today.strftime("%Y-%m-%d")),True), + TreeNode("3.1","Gestern","https://zdf-cdn.live.cellular.de/mediathekV2/broadcast-missed/%s"%((today-timedelta(days=1)).strftime("%Y-%m-%d")),True), + TreeNode("3.2","Vorgestern","https://zdf-cdn.live.cellular.de/mediathekV2/broadcast-missed/%s"%((today-timedelta(days=2)).strftime("%Y-%m-%d")),True), + TreeNode("3.3",(today-timedelta(days=3)).strftime("%A"),"https://zdf-cdn.live.cellular.de/mediathekV2/broadcast-missed/%s"%((today-timedelta(days=3)).strftime("%Y-%m-%d")),True), + TreeNode("3.4",(today-timedelta(days=4)).strftime("%A"),"https://zdf-cdn.live.cellular.de/mediathekV2/broadcast-missed/%s"%((today-timedelta(days=4)).strftime("%Y-%m-%d")),True), + TreeNode("3.5",(today-timedelta(days=5)).strftime("%A"),"https://zdf-cdn.live.cellular.de/mediathekV2/broadcast-missed/%s"%((today-timedelta(days=5)).strftime("%Y-%m-%d")),True), + TreeNode("3.6",(today-timedelta(days=6)).strftime("%A"),"https://zdf-cdn.live.cellular.de/mediathekV2/broadcast-missed/%s"%((today-timedelta(days=6)).strftime("%Y-%m-%d")),True), + TreeNode("3.7",(today-timedelta(days=7)).strftime("%A"),"https://zdf-cdn.live.cellular.de/mediathekV2/broadcast-missed/%s"%((today-timedelta(days=7)).strftime("%Y-%m-%d")),True), + )), + TreeNode("4","Live TV","https://zdf-cdn.live.cellular.de/mediathekV2/live-tv/%s"%(today.strftime("%Y-%m-%d")),True) + ); + @classmethod + def name(self): + return "ZDF"; + + def isSearchable(self): + return False; + + def searchVideo(self, searchText): + return; + + def buildPageMenu(self, link, initCount): + self.gui.log("buildPageMenu: "+link); + jsonObject = json.loads(self.loadPage(link)); + callhash = self.gui.storeJsonFile(jsonObject); + + if("stage" in jsonObject): + for stageObject in jsonObject["stage"]: + if(stageObject["type"]=="video"): + self.buildVideoLink(stageObject,initCount); + + if("cluster" in jsonObject): + for counter, clusterObject in enumerate(jsonObject["cluster"]): + if "teaser" in clusterObject and "name" in clusterObject: + path = "cluster.%d.teaser"%(counter) + self.gui.buildJsonLink(self,clusterObject["name"],path,callhash,initCount) + if("broadcastCluster" in jsonObject): + for counter, clusterObject in enumerate(jsonObject["broadcastCluster"]): + if clusterObject["type"].startswith("teaser") and "name" in clusterObject: + path = "broadcastCluster.%d.teaser"%(counter) + self.gui.buildJsonLink(self,clusterObject["name"],path,callhash,initCount) + if("epgCluster" in jsonObject): + for epgObject in jsonObject["epgCluster"]: + if("liveStream" in epgObject and len(epgObject["liveStream"])>0): + self.buildVideoLink(epgObject["liveStream"], initCount); + + def buildJsonMenu(self, path,callhash, initCount): + jsonObject=self.gui.loadJsonFile(callhash); + jsonObject=self.walkJson(path,jsonObject); + categoriePages=[]; + videoObjects=[]; + + for entry in jsonObject: + if entry["type"] == "brand": + categoriePages.append(entry); + if entry["type"] == "video": + videoObjects.append(entry); + self.gui.log("CategoriePages: %d"%len(categoriePages)); + self.gui.log("VideoPages: %d"%len(videoObjects)); + for categoriePage in categoriePages: + title=categoriePage["titel"]; + subTitle=categoriePage["beschreibung"]; + imageLink=""; + for width,imageObject in categoriePage["teaserBild"].iteritems(): + if int(width)<=840: + imageLink=imageObject["url"]; + url = categoriePage["url"]; + self.gui.buildVideoLink(DisplayObject(title,subTitle,imageLink,"",url,False),self,initCount); + + for videoObject in videoObjects: + self.buildVideoLink(videoObject,initCount); + + def buildVideoLink(self,videoObject,counter): + title=videoObject["headline"]; + subTitle=videoObject["titel"]; + + if(len(title)==0): + title = subTitle; + subTitle = ""; + if("beschreibung" in videoObject): + description=videoObject["beschreibung"]; + imageLink=""; + if("teaserBild" in videoObject): + for width,imageObject in videoObject["teaserBild"].iteritems(): + if int(width)<=840: + imageLink=imageObject["url"]; + if("formitaeten" in videoObject): + links = self.extractLinks(videoObject); + self.gui.buildVideoLink(DisplayObject(title,subTitle,imageLink,description,links,True,None,videoObject.get('length')),self,counter); + else: + link = videoObject["url"]; + self.gui.buildVideoLink(DisplayObject(title,subTitle,imageLink,description,link,"JsonLink",None,videoObject.get('length')),self,counter); + + def playVideoFromJsonLink(self,link): + jsonObject = json.loads(self.loadPage(link)); + links = self.extractLinks(jsonObject["document"]); + self.gui.play(links); + def extractLinks(self,jsonObject): + links={}; + for formitaete in jsonObject["formitaeten"]: + url = formitaete["url"]; + quality = formitaete["quality"]; + hd = formitaete["hd"]; + self.gui.log("quality:%s hd:%s url:%s"%(quality,hd,url)); + if hd == True: + links[4] = SimpleLink(url, -1); + else: + if quality == "low": + links[0] = SimpleLink(url, -1); + if quality == "med": + links[1] = SimpleLink(url, -1); + if quality == "high": + links[2] = SimpleLink(url, -1); + if quality == "veryhigh": + links[3] = SimpleLink(url, -1); + if quality == "auto": + links[3] = SimpleLink(url, -1); + return links; + diff --git a/plugin.video.mediathek/resources/language/English/strings.po b/plugin.video.mediathek/resources/language/English/strings.po new file mode 100644 index 0000000..6e7b1c2 --- /dev/null +++ b/plugin.video.mediathek/resources/language/English/strings.po @@ -0,0 +1,68 @@ +msgid "" +msgstr "" +"Project-Id-Version: plugin.video.mediathek\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: en\n" +"Plural-Forms: nplurals=1; plural=0\n" + +msgctxt "#30001" +msgid "Quality" +msgstr "" + +msgctxt "#30002" +msgid "Low" +msgstr "" + +msgctxt "#30003" +msgid "Medium" +msgstr "" + +msgctxt "#30004" +msgid "High" +msgstr "" + +msgctxt "#30005" +msgid "Very High" +msgstr "" + +msgctxt "#30006" +msgid "HD" +msgstr "" + +msgctxt "#30010" +msgid "Access Method" +msgstr "" + +msgctxt "#30011" +msgid "Online" +msgstr "" + +msgctxt "#30012" +msgid "Local cache" +msgstr "" + +msgctxt "#30020" +msgid "Prefered stream type" +msgstr "" + +msgctxt "#30021" +msgid "HTTP" +msgstr "" + +msgctxt "#30022" +msgid "RTMP" +msgstr "" + +msgctxt "#30023" +msgid "MMS" +msgstr "" + +msgctxt "#30024" +msgid "MOV" +msgstr "" + +msgctxt "#30100" +msgid "Search" +msgstr "" diff --git a/plugin.video.mediathek/resources/language/German/strings.po b/plugin.video.mediathek/resources/language/German/strings.po new file mode 100644 index 0000000..a9c3995 --- /dev/null +++ b/plugin.video.mediathek/resources/language/German/strings.po @@ -0,0 +1,68 @@ +msgid "" +msgstr "" +"Project-Id-Version: plugin.video.mediathek\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: de\n" +"Plural-Forms: nplurals=1; plural=0\n" + +msgctxt "#30001" +msgid "Quality" +msgstr "Qualität" + +msgctxt "#30002" +msgid "Low" +msgstr "Niedrig" + +msgctxt "#30003" +msgid "Medium" +msgstr "Normal" + +msgctxt "#30004" +msgid "High" +msgstr "Hoch" + +msgctxt "#30005" +msgid "Very High" +msgstr "Sehr Hoch" + +msgctxt "#30006" +msgid "HD" +msgstr "HD" + +msgctxt "#30010" +msgid "Access Method" +msgstr "Zugriffsmethode" + +msgctxt "#30011" +msgid "Online" +msgstr "Online" + +msgctxt "#30012" +msgid "Local cache" +msgstr "Lokaler Cache" + +msgctxt "#30020" +msgid "Prefered stream type" +msgstr "Bevorzugter Streamtyp" + +msgctxt "#30021" +msgid "HTTP" +msgstr "HTTP" + +msgctxt "#30022" +msgid "RTMP" +msgstr "RTMP" + +msgctxt "#30023" +msgid "MMS" +msgstr "MMS" + +msgctxt "#30024" +msgid "MOV" +msgstr "MOV" + +msgctxt "#30100" +msgid "Search" +msgstr "Suchen" diff --git a/plugin.video.mediathek/resources/language/Italian/strings.po b/plugin.video.mediathek/resources/language/Italian/strings.po new file mode 100644 index 0000000..d949faa --- /dev/null +++ b/plugin.video.mediathek/resources/language/Italian/strings.po @@ -0,0 +1,68 @@ +msgid "" +msgstr "" +"Project-Id-Version: plugin.video.mediathek\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: it\n" +"Plural-Forms: nplurals=1; plural=0\n" + +msgctxt "#30001" +msgid "Quality" +msgstr "Qualità" + +msgctxt "#30002" +msgid "Low" +msgstr "Bassa" + +msgctxt "#30003" +msgid "Medium" +msgstr "Media" + +msgctxt "#30004" +msgid "High" +msgstr "Alta" + +msgctxt "#30005" +msgid "Very High" +msgstr "altissima" + +msgctxt "#30006" +msgid "HD" +msgstr "HD" + +msgctxt "#30010" +msgid "Access Method" +msgstr "Metodo d'accesso" + +msgctxt "#30011" +msgid "Online" +msgstr "Online" + +msgctxt "#30012" +msgid "Local cache" +msgstr "Lokaler Cache" + +msgctxt "#30020" +msgid "Prefered stream type" +msgstr "Modalità streaming preferita" + +msgctxt "#30021" +msgid "HTTP" +msgstr "HTTP" + +msgctxt "#30022" +msgid "RTMP" +msgstr "RTMP" + +msgctxt "#30023" +msgid "MMS" +msgstr "MMS" + +msgctxt "#30024" +msgid "MOV" +msgstr "MOV" + +msgctxt "#30100" +msgid "Search" +msgstr "Cerca" diff --git a/plugin.video.mediathek/resources/logos/3Sat.jpg b/plugin.video.mediathek/resources/logos/3Sat.jpg Binary files differnew file mode 100644 index 0000000..fc41bdf --- /dev/null +++ b/plugin.video.mediathek/resources/logos/3Sat.jpg diff --git a/plugin.video.mediathek/resources/logos/ARD.jpg b/plugin.video.mediathek/resources/logos/ARD.jpg Binary files differnew file mode 100644 index 0000000..447d58c --- /dev/null +++ b/plugin.video.mediathek/resources/logos/ARD.jpg diff --git a/plugin.video.mediathek/resources/logos/ARTE.jpg b/plugin.video.mediathek/resources/logos/ARTE.jpg Binary files differnew file mode 100644 index 0000000..81ffabe --- /dev/null +++ b/plugin.video.mediathek/resources/logos/ARTE.jpg diff --git a/plugin.video.mediathek/resources/logos/BayernFS.jpg b/plugin.video.mediathek/resources/logos/BayernFS.jpg Binary files differnew file mode 100644 index 0000000..0eb6656 --- /dev/null +++ b/plugin.video.mediathek/resources/logos/BayernFS.jpg diff --git a/plugin.video.mediathek/resources/logos/ORF.jpg b/plugin.video.mediathek/resources/logos/ORF.jpg Binary files differnew file mode 100644 index 0000000..177372e --- /dev/null +++ b/plugin.video.mediathek/resources/logos/ORF.jpg diff --git a/plugin.video.mediathek/resources/logos/ZDF.jpg b/plugin.video.mediathek/resources/logos/ZDF.jpg Binary files differnew file mode 100644 index 0000000..841363c --- /dev/null +++ b/plugin.video.mediathek/resources/logos/ZDF.jpg diff --git a/plugin.video.mediathek/resources/logos/phoenix.jpg b/plugin.video.mediathek/resources/logos/phoenix.jpg Binary files differnew file mode 100644 index 0000000..b56c6ee --- /dev/null +++ b/plugin.video.mediathek/resources/logos/phoenix.jpg diff --git a/plugin.video.mediathek/resources/logos/png/3Sat.png b/plugin.video.mediathek/resources/logos/png/3Sat.png Binary files differnew file mode 100644 index 0000000..7213796 --- /dev/null +++ b/plugin.video.mediathek/resources/logos/png/3Sat.png diff --git a/plugin.video.mediathek/resources/logos/png/ARD.png b/plugin.video.mediathek/resources/logos/png/ARD.png Binary files differnew file mode 100644 index 0000000..274ef4b --- /dev/null +++ b/plugin.video.mediathek/resources/logos/png/ARD.png diff --git a/plugin.video.mediathek/resources/logos/png/ARTE.png b/plugin.video.mediathek/resources/logos/png/ARTE.png Binary files differnew file mode 100644 index 0000000..dfe204f --- /dev/null +++ b/plugin.video.mediathek/resources/logos/png/ARTE.png diff --git a/plugin.video.mediathek/resources/logos/png/BayernFS.png b/plugin.video.mediathek/resources/logos/png/BayernFS.png Binary files differnew file mode 100644 index 0000000..c604109 --- /dev/null +++ b/plugin.video.mediathek/resources/logos/png/BayernFS.png diff --git a/plugin.video.mediathek/resources/logos/png/KI.KA-Plus.png b/plugin.video.mediathek/resources/logos/png/KI.KA-Plus.png Binary files differnew file mode 100644 index 0000000..9bb468c --- /dev/null +++ b/plugin.video.mediathek/resources/logos/png/KI.KA-Plus.png diff --git a/plugin.video.mediathek/resources/logos/png/LICENSE b/plugin.video.mediathek/resources/logos/png/LICENSE new file mode 100644 index 0000000..ffdbc3f --- /dev/null +++ b/plugin.video.mediathek/resources/logos/png/LICENSE @@ -0,0 +1,118 @@ +APPLIES TO ALL FILES in logos/png/ + +CC0 1.0 Universal + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later +claims of infringement build upon, modify, incorporate in other works, reuse +and redistribute as freely as possible in any form whatsoever and for any +purposes, including without limitation commercial purposes. These owners may +contribute to the Commons to promote the ideal of a free culture and the +further production of creative, cultural and scientific works, or to gain +reputation or greater distribution for their Work in part through the use and +efforts of others. + +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with a +Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or her +Copyright and Related Rights in the Work and the meaning and intended legal +effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not limited +to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + + iii. publicity and privacy rights pertaining to a person's image or likeness + depicted in a Work; + + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + + v. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + + vii. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time +extensions), (iii) in any current or future medium and for any number of +copies, and (iv) for any purpose whatsoever, including without limitation +commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes +the Waiver for the benefit of each member of the public at large and to the +detriment of Affirmer's heirs and successors, fully intending that such Waiver +shall not be subject to revocation, rescission, cancellation, termination, or +any other legal or equitable action to disrupt the quiet enjoyment of the Work +by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account +Affirmer's express Statement of Purpose. In addition, to the extent the Waiver +is so judged Affirmer hereby grants to each affected person a royalty-free, +non transferable, non sublicensable, non exclusive, irrevocable and +unconditional license to exercise Affirmer's Copyright and Related Rights in +the Work (i) in all territories worldwide, (ii) for the maximum duration +provided by applicable law or treaty (including future time extensions), (iii) +in any current or future medium and for any number of copies, and (iv) for any +purpose whatsoever, including without limitation commercial, advertising or +promotional purposes (the "License"). The License shall be deemed effective as +of the date CC0 was applied by Affirmer to the Work. Should any part of the +License for any reason be judged legally invalid or ineffective under +applicable law, such partial invalidity or ineffectiveness shall not +invalidate the remainder of the License, and in such case Affirmer hereby +affirms that he or she will not (i) exercise any of his or her remaining +Copyright and Related Rights in the Work or (ii) assert any associated claims +and causes of action with respect to the Work, in either case contrary to +Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + + b. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or otherwise, + including without limitation warranties of title, merchantability, fitness + for a particular purpose, non infringement, or the absence of latent or + other defects, accuracy, or the present or absence of errors, whether or not + discoverable, all to the greatest extent permissible under applicable law. + + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without limitation + any person's Copyright and Related Rights in the Work. Further, Affirmer + disclaims responsibility for obtaining any necessary consents, permissions + or other rights required for any use of the Work. + + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +For more information, please see +<http://creativecommons.org/publicdomain/zero/1.0/> diff --git a/plugin.video.mediathek/resources/logos/png/NDR.png b/plugin.video.mediathek/resources/logos/png/NDR.png Binary files differnew file mode 100644 index 0000000..0cfb60f --- /dev/null +++ b/plugin.video.mediathek/resources/logos/png/NDR.png diff --git a/plugin.video.mediathek/resources/logos/png/ORF.png b/plugin.video.mediathek/resources/logos/png/ORF.png Binary files differnew file mode 100644 index 0000000..350b868 --- /dev/null +++ b/plugin.video.mediathek/resources/logos/png/ORF.png diff --git a/plugin.video.mediathek/resources/logos/png/ZDF.png b/plugin.video.mediathek/resources/logos/png/ZDF.png Binary files differnew file mode 100644 index 0000000..ab47225 --- /dev/null +++ b/plugin.video.mediathek/resources/logos/png/ZDF.png diff --git a/plugin.video.mediathek/resources/logos/png/phoenix.png b/plugin.video.mediathek/resources/logos/png/phoenix.png Binary files differnew file mode 100644 index 0000000..1d708e6 --- /dev/null +++ b/plugin.video.mediathek/resources/logos/png/phoenix.png diff --git a/plugin.video.mediathek/resources/settings.xml b/plugin.video.mediathek/resources/settings.xml new file mode 100644 index 0000000..5a916e4 --- /dev/null +++ b/plugin.video.mediathek/resources/settings.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<settings> + + <!-- General --> + <category label="General"> + <setting id="quality" type="enum" label="30001" lvalues="30002|30003|30004|30005|30006" default="2" /> + <setting id="preferedStreamType" type="enum" label="30020" lvalues="30021|30022|30023|30024" default="1" /> + </category> +</settings> + diff --git a/plugin.video.mediathek/simplexbmc.py b/plugin.video.mediathek/simplexbmc.py new file mode 100644 index 0000000..8924b34 --- /dev/null +++ b/plugin.video.mediathek/simplexbmc.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- +#-------------LicenseHeader-------------- +# plugin.video.mediathek - display german mediathekes +# Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import xbmc, xbmcgui, xbmcplugin,xbmcaddon, sys, urllib, os, time, re +from bs4 import BeautifulSoup; +import json +import hashlib +from mediathek import ComplexLink; + +regex_findLink = re.compile("mms://[^\"]*wmv"); + +__plugin__ = "Mediathek" + +settings = xbmcaddon.Addon(id='plugin.video.mediathek') +translation = settings.getLocalizedString + +class SimpleXbmcGui(object): + def __init__(self,settings): + self.settings = xbmcaddon.Addon(id='plugin.video.mediathek'); + self.quality = int(xbmcplugin.getSetting(int(sys.argv[1]), "quality" )); + self.preferedStreamTyp = int(xbmcplugin.getSetting(int(sys.argv[1]), "preferedStreamType")); + self.log("quality: %s"%(self.quality)); + self.plugin_profile_dir = xbmc.translatePath(settings.getAddonInfo("profile")) + if not os.path.exists(self.plugin_profile_dir): + os.mkdir(self.plugin_profile_dir); + + def log(self, msg): + if not isinstance(msg, (str, unicode)): + xbmc.log("[%s]: %s" % (__plugin__, type(msg))) + else: + xbmc.log("[%s]: %s" % (__plugin__, msg.encode('utf8'))) + + def buildVideoLink(self, displayObject, mediathek, objectCount): + if(displayObject.subTitle == "" or displayObject.subTitle == displayObject.title): + title = self.transformHtmlCodes(displayObject.title); + else: + title = self.transformHtmlCodes(displayObject.title +" - "+ displayObject.subTitle); + if displayObject.date is not None: + title = "(%s) %s"%(time.strftime("%d.%m",displayObject.date),title); + if displayObject.picture is not None: + listItem=xbmcgui.ListItem(title, iconImage="DefaultFolder.png", thumbnailImage=displayObject.picture) + else: + listItem=xbmcgui.ListItem(title, iconImage="DefaultFolder.png") + if(displayObject.isPlayable): + if(displayObject.isPlayable == "PlayList"): + link = displayObject.link[0] + url = "%s?type=%s&action=openPlayList&link=%s" % (sys.argv[0],mediathek.name(), urllib.quote_plus(link.basePath)) + listItem.setProperty('IsPlayable', 'true'); + xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=listItem,isFolder=False,totalItems = objectCount) + elif(displayObject.isPlayable == "JsonLink"): + link = displayObject.link + url = "%s?type=%s&action=openJsonLink&link=%s" % (sys.argv[0],mediathek.name(), urllib.quote_plus(link)) + listItem.setProperty('IsPlayable', 'true'); + listItem.setInfo("video", { + "mediatype":"video", + "title": title, + "plot": self.transformHtmlCodes(displayObject.description), + "duration": displayObject.duration + }) + xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=listItem,isFolder=False,totalItems = objectCount) + else: + self.log(displayObject.title); + link = self.extractLink(displayObject.link); + if(isinstance(link,ComplexLink)): + self.log("PlayPath:"+ link.playPath); + listItem.setProperty("PlayPath", link.playPath); + + self.log("URL:"+ link.basePath); + + metaData = { + "mediatype":"video", + "size": link.size, + "title": title, + "plot": self.transformHtmlCodes(displayObject.description), + "duration": displayObject.duration + } + + if(displayObject.date is not None): + metaData["date"] =time.strftime("%d.%m.%Y",displayObject.date); + metaData["year"] =int(time.strftime("%Y",displayObject.date)); + + listItem.setInfo("video",metaData); + listItem.setProperty('IsPlayable', 'true'); + xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=link.basePath,listitem=listItem,isFolder=False,totalItems = objectCount) + else: + url = "%s?type=%s&action=openTopicPage&link=%s" % (sys.argv[0],mediathek.name(), urllib.quote_plus(displayObject.link)) + xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=listItem,isFolder=True,totalItems = objectCount) + + def transformHtmlCodes(self, content): + return BeautifulSoup(content).prettify(formatter=None); + + def buildMenuLink(self,menuObject,mediathek,objectCount): + title = menuObject.name; + listItem=xbmcgui.ListItem(title, iconImage="DefaultFolder.png") + url = "%s?type=%s&action=openMenu&path=%s" % (sys.argv[0],mediathek.name(), menuObject.path) + xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=listItem,isFolder=True,totalItems = objectCount) + + def storeJsonFile(self,jsonObject,additionalIdentifier = None): + hashGenerator = hashlib.md5(); + hashGenerator.update(sys.argv[2]); + if(additionalIdentifier is not None): + hashGenerator.update(additionalIdentifier); + callhash = hashGenerator.hexdigest(); + storedJsonFile = os.path.join(self.plugin_profile_dir,"%s.json"%callhash); + with open(storedJsonFile, 'wb') as output: + json.dump(jsonObject,output); + return callhash; + + def loadJsonFile(self,callhash): + storedJsonFile = os.path.join(self.plugin_profile_dir,"%s.json"%callhash); + with open(storedJsonFile,"rb") as input: + return json.load(input); + + def buildJsonLink(self,mediathek,title,jsonPath,callhash,objectCount): + listItem=xbmcgui.ListItem(title, iconImage="DefaultFolder.png") + url = "%s?type=%s&action=openJsonPath&path=%s&callhash=%s" % (sys.argv[0],mediathek.name(), jsonPath,callhash) + xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=listItem,isFolder=True,totalItems = objectCount) + + def listAvailableMediathekes(self, mediathekNames): + rootPath = os.path.join(self.settings.getAddonInfo('path'),"resources/logos/png/"); + for name in mediathekNames: + listItem=xbmcgui.ListItem(name, iconImage="DefaultFolder.png",thumbnailImage=os.path.join(rootPath,name+".png")) + url = "%s?type=%s" % (sys.argv[0], name) + xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=listItem,isFolder=True) + def openMenuContext(self): + self.dialogProgress = xbmcgui.DialogProgress(); + + def closeMenuContext(self): + xbmcplugin.endOfDirectory(int(sys.argv[1])) + + def getHomeDir(self): + return self.settings.getAddonInfo("profile"); + + def back(self): + xbmc.executebuiltin("Action(PreviousMenu)"); + + def keyboardInput(self): + keyboard = xbmc.Keyboard("") + keyboard.doModal(); + return keyboard; + + def addSearchButton(self,mediathek): + title = translation(30100); + listItem=xbmcgui.ListItem(title, iconImage="DefaultFolder.png") + if(mediathek is not None): + url = "%s?type=%s&action=search" % (sys.argv[0],mediathek.name()) + else: + url = "%s?action=search" % (sys.argv[0]) + xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=listItem,isFolder=True) + + def readText(self,node,textNode): + try: + node = node.getElementsByTagName(textNode)[0].firstChild; + return unicode(node.data); + except: + return ""; + + def playPlaylist(self, remotePlaylist): + player = xbmc.Player(); + + playerItem = xbmcgui.ListItem(remotePlaylist); + playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO); + playlist.clear(); + for link in regex_findLink.findall(remotePlaylist): + listItem=xbmcgui.ListItem(link); + listItem.setProperty("PlayPath", link); + playlist.add(url=link, listitem=listItem); + + player.play(playlist, playerItem, false); + + def errorOK(self,title="", msg=""): + e = str( sys.exc_info()[ 1 ] ) + self.log(e) + if not title: + title = __plugin__ + if not msg: + msg = "ERROR!" + if(e == None): + xbmcgui.Dialog().ok( title, msg, e ) + else: + xbmcgui.Dialog().ok( title, msg) + def play(self,links): + link = self.extractLink(links); + listItem = xbmcgui.ListItem(path=link.basePath) + xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem=listItem) + + def extractLink(self, links): + if(self.quality in links): + return links[self.quality]; + else: + selectedKey = -1; + for key in links.keys(): + if(key < self.quality and key > selectedKey): + selectedKey = key; + if(selectedKey > -1): + return links[selectedKey]; + else: + selectedKey = links.keys()[0]; + for key in links.keys(): + if(key < selectedKey): + selectedKey = key; + return links[selectedKey]; |