From fc6aa41040bd965f5d7aaa44510d9e032e8126d6 Mon Sep 17 00:00:00 2001 From: rols1 Date: Sun, 3 Nov 2024 10:14:52 +0100 Subject: [PATCH] =?UTF-8?q?=C3=84nderungen=20/=20Korrekturen=20siehe=20cha?= =?UTF-8?q?ngelog.txt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- addon.xml | 2 +- ardundzdf.py | 163 ++++++++++++++++++++------------ changelog.txt | 16 +++- resources/images/podcast-de.png | Bin 0 -> 9820 bytes resources/lib/util.py | 74 ++++++++++----- 6 files changed, 169 insertions(+), 89 deletions(-) create mode 100644 resources/images/podcast-de.png diff --git a/README.md b/README.md index 35537e4..bea2c10 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,8 @@ Funktionen: - Sport - Sender (Sendungen einzelner Radiosender) - funk: Das Content-Netzwerk von ARD und ZDF -- Podcast-Favoriten (manuell erweiterbar) +- Podcasts der Platform www.podcast.de + #### TV-Live-Streams mit Aufnahmefunktion: - ARD- und ZDF-Sender überregional (13) und regional (13 plus WDR Lokalzeitsender), einige ausgewählte Privatsender diff --git a/addon.xml b/addon.xml index 4860b8b..700f8ad 100644 --- a/addon.xml +++ b/addon.xml @@ -1,5 +1,5 @@ - + diff --git a/ardundzdf.py b/ardundzdf.py index 78f7a64..b25c420 100644 --- a/ardundzdf.py +++ b/ardundzdf.py @@ -57,8 +57,8 @@ # VERSION -> addon.xml aktualisieren # 221 # Numerierung für Einzelupdate -VERSION = '5.1.2' -VDATE = '29.10.2024' +VERSION = '5.1.3' +VDATE = '03.11.2024' # (c) 2019 by Roland Scholz, rols1@gmx.de @@ -170,7 +170,7 @@ ARD_Live = '/tv/live' -# ARD-Podcasts - 03.06.2021 alle Links der Classic-Version entfernt +# ARD-Podcasts - 03.06.2021 alle Links der Classic-https://api.ardaudiothek.de/Version entfernt # ARD Audiothek ARD_AUDIO_BASE = 'https://api.ardaudiothek.de/' @@ -215,7 +215,8 @@ THUMBNAILS = os.path.join(USERDATA, "Thumbnails") M3U8STORE = os.path.join(ADDON_DATA, "m3u8") DICTSTORE = os.path.join(ADDON_DATA, "Dict") -SLIDESTORE = os.path.join(ADDON_DATA, "slides") +SLIDESTORE = os.path.join(ADDON_DATA, "slides") +PODIMGSTORE = os.path.join(SLIDESTORE, "PodcastImg") SUBTITLESTORE = os.path.join(ADDON_DATA, "subtitles") TEXTSTORE = os.path.join(ADDON_DATA, "Inhaltstexte") WATCHFILE = os.path.join(ADDON_DATA, "merkliste.xml") @@ -370,6 +371,8 @@ ClearUp(SLIDESTORE, days*86400) # SLIDEESTORE bereinigen ARDNeu_Startpage = os.path.join(SLIDESTORE, "ARDNeu_Startpage") ClearUp(ARDNeu_Startpage, days*86400)# Thumbscache ARDneu bereinigen +ClearUp(PODIMGSTORE, days*86400) # PodcastImg-Cache bereinigen + days = int(SETTINGS.getSetting('pref_TEXTE_store_days')) ClearUp(TEXTSTORE, days*86400) # TEXTSTORE bereinigen @@ -453,16 +456,6 @@ def Main(): fparams="&fparams={'name': '3Sat'}" # 3Sat-Modul addDir(li=li, label="3Sat Mediathek", action="dirList", dirID="resources.lib.my3Sat.Main_3Sat", fanart=R('3sat.png'), thumb=R('3sat.png'), tagline=tagline, fparams=fparams) - - ''' 26.04.2023 api V4.0 funktioniert nicht mehr - vorerst abgeschaltet - 27.05.2023 endgültig - nach wie vor: HTTP Error 410: Gone - if SETTINGS.getSetting('pref_use_funk') == 'true': - tag = 'in den Settings kann das Modul FUNK ein- und ausgeschaltet werden' - tag = u"%s\n\ndie Beiträge sind auch in der ZDF Mediathek enthalten (Menü ZDF-funk)" % tag - fparams="&fparams={}" # funk-Modul - addDir(li=li, label="FUNK", action="dirList", dirID="resources.lib.funk.Main_funk", - fanart=R('funk.png'), thumb=R('funk.png'), tagline=tag, fparams=fparams) - ''' if SETTINGS.getSetting('pref_use_childprg') == 'true': tagline = 'in den Settings kann das Modul Kinderprogramme ein- und ausgeschaltet werden' @@ -1171,11 +1164,6 @@ def Main_ZDFfunk(title): addDir(li=li, label="ZDF-funk-A-Z", action="dirList", dirID="ZDF_AZ", fanart=R('zdf-funk-AZ.png'), thumb=R('zdf-funk-AZ.png'), fparams=fparams) - ''' # 27.05.2023 entfernt, api V4.0 nicht mehr verfügbar - fparams="&fparams={}" # Button funk-Modul hinzufügen - addDir(li=li, label="zum FUNK-Modul", action="dirList", dirID="resources.lib.funk.Main_funk", - fanart=R('zdf-funk.png'), thumb=R('funk.png'), fparams=fparams) - ''' xbmcplugin.endOfDirectory(HANDLE, cacheToDisc=True) ##################################### Start Teletext ############################################### @@ -1517,7 +1505,7 @@ def AudioStart(title): img = R(ICON_MAIN_AUDIO) title_list = [u'Entdecken|ard-entdecken.png', u'Rubriken|ard-rubriken.png', - u'Sport|ard-sport.png'] + u'Sportschau|ard-sport.png'] for item in title_list: title, img = item.split('|') tag='' @@ -1555,24 +1543,26 @@ def AudioStart(title): #---------------------------------------------------------------- # 31.07.2021 Revision nach Renovierung der Audiothek durch die ARD # 19.02.2022 dto +# 02.11.2024 Aktualisierung path Sportschau (neues ARD-Schema +# ../urn:ard:page:../ # def AudioStartHome(title, ID, page='', path=''): # Auswertung Homepage PLog('AudioStartHome: ' + ID) li = xbmcgui.ListItem() ID = py2_decode(ID) - if ID == 'Rubriken': # Rubriken-Liste: eig. api-Call - path = ARD_AUDIO_BASE + "editorialcategories" - if ID == 'Sport': # Menü: Rubrik Sport herausgehoben - path = 'https://www.ardaudiothek.de/rubrik/sport/42914734' + if ID == 'Sportschau': # Menü: Rubrik Sportschau herausgehoben + path = 'https://www.ardaudiothek.de/rubrik/sportschau/urn:ard:page:cebf0dfe68c7f771/' ID = "AudioRubrikWebJson_%s" % "42914734" Audio_get_cluster_rubrik(li='', url=path, title='Sport', ID=ID) return + if ID == 'Rubriken': # Rubriken-Liste: eig. api-Call + path = ARD_AUDIO_BASE + "editorialcategories" if ID == 'Entdecken': #path = ARD_AUDIO_BASE + "homescreen" # Leitseite api path = "https://www.ardaudiothek.de/" # Web: nur teilw. vollständig, Nachladen - page, msg = get_page(path=path) + page, msg = get_page(path=path, header=HEADERS) if page == '': msg1 = "Fehler in AudioStartHome:" msg2 = msg @@ -1594,7 +1584,9 @@ def AudioStartHome(title, ID, page='', path=''): # Auswertung Homepage # 1. Aufruf: (ID=AudioStartHome) -> Liste der Rubriken via api-Call, # 2. Aufruf (Rubrik-Webseite in path) -> Audio_get_cluster_rubrik # (Cluster -> Dict) -> Audio_get_cluster_single -# +# 02.11.2024 den api-Daten fehlt coreId mit neuem ARD-Pfad (Schema +# ../urn:ard:page:../.. In den Web-json-Daten ist coreId vorh. +# def Audio_get_rubriken_web(li, title, path, ID, page): PLog('Audio_get_rubriken_web: ' + ID) CacheTime = 86400 # 24 Std. @@ -1618,15 +1610,15 @@ def Audio_get_rubriken_web(li, title, path, ID, page): if "graphql" in page: # 18.02.2022 auch möglich (alte Form) Obs = jsonObject["_embedded"]['mt:editorialCategories'] else: - Obs = jsonObject["data"]['editorialCategories']["nodes"]# Key-Änderung + Obs = jsonObject["data"]['editorialCategories']["nodes"]# coreId mit neuem Pfad fehlt, s.o. PLog(len(Obs)) - base = "https://www.ardaudiothek.de/rubrik/%s" # Name im Pfad (z.B. ../wissen/..) nicht nötig + base = "https://www.ardaudiothek.de/rubrik/%s/" # Name im Pfad (z.B. ../wissen/..) nicht nötig # base = "https://www.ardaudiothek.de/redirect/%s" # HTTP-error 301 vermeiden - s. get_page img = R("ard-rubriken.png"); thumb = R(ICON_DIR_FOLDER) for ob in Obs: oid = ob["id"] title = ob["title"] - href = base % oid + href = base % oid PLog('10Satz:'); PLog(title); PLog(oid); PLog(href); @@ -2000,16 +1992,19 @@ def Audio_get_webslice(page, mode="web"): #---------------------------------------------------------------- # 28.10.2024 Podcasts podcast.de -# +# Doku: ../Audiothek/Podcast.de # def AudioPodcastDe(title="", Dict_ID=''): PLog('AudioPodcastDe: ' + Dict_ID) path = "https://www.podcast.de/" - CacheTime = 3600 + CacheTime = 3600 # 1 Std. + WebID = "AudioPodcastDe" - page = Dict("load", "AudioPodcastDe", CacheTime=CacheTime) # editorialCategories laden + page = Dict("load", WebID, CacheTime=CacheTime) if page == False or page == '': # Cache miss od. leer - vom Sender holen page, msg = get_page(path=path) + icon = R('podcast-de.png') + xbmcgui.Dialog().notification("Cache podcast.de:","Haltedauer 1 Stunde",icon,3000,sound=False) Dict("store", "AudioPodcastDe", page) if page == '': msg1 = "Fehler in AudioPodcastDe:" @@ -2022,8 +2017,11 @@ def AudioPodcastDe(title="", Dict_ID=''): li = xbmcgui.ListItem() li = home(li, ID='ARD Audiothek') + # umfangreichere json-Daten (Autor, Mail, CR,..) nur bei lchannels=" + # und lshows=" - hier nicht benötigt: json_blocks = stringextract("Scripts -->", "", page) PLog("json_blocks: " + json_blocks[:80]) + rubrics = [u"editors_choice|Tipps der Redaktion", u"dashboard_charts|Podcast-Charts", u"latest_shows|Neue Episoden", u"latest_channels|Neue Podcasts" ] @@ -2034,7 +2032,7 @@ def AudioPodcastDe(title="", Dict_ID=''): #---------------------------------------------------------------- # 1. json-Blöcke aus Webseite laden if Dict_ID == '': - PLog("Step1") + PLog("Step1:") title="Suche auf podcast.de" fparams="&fparams={}" addDir(li=li, label=title, action="dirList", dirID="AudioPodcastDeSearch", @@ -2060,22 +2058,27 @@ def AudioPodcastDe(title="", Dict_ID=''): xbmcplugin.endOfDirectory(HANDLE, cacheToDisc=True) else: #---------------------------------------------------------------- # 2. mp3's - PLog("Step2") + PLog("Step2:") block = Dict("load", Dict_ID) - block = transl_json(block) # json-Umlaute - block = (block.replace(u""", '').replace(u"\\", '')) + block = transl_json(block) # Format ungeeignet für json.loads + PLog(block[:100]) + block = (block.replace(u""", '').replace(u"\\", '').replace(u"u0027", '')\ + .replace(u"&", '&')) items = blockextract('"name":', block) for item in items: title = stringextract('"name": "', '",', item) title=title.replace("#", "*") # z.B.: #544 Für wen.. artist = stringextract('"artist": "', '",', item) # Podcast-Reihe - Plot = artist + Plot = "Podcast-Reihe: [B]%s[/B]" % artist url = stringextract('"url": "', '",', item) url = decode_url(url) # AudioPlayMP3 -> entf. Url-Suffix ?source=feed anz = stringextract('"subscribers": "', '",', item) img = stringextract('"cover_art_url": "', '"', item) + img=img.replace("/33/", "/600/"); img=img.replace("/55/", "/600/") + img=img.replace("/66/", "/600/"); img=img.replace("/100/", "/600/"); + summ = "Weiter zu dieser Folge und zur Podcast-Reihe." PLog("Step2_title: %s, artist: %s, url: %s, anz: %s, img: %s," % (title, Plot, url, anz, img)) title=py2_encode(title); url=py2_encode(url); @@ -2084,7 +2087,7 @@ def AudioPodcastDe(title="", Dict_ID=''): fparams="&fparams={'url': '%s', 'title': '%s', 'thumb': '%s', 'Plot': '%s', 'artist': '%s'}" %\ (quote(url), quote(title), quote(img), quote(Plot), quote(artist)) addDir(li=li, label=title, action="dirList", dirID="AudioPodcastDeSingle", fanart=img, thumb=img, - fparams=fparams, tagline=Plot) + fparams=fparams, tagline=Plot, summary=summ) xbmcplugin.endOfDirectory(HANDLE, cacheToDisc=True) @@ -2113,20 +2116,27 @@ def AudioPodcastDeSearch(dest_url="", query=""): msg2 = "Suche leider fehlgeschlagen." MyDialog(msg1, msg2, '') return + WebID = "AudioPodcastDeSearch" + Dict("store", WebID, page) li = xbmcgui.ListItem() li = home(li, ID='ARD Audiothek') # Home-Button items = blockextract("
", page) for item in items: + artist = stringextract('
', '
', item) # Podcast-Reihe + artist = cleanhtml(artist); artist = artist.strip() img = stringextract('img src="', '"', item) + img=img.replace("/33/", "/600/"); img=img.replace("/55/", "/600/") + img=img.replace("/66/", "/600/"); img=img.replace("/100/", "/600/"); alt = stringextract('alt="', '"', item) alt = unescape(alt) # alt="LANZ & PRECHT" Plot = stringextract('text-muted small">', '
', item) Plot = cleanhtml(Plot); Plot = mystrip(Plot) href = stringextract('Neueste Episode:', '', item) - title = cleanhtml(href); title = mystrip(title); title = unescape(title) + title = cleanhtml(href); title = mystrip(title); + title = unescape(title); title=title.replace("#", "*") title = "%s: [B]%s[/B]" % (alt, title) dest_url = stringextract('href="', '"', href) # podcast.de/episode/644335688/ausgabe-.. if dest_url == "": # Abschnitte Service, Dienst, .. @@ -2149,7 +2159,6 @@ def AudioPodcastDeSearch(dest_url="", query=""): MyDialog(msg1, msg, '') return - li = xbmcgui.ListItem() li = home(li, ID='ARD Audiothek') # Home-Button @@ -2161,8 +2170,12 @@ def AudioPodcastDeSearch(dest_url="", query=""): title = items["name"] title = title.replace("#", "*") Plot = items["description"] + Plot_org=Plot Plot = Plot.replace("\n", "") img = items["thumbnailUrl"] + img=img.replace("/33/", "/600/"); img=img.replace("/55/", "/600/") + img=img.replace("/66/", "/600/"); img=img.replace("/100/", "/600/"); + mp3_url = items["associatedMedia"]["contentUrl"] ser_url = items["partOfSeries"]["url"] # Serien-Url serie = ser_url.split("/")[-1] # z.B. lanz-precht @@ -2174,15 +2187,19 @@ def AudioPodcastDeSearch(dest_url="", query=""): msg1 = "Fehler in AudioPodcastDeSearch:" MyDialog(msg1, msg, '') return - # 1. Button Neueste Episode - PLog("Step2_title: %s, artist: %s, url: %s, img: %s," % (title, Plot, mp3_url, img)) + + summ = "Weiter zu diesem Podcast" + if SETTINGS.getSetting('pref_video_direct') == 'false': + summ = "%s und zum Download" % summ + # 1. Button Neueste Episode + PLog("title_Step2: %s, artist: %s, url: %s, img: %s," % (title, Plot, mp3_url, img)) title=py2_encode(title); mp3_url=py2_encode(mp3_url); img=py2_encode(img); Plot=py2_encode(Plot); fparams="&fparams={'url': '%s', 'title': '%s', 'thumb': '%s', 'Plot': '%s'}" % (quote(mp3_url), quote(title), quote(img), quote_plus(Plot)) addDir(li=li, label=title, action="dirList", dirID="AudioPlayMP3", fanart=img, thumb=img, - fparams=fparams, tagline=Plot) + fparams=fparams, tagline=Plot, summary=summ) AudioPodcastDeArchiv(archiv_url, li) # Buttons Archiv-Beiträge @@ -2205,7 +2222,7 @@ def AudioPodcastDeArchiv(url, li=""): li = xbmcgui.ListItem() li = home(li, ID='ARD Audiothek') # Home-Button - folgen = page.split('episode-list">')[-1] + folgen = page.split('episode-list">')[-1] #
folgen = folgen.split('
')[0] pages = page.split('pagination">')[-1] @@ -2213,13 +2230,19 @@ def AudioPodcastDeArchiv(url, li=""): del items[0] # Neueste Episode löschen for item in items: href = stringextract('href="', '"', item) # Web-Ziel - img = stringextract('img src="', '"', item) + img = stringextract('img src="', '"', item) + img=img.replace("/33/", "/600/"); img=img.replace("/55/", "/600/") + img=img.replace("/66/", "/600/"); img=img.replace("/100/", "/600/"); + title = stringextract('

', '

', item) title=title.strip(); title=unescape(title) title=repl_json_chars(title) Plot = stringextract('text-muted">', '', item) Plot = Plot.strip() + summ = "Weiter zu diesem Podcast" + if SETTINGS.getSetting('pref_video_direct') == 'false': + summ = "%s und zum Download" % summ PLog("archiv_title: %s, mp3_url: %s, img: %s, Plot: %s" % (title, href, img, Plot)) title=py2_encode(title); href=py2_encode(href); @@ -2228,7 +2251,7 @@ def AudioPodcastDeArchiv(url, li=""): fparams="&fparams={'url': '%s', 'title': '%s', 'thumb': '%s', 'Plot': '%s'}" %\ (quote(href), quote(title), quote(img), quote_plus(Plot), ) addDir(li=li, label=title, action="dirList", dirID="AudioPodcastDeSingle", fanart=img, thumb=img, - fparams=fparams, tagline=Plot) + fparams=fparams, tagline=Plot, summary=summ) if pages: # Pagination p = url_org.split("/")[-1] # ?page=1 @@ -2245,7 +2268,6 @@ def AudioPodcastDeArchiv(url, li=""): addDir(li=li, label=title, action="dirList", dirID="AudioPodcastDeArchiv", \ fanart=img, thumb=img, fparams=fparams) - xbmcplugin.endOfDirectory(HANDLE, cacheToDisc=True) #---------------------------------------------------------------- @@ -2254,14 +2276,20 @@ def AudioPodcastDeArchiv(url, li=""): # def AudioPodcastDeSingle(url, title, thumb, Plot, artist=""): PLog('AudioPodcastDeSingle: ' + url) - PLog(artist) + PLog("artist: " + artist) if artist: # einz. mp3-Url plus Podcast-Reihe li = xbmcgui.ListItem() li = home(li, ID='ARD Audiothek') # Home-Button - tag = "Weiter zum Podcast." # Button -> Podcast - summ = "Podcast-Reihe: %s" % Plot + tag = "Weiter zum Podcast" # Button -> Podcast + clen = get_content_length(url) # Größe aus Header ermitteln + if clen: + vsize = u"(Größe [B]%s[/B])." % humanbytes(clen) + else: + vsize = u"(Größe unbekannt)" + tag = "%s %s" % (tag, vsize) + summ = Plot mp3_url = url; img = thumb title=py2_encode(title); mp3_url=py2_encode(mp3_url); img=py2_encode(img); Plot=py2_encode(Plot); @@ -2271,7 +2299,7 @@ def AudioPodcastDeSingle(url, title, thumb, Plot, artist=""): fparams=fparams, tagline=tag, summary=summ) title = "Zur Podcast-Reihe [B]%s[/B]" % artist # Button -> Podcast-Reihe - tag = "%s und und verwandten Themen" % title + tag = "%s und verwandten Themen" % title fparams="&fparams={'query': '%s'}" % artist addDir(li=li, label=title, action="dirList", dirID="AudioPodcastDeSearch", fanart=R('podcast-de.png'), thumb=R('ard-suche.png'), tagline=tag, fparams=fparams) @@ -2294,6 +2322,8 @@ def AudioPodcastDeSingle(url, title, thumb, Plot, artist=""): Plot = items["description"] Plot = Plot.replace("\n", "") img = items["thumbnailUrl"] + img=img.replace("/33/", "/600/"); img=img.replace("/55/", "/600/") + img=img.replace("/66/", "/600/"); img=img.replace("/100/", "/600/"); mp3_url = items["associatedMedia"]["contentUrl"] ser_url = items["partOfSeries"]["url"] # Serien-Url serie = ser_url.split("/")[-1] # z.B. lanz-precht @@ -2302,17 +2332,22 @@ def AudioPodcastDeSingle(url, title, thumb, Plot, artist=""): except Exception as exception: msg = "items_error: " + str(exception) PLog(msg) - msg1 = "Fehler in AudioPodcastDeSearch:" + msg1 = "Fehler in AudioPodcastDeSingle:" MyDialog(msg1, msg, '') return li = xbmcgui.ListItem() - if SETTINGS.getSetting('pref_video_direct') == 'true': # Sofortstart + if SETTINGS.getSetting('pref_video_direct') == 'true': # Sofortstart PLog('Sofortstart: AudioPodcastDeSingle') AudioPlayMP3(mp3_url, title, thumb, Plot) return else: - tag = "[B]Weiter zum Beitrag und Download.[/B]" + clen = get_content_length(url) # Größe aus Header ermitteln + if clen: + vsize = u"(Größe [B]%s[/B])." % humanbytes(clen) + else: + vsize = u"(Größe unbekannt)" + tag = "[B]Weiter zum Podcast und Download[/B] %s" % vsize li = home(li, ID='ARD Audiothek') # Home-Button title=py2_encode(title); mp3_url=py2_encode(mp3_url); img=py2_encode(img); Plot=py2_encode(Plot); @@ -2935,15 +2970,23 @@ def Audio_get_cluster_rubrik(li, url, title, ID=''): if "AudioHomescreen" in ID: # Stage-Beiträge Startseite rubrik_id = ID - else: - rid = url.split('/')[-1] + else: # rubrik_id aus Url-Ende + id_url = url.split("//")[-1] + if id_url.endswith("/"): # i.d.R. alle + id_url = id_url[0:len(id_url)-1] + PLog("id_url: " + id_url) + + if "/" in id_url: # alt, z.B. ../rubrik/42914694/ + rid = id_url.split('/')[-1] + if ":" in id_url: # neu, z.B. ../urn:ard:page:cebf0dfe68c7f771/ + rid = id_url.split(':')[-1] + PLog("rid: " + rid) rubrik_id = "AudioRubrikWebJson_%s" % rid page = Dict("load", rubrik_id, CacheTime=CacheTime) # json-Teil der Webseite schon vorhanden? if page == False or page == '': # Cache miss od. leer - vom Sender holen # Name im Pfad fehlt hier noch, daher Redirect: - page, msg = get_page(path=url, GetOnlyRedirect=True) # Permanent-Redirect-Url - url = page + url, msg = get_page(path=url, GetOnlyRedirect=True) # Permanent-Redirect-Url page, msg = get_page(path=url) if page == '': msg1 = "Fehler in Audio_get_cluster_rubrik:" @@ -3393,7 +3436,8 @@ def AudioWebMP3(url, title, thumb, Plot, ID='', no_gui=''): # AudioPodcastDeSearch) # def AudioPlayMP3(url, title, thumb, Plot, ID=''): - PLog('AudioPlayMP3: ' + title) + PLog('AudioPlayMP3: ' + url) + PLog(title) if ".mp3?" in url or ".m4a?" in url: # AudioPodcastDe, AudioPodcastDeSearch if ".mp3?" in url: # ..644e320a4561.mp3?source=feed @@ -5541,7 +5585,6 @@ def DownloadExtern(url, title, dest_path, key_detailtxt, sub_path=''): suffix='' if url.endswith('.mp3') or url.endswith('.m4a'): # 25.10.2024 Podcasts .m4a -# suffix = '.mp3' suffix = url[-4:] dtyp = 'Podcast ' else: # .mp4 oder .webm diff --git a/changelog.txt b/changelog.txt index fa1b3c2..5f78923 100644 --- a/changelog.txt +++ b/changelog.txt @@ -10,12 +10,26 @@ CHANGE HISTORY max_col 97 -------------- +03.11.2024 5.1.3 + EPG_Search, EPG_Search2: Leerzeichenbehandlung ("+" -> " ") für Suche in + EPG. + Podcast-Erweiterung podcast.de: neue Funktionen AudioPodcastDe*, + Anpassungen in Main, AudioStart, AudioPlayMP3, DownloadExtern, DownloadsList, + VideoTools (suffix .m4a für Podcasts ergänzt), transl_umlaute (u2014), + transl_json (). + home (util), Main, Main_ZDFfunk: Menübuttons für FUNK gelöscht (obsolet). + Audiothek: sportschau-Url angepasst (HTTP Error 308 für PY2 nicht zu + korrigieren), neues ARD-Schema ../urn:ard:page:../. Dict-ID-Anpassung an + Schema (Audio_get_cluster_rubrik). + getRedirect (util): für PYTHON3 bei Redirect-Errors Umstellung auf httplib2, + unveränderte Url-Rückgabe bei PYTHON2. + 13.10.2024 5.1.2 my3Sat: unescape(tag) in get_lazyload, unescape(tag) in SendungenAZ valid_title_chars (util): Zeichen & durch Zeichen + ersetzt. SingleVideo (arte): Ausfall api/opa/v3-Link, Ersatz durch hbbtvv2-Link in (path2), Umbenennung Funktion get_streams_api_opa -> get_streams_hbbtvv2 - neue Auswertung der MP4-Quellen). + (neue Auswertung der MP4-Quellen). ARDSportMedia: ID-Austausch TagesschauXL -> ARD für Aufruf AudioPlayMP3. Parseplaylist: RBB Brandenburg aus der Liste für Einzelauflösungen entfernt (jetzt Mehrkanalstream). diff --git a/resources/images/podcast-de.png b/resources/images/podcast-de.png new file mode 100644 index 0000000000000000000000000000000000000000..c4e6ea5ce9ad048082e1b8613483748c2b151fd4 GIT binary patch literal 9820 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 F{|D}w<*)z% literal 0 HcmV?d00001 diff --git a/resources/lib/util.py b/resources/lib/util.py index 5e4a0ad..7d650cd 100644 --- a/resources/lib/util.py +++ b/resources/lib/util.py @@ -12,7 +12,7 @@ # 17.11.2019 Migration Python3 Modul kodi_six + manuelle Anpassungen # # 113 # Numerierung für Einzelupdate -# Stand: 29.10.2024 +# Stand: 01.11.2024 # Python3-Kompatibilität: from __future__ import absolute_import @@ -313,14 +313,7 @@ def home(li, ID, ltitle=""): fparams="&fparams={'name': '%s'}" % quote(name) addDir(li=li, label=title, action="dirList", dirID="resources.lib.my3Sat.Main_3Sat", fanart=img, thumb=img, summary=summ, fparams=fparams) - ''' # deaktiviert - if ID == 'FUNK': - img = R('funk.png') - name = Home + "FUNK" - fparams="&fparams={}" - addDir(li=li, label=title, action="dirList", dirID="resources.lib.funk.Main_funk", fanart=img, - thumb=img, fparams=fparams) - ''' + if ID == 'Kinderprogramme': img = R('childs.png') name = Home + ID @@ -1272,27 +1265,54 @@ def getHeaders(response): # z.Z. nicht genutzt return headers # ---------------------------------------------------------------------- +# 02.11.2024 für PYTHON2 bei Redirect-Errors unveränderte Rückgabe (neuer +# Versuch in get_page (get_page3) mit requests (falls vorh.) +# Für PYTHON3 bei Redirect-Errors Umstellung auf httplib2. +# Debug 308_Error: www.ardaudiothek.de/rubrik/42914694 (ohne /-Ende) +# def getRedirect(path, header=""): PLog('getRedirect: '+ path) msg="" + parsed = urlparse(path) try: - r = urlopen(path) - except Exception as e: - PLog(str(e)) - if "308:" in str(e) or "307:" in str(e) or "301:" in str(e): # Permanent-Redirect-Url - new_url = e.hdrs.get("Location") - parsed = urlparse(path) - if new_url.startswith("http") == False: # Serveradr. vorh.? - new_url = 'https://%s%s' % (parsed.netloc, new_url) - PLog("HTTP308_301_new_url: " + new_url) - return new_url, '' + if header: + req = Request(path, headers=header) else: - return '', str(e) - - page = r.geturl() - info = r.info() - PLog("redirect: %s, info: %s" % (page, str(info)[:100]))# neue Url + req = Request(path) + r = urlopen(req) + new_url = r.geturl() + if new_url.startswith("http") == False: # z.B. /rubrik/sportschau/.. + new_url = 'https://%s%s' % (parsed.netloc, new_url) # Serveradr. ergänzen + PLog("new_url: " + new_url) + return new_url, msg + except Exception as e: + PLog("redirect_error: " + str(e)) + try: + if "308:" in str(e) or "307:" in str(e) or "301:" in str(e): # Permanent-Redirect-Url + if PYTHON2: # -> get_page (s.o.) + return path, msg + else: + # https://httplib2.readthedocs.io/en/latest/libhttplib2.html + import httplib2 + h = httplib2.Http() # class httplib2.Http, Cache nicht erford. + h.follow_all_redirects = True + r = h.request(path, "GET")[0] + new_url = r['content-location'] + PLog("httplib2_url: " + new_url) + parsed = urlparse(path) + if new_url.startswith("http") == False: # s.o. + new_url = 'https://%s%s' % (parsed.netloc, new_url) + PLog("HTTP308_301_new_url: " + new_url) + if path.find(new_url) == 0: # Debug + PLog("same_new_url_path: " + new_url) + return new_url, msg + + except Exception as e: + PLog("r_error: " + str(e)) + PLog(str(e)) + page=path, msg=str(e) # Fallback + return page, msg # ---------------------------------------------------------------------- @@ -1930,11 +1950,13 @@ def transl_json(line): # json-Umlaute übersetzen , (u'\\u2013', u'-') # Arte: - , (u'\\u2014', u'-') # Arte: - , (u'\\u2019', u'*') # Arte: ' + , (u'\\u2019', u'*') , (u'\\u00f8', u'ø') # ø Kleinbuchstabe o mit Strich, , (u'\\u00e5', u'å') # å Kleinbuchstabe a mit Ring, , (u'\\u00c5', u'Å') # Å Groß A mit Ring , (u'\\u00ab', u'«') # Anführungszeichen links « - , (u'\\u00bb', u'«') # Anführungszeichen rechs » + , (u'\\u0160', u'Š') # AudioPodcastDe: Šuma Covjek + , (u'\\u0161', u'š') # AudioPodcastDe: Ringišpil , (u'\\u2026', u'...') # ... , (u'\\u26bd', u'') # Ball-emoji , (u'\\ufe0f', u'') # Nonspacing Mark @@ -3870,7 +3892,7 @@ def sub_path_conv(sub_path): # Neue Kodivers. ansch. nicht betroffen, Kodi 18.2. OK # def PlayAudio(url, title, thumb, Plot, header=None, FavCall=''): - PLog('PlayAudio:'); PLog(title); PLog(FavCall); + PLog('PlayAudio:'); PLog(url); PLog(title); PLog(FavCall); title = cleanmark(title) # Tags erscheinen im Titel des Player-Windows Plot=Plot.replace('||', '\n') # || Code für LF (\n scheitert in router)