3 from datetime
import datetime
9 from BeautifulSoup
import BeautifulSoup
as BS3
10 from requests
import get
11 from xbmc
import translatePath
as tp
18 addon
= xbmcaddon
.Addon('plugin.video.the.daily.show')
19 pluginhandle
= int(sys
.argv
[1])
20 image_fanart
= tp(os
.path
.join(addon
.getAddonInfo('path'), 'fanart.jpg'))
21 image_fanart_search
= tp(
22 os
.path
.join(addon
.getAddonInfo('path'),
24 xbmcplugin
.setPluginFanart(pluginhandle
, image_fanart
, color2
='0xFFFF3300')
25 TVShowTitle
= 'The Daily Show'
27 if xbmcplugin
.getSetting(pluginhandle
, "sort") == '0':
29 elif xbmcplugin
.getSetting(pluginhandle
, "sort") == '1':
31 elif xbmcplugin
.getSetting(pluginhandle
, "sort") == '2':
37 def __init__(self
, data
):
41 raw_text
= self
.soup('a', {'class': 'full-episode-url'})[0].getText()
43 raw_text
= raw_text
.replace('Full Episode Available', '')
44 m
= re
.search(r
'(.*) - .*', raw_text
)
49 return self
.soup('span', {'class': 'title'})[0].getText().replace('Exclusive - ', '')
52 return self
.soup('a', {'class': 'imageHolder'})[0]['href']
57 def __init__(self
, rtmp
, bitrate
, width
, height
):
59 self
.bitrate
= bitrate
64 def fromUrlData(data
):
67 """width="([0-9]+).*height="([0-9]+).*bitrate="([0-9]+).*<src>(rtmpe[^<]+)</src>""",
71 (width
, height
, bitrate
, rtmp
) = m
.groups()
72 return Episode(rtmp
, int(bitrate
), int(width
), int(height
))
74 # this should not happen
78 return "rtmp: %s, bitrate: %i, width: %i, height: %i" % (self
.rtmp
,
79 self
.bitrate
, self
.width
, self
.height
)
82 def get_episodes(data
):
83 # split urldata into renditions
84 renditions
= re
.findall("(<rendition.*?</rendition>)", data
, re
.S
)
85 # return a list of episodes
86 return [Episode
.fromUrlData(r
) for r
in renditions
]
89 def get_settings_bitrate():
90 # map plugin settings to actual bitrates
91 setting2bitrate
= {'1': 400, '2': 750, '3': 1200, '4': 1700, '5': 2200,
93 setting
= xbmcplugin
.getSetting(pluginhandle
, "bitrate")
94 lbitrate
= setting2bitrate
.get(setting
, 0)
101 log('The Daily Show --> get_url :: url = ' + url
)
104 'Referer': 'http://thedailyshow.cc.com/',
105 'X-Forwarded-For': '12.13.14.15',
107 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US;rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 ( .NET CLR 3.5.30729)',
109 req
= urllib2
.Request(url
, txdata
, txheaders
)
110 response
= urllib2
.urlopen(req
)
111 link
= response
.read()
113 except urllib2
.URLError
as e
:
114 log('Error code: ', e
.code
)
120 def make_in_app_url(**kwargs
):
121 data
= json
.dumps(kwargs
)
122 quoted_data
= urllib2
.quote(data
)
123 url
= "{sysarg}?{data}".format(sysarg
=sys
.argv
[0], data
=quoted_data
)
127 def add_directory_entry(name
, identifier
):
128 """Adds a directory entry to the xbmc ListItem"""
129 url
= make_in_app_url(mode
=identifier
)
130 liz
= xbmcgui
.ListItem(name
, iconImage
="DefaultFolder.png")
131 liz
.setInfo(type="Video", infoLabels
={"Title": name
})
132 liz
.setProperty('fanart_image', image_fanart
)
133 xbmcplugin
.addDirectoryItem(handle
=pluginhandle
, url
=url
,
134 listitem
=liz
, isFolder
=True)
140 msg
= addon
.getLocalizedString(30030)
141 add_directory_entry(msg
, 'full')
142 #msg = addon.getLocalizedString(30032)
143 #add_directory_entry(msg, 'search')
144 #msg = addon.getLocalizedString(30033)
145 #add_directory_entry(msg, 'browse')
146 xbmcplugin
.endOfDirectory(pluginhandle
)
149 def show_message(title
, message
):
150 dialog
= xbmcgui
.Dialog()
151 dialog
.ok(' %s ' % title
, '%s ' % message
)
153 def show_error(message
):
154 show_message('Error', message
)
156 def full_episodes(**ignored
):
157 xbmcplugin
.setContent(pluginhandle
, 'episodes')
158 xbmcplugin
.addSortMethod(pluginhandle
, xbmcplugin
.SORT_METHOD_NONE
)
159 url
= 'http://thedailyshow.cc.com/full-episodes/'
160 # Due to unstructured daily show site, there is no canonical JSON url
161 # so we find the full episode json url presented on the latest full episode
162 soup
= BS3(get(url
).text
)
163 j
= soup
.head
.script
.text
.strip().strip(';').split('=', 1)[1]
166 for zone
, attribs
in jj
.get('manifest').get('zones').items():
167 feed
= attribs
.get('feed')
168 urls
= re
.compile(r
'http[^"]+/f1010/[^"]+').findall(feed
)
172 fallback_urls
= re
.compile(r
'http[^"]+/f1013/[^"]+').findall(feed
)
174 jsonurl
= fallback_urls
[0]
177 # give user feedback on problem here
178 errormsg
= addon
.getLocalizedString(30025)
181 jsonresponse
= json
.loads(get(jsonurl
).content
)
182 episodes
= jsonresponse
.get('result').get('episodes') or []
184 # give user feedback on problem here
185 errormsg
= addon
.getLocalizedString(30026)
188 for episode
in episodes
:
190 if len(episode
.get('images', ())) >= 1:
191 thumbnail
= episode
.get('images')[0].get('url')
192 airdate
= episode
.get('airDate', '0')
193 airdate
= datetime
.fromtimestamp(int(airdate
)).strftime('%Y-%m-%d')
194 liz
= xbmcgui
.ListItem(
195 episode
.get('title'),
196 iconImage
="DefaultFolder.png",
197 thumbnailImage
=thumbnail
)
199 type="Video", infoLabels
={"Title": episode
.get('title'),
201 episode
.get('description'),
202 "Season": episode
.get('season', {}).get('seasonNumber'),
203 "Episode": episode
.get('season', {}).get('episodeNumber'),
204 "premiered": airdate
,
205 "TVShowTitle": TVShowTitle
})
206 liz
.setProperty('IsPlayable', 'true')
207 liz
.setProperty('fanart_image', image_fanart
)
208 url
= make_in_app_url(
209 mode
="play_full_episode",
210 episode_id
=episode
.get('id'),
211 additional_data
=episode
,
213 log("make url: " + str(episode
))
214 xbmcplugin
.addDirectoryItem(handle
=pluginhandle
, url
=url
, listitem
=liz
)
216 xbmcplugin
.endOfDirectory(pluginhandle
)
221 def extract_search_results_from_response(response
):
222 """Creates an xbmc DirectoryItem with ListItems of videos extracted from
225 Returns False if there are no video urls found in the request.
226 Otherwise, returns True."""
228 soup
= BS3(response
.text
)
229 results
= soup
.find('div', {'class': 'search-results'})
234 for entry
in results
.findAll('div', {'class': 'entry'}):
235 video_page_url
= entry
.find('meta', dict(itemprop
="url"))['content']
236 name
= entry
.find('meta', dict(itemprop
="name"))['content']
237 description
= entry
.find(
239 dict(itemprop
="description"))['content']
240 thumbnail
= entry
.find(
242 dict(itemprop
="thumbnailUrl"))['content']
244 # strip unnecessary ?width= parameters that make the image too
246 thumbnail
= re
.search(r
"(.*)\?.*", thumbnail
).groups()[0]
248 duration_match
= re
.search(
251 dict(itemprop
="duration"))['content'])
253 duration
= duration_match
.groups()[0]
254 upload_date
= entry
.find(
256 dict(itemprop
="uploadDate"))['content']
258 url
= "{sysarg}?mode={mode}&name={name}&url={video_page_url}".format(
260 name
=urllib
.quote_plus(name
),
261 video_page_url
=urllib
.quote_plus(video_page_url
),
263 date_and_name
= "%s - %s" % (upload_date
.replace('-','/'), name
)
264 liz
= xbmcgui
.ListItem(date_and_name
, thumbnailImage
=thumbnail
)
265 liz
.setInfo(type="Video", infoLabels
={"Title": name
,
267 "premiered": upload_date
,
268 "aired": upload_date
,
269 "duration": duration
,
270 "TVShowTitle": TVShowTitle
272 liz
.setProperty('IsPlayable', 'true')
273 liz
.setProperty('fanart_image', image_fanart_search
)
274 xbmcplugin
.addDirectoryItem(handle
=pluginhandle
, url
=url
, listitem
=liz
)
279 def get_user_input(title
, default
="", hidden
=False):
280 """Display a virtual keyboard to the user"""
282 keyboard
= xbmc
.Keyboard(default
, title
)
283 keyboard
.setHiddenInput(hidden
)
286 if keyboard
.isConfirmed():
287 return keyboard
.getText()
292 def search(**ignored
):
293 msg
= addon
.getLocalizedString(30032)
294 query
= get_user_input(msg
)
299 "http://www.thedailyshow.com/videos",
300 params
=dict(term
=query
))
302 if extract_search_results_from_response(response
) is True:
303 xbmcplugin
.endOfDirectory(pluginhandle
)
305 mydialogue
= xbmcgui
.Dialog()
306 msg
= addon
.getLocalizedString(30022)
307 mydialogue
.ok(heading
=TVShowTitle
,
311 def browse(**ignored
):
312 """Browse videos by Date"""
313 mydialogue
= xbmcgui
.Dialog()
314 msg
= addon
.getLocalizedString(30020)
315 datestring
= mydialogue
.numeric(type=1, heading
=msg
)
320 day
, month
, year
= datestring
.split("/")
322 urlstring
= "http://www.thedailyshow.com/feeds/search" + \
323 "?startDate={year}-{month}-{day}&tags=&keywords=&sortOrder=desc&sortBy=date&page=1".format(
328 response
= get(urlstring
)
330 if extract_search_results_from_response(response
) is True:
331 xbmcplugin
.endOfDirectory(pluginhandle
)
333 mydialogue
= xbmcgui
.Dialog()
334 msg
= addon
.getLocalizedString(30021)
335 mydialogue
.ok(heading
=TVShowTitle
,
339 def play_full_episode(episode_id
, additional_data
, **ignored
):
340 content_id
= 'mgid:arc:episode:thedailyshow.com:%s' % episode_id
341 url
= 'http://thedailyshow.cc.com/feeds/mrss?uri=' + content_id
343 uris
= re
.compile('<guid isPermaLink="false">(.+?)</guid>').findall(data
)
344 stacked_url
= 'stack://'
346 rtmp
= grab_rtmp(uri
)
347 stacked_url
+= rtmp
.replace(',', ',,') + ' , '
348 stacked_url
= stacked_url
[:-3]
350 log('stacked_url --> %s' % stacked_url
)
352 item
= xbmcgui
.ListItem("ignored", path
=stacked_url
)
353 xbmcplugin
.setResolvedUrl(pluginhandle
, True, item
)
359 url
= 'http://thedailyshow.cc.com/feeds/mediagen/?uri=' + uri
360 mp4_url
= "http://mtvnmobile.vo.llnwd.net/kip0/_pxn=0+_pxK=18639+_pxE=/44620/mtvnorigin"
363 episodes
= get_episodes(data
)
365 # sort episodes by bitrate ascending
366 episodes
.sort(key
=lambda x
: x
.bitrate
)
368 # chose maximum bitrate by default
371 # check user settings
372 lbitrate
= get_settings_bitrate()
374 # use the largest bitrate smaller-or-equal to the user-chosen value
375 ep
= filter(lambda x
: x
.bitrate
<= lbitrate
, episodes
)[-1]
377 furl
= mp4_url
+ ep
.rtmp
.split('viacomccstrm')[2]
378 log('furl --> %s' % furl
)
384 "full": full_episodes
,
385 "play_full_episode": play_full_episode
,
391 decoded
= urllib2
.unquote(data
or "{}")
392 if len(decoded
) >= 1 and decoded
[0] == '?':
393 decoded
= decoded
[1:]
394 log('The Daily Show --> main :: decoded = ' + str(decoded
))
395 parsed_data
= json
.loads(decoded
)
396 mode
= parsed_data
.get('mode') or 'root'
397 mode_handlers
[mode
](**parsed_data
)