def __UpdateVideoItem(self, item, episodeId): """Updates an existing MediaItem with more data. Arguments: item : MediaItem - the MediaItem that needs to be updated episodeId : String - The episodeId, e.g.: VARA_xxxxxx Returns: The original item with more data added to it's properties. Used to update none complete MediaItems (self.complete = False). This could include opening the item's URL to fetch more data and then process that data or retrieve it's real media-URL. The method should at least: * cache the thumbnail to disk (use self.noImage if no thumb is available). * set at least one MediaItemPart with a single MediaStream. * set self.complete = True. if the returned item does not have a MediaItemPart then the self.complete flag will automatically be set back to False. """ Logger.Trace("Using Generic UpdateVideoItem method") # get the subtitle subTitleUrl = "http://tt888.omroep.nl/tt888/%s" % (episodeId,) subTitlePath = subtitlehelper.SubtitleHelper.DownloadSubtitle(subTitleUrl, episodeId + ".srt", format='srt', proxy=self.proxy) item.MediaItemParts = [] part = item.CreateNewEmptyMediaPart() part.Subtitle = subTitlePath for s, b in NpoStream.GetStreamsFromNpo(None, episodeId, proxy=self.proxy): item.complete = True # s = self.GetVerifiableVideoUrl(s) part.AppendMediaStream(s, b) if AddonSettings.IsMinVersion(18): for s, b, p in NpoStream.GetMpdStreamFromNpo(None, episodeId, proxy=self.proxy): item.complete = True # s = self.GetVerifiableVideoUrl(s) stream = part.AppendMediaStream(s, b) for k, v in p.iteritems(): stream.AddProperty(k, v) return item
def __init__(self, channelInfo): """Initialisation of the class. Arguments: channelInfo: ChannelInfo - The channel info object to base this channel on. All class variables should be instantiated here and this method should not be overridden by any derived classes. """ chn_class.Channel.__init__(self, channelInfo) # ============== Actual channel setup STARTS here and should be overwritten from derived classes =============== # setup the urls self.__api = None self.__sso = None if self.channelCode == "vtm": self.noImage = "vtmbeimage.jpg" self.mainListUri = "https://vtm.be/feed/programs?format=json&type=all&only_with_video=true" self.mainListUri = "https://vtm.be/video/?f[0]=sm_field_video_origin_cms_longform%3AVolledige%20afleveringen" self.baseUrl = "https://vtm.be" self.__app = "vtm_watch" self.__sso = "vtm-sso" self.__apiKey = "vtm-b7sJGrKwMJj0VhdZvqLDFvgkJF5NLjNY" # setup the main parsing data in case of HTML htmlVideoRegex = '<img[^>]+class="media-object"[^>]+src="(?<thumburl>[^"]+)[^>]*>[\w\W]{0,1000}?<a[^>]+href="/(?<url>[^"]+)"[^>]*>(?<title>[^<]+)' htmlVideoRegex = Regexer.FromExpresso(htmlVideoRegex) self._AddDataParser( "https://vtm.be/video/?f[0]=sm_field_video_origin_cms_longform%3AVolledige%20afleveringen&", name="HTML Page Video Parser for VTM", # preprocessor=self.AddMoreRecentVideos, parser=htmlVideoRegex, creator=self.CreateVideoItemHtml) recentRegex = '<a href="/(?<url>[^"]+)"[^>]*>\W+(?:<div[^>]+>\W+)+<img[^>]+src="(?<thumburl>[^"]+)"[^>]+>\W*<span[^>]*>\W*(?<subtitle>[^<]+)[\w\W]{0,300}?(?:<div[^>]+class="item-caption-program"[^>]*>(?<title>[^<]+)</div>\W*)</div>\W*</div>\W*</div>\W*</a' # recentRegex = 'data-video-id="(?<url>\d+)"[^>]*>\W+<[^>]+>\W+<img[^>]*src="(?<thumburl>[^"]+)[^>]*>[\W\w]{0,1000}?class="item-caption-title"[^>]*>(?<subtitle>[^<]+)<[^>]+>\W*<[^>]+>\W*<a[^>]+>(?<title>[^<]+)' recentRegex = Regexer.FromExpresso(recentRegex) self._AddDataParser( "https://vtm.be/video/volledige-afleveringen/id", matchType=ParserData.MatchExact, name="Recent Items HTML Video Parser", parser=recentRegex, creator=self.CreateVideoItemHtml ) elif self.channelCode == "q2": self.noImage = "q2beimage.jpg" self.mainListUri = "https://www.q2.be/feed/programs?format=json&type=all&only_with_video=true" self.mainListUri = "https://www.q2.be/video/?f[0]=sm_field_video_origin_cms_longform%3AVolledige%20afleveringen" self.baseUrl = "https://www.q2.be" self.__app = "q2" self.__sso = "q2-sso" self.__apiKey = "q2-html5-NNSMRSQSwGMDAjWKexV4e5Vm6eSPtupk" htmlVideoRegex = '<a[^>]+class="cta-full[^>]+href="/(?<url>[^"]+)"[^>]*>[^<]*</a>\W*<span[^>]*>[^<]*</[^>]*\W*<div[^>]*>\W*<img[^>]+src="(?<thumburl>[^"]+)[\w\W]{0,1000}?<h3[^>]*>(?<title>[^<]+)' htmlVideoRegex = Regexer.FromExpresso(htmlVideoRegex) self._AddDataParser( "https://www.q2.be/video/?f[0]=sm_field_video_origin_cms_longform%3AVolledige%20afleveringen&", name="HTML Page Video Parser for Q2", parser=htmlVideoRegex, creator=self.CreateVideoItemHtml) elif self.channelCode == "stievie": self.__app = "stievie" self.__sso = "stievie-sso" self.__apiKey = "stievie-web-2.8-yz4DSTPshescHUytkWwU9jDxQ28PKTGn" self.noImage = "stievieimage.jpg" self.httpHeaders["Authorization"] = "apikey=%s" % (self.__apiKey, ) # self.mainListUri = "https://vod.medialaan.io/vod/v2/programs?offset=0&limit=0" self.mainListUri = "https://channels.medialaan.io/channels/v1/channels?preview=false" self._AddDataParser(self.mainListUri, json=True, preprocessor=self.StievieMenu, name="JSON Channel overview", parser=("response", "channels"), creator=self.StievieCreateChannelItem) self._AddDataParser("#channel", name="Channel menu parser", preprocessor=self.StievieChannelMenu) # main list parsing self._AddDataParser("https://vod.medialaan.io/vod/v2/programs?offset=0&limit=0", json=True, name="Main program list parsing for Stievie", # preprocessor=self.AddLiveChannel, creator=self.StievieCreateEpisode, parser=("response", "videos")) self._AddDataParser("https://epg.medialaan.io/epg/v2/", json=True, name="EPG Stievie parser", creator=self.StievieCreateEpgItems, parser=("channels", )) self._AddDataParser("https://vod.medialaan.io/vod/v2/programs?query=", name="Stievie Search Parser", json=True, creator=self.StievieCreateEpisode, parser=("response", "videos")) else: raise NotImplementedError("%s not supported yet" % (self.channelCode, )) # generic to all channels htmlEpisodeRegex = '<a[^>]+href="(?<url>[^"]+im_field_program[^"]+)"[^>]+>(?<title>[^(<]+)' htmlEpisodeRegex = Regexer.FromExpresso(htmlEpisodeRegex) self._AddDataParser( "sm_field_video_origin_cms_longform%3AVolledige%20afleveringen", matchType=ParserData.MatchEnd, name="HTML Page Show Parser", preprocessor=self.AddLiveChannel, parser=htmlEpisodeRegex, creator=self.CreateEpisodeItemHtml) self._AddDataParser( "https://(?:vtm.be|www.q2.be)/video/?.+=sm_field_video_origin_cms_longform%3AVolledige%20afleveringen&.+id=\d+", matchType=ParserData.MatchRegex, name="HTML Page Video Updater", updater=self.UpdateVideoItem, requiresLogon=True) self._AddDataParser( "https://vtm.be/video/volledige-afleveringen/id/", name="HTML Page Video Updater New Style (AddMoreRecentVideos)", updater=self.UpdateVideoItem, requiresLogon=True) # setup the main parsing data in case of JSON self._AddDataParser("/feed/programs?format=json&type=all&only_with_video=true", matchType=ParserData.MatchEnd, name="JSON Feed Show Parser for Medialaan", json=True, preprocessor=self.AddLiveChannelAndFetchAllData, creator=self.CreateEpisodeItemJson, parser=("response", "items")) self._AddDataParser("https://vod.medialaan.io/api/1.0/list", json=True, name="JSON Video Parser for Medialaan", preprocessor=self.AddVideoPageItemsJson, parser=("response", "items"), creator=self.CreateVideoItemJson) self._AddDataParser("https://vod.medialaan.io/vod/v2/videos/", matchType=ParserData.MatchRegex, name="JSON Video Updater for Medialaan", updater=self.UpdateVideoItemJson, requiresLogon=True) self._AddDataParser("https://vod.medialaan.io/vod/v2/videos?", name="JSON Video Updater for Medialaan with programOID", updater=self.UpdateVideoEpgItemJson, requiresLogon=True) # self._AddDataParser("https://vtm.be/video?aid=", name="HTML Stream Updater", # requiresLogon=True, updater=self.UpdateVideoItem) self._AddDataParser("#livestream", name="Live Stream Updater for Q2, VTM and Stievie", requiresLogon=True, updater=self.UpdateLiveStream) # =============================================================================================================== # non standard items self.__signature = None self.__signatureTimeStamp = None self.__userId = None self.__cleanRegex = re.compile("<[^>]+>") self.__dashStreamsSupported = AddonSettings.IsMinVersion(18) # Mappings from the normal URL (which has all shows with actual videos and very little # video-less shows) to the JSON ids. Loading can be done using: # import json # fp = file("c:\\temp\\ff.json") # data = json.load(fp) # fp.close() # mapping = dict() # for item in data["response"]["items"]: # if not item["parent_series_oid"]: # continue # mapping[item["title"]] = item["parent_series_oid"] # print json.dumps(mapping) # # TODO: perhap we can do this dynamically? self.__mappings = { "q2": { "Grimm": "256511352168527", "Homeland": "256467029990527", "Vikings": "256528439042527", "The Big Bang Theory": "256467024031527", "Brooklyn Nine-Nine": "256575703668527", "The Graham Norton Show": "256943055386527", "Person of Interest": "256467035258527", "Grounded for Life": "256575957717527", "Valemont": " 256433841939527", "Advocaat van de Duivel": "256816070531527", "Quantico": "256684804053527", "__The Middle": "256577035751527", "Life in Pieces": "256651006814527", "My Wife & Kids": "257045563302527", "Dawson's Creek": "256575773017527", "Dracula": "256588092015527", "__Two and a Half Men": "256490829206527", "Modern Family": "256467031756527", "Game of Thrones": "256588988771527", "Marvel's Agent Carter": "256576832916527", "Jo": "256576812799527", "Hit The Floor": "256594294622527", "Foute Vrienden": "256403630232527", "That '70s Show": "256467039034527", "Covert Affairs": "256467028271527", "__Champions League": "256584896142527", "24: Live Another Day": "256539021922527", "Het Beste van X-Factor Worldwide": "256575966527527", "Mr. Robot": "256757142789527", "Graceland": "256528403044527", "The Glades": "256467029451527", "Arrested Development": "256467009408527", "Dads": "256586859951527", "Marvel's Agents of S.H.I.E.L.D.": "256576835198527", "__The Muppets": "256684804375527", "The Voice USA": "256946841840527", "__Top Gear": "256528438369527", "Friends With Better Lives": "256588965013527", "New Girl": "256467032228527", "Tricked": "256573688559527", "Community": "256973035121527", "Salem": "256676854495527", "Rude Tube": "256433822255527", "Bones": "256404132799527", "Rosewood": "256650982517527", "The Crazy Ones": "256467028697527", "Married with Children": "256576831734527", "Crisis": "256511351727527" }, "vtm": { "Helden van Hier: Door het Vuur": "256588089798527", "Aspe": "256382495645527", "Alloo bij ...": "256943106645527", "De Zonen van Van As": "256407562265527", "Heidi": "256463008600527", "Coppers": "256685693714527", "Chicago Med": "256722572301527", "Altijd Prijs": "256544288119527", "De Vetste Vakantie": "256676855101527", "Familie": "256383171504527", "Binnenstebuiten": "256575685561527", "Code 37": "256407560206527", "De Bunker": "256587035116527", "De Drone School": "256850916997527", "De 25": "256454876662527", "Het Grootste Licht": "256676855233527", "Axel Opgelicht": "256436933413527", "Het Lichaam van Coppens": "256402076016527", "America's Funniest Home Videos": "256547897482527", "Expeditie Paira Daiza": "256574614075527", "Cordon": "256407560819527", "Alloo in de Buitenlandse Gevangenis": "256454773025527", "De Wensboom": "256725880042527", "FAROEK": "256575897637527", "Cathérine": "256856277380527", "De Avonturen van K3": "256595788573527", "Ella": "256588631982527", "De Funnie Show": "256587019478527", "Cycling Cup": "256778013268527", "Dubbelspel": "256920728612527", "Allemaal Chris": "256936077540527", "Baas in Huis": "256463013040527", "De Keuken van Sofie": "256575862270527", "Helden van Hier: In de Lucht": "256996171054527", "Brandweerman Sam": "256475467798527", "Blind Getrouwd": "256589828137527", "Clan": "256407588081527", "De Waarzeggers": "256431811242527", "Alloo bij de Lokale Politie ": "256544207094527", "Het Furchester Hotel": "256831536569527", "Dynamo: Magician Impossible": "256676855209527", "Alloo bij de Wegpolitie": "256676855317527", "De Buurtpolitie": "256403648640527", "Amigo's": "256544290999527", "David": "256586890385527", "Amateurs": "256403567370527", "__Border Security": "256472727848527", "Alloo in de Psychiatrie": "256544231522527", "Danni Lowinski": "256404119004527", "De Disco Dans Show": "256798376480527", "Het Geheime Leven van 5-jarigen": "256611388403527", "__Belgium's Got Talent": "256462951774527", "De Kotmadam": "256403656559527", "Gezond Verstand": "257076990290527", "Beat da Bompaz": "256433651880527", "BK Sumo 2016": "256577054292527", "Beste Kijkers": "256407593035527", "Geert Hoste": "256573544524527", "Benidorm Bastards USA": "256472726922527", "Benidorm Bastards": "256575677508527", "De Kliniek": "256547901203527", "De Rodenburgs": "256407561643527", "__Deze Is Voor Jou": "256728833164527", "Grote Ster, Kleine Ster": "256575950955527", "Alloo bij Jambers": "256595573889527", "De Kroongetuigen": "256407561421527", "Groeten uit": "257040505722527", "Comedy Toppers": "256403657663527", "Little People": "256597844370527", "Rode Neuzen Dag": "257023165962527", "Stadion": "256573559156527", "Spitsbroers": "256471098273527", "Wat Als?": "256407582304527", "Til Death": "256676850133527", "Pac-Man en de Spook Avonturen": "256475476997527", "The Team": "256676855137527", "Vlaamse Streken": "256486551290527", "VTM Telefoneert": "256168188259527", "Sofie in de Keuken van": "256939967974527", "Patrouille Linkeroever": "256676854375527", "So You Think You Can Dance": "256464798482527", "Odd Squad": "256710652408527", "Vind Mijn Familie": "256384038011527", "Nicholas": "256454884349527", "Little Big Shots": "256897888122527", "Liefde voor Sterren tegen de Muziek op": "256597815326527", "LouisLouise": "256598003727527", "Jonas & Van Geel": "256595778045527", "Project K": "256611396531527", "Hollywood in 't echt": "256594308987527", "Maya de Bij": "256547919980527", "Zone Stad ": "15777992529", "Jill": "256853912126527", "Moerkerke en de mannen": "256676855221527", "Met Vier in Bed": "256547903459527", "Vinger Aan De Poot": "256547852448527", "Mijn Pop-uprestaurant!": "256477480591527", "Pak Ace": "256729926613527", "Vossenstreken": "256433821163527", "The Voice Kids": "256676855365527", "Wittekerke": "256403641361527", "Total Loss in het Bos": "256664909485527", "Telefacts": "256407577960527", "Royalty": "256407571395527", "Lang Leve...": "256403648386527", "Valkuil": "256676854981527", "Moerkerke en de Vrouwen": "257108815284527", "__Lotgenoten": "256586980503527", "Safety First": "256402022747527", "The Voice van Vlaanderen": "256577041480527", "Zuidflank": "256404868560527", "Rijker dan je Denkt?": "256403651256527", "Special Forces": "257001201929527", "Uit de Kast": "256611410411527", "VTM NIEUWS": "256547855317527", "Wild van Dieren": "256084514960527", "Shades of Blue": "256757101999527", "Het Weer": "256547857195527", "Tegen de Sterren op": "256407581965527", "K3 zoekt K3": "256576815747527", "Turbo FAST": "256676855125527", "The Band": "256996808958527", "Ligt er Flan op de Mont Blanc?": "257007717785527", "Zijn er nog Kroketten?": "256403645293527", "Liefde voor Muziek": "256480753274527", "Tom Boonen: My Ride, My Fight, My Life": "256797352477527", "McLeod's Daughters": "256472731081527", "Is er Wifi in Tahiti?": "256547902518527", "Moordvrouw": "256496106525527", "The Amazing Spiez!": "256882084522527", "Wij zijn K3": "256725887906527" } } # =============================================================================================================== # Test cases: # ====================================== Actual channel setup STOPS here ======================================= return
def GetXBMCPlayList(self, bitrate, updateItemUrls=False, proxy=None): """ Creates a XBMC Playlist containing the MediaItemParts in this MediaItem Keyword Arguments: bitrate : integer - The bitrate of the streams that should be in the playlist. Given in kbps updateItemUrls : [opt] boolean - If specified, the Playlist items will have a path pointing to the actual stream proxy : [opt] ProxyInfo - The proxy to set Returns: a XBMC Playlist for this MediaItem If the Bitrate keyword is omitted the the bitrate is retrieved using the default bitrate settings: """ playList = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) srt = None playListItems = [] if not updateItemUrls: # if we are not using the resolveUrl method, we need to clear the playlist and set the index playList.clear() currentIndex = 0 else: # copy into a list so we can add stuff in between (we can't do that in an # XBMC PlayList) and then create a new playlist item currentIndex = playList.getposition( ) # this is the location at which we are now. if currentIndex < 0: # no items where there, so we can just start at position 0 currentIndex = 0 Logger.Info( "Updating the playlist for item at position %s and trying to preserve other playlist items", currentIndex) for i in range(0, len(playList)): Logger.Trace("Copying playList item %s out of %s", i + 1, len(playList)) playListItems.append((playList[i].getfilename(), playList[i])) startList = reduce( lambda x, y: "%s\n%s" % (x, y[0]), playListItems, "Starting with Playlist Items (%s)" % (len(playListItems), )) Logger.Debug(startList) playList.clear() logText = "Creating playlist for Bitrate: %s kbps\n%s\nSelected Streams:\n" % ( bitrate, self) # for each MediaItemPart get the URL, starting at the current index index = currentIndex for part in self.MediaItemParts: if len(part.MediaStreams) == 0: Logger.Warning("Ignoring empty MediaPart: %s", part) continue # get the playlist item (stream, xbmcItem) = part.GetXBMCPlayListItem( self, bitrate, updateItemUrls=updateItemUrls) logText = "%s\n + %s" % (logText, stream) streamUrl = stream.Url xbmcParams = dict() if proxy: if stream.Downloaded: logText = "%s\n + Not adding proxy as the stream is already downloaded" % ( logText, ) elif proxy.Scheme.startswith( "http") and not stream.Url.startswith("http"): logText = "%s\n + Not adding proxy due to scheme mismatch" % ( logText, ) elif proxy.Scheme == "dns": logText = "%s\n + Not adding DNS proxy for Kodi streams" % ( logText, ) elif not proxy.UseProxyForUrl(streamUrl): logText = "%s\n + Not adding proxy due to filter mismatch" % ( logText, ) else: if AddonSettings.IsMinVersion(17): # See ffmpeg proxy in https://github.com/xbmc/xbmc/commit/60b21973060488febfdc562a415e11cb23eb9764 xbmcItem.setProperty("proxy.host", proxy.Proxy) xbmcItem.setProperty("proxy.port", str(proxy.Port)) xbmcItem.setProperty("proxy.type", proxy.Scheme) if proxy.Username: xbmcItem.setProperty("proxy.user", proxy.Username) if proxy.Password: xbmcItem.setProperty("proxy.password", proxy.Password) logText = "%s\n + Adding (Krypton) %s" % (logText, proxy) else: xbmcParams["HttpProxy"] = proxy.GetProxyAddress() logText = "%s\n + Adding (Pre-Krypton) %s" % ( logText, proxy) # Now add the actual HTTP headers for k in part.HttpHeaders: xbmcParams[k] = HtmlEntityHelper.UrlEncode(part.HttpHeaders[k]) if xbmcParams: xbmcQueryString = reduce( lambda x, y: "%s&%s=%s" % (x, y, xbmcParams[y]), xbmcParams.keys(), "").lstrip("&") Logger.Debug("Adding Kodi Stream parameters: %s\n%s", xbmcParams, xbmcQueryString) streamUrl = "%s|%s" % (stream.Url, xbmcQueryString) if index == currentIndex and index < len(playListItems): # We need to replace the current item. Logger.Trace( "Replacing current Kodi ListItem at Playlist index %s (of %s)", index, len(playListItems)) playListItems[index] = (streamUrl, xbmcItem) else: # We need to add at the current index Logger.Trace("Inserting Kodi ListItem at Playlist index %s", index) playListItems.insert(index, (streamUrl, xbmcItem)) index += 1 # for now we just add the last subtitle, this will not work if each # part has it's own subtitles. srt = part.Subtitle Logger.Info(logText) endList = reduce( lambda x, y: "%s\n%s" % (x, y[0]), playListItems, "Ended with Playlist Items (%s)" % (len(playListItems), )) Logger.Debug(endList) for playListItem in playListItems: playList.add(playListItem[0], playListItem[1]) return playList, srt