Skip to content

Commit e8ba647

Browse files
[plugin.video.tagesschau] 2.6.0
1 parent 3e58dcd commit e8ba647

6 files changed

Lines changed: 195 additions & 130 deletions

File tree

plugin.video.tagesschau/addon.xml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<addon
33
id="plugin.video.tagesschau"
4-
version="2.5.7"
4+
version="2.6.0"
55
name="Tagesschau"
66
provider-name="J.Schumacher, H.Saul, C.Prasch">
77
<requires>
88
<import addon="xbmc.python" version="3.0.0"></import>
9+
<import addon="script.module.infotagger" version="0.0.3" />
910
</requires>
1011

1112
<extension point="xbmc.python.pluginsource" library="main.py">
@@ -31,6 +32,16 @@
3132
<screenshot>resources/assets/screenshot_3.png</screenshot>
3233
</assets>
3334
<news>
35+
version 2.6.0 (2025-12-21)
36+
* Searching for "Tagesschau 20 Uhr" broadcasts changed to find more entries. Number of searched entries can be configured in settings.
37+
* Updates some internas to prevent warnings in Kodi logfile.
38+
39+
version 2.5.9 (2024-11-17)
40+
* All livestreams (not only one) are now listed in the subfolder "Livestreams"
41+
42+
version 2.5.8 (2024-11-02)
43+
* Detection of available livestream changed
44+
3445
version 2.5.7 (2024-08-17)
3546
* Fixed empty news list because of changes in ARD API
3647

plugin.video.tagesschau/libs/tagesschau.py

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
#
1818

19-
import sys, os, urllib, urllib.parse, logging
19+
import sys, os, urllib, urllib.parse
2020
import xbmc, xbmcplugin, xbmcgui, xbmcaddon, xbmcvfs
2121
#import web_pdb
2222

23-
from libs.tagesschau_json_api import VideoContentProvider, JsonSource, addon
23+
from libs.tagesschau_json_api import VideoContentProvider, addon
2424
from libs.subtitles import download_subtitles
25+
from infotagger.listitem import ListItemInfoTag
2526

2627
# -- Constants ----------------------------------------------
2728
ADDON_ID = 'plugin.video.tagesschau'
@@ -33,9 +34,6 @@
3334
ID_PARAM = 'tsid'
3435
URL_PARAM = 'url'
3536

36-
# -- Settings -----------------------------------------------
37-
logger = logging.getLogger("plugin.video.tagesschau.api")
38-
3937
# -- Settings -----------------------------------------------
4038
quality_id = addon.getSetting('quality')
4139
quality = ['M', 'L', 'X'][int(quality_id)]
@@ -61,10 +59,13 @@
6159
def addVideoContentDirectory(title, method):
6260
url_data = { ACTION_PARAM: 'list_feed', FEED_PARAM: method }
6361
url = 'plugin://' + ADDON_ID + '/?' + urllib.parse.urlencode(url_data)
64-
li = xbmcgui.ListItem(str(title))
62+
li = xbmcgui.ListItem()
6563
li.setArt({'thumb':ICON_IMG, 'landscape':LOGO_IMG, 'icon':ICON_IMG})
6664
li.setProperty('Fanart_Image', FANART_IMG)
67-
li.setInfo(type="video", infoLabels={ "Title": str(title), "Plot": str(title) }) #"mediatype": "video"
65+
66+
infoLabels={ "title": str(title), "plot": str(title) }
67+
ListItemInfoTag(li, 'video').set_info(infoLabels)
68+
6869
xbmcplugin.setContent(int(sys.argv[1]), 'files')
6970
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=li, isFolder=True)
7071

