├── resources ├── icon.png ├── logos │ ├── ARD.jpg │ ├── ORF.jpg │ ├── ZDF.jpg │ ├── 3Sat.jpg │ ├── ARTE.jpg │ ├── BayernFS.jpg │ ├── phoenix.jpg │ └── png │ │ ├── 3Sat.png │ │ ├── ARD.png │ │ ├── ARTE.png │ │ ├── NDR.png │ │ ├── ORF.png │ │ ├── ZDF.png │ │ ├── KI.KA.png │ │ ├── phoenix.png │ │ ├── BayernFS.png │ │ └── LICENSE ├── settings.xml └── language │ ├── resource.language.en_gb │ └── strings.po │ ├── resource.language.de_de │ └── strings.po │ └── resource.language.it_it │ └── strings.po ├── README.md ├── default.py ├── mediathek ├── factory.py ├── __init__.py ├── zdf.py ├── kika.py ├── dreisat.py ├── ard.py └── arte.py ├── addon.xml ├── simplexbmc.py └── LICENSE.txt /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raptor2101/Mediathek/HEAD/resources/icon.png -------------------------------------------------------------------------------- /resources/logos/ARD.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raptor2101/Mediathek/HEAD/resources/logos/ARD.jpg -------------------------------------------------------------------------------- /resources/logos/ORF.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raptor2101/Mediathek/HEAD/resources/logos/ORF.jpg -------------------------------------------------------------------------------- /resources/logos/ZDF.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raptor2101/Mediathek/HEAD/resources/logos/ZDF.jpg -------------------------------------------------------------------------------- /resources/logos/3Sat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raptor2101/Mediathek/HEAD/resources/logos/3Sat.jpg -------------------------------------------------------------------------------- /resources/logos/ARTE.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raptor2101/Mediathek/HEAD/resources/logos/ARTE.jpg -------------------------------------------------------------------------------- /resources/logos/BayernFS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raptor2101/Mediathek/HEAD/resources/logos/BayernFS.jpg -------------------------------------------------------------------------------- /resources/logos/phoenix.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raptor2101/Mediathek/HEAD/resources/logos/phoenix.jpg -------------------------------------------------------------------------------- /resources/logos/png/3Sat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raptor2101/Mediathek/HEAD/resources/logos/png/3Sat.png -------------------------------------------------------------------------------- /resources/logos/png/ARD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raptor2101/Mediathek/HEAD/resources/logos/png/ARD.png -------------------------------------------------------------------------------- /resources/logos/png/ARTE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raptor2101/Mediathek/HEAD/resources/logos/png/ARTE.png -------------------------------------------------------------------------------- /resources/logos/png/NDR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raptor2101/Mediathek/HEAD/resources/logos/png/NDR.png -------------------------------------------------------------------------------- /resources/logos/png/ORF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raptor2101/Mediathek/HEAD/resources/logos/png/ORF.png -------------------------------------------------------------------------------- /resources/logos/png/ZDF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raptor2101/Mediathek/HEAD/resources/logos/png/ZDF.png -------------------------------------------------------------------------------- /resources/logos/png/KI.KA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raptor2101/Mediathek/HEAD/resources/logos/png/KI.KA.png -------------------------------------------------------------------------------- /resources/logos/png/phoenix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raptor2101/Mediathek/HEAD/resources/logos/png/phoenix.png -------------------------------------------------------------------------------- /resources/logos/png/BayernFS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raptor2101/Mediathek/HEAD/resources/logos/png/BayernFS.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This plugin won't get any further support. Use it as long it works. 2 | 3 | The master-branch is ported to Py3 but got rejected during the resubmission. 4 | -------------------------------------------------------------------------------- /resources/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /resources/language/resource.language.en_gb/strings.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: plugin.video.mediathek\n" 4 | "MIME-Version: 1.0\n" 5 | "Content-Type: text/plain; charset=UTF-8\n" 6 | "Content-Transfer-Encoding: 8bit\n" 7 | "Language: en\n" 8 | "Plural-Forms: nplurals=1; plural=0\n" 9 | 10 | msgctxt "#30001" 11 | msgid "Quality" 12 | msgstr "" 13 | 14 | msgctxt "#30002" 15 | msgid "Low" 16 | msgstr "" 17 | 18 | msgctxt "#30003" 19 | msgid "Medium" 20 | msgstr "" 21 | 22 | msgctxt "#30004" 23 | msgid "High" 24 | msgstr "" 25 | 26 | msgctxt "#30005" 27 | msgid "Very High" 28 | msgstr "" 29 | 30 | msgctxt "#30006" 31 | msgid "HD" 32 | msgstr "" 33 | 34 | msgctxt "#30010" 35 | msgid "Access Method" 36 | msgstr "" 37 | 38 | msgctxt "#30011" 39 | msgid "Online" 40 | msgstr "" 41 | 42 | msgctxt "#30012" 43 | msgid "Local cache" 44 | msgstr "" 45 | 46 | msgctxt "#30020" 47 | msgid "Prefered stream type" 48 | msgstr "" 49 | 50 | msgctxt "#30021" 51 | msgid "HTTP" 52 | msgstr "" 53 | 54 | msgctxt "#30022" 55 | msgid "RTMP" 56 | msgstr "" 57 | 58 | msgctxt "#30023" 59 | msgid "MMS" 60 | msgstr "" 61 | 62 | msgctxt "#30024" 63 | msgid "MOV" 64 | msgstr "" 65 | 66 | msgctxt "#30100" 67 | msgid "Search" 68 | msgstr "" 69 | -------------------------------------------------------------------------------- /resources/language/resource.language.de_de/strings.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: plugin.video.mediathek\n" 4 | "MIME-Version: 1.0\n" 5 | "Content-Type: text/plain; charset=UTF-8\n" 6 | "Content-Transfer-Encoding: 8bit\n" 7 | "Language: de\n" 8 | "Plural-Forms: nplurals=1; plural=0\n" 9 | 10 | msgctxt "#30001" 11 | msgid "Quality" 12 | msgstr "Qualität" 13 | 14 | msgctxt "#30002" 15 | msgid "Low" 16 | msgstr "Niedrig" 17 | 18 | msgctxt "#30003" 19 | msgid "Medium" 20 | msgstr "Normal" 21 | 22 | msgctxt "#30004" 23 | msgid "High" 24 | msgstr "Hoch" 25 | 26 | msgctxt "#30005" 27 | msgid "Very High" 28 | msgstr "Sehr Hoch" 29 | 30 | msgctxt "#30006" 31 | msgid "HD" 32 | msgstr "HD" 33 | 34 | msgctxt "#30010" 35 | msgid "Access Method" 36 | msgstr "Zugriffsmethode" 37 | 38 | msgctxt "#30011" 39 | msgid "Online" 40 | msgstr "Online" 41 | 42 | msgctxt "#30012" 43 | msgid "Local cache" 44 | msgstr "Lokaler Cache" 45 | 46 | msgctxt "#30020" 47 | msgid "Prefered stream type" 48 | msgstr "Bevorzugter Streamtyp" 49 | 50 | msgctxt "#30021" 51 | msgid "HTTP" 52 | msgstr "HTTP" 53 | 54 | msgctxt "#30022" 55 | msgid "RTMP" 56 | msgstr "RTMP" 57 | 58 | msgctxt "#30023" 59 | msgid "MMS" 60 | msgstr "MMS" 61 | 62 | msgctxt "#30024" 63 | msgid "MOV" 64 | msgstr "MOV" 65 | 66 | msgctxt "#30100" 67 | msgid "Search" 68 | msgstr "Suchen" 69 | -------------------------------------------------------------------------------- /resources/language/resource.language.it_it/strings.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: plugin.video.mediathek\n" 4 | "MIME-Version: 1.0\n" 5 | "Content-Type: text/plain; charset=UTF-8\n" 6 | "Content-Transfer-Encoding: 8bit\n" 7 | "Language: it\n" 8 | "Plural-Forms: nplurals=1; plural=0\n" 9 | 10 | msgctxt "#30001" 11 | msgid "Quality" 12 | msgstr "Qualità" 13 | 14 | msgctxt "#30002" 15 | msgid "Low" 16 | msgstr "Bassa" 17 | 18 | msgctxt "#30003" 19 | msgid "Medium" 20 | msgstr "Media" 21 | 22 | msgctxt "#30004" 23 | msgid "High" 24 | msgstr "Alta" 25 | 26 | msgctxt "#30005" 27 | msgid "Very High" 28 | msgstr "altissima" 29 | 30 | msgctxt "#30006" 31 | msgid "HD" 32 | msgstr "HD" 33 | 34 | msgctxt "#30010" 35 | msgid "Access Method" 36 | msgstr "Metodo d'accesso" 37 | 38 | msgctxt "#30011" 39 | msgid "Online" 40 | msgstr "Online" 41 | 42 | msgctxt "#30012" 43 | msgid "Local cache" 44 | msgstr "Lokaler Cache" 45 | 46 | msgctxt "#30020" 47 | msgid "Prefered stream type" 48 | msgstr "Modalità streaming preferita" 49 | 50 | msgctxt "#30021" 51 | msgid "HTTP" 52 | msgstr "HTTP" 53 | 54 | msgctxt "#30022" 55 | msgid "RTMP" 56 | msgstr "RTMP" 57 | 58 | msgctxt "#30023" 59 | msgid "MMS" 60 | msgstr "MMS" 61 | 62 | msgctxt "#30024" 63 | msgid "MOV" 64 | msgstr "MOV" 65 | 66 | msgctxt "#30100" 67 | msgid "Search" 68 | msgstr "Cerca" 69 | -------------------------------------------------------------------------------- /default.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -------------LicenseHeader-------------- 3 | # plugin.video.mediathek - Gives access to most video-platforms from German public service broadcasters 4 | # Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | import os 20 | import sys 21 | import urllib 22 | import xbmc 23 | import xbmcaddon 24 | from simplexbmc import SimpleXbmcGui 25 | 26 | __plugin__ = "mediathek" 27 | 28 | settings = xbmcaddon.Addon(id='plugin.video.mediathek') 29 | 30 | gui = SimpleXbmcGui(settings) 31 | 32 | 33 | DIR_HOME = xbmc.translatePath(settings.getAddonInfo("profile")) 34 | if not os.path.exists(DIR_HOME): 35 | os.mkdir(DIR_HOME) 36 | 37 | gui.renderMenu() 38 | -------------------------------------------------------------------------------- /mediathek/factory.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -------------LicenseHeader-------------- 3 | # plugin.video.Mediathek - Gives access to most video-platforms from German public service broadcasters 4 | # Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | from mediathek.ard import * 19 | from mediathek.zdf import * 20 | from mediathek.arte import * 21 | from mediathek.dreisat import * 22 | from mediathek.kika import * 23 | 24 | 25 | class MediathekFactory(object): 26 | def __init__(self): 27 | self.avaibleMediathekes = { 28 | ARDMediathek.name(): ARDMediathek, 29 | ZDFMediathek.name(): ZDFMediathek, 30 | ARTEMediathek.name(): ARTEMediathek, 31 | DreiSatMediathek.name(): DreiSatMediathek, 32 | # ORFMediathek.name():ORFMediathek, 33 | # NDRMediathek.name():NDRMediathek, 34 | KIKA.name(): KIKA 35 | } 36 | 37 | def getAvaibleMediathekTypes(self): 38 | return sorted(self.avaibleMediathekes.keys()) 39 | 40 | def getMediathek(self, mediathekName, gui): 41 | return self.avaibleMediathekes[mediathekName](gui) 42 | -------------------------------------------------------------------------------- /addon.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | video 16 | 17 | 18 | Ermöglicht den Zugriff auf alle deutschen Mediatheken der öffentlich Rechtlichen 19 | Gives access to most video-platforms from German public service broadcasters 20 | Fornisce l'accesso a gran parte delle piattaforme video operate dalle emittenti pubbliche tedesche 21 | Ermöglicht den Zugriff auf alle deutschen Mediatheken der öffentlich Rechtlichen. 22 | Aktuell implementiert sind: 23 | 3Sat, ARD, ZDF, ARTE, ORF, NDR, KiKa (ohne Kikaninchen) 24 | 25 | Gives access to most video-platforms from German public service broadcasters. 26 | Currently implemented: 27 | 3Sat, ARD, ZDF, ARTE, ORF, NDR, KiKa (without Kikaninchen) 28 | 29 | Fornisce l'accesso a gran parte delle piattaforme video operate dalle emittenti pubbliche tedesche. 30 | Al momento sono supportate le seguenti emittenti: 31 | 3Sat, ARD, ZDF, ARTE, ORF, NDR, KiKa (senza Kikaninchen) 32 | 33 | de 34 | all 35 | GNU GENERAL PUBLIC LICENSE. Version 3, 29 June 2007 36 | https://github.com/raptor2101/Mediathek/issues 37 | https://github.com/raptor2101/Mediathek 38 | https://github.com/raptor2101/Mediathek 39 | raptor2101@gmx.de 40 | 0.10.0 (2020-01-03) 41 | - Migrate to python3 (matrix) 42 | - 3Sat is completly broken -> use ZDF instead. Will be fixed soon. 43 | - KiKa seems partly broken -> Will be fixed soon. 44 | 0.9.3 (2020-01-05) 45 | - [fix] arte categories 46 | 47 | 48 | resources/icon.png 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /mediathek/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -------------LicenseHeader-------------- 3 | # plugin.video.Mediathek - Gives access to most video-platforms from German public service broadcasters 4 | # Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | import socket 19 | import requests 20 | 21 | socket.setdefaulttimeout(1) 22 | 23 | 24 | class SimpleLink(object): 25 | def __init__(self, basePath, size): 26 | self.basePath = basePath 27 | self.size = size 28 | 29 | 30 | class ComplexLink(object): 31 | def __init__(self, basePath, playPath, size): 32 | self.basePath = basePath 33 | self.playPath = playPath 34 | self.size = size 35 | 36 | 37 | class TreeNode(object): 38 | def __init__(self, path, name, link, displayElements, childNodes=None): 39 | if childNodes is None: 40 | childNodes = [] 41 | self.name = name 42 | self.path = path 43 | self.link = link 44 | self.displayElements = displayElements 45 | self.childNodes = childNodes 46 | 47 | 48 | class DisplayObject(object): 49 | def __init__(self, title, subTitle, picture, description, link=None, isPlayable=True, date=None, duration=None): 50 | if link is None: 51 | link = [] 52 | self.title = title 53 | self.subTitle = subTitle 54 | self.link = link 55 | self.picture = picture 56 | self.isPlayable = isPlayable 57 | self.description = description 58 | self.date = date 59 | self.duration = duration 60 | 61 | 62 | class Mediathek(object): 63 | def loadPage(self, url, values=None, maxTimeout=None): 64 | safe_url = url.replace(" ", "%20").replace("&", "&") 65 | self.gui.log("download %s" % safe_url) 66 | content = requests.get(safe_url, allow_redirects=True) 67 | return content.text; 68 | #if content.encoding is not None: 69 | # return content.text.encode(content.encoding) 70 | #else: 71 | # return content.text 72 | 73 | def buildMenu(self, path, treeNode=None): 74 | if isinstance(path, (str, bytes)): 75 | path = path.split('.') 76 | if len(path) > 0: 77 | index = int(path.pop(0)) 78 | 79 | if treeNode is None: 80 | treeNode = self.menuTree[index] 81 | else: 82 | treeNode = treeNode.childNodes[index] 83 | self.buildMenu(path, treeNode) 84 | else: 85 | if treeNode is None: 86 | treeNode = self.menuTree[0] 87 | self.gui.log(treeNode.name) 88 | for childNode in treeNode.childNodes: 89 | self.gui.buildMenuLink(childNode, self, len(treeNode.childNodes)) 90 | if treeNode.displayElements: 91 | self.buildPageMenu(treeNode.link, len(treeNode.childNodes)) 92 | 93 | def displayCategories(self): 94 | if len(self.menuTree) > 1 or not self.menuTree[0].displayElements: 95 | for treeNode in self.menuTree: 96 | self.gui.buildMenuLink(treeNode, self, len(self.menuTree)) 97 | else: 98 | self.buildPageMenu(self.menuTree[0].link, 0) 99 | 100 | def walkJson(self, path, jsonObject): 101 | path = path.split('.') 102 | i = 0 103 | while i < len(path): 104 | if isinstance(jsonObject, list): 105 | index = int(path.pop(0)) 106 | else: 107 | index = path.pop(0) 108 | jsonObject = jsonObject[index] 109 | 110 | return jsonObject 111 | -------------------------------------------------------------------------------- /resources/logos/png/LICENSE: -------------------------------------------------------------------------------- 1 | APPLIES TO ALL FILES in logos/png/ 2 | 3 | CC0 1.0 Universal 4 | 5 | Statement of Purpose 6 | 7 | The laws of most jurisdictions throughout the world automatically confer 8 | exclusive Copyright and Related Rights (defined below) upon the creator and 9 | subsequent owner(s) (each and all, an "owner") of an original work of 10 | authorship and/or a database (each, a "Work"). 11 | 12 | Certain owners wish to permanently relinquish those rights to a Work for the 13 | purpose of contributing to a commons of creative, cultural and scientific 14 | works ("Commons") that the public can reliably and without fear of later 15 | claims of infringement build upon, modify, incorporate in other works, reuse 16 | and redistribute as freely as possible in any form whatsoever and for any 17 | purposes, including without limitation commercial purposes. These owners may 18 | contribute to the Commons to promote the ideal of a free culture and the 19 | further production of creative, cultural and scientific works, or to gain 20 | reputation or greater distribution for their Work in part through the use and 21 | efforts of others. 22 | 23 | For these and/or other purposes and motivations, and without any expectation 24 | of additional consideration or compensation, the person associating CC0 with a 25 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 26 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 27 | and publicly distribute the Work under its terms, with knowledge of his or her 28 | Copyright and Related Rights in the Work and the meaning and intended legal 29 | effect of CC0 on those rights. 30 | 31 | 1. Copyright and Related Rights. A Work made available under CC0 may be 32 | protected by copyright and related or neighboring rights ("Copyright and 33 | Related Rights"). Copyright and Related Rights include, but are not limited 34 | to, the following: 35 | 36 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 37 | and translate a Work; 38 | 39 | ii. moral rights retained by the original author(s) and/or performer(s); 40 | 41 | iii. publicity and privacy rights pertaining to a person's image or likeness 42 | depicted in a Work; 43 | 44 | iv. rights protecting against unfair competition in regards to a Work, 45 | subject to the limitations in paragraph 4(a), below; 46 | 47 | v. rights protecting the extraction, dissemination, use and reuse of data in 48 | a Work; 49 | 50 | vi. database rights (such as those arising under Directive 96/9/EC of the 51 | European Parliament and of the Council of 11 March 1996 on the legal 52 | protection of databases, and under any national implementation thereof, 53 | including any amended or successor version of such directive); and 54 | 55 | vii. other similar, equivalent or corresponding rights throughout the world 56 | based on applicable law or treaty, and any national implementations thereof. 57 | 58 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 59 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 60 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 61 | and Related Rights and associated claims and causes of action, whether now 62 | known or unknown (including existing as well as future claims and causes of 63 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 64 | duration provided by applicable law or treaty (including future time 65 | extensions), (iii) in any current or future medium and for any number of 66 | copies, and (iv) for any purpose whatsoever, including without limitation 67 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 68 | the Waiver for the benefit of each member of the public at large and to the 69 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 70 | shall not be subject to revocation, rescission, cancellation, termination, or 71 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 72 | by the public as contemplated by Affirmer's express Statement of Purpose. 73 | 74 | 3. Public License Fallback. Should any part of the Waiver for any reason be 75 | judged legally invalid or ineffective under applicable law, then the Waiver 76 | shall be preserved to the maximum extent permitted taking into account 77 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 78 | is so judged Affirmer hereby grants to each affected person a royalty-free, 79 | non transferable, non sublicensable, non exclusive, irrevocable and 80 | unconditional license to exercise Affirmer's Copyright and Related Rights in 81 | the Work (i) in all territories worldwide, (ii) for the maximum duration 82 | provided by applicable law or treaty (including future time extensions), (iii) 83 | in any current or future medium and for any number of copies, and (iv) for any 84 | purpose whatsoever, including without limitation commercial, advertising or 85 | promotional purposes (the "License"). The License shall be deemed effective as 86 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 87 | License for any reason be judged legally invalid or ineffective under 88 | applicable law, such partial invalidity or ineffectiveness shall not 89 | invalidate the remainder of the License, and in such case Affirmer hereby 90 | affirms that he or she will not (i) exercise any of his or her remaining 91 | Copyright and Related Rights in the Work or (ii) assert any associated claims 92 | and causes of action with respect to the Work, in either case contrary to 93 | Affirmer's express Statement of Purpose. 94 | 95 | 4. Limitations and Disclaimers. 96 | 97 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 98 | surrendered, licensed or otherwise affected by this document. 99 | 100 | b. Affirmer offers the Work as-is and makes no representations or warranties 101 | of any kind concerning the Work, express, implied, statutory or otherwise, 102 | including without limitation warranties of title, merchantability, fitness 103 | for a particular purpose, non infringement, or the absence of latent or 104 | other defects, accuracy, or the present or absence of errors, whether or not 105 | discoverable, all to the greatest extent permissible under applicable law. 106 | 107 | c. Affirmer disclaims responsibility for clearing rights of other persons 108 | that may apply to the Work or any use thereof, including without limitation 109 | any person's Copyright and Related Rights in the Work. Further, Affirmer 110 | disclaims responsibility for obtaining any necessary consents, permissions 111 | or other rights required for any use of the Work. 112 | 113 | d. Affirmer understands and acknowledges that Creative Commons is not a 114 | party to this document and has no duty or obligation with respect to this 115 | CC0 or use of the Work. 116 | 117 | For more information, please see 118 | 119 | -------------------------------------------------------------------------------- /mediathek/zdf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -------------LicenseHeader-------------- 3 | # plugin.video.Mediathek - Gives access to most video-platforms from German public service broadcasters 4 | # Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | import json 20 | import time 21 | from datetime import datetime, timedelta 22 | 23 | from mediathek import * 24 | 25 | 26 | class ZDFMediathek(Mediathek): 27 | def __init__(self, simpleXbmcGui): 28 | self.gui = simpleXbmcGui 29 | 30 | today = datetime.today() 31 | 32 | self.__mediathekUrl = "https://zdf-cdn.live.cellular.de/mediathekV2/" 33 | documentUrl = self.__mediathekUrl + "document/" 34 | mediathekUrlMissedFormat = self.__mediathekUrl + "broadcast-missed/%s" 35 | self.menuTree = ( 36 | TreeNode("0", "Startseite", self.__mediathekUrl + "start-page", True), 37 | TreeNode("1", "Kategorien", "", False, ( 38 | TreeNode("1.0", u"Comedy/Show", documentUrl + "comedy-und-show-100", True), 39 | TreeNode("1.1", u"Doku/Wissen", documentUrl + "doku-wissen-104", True), 40 | TreeNode("1.2", u"Filme", documentUrl + "filme-104", True), 41 | TreeNode("1.3", u"Geschichte", documentUrl + "geschichte-108", True), 42 | TreeNode("1.4", u"nachrichten", documentUrl + "nachrichten-100", True), 43 | TreeNode("1.5", u"Kinder/ZDFtivi", documentUrl + "kinder-100", True), 44 | TreeNode("1.6", u"Krimi", documentUrl + "krimi-102", True), 45 | TreeNode("1.7", u"Kultur", documentUrl + "kultur-104", True), 46 | TreeNode("1.8", u"Politik/Gesellschaft", documentUrl + "politik-gesellschaft-102", True), 47 | TreeNode("1.9", u"Serien", documentUrl + "serien-100", True), 48 | TreeNode("1.10", u"Sport", documentUrl + "sport-106", True), 49 | TreeNode("1.11", u"Verbraucher", documentUrl + "verbraucher-102", True), 50 | )), 51 | TreeNode("2", "Sendungen von A-Z", self.__mediathekUrl + "brands-alphabetical", True), 52 | TreeNode("3", "Sendung verpasst?", "", False, ( 53 | TreeNode("3.0", "Heute", mediathekUrlMissedFormat % (today.strftime("%Y-%m-%d")), True), 54 | TreeNode("3.1", "Gestern", mediathekUrlMissedFormat % ((today - timedelta(days=1)).strftime("%Y-%m-%d")), True), 55 | TreeNode("3.2", "Vorgestern", mediathekUrlMissedFormat % ((today - timedelta(days=2)).strftime("%Y-%m-%d")), True), 56 | TreeNode("3.3", (today - timedelta(days=3)).strftime("%A"), mediathekUrlMissedFormat % ((today - timedelta(days=3)).strftime("%Y-%m-%d")), True), 57 | TreeNode("3.4", (today - timedelta(days=4)).strftime("%A"), mediathekUrlMissedFormat % ((today - timedelta(days=4)).strftime("%Y-%m-%d")), True), 58 | TreeNode("3.5", (today - timedelta(days=5)).strftime("%A"), mediathekUrlMissedFormat % ((today - timedelta(days=5)).strftime("%Y-%m-%d")), True), 59 | TreeNode("3.6", (today - timedelta(days=6)).strftime("%A"), mediathekUrlMissedFormat % ((today - timedelta(days=6)).strftime("%Y-%m-%d")), True), 60 | TreeNode("3.7", (today - timedelta(days=7)).strftime("%A"), mediathekUrlMissedFormat % ((today - timedelta(days=7)).strftime("%Y-%m-%d")), True), 61 | )), 62 | TreeNode("4", "Live TV", self.__mediathekUrl + "live-tv/%s" % (today.strftime("%Y-%m-%d")), True) 63 | ) 64 | 65 | @classmethod 66 | def name(self): 67 | return "ZDF" 68 | 69 | def isSearchable(self): 70 | return True 71 | 72 | def searchVideo(self, searchText): 73 | self.buildPageMenu(self.__mediathekUrl + "search?q=%s" % searchText, 0) 74 | 75 | def buildPageMenu(self, link, initCount): 76 | self.gui.log("buildPageMenu: " + link) 77 | jsonObject = json.loads(self.loadPage(link)) 78 | callhash = self.gui.storeJsonFile(jsonObject) 79 | 80 | if "stage" in jsonObject: 81 | for stageObject in jsonObject["stage"]: 82 | if stageObject["type"] == "video": 83 | self.buildVideoLink(stageObject, initCount) 84 | if "results" in jsonObject: 85 | for stageObject in jsonObject["results"]: 86 | if stageObject["type"] == "video": 87 | self.buildVideoLink(stageObject, initCount) 88 | if "cluster" in jsonObject: 89 | for counter, clusterObject in enumerate(jsonObject["cluster"]): 90 | if "teaser" in clusterObject and "name" in clusterObject: 91 | path = "cluster.%d.teaser" % (counter) 92 | self.gui.buildJsonLink(self, clusterObject["name"], path, callhash, initCount) 93 | if "broadcastCluster" in jsonObject: 94 | for counter, clusterObject in enumerate(jsonObject["broadcastCluster"]): 95 | if clusterObject["type"].startswith("teaser") and "name" in clusterObject: 96 | path = "broadcastCluster.%d.teaser" % (counter) 97 | self.gui.buildJsonLink(self, clusterObject["name"], path, callhash, initCount) 98 | if "epgCluster" in jsonObject: 99 | for epgObject in jsonObject["epgCluster"]: 100 | if "liveStream" in epgObject and len(epgObject["liveStream"]) > 0: 101 | self.buildVideoLink(epgObject["liveStream"], initCount) 102 | 103 | def buildJsonMenu(self, path, callhash, initCount): 104 | jsonObject = self.gui.loadJsonFile(callhash) 105 | jsonObject = self.walkJson(path, jsonObject) 106 | categoriePages = [] 107 | videoObjects = [] 108 | 109 | for entry in jsonObject: 110 | if entry["type"] == "brand": 111 | categoriePages.append(entry) 112 | if entry["type"] == "video": 113 | videoObjects.append(entry) 114 | self.gui.log("CategoriePages: %d" % len(categoriePages)) 115 | self.gui.log("VideoPages: %d" % len(videoObjects)) 116 | for categoriePage in categoriePages: 117 | title = categoriePage["titel"] 118 | subTitle = categoriePage["beschreibung"] 119 | imageLink = "" 120 | for width, imageObject in list(categoriePage["teaserBild"].items()): 121 | if int(width) <= 840: 122 | imageLink = imageObject["url"] 123 | url = categoriePage["url"] 124 | self.gui.buildVideoLink(DisplayObject(title, subTitle, imageLink, "", url, False), self, initCount) 125 | 126 | for videoObject in videoObjects: 127 | self.buildVideoLink(videoObject, initCount) 128 | 129 | def buildVideoLink(self, videoObject, counter): 130 | title = videoObject["headline"] 131 | subTitle = videoObject["titel"] 132 | 133 | if len(title) == 0: 134 | title = subTitle 135 | subTitle = "" 136 | if "beschreibung" in videoObject: 137 | description = videoObject["beschreibung"] 138 | imageLink = "" 139 | if "teaserBild" in videoObject: 140 | for width, imageObject in list(videoObject["teaserBild"].items()): 141 | if int(width) <= 840: 142 | imageLink = imageObject["url"] 143 | if "visibleFrom" in videoObject: 144 | date = time.strptime(videoObject["visibleFrom"], "%d.%m.%Y %H:%M") 145 | else: 146 | date = time.gmtime() 147 | if "formitaeten" in videoObject: 148 | links = self.extractLinks(videoObject) 149 | self.gui.buildVideoLink( 150 | DisplayObject(title, subTitle, imageLink, description, links, True, date, videoObject.get('length')), 151 | self, counter) 152 | else: 153 | link = videoObject["url"] 154 | videoLength = videoObject.get('length') 155 | displayObject = DisplayObject(title, subTitle, imageLink, description, link, "JsonLink", date, videoLength) 156 | self.gui.buildVideoLink(displayObject, self, counter) 157 | 158 | def playVideoFromJsonLink(self, link): 159 | jsonObject = json.loads(self.loadPage(link)) 160 | links = self.extractLinks(jsonObject["document"]) 161 | self.gui.play(links) 162 | 163 | def extractLinks(self, jsonObject): 164 | self.gui.log(json.dumps(jsonObject, sort_keys=True)); 165 | links = {} 166 | for formitaete in jsonObject["formitaeten"]: 167 | url = formitaete["url"] 168 | quality = formitaete["quality"] 169 | hd = formitaete["hd"] 170 | self.gui.log("quality:%s hd:%s url:%s" % (quality, hd, url)) 171 | if hd is True: 172 | links[4] = SimpleLink(url, -1) 173 | else: 174 | if quality == "low": 175 | links[0] = SimpleLink(url, -1) 176 | if quality == "med": 177 | links[1] = SimpleLink(url, -1) 178 | if quality == "high": 179 | links[2] = SimpleLink(url, -1) 180 | if quality == "veryhigh": 181 | links[3] = SimpleLink(url, -1) 182 | if quality == "auto": 183 | links[3] = SimpleLink(url, -1) 184 | return links 185 | -------------------------------------------------------------------------------- /mediathek/kika.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -------------LicenseHeader-------------- 3 | # plugin.video.Mediathek - Gives access to most video-platforms from German public service broadcasters 4 | # Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | import re 19 | import time 20 | 21 | from bs4 import BeautifulSoup 22 | 23 | from mediathek import * 24 | 25 | 26 | class KIKA(Mediathek): 27 | def __init__(self, simpleXbmcGui): 28 | self.gui = simpleXbmcGui 29 | self.rootLink = "http://www.kika.de" 30 | self.menuTree = ( 31 | TreeNode("0", "Videos", self.rootLink + "/videos/index.html", True), 32 | TreeNode("1", "Sendungen von A-Z", "", False, 33 | ( 34 | TreeNode("1.0", "A", self.rootLink + "/sendungen/sendungenabisz100_page-A_zc-05fb1331.html", True), 35 | TreeNode("1.1", "B", self.rootLink + "/sendungen/sendungenabisz100_page-B_zc-1775e6d8.html", True), 36 | TreeNode("1.2", "C", self.rootLink + "/sendungen/sendungenabisz100_page-C_zc-6248eba0.html", True), 37 | TreeNode("1.3", "D", self.rootLink + "/sendungen/sendungenabisz100_page-D_zc-e090a8fb.html", True), 38 | TreeNode("1.4", "E", self.rootLink + "/sendungen/sendungenabisz100_page-E_zc-ec2376ed.html", True), 39 | TreeNode("1.5", "F", self.rootLink + "/sendungen/sendungenabisz100_page-F_zc-f76734a0.html", True), 40 | TreeNode("1.6", "G", self.rootLink + "/sendungen/sendungenabisz100_page-G_zc-34bda7c3.html", True), 41 | TreeNode("1.7", "H", self.rootLink + "/sendungen/sendungenabisz100_page-H_zc-7e25e70a.html", True), 42 | TreeNode("1.8", "I", self.rootLink + "/sendungen/sendungenabisz100_page-I_zc-b7f774f5.html", True), 43 | TreeNode("1.9", "J", self.rootLink + "/sendungen/sendungenabisz100_page-J_zc-3130680a.html", True), 44 | TreeNode("1.10", "K", self.rootLink + "/sendungen/sendungenabisz100_page-K_zc-c8f76ba1.html", True), 45 | TreeNode("1.11", "L", self.rootLink + "/sendungen/sendungenabisz100_page-L_zc-bbebc1a7.html", True), 46 | TreeNode("1.12", "M", self.rootLink + "/sendungen/sendungenabisz100_page-M_zc-00574a43.html", True), 47 | TreeNode("1.13", "N", self.rootLink + "/sendungen/sendungenabisz100_page-N_zc-b079366f.html", True), 48 | TreeNode("1.14", "O", self.rootLink + "/sendungen/sendungenabisz100_page-O_zc-febc55f5.html", True), 49 | TreeNode("1.15", "P", self.rootLink + "/sendungen/sendungenabisz100_page-P_zc-2c1a492f.html", True), 50 | TreeNode("1.16", "Q", self.rootLink + "/sendungen/sendungenabisz100_page-Q_zc-2cb019d6.html", True), 51 | TreeNode("1.17", "R", self.rootLink + "/sendungen/sendungenabisz100_page-R_zc-cab3e22b.html", True), 52 | TreeNode("1.18", "S", self.rootLink + "/sendungen/sendungenabisz100_page-S_zc-e7f420d0.html", True), 53 | TreeNode("1.19", "T", self.rootLink + "/sendungen/sendungenabisz100_page-T_zc-84a2709f.html", True), 54 | TreeNode("1.20", "U", self.rootLink + "/sendungen/sendungenabisz100_page-U_zc-a26c1157.html", True), 55 | TreeNode("1.21", "V", self.rootLink + "/sendungen/sendungenabisz100_page-V_zc-1fc26dc3.html", True), 56 | TreeNode("1.22", "W", self.rootLink + "/sendungen/sendungenabisz100_page-W_zc-25c5c777.html", True), 57 | TreeNode("1.23", "Y", self.rootLink + "/sendungen/sendungenabisz100_page-Y_zc-388beba7.html", True), 58 | TreeNode("1.24", "Z", self.rootLink + "/sendungen/sendungenabisz100_page-Z_zc-e744950d.html", True), 59 | TreeNode("1.25", "...", self.rootLink + "/sendungen/sendungenabisz100_page-1_zc-43c28d56.html", True) 60 | ) 61 | ) 62 | ) 63 | 64 | self.regex_videoLinks = re.compile("(.*?)") 71 | self.regex_xml_title = re.compile("(.*?)") 72 | self.regex_xml_time = re.compile("(.*?)") 73 | self.regex_xml_image = re.compile("\\s*?(.*?)") 74 | self.regex_xml_videoLink = re.compile("\\s*?(.*?).*?(.*?)\\s*?", re.DOTALL) 75 | self.regex_videoLink = re.compile("rtmp://.*?\.mp4") 76 | 77 | @classmethod 78 | def name(self): 79 | return "KI.KA" 80 | 81 | @classmethod 82 | def isSearchable(self): 83 | return False 84 | 85 | @classmethod 86 | def searchVideo(self, searchText): 87 | return 88 | 89 | def buildVideoLink(self, pageLink): 90 | xmlPage = self.loadPage(self.rootLink + pageLink) 91 | channel = self.regex_xml_channel.search(xmlPage) 92 | if channel is not None: 93 | channel = channel.group(1) 94 | title = self.regex_xml_title.search(xmlPage).group(1) 95 | image = self.regex_xml_image.search(xmlPage).group(1).replace("**aspectRatio**", "tlarge169").replace("**width**", "1472") 96 | 97 | self.gui.log("%s %s" % (title, image)) 98 | links = {} 99 | for match in self.regex_xml_videoLink.finditer(xmlPage): 100 | profile = match.group(1) 101 | directLink = match.group(2) 102 | #self.gui.log("%s %s"%(profile,directLink)) 103 | if "MP4 Web S" in profile: 104 | links[0] = SimpleLink(directLink, 0) 105 | if "MP4 Web L" in profile: 106 | links[1] = SimpleLink(directLink, 0) 107 | if "MP4 Web L+" in profile: 108 | links[2] = SimpleLink(directLink, 0) 109 | if "MP4 Web XL" in profile: 110 | links[3] = SimpleLink(directLink, 0) 111 | 112 | date = time.strptime(self.regex_xml_time.search(xmlPage).group(1), u"%d.%m.%Y %H:%M") 113 | if channel is not None: 114 | return DisplayObject(channel, title, image, "", links, True, date) 115 | else: 116 | return DisplayObject(title, "", image, "", links, True, date) 117 | 118 | def buildPageMenu(self, link, initCount): 119 | videoLinks = set() 120 | pageContent = self.loadPage(link) 121 | htmlPage = BeautifulSoup(pageContent, 'html.parser') 122 | 123 | htmlElements = htmlPage.select(self.selector_videoPages) 124 | self.gui.log("found %d htmlElements" % len(htmlElements)) 125 | self.extractConfigLinks(videoLinks, pageContent) 126 | self.extractVideoLinks(videoLinks, htmlElements) 127 | 128 | if len(videoLinks) == 0: 129 | htmlElements = htmlPage.select(self.selector_allVideoPage) 130 | self.extractVideoLinks(videoLinks, htmlElements) 131 | count = initCount + len(videoLinks) 132 | 133 | self.extractSubFolders(htmlPage, count) 134 | displayObjects = set() 135 | for link in videoLinks: 136 | displayObject = self.buildVideoLink(link) 137 | displayObjects.add(displayObject) 138 | displayObjects_sorted = sorted(displayObjects, key=lambda displayObject: displayObject.date) 139 | self.gui.log("found %d display obj " % len(displayObjects)) 140 | for displayObject in displayObjects_sorted: 141 | self.gui.buildVideoLink(displayObject, self, count) 142 | 143 | def extractVideoLinks(self, videoLinks, htmlElements): 144 | for item in htmlElements: 145 | link = self.rootLink + item['href'] 146 | videoPage = self.loadPage(link) 147 | for match in self.regex_videoLinks.finditer(videoPage): 148 | link = match.group(1) + "-avCustom.xml" 149 | if link not in videoLinks: 150 | videoLinks.add(link) 151 | self.gui.log("found %d video links" % len(videoLinks)) 152 | 153 | def extractConfigLinks(self, videoLinks, pageContent): 154 | directLinks = list(self.regex_configLinks.finditer(pageContent)) 155 | for match in directLinks: 156 | link = match.group(1) 157 | if link not in videoLinks: 158 | videoLinks.add(link) 159 | self.gui.log("found %d config links" % len(videoLinks)) 160 | 161 | def extractSubFolders(self, htmlPage, initCount): 162 | htmlElements = htmlPage.select(self.selector_seriesPages) + htmlPage.select(self.selector_allVideoPage) 163 | self.gui.log("found %d page links" % len(htmlElements)) 164 | count = initCount + len(htmlElements) 165 | displayObjects = set() 166 | for item in htmlElements: 167 | link = self.rootLink + item['href'] 168 | title = item['title'] 169 | # i'am uncertain why this is needed. a.linkAll should be found by selector_allVideoPage 170 | if item.has_attr('onclick'): 171 | self.gui.log("onclick detected - skip link") 172 | continue 173 | if title == "": 174 | continue 175 | displayObject = DisplayObject("Alle Videos", title, "", "", link, False) 176 | displayObjects.add(displayObject) 177 | displayObjects_sorted = sorted(displayObjects, key=lambda displayObject: displayObject.title) 178 | for displayObject in displayObjects_sorted: 179 | self.gui.buildVideoLink(displayObject, self, count) 180 | return count 181 | -------------------------------------------------------------------------------- /mediathek/dreisat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -------------LicenseHeader-------------- 3 | # plugin.video.Mediathek - Gives access to most video-platforms from German public service broadcasters 4 | # Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | import json 19 | import re 20 | import time 21 | from xml.dom import minidom 22 | 23 | from mediathek import * 24 | 25 | regex_dateString = re.compile("\\d{2}\\.((\\w{3})|(\\d{2}))\\.\\d{4}") 26 | month_replacements = { 27 | "Jan": "01", 28 | "Feb": "02", 29 | "Mar": "03", 30 | "Apr": "04", 31 | "May": "05", 32 | "Jun": "06", 33 | "Jul": "07", 34 | "Aug": "08", 35 | "Sep": "09", 36 | "Oct": "10", 37 | "Nov": "11", 38 | "Dec": "12", 39 | } 40 | 41 | 42 | class DreiSatMediathek(Mediathek): 43 | @classmethod 44 | def name(self): 45 | return "3Sat" 46 | 47 | def isSearchable(self): 48 | return True 49 | 50 | def __init__(self, simpleXbmcGui): 51 | self.gui = simpleXbmcGui 52 | if self.gui.preferedStreamTyp == 0: 53 | self.baseType = "video/x-ms-asf" 54 | elif self.gui.preferedStreamTyp == 1: 55 | self.baseType = "video/x-ms-asf" 56 | elif self.gui.preferedStreamTyp == 2: 57 | self.baseType = "video/x-ms-asf" 58 | else: 59 | self.baseType = "video/quicktime" 60 | self.webEmType = "video/webm" 61 | self.menuTree = ( 62 | TreeNode("0", "Hauptseite", "http://www.3sat.de/mediathek/", True), 63 | TreeNode("1", "Sendungen von A-Z", "", False, ( 64 | TreeNode("1.0", "Kulturzeit", "http://www.3sat.de/mediathek/?red=kulturzeit", True), 65 | TreeNode("1.1", "Nano", "http://www.3sat.de/mediathek/?red=nano", True), 66 | TreeNode("1.2", "marko", "http://www.3sat.de/mediathek/?red=makro", True), 67 | TreeNode("1.3", "scobel", "http://www.3sat.de/mediathek/?red=scobel", True), 68 | TreeNode("1.4", "Wissenschaftsdoku", "http://www.3sat.de/mediathek/?red=wido", True), 69 | TreeNode("1.5", "3satbuchzeit", "http://www.3sat.de/mediathek/?red=buchzeit", True), 70 | TreeNode("1.6", "Ab 18!", "http://www.3sat.de/mediathek/?red=ab18", True), 71 | TreeNode("1.7", "auslandsjournal extra", "http://www.3sat.de/mediathek/?red=ajextra", True), 72 | TreeNode("1.8", "Bauerfeind", "http://www.3sat.de/mediathek/?red=bauerfeind", True), 73 | TreeNode("1.9", "Besonders normal", "http://www.3sat.de/mediathek/?red=besondersnormal", True), 74 | TreeNode("1.10", "Close up", "http://www.3sat.de/mediathek/?red=closeup", True), 75 | TreeNode("1.11", "Film", "http://www.3sat.de/mediathek/?red=film", True), 76 | TreeNode("1.12", "hitec", "http://www.3sat.de/mediathek/?red=hitec", True), 77 | TreeNode("1.13", "Kabarett / Comedy", "http://www.3sat.de/mediathek/?red=kabarett", True), 78 | TreeNode("1.14", "Kennwort Kino", "http://www.3sat.de/mediathek/?red=kennwortkino", True), 79 | TreeNode("1.15", "Kulturpalast", "http://www.3sat.de/mediathek/?red=kulturpalast", True), 80 | TreeNode("1.16", "Museumscheck", "http://www.3sat.de/mediathek/?red=museumscheck", True), 81 | TreeNode("1.17", "Musik", "http://www.3sat.de/mediathek/?red=musik", True), 82 | TreeNode("1.18", u"Peter Voß fragt", "http://www.3sat.de/mediathek/?red=begegnungen", True), 83 | TreeNode("1.19", "Philosophie", "http://www.3sat.de/mediathek/?red=philosophie", True), 84 | TreeNode("1.20", "Pixelmacher", "http://www.3sat.de/mediathek/?red=pxl", True), 85 | TreeNode("1.21", "SCHWEIZWEIT", "http://www.3sat.de/mediathek/?red=schweizweit", True) 86 | )), 87 | ) 88 | 89 | self.rootLink = "http://www.3sat.de" 90 | self.searchLink = 'http://www.3sat.de/mediathek/?mode=suche&query=%s' 91 | 92 | self.history_counter = re.compile("verpasst(\\d+)") 93 | self.regex_nextLink = re.compile("(\\/\\/www.3sat.de\\/mediathek\\/\\?mode=verpasst(\\d+)&red=.*?)\"") 94 | self.regex_objectLink = re.compile("obj=(\\d+)") 95 | self.regex_dimensions = re.compile("(\\d)+x(\\d+)") 96 | self.xmlService_Link = "http://www.3sat.de/mediathek/xmlservice.php/v3/web/beitragsDetails?id=%s" 97 | self.jsonService_Link = "http://tmd.3sat.de/tmd/2/ngplayer_2_3/vod/ptmd/3sat/%s" 98 | 99 | def buildPageMenu(self, link, initCount): 100 | self.gui.log("buildPageMenu: " + link) 101 | match = self.history_counter.search(link) 102 | if match is not None: 103 | history_counter = int(match.group(1)) 104 | else: 105 | history_counter = 0 106 | mainPage = self.loadPage(link) 107 | matches = self.regex_objectLink.finditer(mainPage); 108 | matches = list(matches) 109 | counter = len(matches) + initCount 110 | 111 | objectIds = [] 112 | for match in matches: 113 | objectId = match.group(1) 114 | if objectId not in objectIds: 115 | objectIds.append(objectId) 116 | 117 | for objectId in objectIds: 118 | self.buildVideoLink(objectId, counter) 119 | 120 | for match in self.regex_nextLink.finditer(mainPage): 121 | if int(match.group(2)) == history_counter + 1: 122 | link = "http:" + match.group(1) 123 | self.gui.buildVideoLink(DisplayObject("Weiter", match.group(2), "", "", link, False, None, None), self, counter) 124 | break 125 | 126 | def buildVideoLink(self, objectId, counter): 127 | xmlPage = self.loadPage(self.xmlService_Link % objectId) 128 | xmlPage = minidom.parseString(xmlPage) 129 | videoNode = xmlPage.getElementsByTagName("video")[0] 130 | informationNode = xmlPage.getElementsByTagName("information")[0] 131 | detailsNode = xmlPage.getElementsByTagName("details")[0] 132 | title = str(self.readText(informationNode, "title")) 133 | description = str(self.readText(informationNode, "detail")) 134 | basename = self.readText(detailsNode, "basename") 135 | length = self.readText(detailsNode, "lengthSec") 136 | date = self.parseDate(self.readText(detailsNode, "airtime")) 137 | maxSize = 0 138 | for imageNode in xmlPage.getElementsByTagName("teaserimages")[0].getElementsByTagName("teaserimage"): 139 | match = self.regex_dimensions.search(imageNode.getAttribute("key")) 140 | size = int(match.group(1)) * int(match.group(2)) 141 | if size > maxSize: 142 | maxSize = size 143 | imageLink = "http:" + imageNode.firstChild.data 144 | 145 | basenameLink = self.jsonService_Link % basename 146 | displayObject = DisplayObject(title, "", imageLink, description, basenameLink, "JsonLink", date, length) 147 | self.gui.buildVideoLink(displayObject, self, counter) 148 | 149 | def playVideoFromJsonLink(self, link): 150 | page = self.loadPage(link) 151 | jsonObject = json.loads(page) 152 | links = self.extractLinks(jsonObject["priorityList"]) 153 | self.gui.play(links) 154 | 155 | def extractLinks(self, jsonList): 156 | links = {} 157 | for jsonListObject in jsonList: 158 | for formitaete in jsonListObject["formitaeten"]: 159 | for jsonObject in formitaete["qualities"]: 160 | quality = jsonObject["quality"] 161 | hd = jsonObject["hd"] 162 | url = jsonObject["audio"]["tracks"][0]["uri"] 163 | if "manifest.f4m" in url: 164 | continue 165 | self.gui.log("quality:%s hd:%s url:%s" % (quality, hd, url)) 166 | if hd is True: 167 | links[4] = SimpleLink(url, -1) 168 | else: 169 | if quality == "low": 170 | links[0] = SimpleLink(url, -1) 171 | if quality == "med": 172 | links[1] = SimpleLink(url, -1) 173 | if quality == "high": 174 | links[2] = SimpleLink(url, -1) 175 | if quality == "veryhigh": 176 | links[3] = SimpleLink(url, -1) 177 | if quality == "auto": 178 | links[3] = SimpleLink(url, -1) 179 | return links 180 | 181 | def searchVideo(self, searchText): 182 | self.buildPageMenu(self.searchLink % searchText, 0) 183 | 184 | def readText(self, node, textNode): 185 | try: 186 | node = node.getElementsByTagName(textNode)[0].firstChild 187 | return str(node.data) 188 | except: 189 | return "" 190 | 191 | def loadConfigXml(self, link): 192 | self.gui.log("load:" + link) 193 | xmlPage = self.loadPage(link) 194 | return minidom.parseString(xmlPage) 195 | 196 | def extractVideoObjects(self, rssFeed, initCount): 197 | nodes = rssFeed.getElementsByTagName("item") 198 | nodeCount = initCount + len(nodes) 199 | for itemNode in nodes: 200 | try: 201 | self.extractVideoInformation(itemNode, nodeCount) 202 | except: 203 | pass 204 | 205 | def parseDate(self, dateString): 206 | dateString = regex_dateString.search(dateString).group() 207 | for month in list(month_replacements.keys()): 208 | dateString = dateString.replace(month, month_replacements[month]) 209 | return time.strptime(dateString, "%d.%m.%Y") 210 | 211 | def extractVideoInformation(self, itemNode, nodeCount): 212 | title = self.readText(itemNode, "title") 213 | self.gui.log(title) 214 | dateString = self.readText(itemNode, "pubDate") 215 | pubDate = self.parseDate(dateString) 216 | descriptionNode = itemNode.getElementsByTagName("description")[0].firstChild.data 217 | description = str(descriptionNode) 218 | picture = "" 219 | pictureNodes = itemNode.getElementsByTagName("media:thumbnail") 220 | if len(pictureNodes) > 0: 221 | picture = pictureNodes[0].getAttribute("url") 222 | links = {} 223 | for contentNode in itemNode.getElementsByTagName("media:content"): 224 | height = int(contentNode.getAttribute("height")) 225 | url = contentNode.getAttribute("url") 226 | size = int(contentNode.getAttribute("fileSize")) 227 | if height < 300: 228 | links[0] = SimpleLink(url, size) 229 | elif height < 480: 230 | links[1] = SimpleLink(url, size) 231 | else: 232 | links[2] = SimpleLink(url, size) 233 | if links: 234 | self.gui.buildVideoLink(DisplayObject(title, "", picture, description, links, True, pubDate), self, nodeCount) 235 | -------------------------------------------------------------------------------- /mediathek/ard.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -------------LicenseHeader-------------- 3 | # plugin.video.Mediathek - Gives access to most video-platforms from German public service broadcasters 4 | # Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | import base64 19 | import json 20 | import re 21 | import time 22 | import urllib 23 | 24 | from mediathek import * 25 | 26 | 27 | class ARDMediathek(Mediathek): 28 | def __init__(self, simpleXbmcGui): 29 | self.gui = simpleXbmcGui 30 | self.rootLink = "https://www.ardmediathek.de" 31 | self.menuTree = ( 32 | TreeNode("0", "Alle", self.rootLink + "/ard/", True), 33 | TreeNode("1", "Das Erste", self.rootLink + "/daserste/", True), 34 | TreeNode("2", "BR", self.rootLink + "/br/", True), 35 | TreeNode("3", "HR", self.rootLink + "/hr/", True), 36 | TreeNode("4", "MDR", self.rootLink + "/mdr/", True), 37 | TreeNode("5", "NDR", self.rootLink + "/ndr/", True), 38 | TreeNode("6", "Radio Bremen", self.rootLink + "/radiobremen/", True), 39 | TreeNode("7", "RBB", self.rootLink + "/rbb/", True), 40 | TreeNode("8", "SR", self.rootLink + "/sr/", True), 41 | TreeNode("9", "SWR", self.rootLink + "/swr/", True), 42 | TreeNode("10", "WDR", self.rootLink + "/wdr/", True), 43 | TreeNode("11", "ONE", self.rootLink + "/one/", True), 44 | TreeNode("12", "ARD-alpha", self.rootLink + "/alpha/", True) 45 | ) 46 | self.configLink = self.rootLink + "/play/media/%s?devicetype=pc&feature=flash" 47 | self.regex_VideoPageLink = re.compile("\s+?

