\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.
--------------------------------------------------------------------------------