@@ -83,20 +84,20 @@ def getListItem(videocontent):
8384
li.setArt({'thumb':image_url, 'landscape':image_url})
8485
li.setProperty('Fanart_Image', fanart_url)
8586
li.setProperty('IsPlayable', 'true')
86-
li.setInfo(type="video",
87-
infoLabels={ "Title": str(title),
88-
"Plot": str(videocontent.description),
89-
"Duration": str((videocontent.duration or 0)/60),
90-
"mediatype": "video"
91-
}
92-
)
87+
88+
infoLabels={ "title": str(title),
89+
"plot": str(videocontent.description),
90+
"duration": (videocontent.duration or 0)/60,
91+
"mediatype": "video"
92+
}
93+
ListItemInfoTag(li, 'video').set_info(infoLabels)
94+
9395
if( videocontent.timestamp ):
94-
li.setInfo(type="video",
95-
infoLabels={ "premiered": str(videocontent.timestamp.strftime('%d.%m.%Y')),
96-
"aired": str(videocontent.timestamp.strftime('%d.%m.%Y')),
97-
"date": str(videocontent.timestamp.strftime('%d.%m.%Y'))
98-
}
99-
)
96+
infoLabels={ "premiered": str(videocontent.timestamp.strftime('%d.%m.%Y')),
97+
"aired": str(videocontent.timestamp.strftime('%d.%m.%Y')),
98+
"date": str(videocontent.timestamp.strftime('%d.%m.%Y'))
99+
}
100+
ListItemInfoTag(li, 'video').set_info(infoLabels)
100101

101102
return li
102103

@@ -137,7 +138,7 @@ def tagesschau():
137138
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_NONE)
138139

139140
params = get_params()
140-
provider = VideoContentProvider(JsonSource())
141+
provider = VideoContentProvider()
141142

142143
if params.get(ACTION_PARAM) == 'play_video':
143144
subtitles_file = None
@@ -173,21 +174,9 @@ def tagesschau():
173174
addVideoContentItems(videos, params[FEED_PARAM])
174175

175176
else:
176-
# populate root directory
177-
# check whether there is a livestream
178-
videos = provider.livestreams()
179-
if(len(videos) == 1):
180-
li = xbmcgui.ListItem(strings['livestreams'])
181-
li.setArt({'thumb':ICON_IMG, 'landscape':LOGO_IMG, 'icon':ICON_IMG})
182-
li.setProperty('Fanart_Image', FANART_IMG)
183-
li.setProperty('IsPlayable', 'true')
184-
li.setInfo(type="video", infoLabels={ "Title": strings['livestreams'], "Plot": strings['livestreams'] })
185-
url = getUrl(videos[0], "livestreams")
186-
xbmcplugin.setContent(int(sys.argv[1]), 'videos')
187-
xbmcplugin.addDirectoryItem(int(sys.argv[1]), url, li, False)
188-
189177
# add directories for other feeds
190178
add_named_directory = lambda x: addVideoContentDirectory(strings[x], x)
179+
add_named_directory('livestreams')
191180
add_named_directory('latest_videos')
192181
add_named_directory('latest_broadcasts')
193182
add_named_directory('tagesschau_20')

plugin.video.tagesschau/libs/tagesschau_json_api.py

Lines changed: 68 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,18 @@
1818

1919
try: import json
2020
except ImportError: import simplejson as json
21-
import logging, datetime, re, urllib.request, xbmc, xbmcaddon
21+
import datetime, re, urllib.request, xbmc, xbmcaddon
2222
#import web_pdb
2323
#web_pdb.set_trace()
2424

2525
# -- Constants ----------------------------------------------
2626
ADDON_ID = 'plugin.video.tagesschau'
27-
28-
logger = logging.getLogger("plugin.video.tagesschau.api")
2927
base_url = "https://www.tagesschau.de/api2u/"
3028

3129
addon = xbmcaddon.Addon(id=ADDON_ID)
3230
showage = addon.getSettingBool('ShowAge')
3331
tt_listopt = addon.getSetting('tt_list')
32+
ts20_count = int(addon.getSetting('ts20_count'))
3433
hide_europadruck = addon.getSettingBool('hide_europadruck')
3534
hide_wolkenfilm = addon.getSettingBool('hide_wolkenfilm')
3635

@@ -176,13 +175,23 @@ def parse_broadcast(self, jsonbroadcast, title="" ):
176175