(.*?)<\/p>\s+?

(.*?)<\/h4>\s+?

(?:(\d+.\d+.\d+) \| )?(\d*) Min.") 48 | self.regex_CategoryPageLink = re.compile("(?:.|\n)+?

(.*?)<\/h4>") 49 | self.pageSelectString = "&mcontent%s=page.%s" 50 | self.regex_DetermineSelectedPage = re.compile("&mcontents{0,1}=page.(\d+)") 51 | 52 | self.regex_videoLinks = re.compile("\"_quality\":(\d).*?\"_stream\":\[?\"(.*?)\"") 53 | self.regex_pictureLink = re.compile("_previewImage\":\"(.*?)\"") 54 | 55 | self.regex_Date = re.compile("\\d{2}\\.\\d{2}\\.\\d{2}") 56 | self.__date_format_broadcasted_on = "%Y-%m-%dT%H:%M:%S.%fZ" 57 | self.__date_format_broadcasted_on_old = "%Y-%m-%dT%H:%M:%SZ" 58 | 59 | self.replace_html = re.compile("<.*?>") 60 | self.regex_DetermineClient = re.compile(self.rootLink + "/(.*)/") 61 | self.categoryListingKey = "$ROOT_QUERY.widget({\"client\":\"%s\",\"pageNumber\":%s,\"pageSize\":%s,\"widgetId\":\"%s\"})" 62 | self.playerLink = self.rootLink + "/ard/player/%s" 63 | self.regex_ExtractJson = re.compile("__APOLLO_STATE__ = ({.*});") 64 | self.tumbnail_size = "600" 65 | 66 | self.variables = "{\"widgetId\":\"%s\",\"client\":\"%s\",\"pageNumber\":%d,\"pageSize\":%d}" 67 | self.extension = "{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"915283a7f9b1fb8a5b2628aaa45aef8831f789a8ffdb31aa81fcae53945ee712\"}}" 68 | self.publicGateway = "https://api.ardmediathek.de/public-gateway?variables=%s&extensions=%s" 69 | 70 | def __getBroadcastedOnDate(self, teaserContent): 71 | """parse time in broadcastedOn property""" 72 | if teaserContent["broadcastedOn"] is not None: 73 | broadcasted_on = teaserContent["broadcastedOn"] 74 | try: 75 | date = time.strptime(broadcasted_on, self.__date_format_broadcasted_on) 76 | except ValueError: 77 | try: 78 | # retry with old format 79 | date = time.strptime(broadcasted_on, self.__date_format_broadcasted_on_old) 80 | except ValueError: 81 | date = None 82 | else: 83 | date = None 84 | return date 85 | 86 | @classmethod 87 | def name(self): 88 | return "ARD" 89 | 90 | def isSearchable(self): 91 | return False 92 | 93 | def extractJsonFromPage(self, link): 94 | pageContent = self.loadPage(link) 95 | content = self.regex_ExtractJson.search(pageContent).group(1) 96 | return json.loads(content) 97 | 98 | def buildPageMenu(self, link, initCount): 99 | self.gui.log("Build Page Menu: %s" % link) 100 | jsonContent = self.extractJsonFromPage(link) 101 | callHash = self.gui.storeJsonFile(jsonContent) 102 | client = self.regex_DetermineClient.search(link).group(1) 103 | 104 | for key in jsonContent: 105 | if key.startswith("Widget:"): 106 | self.GenerateCaterogyLink(jsonContent[key], callHash, jsonContent, client) 107 | 108 | def GenerateCaterogyLink(self, widgetContent, callHash, jsonContent, client): 109 | widgetId = widgetContent["id"] 110 | listingKey = self.buildcategoryListingKey(client, widgetId, jsonContent) 111 | title = widgetContent["title"] 112 | if widgetContent["titleVisible"] is True: 113 | self.gui.buildJsonLink(self, title, "%s.%s" % (client, widgetId), callHash, 0) 114 | else: 115 | if listingKey in jsonContent: 116 | widgetContent = jsonContent[listingKey] 117 | self.GenerateCaterogyLinks(widgetContent, jsonContent) 118 | 119 | def buildcategoryListingKey(self, client, widgetId, jsonContent): 120 | # ich werd zum elch... erst noch die "dynamische" Pagesize/Number nachschlagen ... 121 | widgetContent = jsonContent["Widget:%s" % widgetId] 122 | paginationId = widgetContent["pagination"]["id"] 123 | paginationContent = jsonContent[paginationId] 124 | pageSize = paginationContent["pageSize"] 125 | pageNumber = paginationContent["pageNumber"] 126 | 127 | return self.categoryListingKey % (client, pageNumber, pageSize, widgetId) 128 | 129 | def buildJsonLink(self, client, widgetId, jsonContent): 130 | # es wird immer besser ... 131 | widgetContent = jsonContent["Widget:%s" % widgetId] 132 | paginationId = widgetContent["pagination"]["id"] 133 | paginationContent = jsonContent[paginationId] 134 | pageSize = paginationContent["pageSize"] 135 | pageNumber = paginationContent["pageNumber"] 136 | 137 | variables = urllib.parse.quote_plus(self.variables % (widgetId, client, pageNumber, pageSize)) 138 | extension = urllib.parse.quote_plus(self.extension) 139 | return self.publicGateway % (variables, extension) 140 | 141 | def buildJsonMenu(self, path, callhash, initCount): 142 | jsonContent = self.gui.loadJsonFile(callhash) 143 | path = path.split(".") 144 | client = path[0] 145 | widgetId = path[1] 146 | 147 | listingKey = self.buildcategoryListingKey(client, widgetId, jsonContent) 148 | if listingKey in jsonContent: 149 | widgetContent = jsonContent[listingKey] 150 | self.GenerateCaterogyLinks(widgetContent, jsonContent) 151 | else: 152 | link = self.buildJsonLink(client, widgetId, jsonContent) 153 | 154 | pageContent = self.loadPage(link) 155 | jsonContent = json.loads(pageContent) 156 | 157 | dataObject = jsonContent["data"] 158 | widgetObject = dataObject["widget"] 159 | 160 | for jsonObject in widgetObject["teasers"]: 161 | self.GenerateTeaserLink(jsonObject) 162 | 163 | def GenerateCaterogyLinks(self, widgetContent, jsonContent): 164 | for teaser in widgetContent["teasers"]: 165 | teaserId = teaser["id"] 166 | self.GenerateVideoLink(jsonContent[teaserId], jsonContent) 167 | 168 | def GenerateTeaserLink(self, teaserContent): 169 | title = teaserContent["shortTitle"] 170 | subTitle = None 171 | picture = None 172 | images = teaserContent["images"] 173 | for key in images: 174 | imageObject = images[key] 175 | if type(imageObject) is dict: 176 | picture = imageObject["src"].replace("{width}", self.tumbnail_size) 177 | 178 | duration = teaserContent["duration"] 179 | date = self.__getBroadcastedOnDate(teaserContent) 180 | videoLink = self.playerLink % teaserContent["links"]["target"]["id"]; 181 | videoLink = base64.b64encode(videoLink.encode()) 182 | displayObject = DisplayObject(title, subTitle, picture, "", videoLink, "JsonLink", date, duration) 183 | self.gui.buildVideoLink(displayObject, self, 0) 184 | 185 | def GenerateVideoLink(self, teaserContent, jsonContent): 186 | title = teaserContent["shortTitle"] 187 | subTitle = None 188 | picture = self.getPictureLink(teaserContent["images"], jsonContent) 189 | videoLinks = self.getVideoLinks(teaserContent["links"], jsonContent); 190 | videoLinks = base64.b64encode(videoLinks.encode()) 191 | date = self.__getBroadcastedOnDate(teaserContent) 192 | duration = teaserContent["duration"] 193 | if 'type' in teaserContent and teaserContent[u'type'] == u"live": 194 | title = u"live " + title 195 | date = None 196 | displayObject = DisplayObject(title, subTitle, picture, "", videoLinks, "JsonLink", date, duration) 197 | self.gui.buildVideoLink(displayObject, self, 0) 198 | 199 | def getVideoLinks(self, linkSource, jsonContent): 200 | # WTF geht es noch sinnloser? 201 | key = linkSource["id"] 202 | key = jsonContent[key]["target"]["id"] 203 | return self.playerLink % jsonContent[key]["id"] 204 | 205 | def getPictureLink(self, pictureSource, jsonContent): 206 | if pictureSource is not None: 207 | key = pictureSource["id"] 208 | pictureConfig = jsonContent[key] 209 | for key in pictureConfig: 210 | if key.startswith("aspect") and pictureConfig[key] is not None: 211 | key = pictureConfig[key]["id"] 212 | return jsonContent[key]["src"].replace("{width}", self.tumbnail_size) 213 | return None 214 | 215 | def playVideoFromJsonLink(self, link): 216 | link = base64.b64decode(link).decode() 217 | # WTF OHHHHHHHHH JAAAAAA - es geht noch sinnloser... 218 | self.gui.log("Play from JSON Link %s" % link) 219 | jsonContent = self.extractJsonFromPage(link) 220 | 221 | videoLinks = {} 222 | for key in jsonContent: 223 | if "_mediaStreamArray." in key: 224 | streamConfig = jsonContent[key] 225 | if streamConfig["_quality"] == "auto": 226 | quality = 3 227 | else: 228 | quality = int(streamConfig["_quality"]) 229 | link = streamConfig["_stream"]["json"][0] 230 | if not link.startswith("http"): 231 | link = "https:" + link 232 | self.gui.log("VideoLink: " + link) 233 | videoLinks[quality] = SimpleLink(link, -1) 234 | if len(videoLinks) > 0: 235 | self.gui.play(videoLinks) 236 | else: 237 | self.gui.log("Nothing playable found") 238 | -------------------------------------------------------------------------------- /mediathek/arte.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -------------LicenseHeader-------------- 3 | # plugin.video.Mediathek - Gives access to most video-platforms from German public service broadcasters 4 | # Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | import json 19 | import re 20 | import time 21 | 22 | from bs4 import BeautifulSoup 23 | 24 | from mediathek import * 25 | 26 | regex_dateString = re.compile("\\d{1,2} ((\\w{3})|(\\d{2})) \\d{4}") 27 | month_replacements = { 28 | "Jan": "01", 29 | "Feb": "02", 30 | "Mar": "03", 31 | "Apr": "04", 32 | "May": "05", 33 | "Jun": "06", 34 | "Jul": "07", 35 | "Aug": "08", 36 | "Sep": "09", 37 | "Oct": "10", 38 | "Nov": "11", 39 | "Dec": "12", 40 | } 41 | 42 | 43 | class ARTEMediathek(Mediathek): 44 | 45 | @classmethod 46 | def name(self): 47 | return "ARTE" 48 | 49 | @classmethod 50 | def isSearchable(self): 51 | return True 52 | 53 | def __init__(self, simpleXbmcGui): 54 | self.gui = simpleXbmcGui 55 | self.rootLink = "http://www.arte.tv" 56 | self.basePage = self.rootLink + "/de/" 57 | self.jsonLink = "https://api.arte.tv/api/player/v1/config/de/%s?lifeCycle=1" 58 | self.serachLink = self.rootLink + "/de/search/?q=%s" 59 | self.menuTree = ( 60 | TreeNode("0", "Arte+7", "mainPage", True), 61 | TreeNode("1", "Sendungen von A-Z", "showCluster", True), 62 | TreeNode("2", "Kategorien", "showCategories", True), 63 | ) 64 | 65 | self.selector_videoPages = "li.video > a" 66 | 67 | self.regex_findVideoIds = re.compile("(\d{6}-\d{3})(-A)") 68 | self.regex_JSONPageLink = re.compile("http://arte.tv/papi/tvguide/videos/stream/player/D/\d{6}-\d{3}.+?/ALL/ALL.json") 69 | self.regex_JSON_VideoLink = re.compile("\"HTTP_MP4_.+?\":{.*?\"bitrate\":(\d+),.*?\"url\":\"(http://.*?.mp4)\".*?\"versionShortLibelle\":\"([a-zA-Z]{2})\".*?}") 70 | self.regex_JSON_ImageLink = re.compile("\"IUR\":\"(http://.*?\\.arte\\.tv/papi/tvguide/images/.*?\\..{3})\"") 71 | self.regex_JSON_Detail = re.compile("\"VDE\":\"(.*?)\"") 72 | self.regex_JSON_Titel = re.compile("\"VTI\":\"(.*?)\"") 73 | 74 | regexSourceString = "%s=\"([\[{].*?[}\]])\"" 75 | 76 | self.regex_cluster = re.compile("\"kind\":\"MAGAZINE\",\"programId\":\"RC-\d+\",\"language\":\"\w{2}\",\"url\":\"(http.*?)\",\"title\":\"(.*?)\",\"subtitle\":(\"(.*?)\"|null),\"images\"") 77 | self.regex_categories = re.compile("\"link\":{\"page\":\"\w{3}\",\"title\":\"(.*?)\",\"url\":\"(http.*?)\"}") 78 | self.regex_playlists = re.compile(regexSourceString % "data-highlightedPlaylists") 79 | 80 | self.searchContent = re.compile(regexSourceString % "data-results") 81 | 82 | self.regex_ExtractJson = re.compile("__INITIAL_STATE__ = ({.*});") 83 | 84 | def searchVideo(self, searchText): 85 | link = self.serachLink % searchText 86 | pageContent = self.loadPage(link) 87 | self.extractVideoLinks(pageContent, 0) 88 | 89 | def buildPageMenu(self, link, initCount): 90 | if link == "showCluster": 91 | self.showCluster() 92 | elif link == "mainPage": 93 | self.showMainPage() 94 | elif link == "showCategories": 95 | self.showCategories() 96 | else: 97 | if not link.startswith("http"): 98 | link = self.rootLink + link 99 | self.parsePage(link) 100 | 101 | def extractJsonFromPage(self, link): 102 | pageContent = self.loadPage(link) 103 | content = self.regex_ExtractJson.search(pageContent).group(1) 104 | pageContent = BeautifulSoup(content, "html.parser") 105 | jsonContent = pageContent.prettify(formatter=None) 106 | return json.loads(jsonContent) 107 | 108 | def parsePage(self, link): 109 | jsonContent = self.extractJsonFromPage(link) 110 | page = jsonContent["pages"] 111 | currentCode = page["currentCode"] 112 | for zone in page["list"][currentCode]["zones"]: 113 | if "videos" in zone["code"]["name"]: 114 | for teaser in zone["data"]: 115 | self.buildVideoEntry(teaser) 116 | 117 | def showMainPage(self): 118 | self.gui.log("buildPageMenu: " + self.basePage) 119 | jsonContent = self.extractJsonFromPage(self.basePage) 120 | zones = jsonContent["pages"]["list"]["HOME_de_{}"]["zones"] 121 | for zone in zones: 122 | if "highlights" in zone["code"]["name"] or "playlists" in zone["code"]["name"]: 123 | for teaser in zone["data"]: 124 | self.buildVideoEntry(teaser) 125 | 126 | def buildJsonMenu(self, path, callhash, initCount): 127 | jsonContent = self.gui.loadJsonFile(callhash) 128 | for teaser in jsonContent["data"]: 129 | self.buildVideoEntry(teaser) 130 | 131 | def buildJsonLink(self, name, jsonContent): 132 | callhash = self.gui.storeJsonFile(jsonContent, name) 133 | self.gui.buildJsonLink(self, name, "init", callhash, 0) 134 | 135 | def extractVideoLinksFromHtml(self, htmlPage): 136 | someMatch = False 137 | for regex in self.regex_extractVideoSources: 138 | match = regex.search(htmlPage) 139 | if match is not None: 140 | someMatch = True 141 | content = BeautifulSoup(match.group(1), "html.parser") 142 | jsonContent = json.loads(content.prettify(formatter=None)) 143 | self.extractVideoLinksFromJson(jsonContent) 144 | return someMatch 145 | 146 | def extractVideoLinksFromJson(self, jsonContent): 147 | for jsonObject in jsonContent["videos"]: 148 | self.buildVideoEntry(jsonObject) 149 | 150 | def showCategories(self): 151 | jsonContent = self.extractJsonFromPage(self.basePage) 152 | for zone in jsonContent["pages"]["list"]["HOME_de_{}"]["zones"]: 153 | if zone["link"] and "title" in zone: 154 | title = zone["title"] 155 | if isinstance(title, str): 156 | title = title.encode('utf8') 157 | self.buildJsonLink(title, zone) 158 | 159 | def showCluster(self): 160 | jsonContent = self.extractJsonFromPage(self.basePage) 161 | for zone in jsonContent["pages"]["list"]["HOME_de_{}"]["zones"]: 162 | if ("magazine" in zone["code"]["name"]): 163 | for teaser in zone["data"]: 164 | self.buildVideoEntry(teaser) 165 | 166 | def buildMenuEntry(self, menuItem): 167 | title = menuItem["title"] 168 | subTitle = menuItem["subtitle"] 169 | link = menuItem["permalink"] 170 | self.gui.buildVideoLink(DisplayObject(title, subTitle, "", "", link, False, None), self, 0) 171 | 172 | def buildVideoEntry(self, jsonObject): 173 | title = str(jsonObject["title"]) 174 | if jsonObject["subtitle"] is not None: 175 | subTitle = str(jsonObject["subtitle"]) 176 | else: 177 | subTitle = None 178 | if "teaser" in jsonObject: 179 | detail = str(jsonObject["teaser"]) 180 | else: 181 | detail = "" 182 | 183 | pictures = None 184 | pictureUrl = None 185 | if "thumbnails" in jsonObject: 186 | pictures = jsonObject["thumbnails"] 187 | if "images" in jsonObject: 188 | square = jsonObject["images"]["square"] 189 | landscape = jsonObject["images"]["landscape"] 190 | if square is not None: 191 | pictures = square["resolutions"] 192 | else: 193 | pictures = landscape["resolutions"] 194 | if pictures is not None: 195 | picture = None 196 | for pictureItem in pictures: 197 | if picture is None or picture["w"] < pictureItem["w"]: 198 | picture = pictureItem 199 | pictureUrl = picture["url"] 200 | if pictureUrl is None and "mainImage" in jsonObject: 201 | pictureUrl = jsonObject["mainImage"]["url"] 202 | 203 | pubDate = None 204 | if "scheduled_on" in jsonObject: 205 | pubDate = time.strptime(jsonObject["scheduled_on"], "%Y-%m-%d") 206 | if "publicationBegin" in jsonObject: 207 | pubDate = time.strptime(jsonObject["publicationBegin"], "%Y-%m-%dT%H:%M:%SZ") 208 | if "availability" in jsonObject and jsonObject["availability"] is not None and jsonObject["availability"]["start"] is not None: 209 | pubDate = time.strptime(jsonObject["availability"]["start"], "%Y-%m-%dT%H:%M:%SZ") 210 | 211 | duration = None 212 | if "duration" in jsonObject and jsonObject["duration"] is not None: 213 | duration = int(jsonObject["duration"]) 214 | if "durationSeconds" in jsonObject and jsonObject["durationSeconds"] is not None: 215 | duration = int(jsonObject["durationSeconds"]) 216 | if duration is None: 217 | duration = 0 218 | 219 | kind = jsonObject["kind"] 220 | if kind is not None and kind["code"] in ("SHOW", "MANUAL_CLIP"): 221 | link = self.jsonLink % jsonObject["programId"] 222 | 223 | self.gui.buildVideoLink( 224 | DisplayObject(title, subTitle, pictureUrl, detail, link, "JsonLink", pubDate, duration), self, 0) 225 | else: 226 | link = jsonObject["url"] 227 | self.gui.buildVideoLink(DisplayObject(title, subTitle, pictureUrl, detail, link, False, pubDate, duration), self, 0) 228 | 229 | def playVideoFromJsonLink(self, link): 230 | jsonObject = json.loads(self.loadPage(link)) 231 | links = self.extractLinks(jsonObject["videoJsonPlayer"]) 232 | self.gui.play(links) 233 | 234 | def extractLinks(self, jsonObject): 235 | links = {} 236 | 237 | vsrObjects = jsonObject["VSR"] 238 | if not isinstance(vsrObjects, dict): 239 | return links 240 | 241 | for videoObject in list(jsonObject["VSR"].values()): 242 | if videoObject["versionShortLibelle"] != "DE" and videoObject["versionShortLibelle"] != "OmU": 243 | continue 244 | if videoObject["mediaType"] == "mp4": 245 | url = videoObject["url"] 246 | quality = videoObject["quality"] 247 | self.gui.log("%s %s" % (quality, url)) 248 | if quality == "MQ": 249 | links[0] = SimpleLink(url, -1) 250 | if quality == "HQ": 251 | links[1] = SimpleLink(url, -1) 252 | if quality == "EQ": 253 | links[2] = SimpleLink(url, -1) 254 | if quality == "SQ": 255 | links[3] = SimpleLink(url, -1) 256 | return links 257 | 258 | def extractVideoLinks(self, htmlPage, initCount): 259 | links = set() 260 | jsonLinks = set() 261 | for videoPageLink in self.regex_findVideoIds.finditer(htmlPage): 262 | link = self.jsonLink % videoPageLink.group(1) 263 | if link not in jsonLinks: 264 | jsonLinks.add(link) 265 | 266 | linkCount = initCount + len(links) 267 | for link in links: 268 | if not link.startswith(self.rootLink): 269 | videoPage = self.loadPage(self.rootLink + link) 270 | else: 271 | videoPage = self.loadPage(link) 272 | match = self.regex_JSONPageLink.search(videoPage) 273 | if match is not None: 274 | jsonLinks.add(match.group(0)) 275 | 276 | linkCount = linkCount + len(jsonLinks) 277 | 278 | self.gui.log("Found %s unique links" % len(jsonLinks)) 279 | displayObjects = list() 280 | for link in jsonLinks: 281 | jsonPage = json.loads(self.loadPage(link)) 282 | displayObject = self.extractVideoLinksFromJSONPage(jsonPage["videoJsonPlayer"]) 283 | if displayObject is not None: 284 | self.gui.buildVideoLink(displayObject, self, linkCount) 285 | 286 | def extractVideoLinksFromJSONPage(self, jsonPage): 287 | videoLinks = self.extractLinks(jsonPage) 288 | if len(videoLinks) == 0: 289 | return 290 | 291 | picture = jsonPage["VTU"]["IUR"] 292 | title = jsonPage["VTI"] 293 | 294 | subTitle = "" 295 | if "subtitle" in jsonPage: 296 | subTitle = jsonPage["subtitle"] 297 | 298 | detail = "" 299 | if "V7T" in jsonPage: 300 | detail = self.gui.transformHtmlCodes(jsonPage["V7T"]) 301 | 302 | duration = None 303 | if "videoDurationSeconds" in jsonPage: 304 | duration = jsonPage["videoDurationSeconds"] 305 | 306 | if "VRA" in jsonPage: 307 | pubDate = time.strptime(jsonPage["VRA"], "%d/%m/%Y %H:%M:%S +0000") 308 | else: 309 | pubDate = time.gmtime() 310 | return DisplayObject(title, subTitle, picture, detail, videoLinks, True, pubDate, duration) 311 | -------------------------------------------------------------------------------- /simplexbmc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -------------LicenseHeader-------------- 3 | # plugin.video.mediathek - display german mediathekes 4 | # Copyright (C) 2010 Raptor 2101 [raptor2101@gmx.de] 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | import os 19 | import re 20 | import sys 21 | import time 22 | import urllib 23 | import xbmc 24 | import xbmcaddon 25 | import xbmcgui 26 | import xbmcplugin 27 | 28 | 29 | from bs4 import BeautifulSoup 30 | import json 31 | import hashlib 32 | from mediathek import ComplexLink 33 | from mediathek.factory import MediathekFactory 34 | 35 | regex_findLink = re.compile("mms://[^\"]*wmv") 36 | 37 | __plugin__ = "Mediathek" 38 | 39 | settings = xbmcaddon.Addon(id='plugin.video.mediathek') 40 | translation = settings.getLocalizedString 41 | 42 | 43 | class SimpleXbmcGui(object): 44 | def __init__(self, settings): 45 | self.__addon_handle = int(sys.argv[1]) 46 | self.__addon_name = 'plugin.video.mediathek' 47 | xbmcplugin.setContent(self.__addon_handle, 'tvshows') 48 | self.settings = xbmcaddon.Addon(id='plugin.video.mediathek') 49 | self.quality = int(xbmcplugin.getSetting(int(sys.argv[1]), "quality")) 50 | self.preferedStreamTyp = int(xbmcplugin.getSetting(int(sys.argv[1]), "preferedStreamType")) 51 | self.log("quality: %s" % self.quality) 52 | self.plugin_profile_dir = xbmc.translatePath(settings.getAddonInfo("profile")) 53 | if not os.path.exists(self.plugin_profile_dir): 54 | os.mkdir(self.plugin_profile_dir) 55 | 56 | def log(self, msg): 57 | if not isinstance(msg, (bytes, str)): 58 | xbmc.log("[%s]: %s" % (__plugin__, type(msg))) 59 | else: 60 | xbmc.log("[%s]: %s" % (__plugin__, msg.encode('utf8'))) 61 | 62 | def buildVideoLink(self, displayObject, mediathek, objectCount): 63 | metaData = self.BuildMetaData(displayObject) 64 | if displayObject.picture is not None: 65 | listItem = xbmcgui.ListItem(metaData["title"], iconImage="DefaultFolder.png", thumbnailImage=displayObject.picture) 66 | else: 67 | listItem = xbmcgui.ListItem(metaData["title"], iconImage="DefaultFolder.png") 68 | listItem.setInfo("Video", metaData) 69 | listItem.setInfo("video", metaData) 70 | 71 | if displayObject.isPlayable: 72 | if displayObject.isPlayable == "PlayList": 73 | link = displayObject.link[0] 74 | url = "%s?type=%s&action=openPlayList&link=%s" % (sys.argv[0], mediathek.name(), urllib.parse.quote_plus(link.basePath)) 75 | listItem.setProperty('IsPlayable', 'true') 76 | xbmcplugin.addDirectoryItem(int(sys.argv[1]), url, listItem, False, objectCount) 77 | elif displayObject.isPlayable == "JsonLink": 78 | link = displayObject.link 79 | url = "%s?type=%s&action=openJsonLink&link=%s" % (sys.argv[0], mediathek.name(), urllib.parse.quote_plus(link)) 80 | listItem.setProperty('IsPlayable', 'true') 81 | xbmcplugin.addDirectoryItem(int(sys.argv[1]), url, listItem, False, objectCount) 82 | else: 83 | if(len(displayObject.link) > 0): 84 | link = self.extractLink(displayObject.link) 85 | if isinstance(link, ComplexLink): 86 | self.log("PlayPath:" + link.playPath) 87 | listItem.setProperty("PlayPath", link.playPath) 88 | listItem.setProperty('IsPlayable', 'true') 89 | xbmcplugin.addDirectoryItem(int(sys.argv[1]), link.basePath, listItem, False, objectCount) 90 | else: 91 | listItem.setIsFolder(True) 92 | try: 93 | url = "%s?type=%s&action=openTopicPage&link=%s" % (sys.argv[0], mediathek.name(), urllib.parse.quote_plus(displayObject.link)) 94 | except: 95 | url = "%s?type=%s&action=openTopicPage&link=%s" % (sys.argv[0], mediathek.name(), urllib.parse.quote_plus(displayObject.link.encode('utf-8'))) 96 | xbmcplugin.addDirectoryItem(int(sys.argv[1]), url, listItem, True, objectCount) 97 | 98 | def BuildMetaData(self, displayObject): 99 | if displayObject.subTitle is None or displayObject.subTitle == "" or displayObject.subTitle == displayObject.title: 100 | title = self.transformHtmlCodes(displayObject.title.rstrip()) 101 | else: 102 | title = self.transformHtmlCodes(displayObject.title.rstrip() + " - " + displayObject.subTitle.rstrip()) 103 | if displayObject.date is not None: 104 | title = "(%s) %s" % (time.strftime("%d.%m", displayObject.date), title.rstrip()) 105 | 106 | metaData = { 107 | "mediatype": "video", 108 | "title": title, 109 | "plotoutline": self.transformHtmlCodes(displayObject.description) 110 | } 111 | 112 | if displayObject.duration is not None: 113 | metaData["duration"] = int(displayObject.duration) 114 | 115 | if displayObject.date is not None: 116 | metaData["date"] = time.strftime("%Y-%m-%d", displayObject.date) 117 | metaData["year"] = int(time.strftime("%Y", displayObject.date)) 118 | return metaData 119 | 120 | def transformHtmlCodes(self, content): 121 | return BeautifulSoup(content, "html.parser").prettify(formatter=None) 122 | 123 | def buildMenuLink(self, menuObject, mediathek, objectCount): 124 | title = menuObject.name 125 | listItem = xbmcgui.ListItem(title, iconImage="DefaultFolder.png") 126 | url = "%s?type=%s&action=openMenu&path=%s" % (sys.argv[0], mediathek.name(), menuObject.path) 127 | xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=listItem, isFolder=True, totalItems=objectCount) 128 | 129 | def storeJsonFile(self, jsonObject, additionalIdentifier=None): 130 | hashGenerator = hashlib.md5() 131 | hashGenerator.update(str(sys.argv[2]).encode('utf-8')) 132 | if additionalIdentifier is not None: 133 | hashGenerator.update(additionalIdentifier) 134 | callhash = hashGenerator.hexdigest() 135 | storedJsonFile = os.path.join(self.plugin_profile_dir, "%s.json" % callhash) 136 | with open(storedJsonFile, 'w') as output: 137 | json.dump(jsonObject, output) 138 | return callhash 139 | 140 | def loadJsonFile(self, callhash): 141 | storedJsonFile = os.path.join(self.plugin_profile_dir, "%s.json" % callhash) 142 | with open(storedJsonFile, "rb") as input: 143 | return json.load(input) 144 | 145 | def buildJsonLink(self, mediathek, title, jsonPath, callhash, objectCount): 146 | listItem = xbmcgui.ListItem(title, iconImage="DefaultFolder.png") 147 | url = "%s?type=%s&action=openJsonPath&path=%s&callhash=%s" % (sys.argv[0], mediathek.name(), jsonPath, callhash) 148 | xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=listItem, isFolder=True, totalItems=objectCount) 149 | 150 | def listAvailableMediathekes(self, mediathekNames): 151 | rootPath = os.path.join(self.settings.getAddonInfo('path'), "resources/logos/png/") 152 | for name in mediathekNames: 153 | listItem = xbmcgui.ListItem(name, iconImage="DefaultFolder.png", thumbnailImage=os.path.join(rootPath, name + ".png")) 154 | url = "%s?type=%s" % (sys.argv[0], name) 155 | xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=listItem, isFolder=True) 156 | 157 | def getParams(self): 158 | paramDict = {} 159 | try: 160 | if sys.argv[2]: 161 | paramPairs = sys.argv[2][1:].split("&") 162 | for paramsPair in paramPairs: 163 | paramSplits = paramsPair.split('=') 164 | if (len(paramSplits)) == 2: 165 | paramDict[paramSplits[0]] = paramSplits[1] 166 | except: 167 | errorOK() 168 | return paramDict 169 | 170 | def renderMenu(self): 171 | progress = xbmcgui.DialogProgress() 172 | params = self.getParams(); 173 | mediathekName = params.get("type", "") 174 | action = params.get("action", "") 175 | 176 | 177 | 178 | self.log("Quality: %s" % self.quality) 179 | self.log("argv[0]: %s" % sys.argv[0]) 180 | self.log("argv[1]: %s" % sys.argv[1]) 181 | factory = MediathekFactory() 182 | 183 | if mediathekName == "": 184 | if action == "": 185 | self.addSearchButton(None) 186 | self.listAvailableMediathekes(factory.getAvaibleMediathekTypes()) 187 | else: 188 | result = self.keyboardInput() 189 | if result.isConfirmed(): 190 | searchText = str(result.getText()) 191 | for name in factory.getAvaibleMediathekTypes(): 192 | mediathek = factory.getMediathek(name, self) 193 | if mediathek.isSearchable(): 194 | mediathek.searchVideo(searchText) 195 | else: 196 | self.back() 197 | 198 | else: 199 | cat = int(params.get("cat", 0)) 200 | mediathek = factory.getMediathek(mediathekName, self) 201 | 202 | if action == "openTopicPage": 203 | link = urllib.parse.unquote_plus(params.get("link", "")) 204 | self.log(link) 205 | mediathek.buildPageMenu(link, 0) 206 | elif action == "openPlayList": 207 | link = urllib.parse.unquote_plus(params.get("link", "")) 208 | self.log(link) 209 | remotePlaylist = mediathek.loadPage(link) 210 | self.playPlaylist(remotePlaylist) 211 | elif action == "openMenu": 212 | path = params.get("path", "0") 213 | mediathek.buildMenu(path) 214 | elif action == "search": 215 | result = self.keyboardInput() 216 | if result.isConfirmed(): 217 | searchText = str(result.getText()) 218 | mediathek.searchVideo(searchText) 219 | else: 220 | self.back() 221 | elif action == "openJsonPath": 222 | path = params.get("path", "0") 223 | callhash = params.get("callhash", "0") 224 | mediathek.buildJsonMenu(path, callhash, 0) 225 | elif action == "openJsonLink": 226 | link = urllib.parse.unquote_plus(params.get("link", "")) 227 | mediathek.playVideoFromJsonLink(link) 228 | else: 229 | if mediathek.isSearchable(): 230 | self.addSearchButton(mediathek) 231 | mediathek.displayCategories() 232 | xbmcplugin.endOfDirectory(int(sys.argv[1])) 233 | 234 | def getHomeDir(self): 235 | return self.settings.getAddonInfo("profile") 236 | 237 | def back(self): 238 | xbmc.executebuiltin("Action(PreviousMenu)") 239 | 240 | def keyboardInput(self): 241 | keyboard = xbmc.Keyboard("") 242 | keyboard.doModal() 243 | return keyboard 244 | 245 | def addSearchButton(self, mediathek): 246 | title = translation(30100) 247 | listItem = xbmcgui.ListItem(title, iconImage="DefaultFolder.png") 248 | if mediathek is not None: 249 | url = "%s?type=%s&action=search" % (sys.argv[0], mediathek.name()) 250 | else: 251 | url = "%s?action=search" % (sys.argv[0]) 252 | xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=listItem, isFolder=True) 253 | 254 | def readText(self, node, textNode): 255 | try: 256 | node = node.getElementsByTagName(textNode)[0].firstChild 257 | return str(node.data) 258 | except: 259 | return "" 260 | 261 | def playPlaylist(self, remotePlaylist): 262 | player = xbmc.Player() 263 | 264 | playerItem = xbmcgui.ListItem(remotePlaylist) 265 | playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) 266 | playlist.clear() 267 | for link in regex_findLink.findall(remotePlaylist): 268 | listItem = xbmcgui.ListItem(link) 269 | listItem.setProperty("PlayPath", link) 270 | playlist.add(url=link, listitem=listItem) 271 | 272 | player.play(playlist, playerItem, False) 273 | 274 | def errorOK(self, title="", msg=""): 275 | e = str(sys.exc_info()[1]) 276 | self.log(e) 277 | if not title: 278 | title = __plugin__ 279 | if not msg: 280 | msg = "ERROR!" 281 | if e is None: 282 | xbmcgui.Dialog().ok(title, msg, e) 283 | else: 284 | xbmcgui.Dialog().ok(title, msg) 285 | 286 | def play(self, links): 287 | link = self.extractLink(links) 288 | listItem = xbmcgui.ListItem(path=link.basePath) 289 | xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem=listItem) 290 | 291 | def extractLink(self, links): 292 | if self.quality in links: 293 | return links[self.quality] 294 | else: 295 | selectedKey = -1 296 | keys = list(links.keys()) 297 | for key in keys: 298 | if self.quality > key > selectedKey: 299 | selectedKey = key 300 | if selectedKey > -1: 301 | return links[selectedKey] 302 | else: 303 | selectedKey = keys[0] 304 | for key in keys: 305 | if key < selectedKey: 306 | selectedKey = key 307 | return links[selectedKey] 308 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | 3 | Version 3, 29 June 2007 4 | 5 | Copyright © 2007 Free Software Foundation, Inc. 6 | 7 | Everyone is permitted to copy and distribute verbatim copies of this license 8 | document, but changing it is not allowed. 9 | Preamble 10 | 11 | The GNU General Public License is a free, copyleft license for software and 12 | other kinds of works. 13 | 14 | The licenses for most software and other practical works are designed to take 15 | away your freedom to share and change the works. By contrast, the GNU General 16 | Public License is intended to guarantee your freedom to share and change all 17 | versions of a program--to make sure it remains free software for all its users. 18 | We, the Free Software Foundation, use the GNU General Public License for most of 19 | our software; it applies also to any other work released this way by its 20 | authors. You can apply it to your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not price. Our 23 | General Public Licenses are designed to make sure that you have the freedom to 24 | distribute copies of free software (and charge for them if you wish), that you 25 | receive source code or can get it if you want it, that you can change the 26 | software or use pieces of it in new free programs, and that you know you can do 27 | these things. 28 | 29 | To protect your rights, we need to prevent others from denying you these rights 30 | or asking you to surrender the rights. Therefore, you have certain 31 | responsibilities if you distribute copies of the software, or if you modify it: 32 | responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether gratis or for a 35 | fee, you must pass on to the recipients the same freedoms that you received. You 36 | must make sure that they, too, receive or can get the source code. And you must 37 | show them these terms so they know their rights. 38 | 39 | Developers that use the GNU GPL protect your rights with two steps: (1) assert 40 | copyright on the software, and (2) offer you this License giving you legal 41 | permission to copy, distribute and/or modify it. 42 | 43 | For the developers' and authors' protection, the GPL clearly explains that there 44 | is no warranty for this free software. For both users' and authors' sake, the 45 | GPL requires that modified versions be marked as changed, so that their problems 46 | will not be attributed erroneously to authors of previous versions. 47 | 48 | Some devices are designed to deny users access to install or run modified 49 | versions of the software inside them, although the manufacturer can do so. This 50 | is fundamentally incompatible with the aim of protecting users' freedom to 51 | change the software. The systematic pattern of such abuse occurs in the area of 52 | products for individuals to use, which is precisely where it is most 53 | unacceptable. Therefore, we have designed this version of the GPL to prohibit 54 | the practice for those products. If such problems arise substantially in other 55 | domains, we stand ready to extend this provision to those domains in future 56 | versions of the GPL, as needed to protect the freedom of users. 57 | 58 | Finally, every program is threatened constantly by software patents. States 59 | should not allow patents to restrict development and use of software on 60 | general-purpose computers, but in those that do, we wish to avoid the special 61 | danger that patents applied to a free program could make it effectively 62 | proprietary. To prevent this, the GPL assures that patents cannot be used to 63 | render the program non-free. 64 | 65 | The precise terms and conditions for copying, distribution and modification 66 | follow. 67 | TERMS AND CONDITIONS 68 | 0. Definitions. 69 | 70 | “This License” refers to version 3 of the GNU General Public License. 71 | 72 | “Copyright” also means copyright-like laws that apply to other kinds of works, 73 | such as semiconductor masks. 74 | 75 | “The Program” refers to any copyrightable work licensed under this License. Each 76 | licensee is addressed as “you”. “Licensees” and “recipients” may be individuals 77 | or organizations. 78 | 79 | To “modify” a work means to copy from or adapt all or part of the work in a 80 | fashion requiring copyright permission, other than the making of an exact copy. 81 | The resulting work is called a “modified version” of the earlier work or a work 82 | “based on” the earlier work. 83 | 84 | A “covered work” means either the unmodified Program or a work based on the 85 | Program. 86 | 87 | To “propagate” a work means to do anything with it that, without permission, 88 | would make you directly or secondarily liable for infringement under applicable 89 | copyright law, except executing it on a computer or modifying a private copy. 90 | Propagation includes copying, distribution (with or without modification), 91 | making available to the public, and in some countries other activities as well. 92 | 93 | To “convey” a work means any kind of propagation that enables other parties to 94 | make or receive copies. Mere interaction with a user through a computer network, 95 | with no transfer of a copy, is not conveying. 96 | 97 | An interactive user interface displays “Appropriate Legal Notices” to the extent 98 | that it includes a convenient and prominently visible feature that (1) displays 99 | an appropriate copyright notice, and (2) tells the user that there is no 100 | warranty for the work (except to the extent that warranties are provided), that 101 | licensees may convey the work under this License, and how to view a copy of this 102 | License. If the interface presents a list of user commands or options, such as a 103 | menu, a prominent item in the list meets this criterion. 104 | 1. Source Code. 105 | 106 | The “source code” for a work means the preferred form of the work for making 107 | modifications to it. “Object code” means any non-source form of a work. 108 | 109 | A “Standard Interface” means an interface that either is an official standard 110 | defined by a recognized standards body, or, in the case of interfaces specified 111 | for a particular programming language, one that is widely used among developers 112 | working in that language. 113 | 114 | The “System Libraries” of an executable work include anything, other than the 115 | work as a whole, that (a) is included in the normal form of packaging a Major 116 | Component, but which is not part of that Major Component, and (b) serves only to 117 | enable use of the work with that Major Component, or to implement a Standard 118 | Interface for which an implementation is available to the public in source code 119 | form. A “Major Component”, in this context, means a major essential component 120 | (kernel, window system, and so on) of the specific operating system (if any) on 121 | which the executable work runs, or a compiler used to produce the work, or an 122 | object code interpreter used to run it. 123 | 124 | The “Corresponding Source” for a work in object code form means all the source 125 | code needed to generate, install, and (for an executable work) run the object 126 | code and to modify the work, including scripts to control those activities. 127 | However, it does not include the work's System Libraries, or general-purpose 128 | tools or generally available free programs which are used unmodified in 129 | performing those activities but which are not part of the work. For example, 130 | Corresponding Source includes interface definition files associated with source 131 | files for the work, and the source code for shared libraries and dynamically 132 | linked subprograms that the work is specifically designed to require, such as by 133 | intimate data communication or control flow between those subprograms and other 134 | parts of the work. 135 | 136 | The Corresponding Source need not include anything that users can regenerate 137 | automatically from other parts of the Corresponding Source. 138 | 139 | The Corresponding Source for a work in source code form is that same work. 140 | 2. Basic Permissions. 141 | 142 | All rights granted under this License are granted for the term of copyright on 143 | the Program, and are irrevocable provided the stated conditions are met. This 144 | License explicitly affirms your unlimited permission to run the unmodified 145 | Program. The output from running a covered work is covered by this License only 146 | if the output, given its content, constitutes a covered work. This License 147 | acknowledges your rights of fair use or other equivalent, as provided by 148 | copyright law. 149 | 150 | You may make, run and propagate covered works that you do not convey, without 151 | conditions so long as your license otherwise remains in force. You may convey 152 | covered works to others for the sole purpose of having them make modifications 153 | exclusively for you, or provide you with facilities for running those works, 154 | provided that you comply with the terms of this License in conveying all 155 | material for which you do not control copyright. Those thus making or running 156 | the covered works for you must do so exclusively on your behalf, under your 157 | direction and control, on terms that prohibit them from making any copies of 158 | your copyrighted material outside their relationship with you. 159 | 160 | Conveying under any other circumstances is permitted solely under the conditions 161 | stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 162 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 163 | 164 | No covered work shall be deemed part of an effective technological measure under 165 | any applicable law fulfilling obligations under article 11 of the WIPO copyright 166 | treaty adopted on 20 December 1996, or similar laws prohibiting or restricting 167 | circumvention of such measures. 168 | 169 | When you convey a covered work, you waive any legal power to forbid 170 | circumvention of technological measures to the extent such circumvention is 171 | effected by exercising rights under this License with respect to the covered 172 | work, and you disclaim any intention to limit operation or modification of the 173 | work as a means of enforcing, against the work's users, your or third parties' 174 | legal rights to forbid circumvention of technological measures. 175 | 4. Conveying Verbatim Copies. 176 | 177 | You may convey verbatim copies of the Program's source code as you receive it, 178 | in any medium, provided that you conspicuously and appropriately publish on each 179 | copy an appropriate copyright notice; keep intact all notices stating that this 180 | License and any non-permissive terms added in accord with section 7 apply to the 181 | code; keep intact all notices of the absence of any warranty; and give all 182 | recipients a copy of this License along with the Program. 183 | 184 | You may charge any price or no price for each copy that you convey, and you may 185 | offer support or warranty protection for a fee. 186 | 5. Conveying Modified Source Versions. 187 | 188 | You may convey a work based on the Program, or the modifications to produce it 189 | from the Program, in the form of source code under the terms of section 4, 190 | provided that you also meet all of these conditions: 191 | a) The work must carry prominent notices stating that you modified it, and 192 | giving a relevant date. 193 | b) The work must carry prominent notices stating that it is released under this 194 | License and any conditions added under section 7. This requirement modifies the 195 | requirement in section 4 to “keep intact all notices”. 196 | c) You must license the entire work, as a whole, under this License to anyone 197 | who comes into possession of a copy. This License will therefore apply, along 198 | with any applicable section 7 additional terms, to the whole of the work, and 199 | all its parts, regardless of how they are packaged. This License gives no 200 | permission to license the work in any other way, but it does not invalidate such 201 | permission if you have separately received it. 202 | d) If the work has interactive user interfaces, each must display Appropriate 203 | Legal Notices; however, if the Program has interactive interfaces that do not 204 | display Appropriate Legal Notices, your work need not make them do so. 205 | 206 | A compilation of a covered work with other separate and independent works, which 207 | are not by their nature extensions of the covered work, and which are not 208 | combined with it such as to form a larger program, in or on a volume of a 209 | storage or distribution medium, is called an “aggregate” if the compilation and 210 | its resulting copyright are not used to limit the access or legal rights of the 211 | compilation's users beyond what the individual works permit. Inclusion of a 212 | covered work in an aggregate does not cause this License to apply to the other 213 | parts of the aggregate. 214 | 6. Conveying Non-Source Forms. 215 | 216 | You may convey a covered work in object code form under the terms of sections 4 217 | and 5, provided that you also convey the machine-readable Corresponding Source 218 | under the terms of this License, in one of these ways: 219 | a) Convey the object code in, or embodied in, a physical product (including a 220 | physical distribution medium), accompanied by the Corresponding Source fixed on 221 | a durable physical medium customarily used for software interchange. 222 | b) Convey the object code in, or embodied in, a physical product (including a 223 | physical distribution medium), accompanied by a written offer, valid for at 224 | least three years and valid for as long as you offer spare parts or customer 225 | support for that product model, to give anyone who possesses the object code 226 | either (1) a copy of the Corresponding Source for all the software in the 227 | product that is covered by this License, on a durable physical medium 228 | customarily used for software interchange, for a price no more than your 229 | reasonable cost of physically performing this conveying of source, or (2) access 230 | to copy the Corresponding Source from a network server at no charge. 231 | c) Convey individual copies of the object code with a copy of the written offer 232 | to provide the Corresponding Source. This alternative is allowed only 233 | occasionally and noncommercially, and only if you received the object code with 234 | such an offer, in accord with subsection 6b. 235 | d) Convey the object code by offering access from a designated place (gratis or 236 | for a charge), and offer equivalent access to the Corresponding Source in the 237 | same way through the same place at no further charge. You need not require 238 | recipients to copy the Corresponding Source along with the object code. If the 239 | place to copy the object code is a network server, the Corresponding Source may 240 | be on a different server (operated by you or a third party) that supports 241 | equivalent copying facilities, provided you maintain clear directions next to 242 | the object code saying where to find the Corresponding Source. Regardless of 243 | what server hosts the Corresponding Source, you remain obligated to ensure that 244 | it is available for as long as needed to satisfy these requirements. 245 | e) Convey the object code using peer-to-peer transmission, provided you inform 246 | other peers where the object code and Corresponding Source of the work are being 247 | offered to the general public at no charge under subsection 6d. 248 | 249 | A separable portion of the object code, whose source code is excluded from the 250 | Corresponding Source as a System Library, need not be included in conveying the 251 | object code work. 252 | 253 | A “User Product” is either (1) a “consumer product”, which means any tangible 254 | personal property which is normally used for personal, family, or household 255 | purposes, or (2) anything designed or sold for incorporation into a dwelling. In 256 | determining whether a product is a consumer product, doubtful cases shall be 257 | resolved in favor of coverage. For a particular product received by a particular 258 | user, “normally used” refers to a typical or common use of that class of 259 | product, regardless of the status of the particular user or of the way in which 260 | the particular user actually uses, or expects or is expected to use, the 261 | product. A product is a consumer product regardless of whether the product has 262 | substantial commercial, industrial or non-consumer uses, unless such uses 263 | represent the only significant mode of use of the product. 264 | 265 | “Installation Information” for a User Product means any methods, procedures, 266 | authorization keys, or other information required to install and execute 267 | modified versions of a covered work in that User Product from a modified version 268 | of its Corresponding Source. The information must suffice to ensure that the 269 | continued functioning of the modified object code is in no case prevented or 270 | interfered with solely because modification has been made. 271 | 272 | If you convey an object code work under this section in, or with, or 273 | specifically for use in, a User Product, and the conveying occurs as part of a 274 | transaction in which the right of possession and use of the User Product is 275 | transferred to the recipient in perpetuity or for a fixed term (regardless of 276 | how the transaction is characterized), the Corresponding Source conveyed under 277 | this section must be accompanied by the Installation Information. But this 278 | requirement does not apply if neither you nor any third party retains the 279 | ability to install modified object code on the User Product (for example, the 280 | work has been installed in ROM). 281 | 282 | The requirement to provide Installation Information does not include a 283 | requirement to continue to provide support service, warranty, or updates for a 284 | work that has been modified or installed by the recipient, or for the User 285 | Product in which it has been modified or installed. Access to a network may be 286 | denied when the modification itself materially and adversely affects the 287 | operation of the network or violates the rules and protocols for communication 288 | across the network. 289 | 290 | Corresponding Source conveyed, and Installation Information provided, in accord 291 | with this section must be in a format that is publicly documented (and with an 292 | implementation available to the public in source code form), and must require no 293 | special password or key for unpacking, reading or copying. 294 | 7. Additional Terms. 295 | 296 | “Additional permissions” are terms that supplement the terms of this License by 297 | making exceptions from one or more of its conditions. Additional permissions 298 | that are applicable to the entire Program shall be treated as though they were 299 | included in this License, to the extent that they are valid under applicable 300 | law. If additional permissions apply only to part of the Program, that part may 301 | be used separately under those permissions, but the entire Program remains 302 | governed by this License without regard to the additional permissions. 303 | 304 | When you convey a copy of a covered work, you may at your option remove any 305 | additional permissions from that copy, or from any part of it. (Additional 306 | permissions may be written to require their own removal in certain cases when 307 | you modify the work.) You may place additional permissions on material, added by 308 | you to a covered work, for which you have or can give appropriate copyright 309 | permission. 310 | 311 | Notwithstanding any other provision of this License, for material you add to a 312 | covered work, you may (if authorized by the copyright holders of that material) 313 | supplement the terms of this License with terms: 314 | a) Disclaiming warranty or limiting liability differently from the terms of 315 | sections 15 and 16 of this License; or 316 | b) Requiring preservation of specified reasonable legal notices or author 317 | attributions in that material or in the Appropriate Legal Notices displayed by 318 | works containing it; or 319 | c) Prohibiting misrepresentation of the origin of that material, or requiring 320 | that modified versions of such material be marked in reasonable ways as 321 | different from the original version; or 322 | d) Limiting the use for publicity purposes of names of licensors or authors of 323 | the material; or 324 | e) Declining to grant rights under trademark law for use of some trade names, 325 | trademarks, or service marks; or 326 | f) Requiring indemnification of licensors and authors of that material by anyone 327 | who conveys the material (or modified versions of it) with contractual 328 | assumptions of liability to the recipient, for any liability that these 329 | contractual assumptions directly impose on those licensors and authors. 330 | 331 | All other non-permissive additional terms are considered “further restrictions” 332 | within the meaning of section 10. If the Program as you received it, or any part 333 | of it, contains a notice stating that it is governed by this License along with 334 | a term that is a further restriction, you may remove that term. If a license 335 | document contains a further restriction but permits relicensing or conveying 336 | under this License, you may add to a covered work material governed by the terms 337 | of that license document, provided that the further restriction does not survive 338 | such relicensing or conveying. 339 | 340 | If you add terms to a covered work in accord with this section, you must place, 341 | in the relevant source files, a statement of the additional terms that apply to 342 | those files, or a notice indicating where to find the applicable terms. 343 | 344 | Additional terms, permissive or non-permissive, may be stated in the form of a 345 | separately written license, or stated as exceptions; the above requirements 346 | apply either way. 347 | 8. Termination. 348 | 349 | You may not propagate or modify a covered work except as expressly provided 350 | under this License. Any attempt otherwise to propagate or modify it is void, and 351 | will automatically terminate your rights under this License (including any 352 | patent licenses granted under the third paragraph of section 11). 353 | 354 | However, if you cease all violation of this License, then your license from a 355 | particular copyright holder is reinstated (a) provisionally, unless and until 356 | the copyright holder explicitly and finally terminates your license, and (b) 357 | permanently, if the copyright holder fails to notify you of the violation by 358 | some reasonable means prior to 60 days after the cessation. 359 | 360 | Moreover, your license from a particular copyright holder is reinstated 361 | permanently if the copyright holder notifies you of the violation by some 362 | reasonable means, this is the first time you have received notice of violation 363 | of this License (for any work) from that copyright holder, and you cure the 364 | violation prior to 30 days after your receipt of the notice. 365 | 366 | Termination of your rights under this section does not terminate the licenses of 367 | parties who have received copies or rights from you under this License. If your 368 | rights have been terminated and not permanently reinstated, you do not qualify 369 | to receive new licenses for the same material under section 10. 370 | 9. Acceptance Not Required for Having Copies. 371 | 372 | You are not required to accept this License in order to receive or run a copy of 373 | the Program. Ancillary propagation of a covered work occurring solely as a 374 | consequence of using peer-to-peer transmission to receive a copy likewise does 375 | not require acceptance. However, nothing other than this License grants you 376 | permission to propagate or modify any covered work. These actions infringe 377 | copyright if you do not accept this License. Therefore, by modifying or 378 | propagating a covered work, you indicate your acceptance of this License to do 379 | so. 380 | 10. Automatic Licensing of Downstream Recipients. 381 | 382 | Each time you convey a covered work, the recipient automatically receives a 383 | license from the original licensors, to run, modify and propagate that work, 384 | subject to this License. You are not responsible for enforcing compliance by 385 | third parties with this License. 386 | 387 | An “entity transaction” is a transaction transferring control of an 388 | organization, or substantially all assets of one, or subdividing an 389 | organization, or merging organizations. If propagation of a covered work results 390 | from an entity transaction, each party to that transaction who receives a copy 391 | of the work also receives whatever licenses to the work the party's predecessor 392 | in interest had or could give under the previous paragraph, plus a right to 393 | possession of the Corresponding Source of the work from the predecessor in 394 | interest, if the predecessor has it or can get it with reasonable efforts. 395 | 396 | You may not impose any further restrictions on the exercise of the rights 397 | granted or affirmed under this License. For example, you may not impose a 398 | license fee, royalty, or other charge for exercise of rights granted under this 399 | License, and you may not initiate litigation (including a cross-claim or 400 | counterclaim in a lawsuit) alleging that any patent claim is infringed by 401 | making, using, selling, offering for sale, or importing the Program or any 402 | portion of it. 403 | 11. Patents. 404 | 405 | A “contributor” is a copyright holder who authorizes use under this License of 406 | the Program or a work on which the Program is based. The work thus licensed is 407 | called the contributor's “contributor version”. 408 | 409 | A contributor's “essential patent claims” are all patent claims owned or 410 | controlled by the contributor, whether already acquired or hereafter acquired, 411 | that would be infringed by some manner, permitted by this License, of making, 412 | using, or selling its contributor version, but do not include claims that would 413 | be infringed only as a consequence of further modification of the contributor 414 | version. For purposes of this definition, “control” includes the right to grant 415 | patent sublicenses in a manner consistent with the requirements of this License. 416 | 417 | Each contributor grants you a non-exclusive, worldwide, royalty-free patent 418 | license under the contributor's essential patent claims, to make, use, sell, 419 | offer for sale, import and otherwise run, modify and propagate the contents of 420 | its contributor version. 421 | 422 | In the following three paragraphs, a “patent license” is any express agreement 423 | or commitment, however denominated, not to enforce a patent (such as an express 424 | permission to practice a patent or covenant not to sue for patent infringement). 425 | To “grant” such a patent license to a party means to make such an agreement or 426 | commitment not to enforce a patent against the party. 427 | 428 | If you convey a covered work, knowingly relying on a patent license, and the 429 | Corresponding Source of the work is not available for anyone to copy, free of 430 | charge and under the terms of this License, through a publicly available network 431 | server or other readily accessible means, then you must either (1) cause the 432 | Corresponding Source to be so available, or (2) arrange to deprive yourself of 433 | the benefit of the patent license for this particular work, or (3) arrange, in a 434 | manner consistent with the requirements of this License, to extend the patent 435 | license to downstream recipients. “Knowingly relying” means you have actual 436 | knowledge that, but for the patent license, your conveying the covered work in a 437 | country, or your recipient's use of the covered work in a country, would 438 | infringe one or more identifiable patents in that country that you have reason 439 | to believe are valid. 440 | 441 | If, pursuant to or in connection with a single transaction or arrangement, you 442 | convey, or propagate by procuring conveyance of, a covered work, and grant a 443 | patent license to some of the parties receiving the covered work authorizing 444 | them to use, propagate, modify or convey a specific copy of the covered work, 445 | then the patent license you grant is automatically extended to all recipients of 446 | the covered work and works based on it. 447 | 448 | A patent license is “discriminatory” if it does not include within the scope of 449 | its coverage, prohibits the exercise of, or is conditioned on the non-exercise 450 | of one or more of the rights that are specifically granted under this License. 451 | You may not convey a covered work if you are a party to an arrangement with a 452 | third party that is in the business of distributing software, under which you 453 | make payment to the third party based on the extent of your activity of 454 | conveying the work, and under which the third party grants, to any of the 455 | parties who would receive the covered work from you, a discriminatory patent 456 | license (a) in connection with copies of the covered work conveyed by you (or 457 | copies made from those copies), or (b) primarily for and in connection with 458 | specific products or compilations that contain the covered work, unless you 459 | entered into that arrangement, or that patent license was granted, prior to 28 460 | March 2007. 461 | 462 | Nothing in this License shall be construed as excluding or limiting any implied 463 | license or other defenses to infringement that may otherwise be available to you 464 | under applicable patent law. 465 | 12. No Surrender of Others' Freedom. 466 | 467 | If conditions are imposed on you (whether by court order, agreement or 468 | otherwise) that contradict the conditions of this License, they do not excuse 469 | you from the conditions of this License. If you cannot convey a covered work so 470 | as to satisfy simultaneously your obligations under this License and any other 471 | pertinent obligations, then as a consequence you may not convey it at all. For 472 | example, if you agree to terms that obligate you to collect a royalty for 473 | further conveying from those to whom you convey the Program, the only way you 474 | could satisfy both those terms and this License would be to refrain entirely 475 | from conveying the Program. 476 | 13. Use with the GNU Affero General Public License. 477 | 478 | Notwithstanding any other provision of this License, you have permission to link 479 | or combine any covered work with a work licensed under version 3 of the GNU 480 | Affero General Public License into a single combined work, and to convey the 481 | resulting work. The terms of this License will continue to apply to the part 482 | which is the covered work, but the special requirements of the GNU Affero 483 | General Public License, section 13, concerning interaction through a network 484 | will apply to the combination as such. 485 | 14. Revised Versions of this License. 486 | 487 | The Free Software Foundation may publish revised and/or new versions of the GNU 488 | General Public License from time to time. Such new versions will be similar in 489 | spirit to the present version, but may differ in detail to address new problems 490 | or concerns. 491 | 492 | Each version is given a distinguishing version number. If the Program specifies 493 | that a certain numbered version of the GNU General Public License “or any later 494 | version” applies to it, you have the option of following the terms and 495 | conditions either of that numbered version or of any later version published by 496 | the Free Software Foundation. If the Program does not specify a version number 497 | of the GNU General Public License, you may choose any version ever published by 498 | the Free Software Foundation. 499 | 500 | If the Program specifies that a proxy can decide which future versions of the 501 | GNU General Public License can be used, that proxy's public statement of 502 | acceptance of a version permanently authorizes you to choose that version for 503 | the Program. 504 | 505 | Later license versions may give you additional or different permissions. 506 | However, no additional obligations are imposed on any author or copyright holder 507 | as a result of your choosing to follow a later version. 508 | 15. Disclaimer of Warranty. 509 | 510 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 511 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER 512 | PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER 513 | EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 514 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE 515 | QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE 516 | DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 517 | 16. Limitation of Liability. 518 | 519 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY 520 | COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS 521 | PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, 522 | INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE 523 | THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED 524 | INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE 525 | PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY 526 | HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 527 | 17. Interpretation of Sections 15 and 16. 528 | 529 | If the disclaimer of warranty and limitation of liability provided above cannot 530 | be given local legal effect according to their terms, reviewing courts shall 531 | apply local law that most closely approximates an absolute waiver of all civil 532 | liability in connection with the Program, unless a warranty or assumption of 533 | liability accompanies a copy of the Program in return for a fee. --------------------------------------------------------------------------------