177176
def parse_livestream(self, jsonlivestream):
178177
"""Parses the video JSON into a VideoContent object."""
179-
tsid = "livestream"
180-
title = "Livestream"
181-
timestamp = None
178+
tsid = jsonlivestream["sophoraId"]
179+
title = jsonlivestream["title"]
180+
181+
if( "date" in jsonlivestream ):
182+
timestamp = self._parse_date(jsonlivestream["date"])
183+
else:
184+
timestamp = datetime.datetime.now()
185+
186+
if( title.lower() == "tagesschau" ):
187+
title = title + timestamp.strftime(' vom %d.%m.%Y %H:%M')
188+
182189
imageurls = {}
183190
imageurls = self._parse_image_urls(jsonlivestream["teaserImage"]["imageVariants"])
184191
videourls = self.parse_video_urls(jsonlivestream["streams"])
185-
return VideoContent(tsid, title, timestamp, videourls, imageurls)
192+
duration = int(jsonlivestream["tracking"][1]["length"])
193+
description = title
194+
return VideoContent(tsid, title, timestamp, videourls, imageurls, duration, description)
186195

187196
def parse_video_urls(self, jsonvariants):
188197
"""Parses the video mediadata JSON into a dict mapping variant name to URL."""
@@ -210,24 +219,23 @@ def _parse_image_urls(self, jsonvariants):
210219
class VideoContentProvider(object):
211220
"""Provides access to the VideoContent offered by the tagesschau JSON API."""
212221

213-
def __init__(self, jsonsource):
214-
self._jsonsource = jsonsource
222+
def __init__(self):
215223
self._parser = VideoContentParser()
216-
self._logger = logging.getLogger("plugin.video.tagesschau.api.VideoContentProvider")
217224

218225
def livestreams(self):
219226
"""Retrieves the livestream(s) currently on the air.
220227
221228
Returns:
222229
A list of VideoContent object for livestream(s) on the air.
223230
"""
224-
self._logger.info("retrieving livestream(s)")
225231
videos = []
226-
data = self._jsonsource.livestreams()
232+
233+
url = base_url + "channels"
234+
data = json.loads( urllib.request.urlopen(url).read() )
235+
227236
for jsonstream in data["channels"]:
228-
if( not "date" in jsonstream ): # livestream has no date
229-
video = self._parser.parse_livestream(jsonstream)
230-
videos.append(video)
237+
video = self._parser.parse_livestream(jsonstream)
238+
videos.append(video)
231239

232240
return videos
233241

@@ -237,10 +245,11 @@ def latest_videos(self):
237245
Returns:
238246
A list of VideoContent items.
239247
"""
240-
self._logger.info("retrieving videos")
241-
242248
videos = []
243-
data = self._jsonsource.latest_videos()
249+
250+
url = base_url + "news"
251+
data = json.loads( urllib.request.urlopen(url).read() )
252+
244253
for jsonvideo in data["news"]:
245254
try:
246255
if( (jsonvideo["type"] == "video") and (jsonvideo["tracking"][0]["src"] == "tagesschau") ):
@@ -252,9 +261,8 @@ def latest_videos(self):
252261
video = self._parser.parse_video(jsonvideo)
253262
videos.append(video)
254263
except:
255-
self._logger.info("ignoring")
264+
pass
256265

257-
self._logger.info("found " + str(len(videos)) + " videos")
258266
return videos
259267

260268
def latest_broadcasts(self):
@@ -263,18 +271,19 @@ def latest_broadcasts(self):
263271
Returns:
264272
A list of VideoContent items.
265273
"""
266-
self._logger.info("retrieving broadcasts")
267274
videos = []
268-
data = self._jsonsource.latest_broadcasts()
275+
276+
url = base_url + "channels"
277+
data = json.loads( urllib.request.urlopen(url).read() )
278+
269279
for jsonbroadcast in data["channels"]:
270280
try:
271281
if( ("date" in jsonbroadcast) and ("title" in jsonbroadcast) ): # Filter out livestream which has no date
272282
video = self._parser.parse_broadcast(jsonbroadcast)
273283
videos.append(video)
274284
except:
275-
self._logger.info("ignoring")
285+
pass
276286

277-
self._logger.info("found " + str(len(videos)) + " videos")
278287
return videos
279288

280289
def tagesschau_20(self):
@@ -283,20 +292,24 @@ def tagesschau_20(self):
283292
Returns:
284293
A list of VideoContent items.
285294
"""
286-
self._logger.info("retrieving tagesschau 20:00")
287295
videos = []
288-
data = self._jsonsource.tagesschau_20()
289-
for jsonvideo in data["searchResults"]:
290-
try:
291-
if( jsonvideo["type"] == "video" ):
292-
length = int(jsonvideo["tracking"][1]["length"])
293-
if( (length >= 890) and (length <= 910) ):
294-
video = self._parser.parse_broadcast(jsonvideo, "Tagesschau")
295-
videos.append(video)
296-
except:
297-
self._logger.info("ignoring")
296+
297+
page = 0
298+
while (len(videos) < ts20_count) and (page < 10):
299+
url = base_url + "search/?searchText=Tagesschau+20+Uhr&pageSize=30&resultPage=" + str(page)
300+
data = json.loads( urllib.request.urlopen(url).read() )
301+
page += 1
302+
303+
for jsonvideo in data["searchResults"]:
304+
try:
305+
if( jsonvideo["type"] == "video" ):
306+
length = int(jsonvideo["tracking"][1]["length"])
307+
if( (length >= 890) and (length <= 910) ):
308+
video = self._parser.parse_broadcast(jsonvideo, "Tagesschau")
309+
videos.append(video)
310+
except:
311+
pass
298312

299-
self._logger.info("found " + str(len(videos)) + " videos")
300313
return videos
301314

302315
def tagesthemen(self):
@@ -305,55 +318,27 @@ def tagesthemen(self):
305318
Returns:
306319
A list of VideoContent items.
307320
"""
308-
self._logger.info("retrieving tagesthemen")
309321
videos = []
310-
data = self._jsonsource.tagesthemen()
311-
for jsonvideo in data["searchResults"]:
312-
try:
313-
if( jsonvideo["type"] == "video" ):
314-
length = int(jsonvideo["tracking"][1]["length"])
315-
video = self._parser.parse_broadcast(jsonvideo)
316322

317-
if( tt_listopt == "0" ):
318-
if( length >= 1100 ):
323+
for page in range(2):
324+
url = base_url + "search/?searchText=tagesthemen&pageSize=50&resultPage=" + str(page)
325+
data = json.loads( urllib.request.urlopen(url).read() )
326+
327+
for jsonvideo in data["searchResults"]:
328+
try:
329+
if( jsonvideo["type"] == "video" ):
330+
length = int(jsonvideo["tracking"][1]["length"])
331+
video = self._parser.parse_broadcast(jsonvideo)
332+
333+
if( tt_listopt == "0" ):
334+
if( length >= 1100 ):
335+
videos.append(video)
336+
elif( tt_listopt == "1" ):
337+
if( length < 1100 ):
338+
videos.append(video)
339+
else:
319340
videos.append(video)
320-
elif( tt_listopt == "1" ):
321-
if( length < 1100 ):
322-
videos.append(video)
323-
else:
324-
videos.append(video)
325-
except:
326-
self._logger.info("ignoring")
341+
except:
342+
pass
327343

328-
self._logger.info("found " + str(len(videos)) + " videos")
329344
return videos
330-
331-
332-
class JsonSource(object):
333-
"""Provides access to the raw objects parsed from the TS JSON API.
334-
Can be replaced for unittesting purposes."""
335-
336-
def livestreams(self):
337-
"""Returns the parsed JSON structure for livestreams."""
338-
handle = urllib.request.urlopen(base_url + "channels")
339-
return json.loads(handle.read())
340-
341-
def latest_videos(self):
342-
"""Returns the parsed JSON structure for the latest videos."""
343-
handle = urllib.request.urlopen(base_url + "news")
344-
return json.loads(handle.read())
345-
346-
def latest_broadcasts(self):
347-
"""Returns the parsed JSON structure for the latest broadcasts."""
348-
handle = urllib.request.urlopen(base_url + "channels")
349-
return json.loads(handle.read())
350-
351-
def tagesschau_20(self):
352-
"""Returns the parsed JSON structure for 20:00 tagesschau"""
353-
handle = urllib.request.urlopen(base_url + "search/?searchText=Tagesschau+20+Uhr")
354-
return json.loads(handle.read())
355-
356-
def tagesthemen(self):
357-
"""Returns the parsed JSON structure for tagesthemen"""
358-
handle = urllib.request.urlopen(base_url + "search/?searchText=tagesthemen")
359-
return json.loads(handle.read())

0 commit comments

Comments
 (0)