| 81 | | ('VPODCAST_DIR', None, 'Directory for downloaded podcasts') |
| | 92 | ('VPODCAST_DIR', os.path.join(config.FREEVO_CACHEDIR, 'vposcasts'), 'Directory for downloaded podcasts'), |
| | 93 | ('YOUTUBE_USERNAME', None, 'YouTube user name (optional)'), |
| | 94 | ('YOUTUBE_PASSWORD', None, 'YouTube password (optional)'), |
| | 95 | ('YOUTUBE_FORMAT', '18', 'YouTube format 18=high 17=mobile (optional)'), |
| | 96 | ('VPODCAST_BUFFERING_TIME', 20, 'Length of time to wait while buffering the poscast'), |
| | 97 | ('VPODCAST_BUFFERING_SIZE', 20*1024, 'size of downloaded file to start playing'), |
| 96 | | pc_dir = config.VPODCAST_DIR + '/' + pcdir[0] |
| 97 | | if not os.path.isdir(pc_dir): |
| 98 | | os.makedirs(pc_dir) |
| 99 | | |
| 100 | | |
| 101 | | class VPVideoItem(VideoItem): |
| 102 | | """ |
| 103 | | Video podcast video item |
| 104 | | """ |
| 105 | | def __init__(self, name, url, parent): |
| 106 | | """ Initialise the VPVideoItem class """ |
| 107 | | self.vp_url = url |
| 108 | | url = name |
| 109 | | VideoItem.__init__(self, name, parent) |
| 110 | | |
| 111 | | |
| 112 | | def play(self, arg=None, menuw=None): |
| 113 | | """ Play this Podcast""" |
| 114 | | |
| 115 | | # play the item. |
| 116 | | isYT = self.vp_url.find('youtube.com') #YouTube podcast |
| 117 | | isMC = self.vp_url.find('metacafe.com') #Metacafe podcast |
| 118 | | |
| 119 | | if isYT != -1: |
| 120 | | self.download_url = self.youtube(self.vp_url) |
| 121 | | |
| 122 | | elif isMC != -1: |
| 123 | | self.download_url = self.metacafe(self.vp_url) |
| 124 | | |
| 125 | | else: |
| 126 | | self.download_url = self.vp_url |
| 127 | | |
| 128 | | if not os.path.exists(self.filename): |
| 129 | | background = BGDownload(self.download_url, self.filename) |
| 130 | | background.start() |
| 131 | | popup = PopupBox(text=_('Buffering podcast...')) |
| 132 | | popup.show() |
| 133 | | time.sleep(20) # 20s. buffering time |
| 134 | | popup.destroy() |
| 135 | | |
| 136 | | # call the play funuction of VideoItem |
| 137 | | VideoItem.play(self, menuw=menuw, arg=arg) |
| 138 | | |
| 139 | | |
| 140 | | def youtube(self, url): |
| 141 | | const_video_url_str = 'http://www.youtube.com/watch?v=%s' |
| 142 | | const_video_url_re = re.compile( |
| 143 | | r'^((?:http://)?(?:\w+\.)?youtube\.com/(?:v/|(?:watch(?:\.php)?)?\?(?:.+&)?v=))?([0-9A-Za-z_-]+)(?(1)[&/].*)?$') |
| 144 | | const_url_t_param_re = re.compile(r', "t": "([^"]+)"') |
| 145 | | const_video_url_real_str = 'http://www.youtube.com/get_video?video_id=%s&t=%s' |
| 146 | | const_video_title_re = re.compile(r'<title>YouTube - ([^<]*)</title>', re.M | re.I) |
| 147 | | try: |
| 148 | | video_url_mo = const_video_url_re.match(url) |
| 149 | | video_url_id = video_url_mo.group(2) |
| 150 | | video_url = const_video_url_str % video_url_id |
| 151 | | # Retrieve video webpage |
| 152 | | video_webpage = urllib.urlopen(video_url).read() |
| 153 | | match = const_url_t_param_re.search(video_webpage) |
| 154 | | if match is None: |
| 155 | | _debug_('No matches found for youtube', DWARNING) |
| 156 | | video_url_t_param = match.group(1) |
| 157 | | # Retrieve real video URL |
| 158 | | video_url_real = const_video_url_real_str % (video_url_id, video_url_t_param) |
| 159 | | return video_url_real |
| 160 | | except Exception, why: |
| 161 | | _debug_('Cannot read youtube URL: %s' (why,), DWARNING) |
| 162 | | |
| 163 | | |
| 164 | | def metacafe(self, url): |
| 165 | | video_url = url |
| 166 | | const_video_url_re = re.compile(r'(?:http://)?(?:www\.)?metacafe\.com/watch/([^/]+)/([^/]+/)?.*') |
| 167 | | const_normalized_url_str = 'http://www.metacafe.com/watch/%s/' |
| 168 | | const_age_post_data = r'allowAdultContent=1&submit=Continue+-+I%27m+over+18' |
| 169 | | const_video_mediaurl_re = re.compile(r'&mediaURL=([^&]+)&', re.M) |
| 170 | | |
| 171 | | try: |
| 172 | | # Verify video URL format and extract URL data to normalize URL |
| 173 | | video_url_mo = const_video_url_re.match(video_url) |
| 174 | | if video_url_mo is None: |
| 175 | | _debug_('No matches found for metacafe', DWARNING) |
| 176 | | video_url_id = video_url_mo.group(1) |
| 177 | | video_url_title = (video_url_mo.group(2) is not None) and video_url_mo.group(2)[:-1] or None |
| 178 | | video_url = const_normalized_url_str % video_url_id |
| 179 | | |
| 180 | | # Retrieve video webpage |
| 181 | | video_webpage = urllib.urlopen(video_url).read() |
| 182 | | # Retrieve real video URL |
| 183 | | video_url_real = self.extract_step('Extracting real video URL', 'unable to extract real video URL', \ |
| 184 | | const_video_mediaurl_re, video_webpage) |
| 185 | | return video_url_real |
| 186 | | except Exception, why: |
| 187 | | _debug_('Cannot read metacafe URL: %s' (why,), DWARNING) |
| 188 | | |
| 189 | | |
| 190 | | def extract_step(self, step_title, step_error, regexp, data): |
| 191 | | try: |
| 192 | | match = regexp.search(data) |
| 193 | | if match is None: |
| 194 | | error_advice_exit(step_error) |
| 195 | | |
| 196 | | extracted_data = match.group(1) |
| 197 | | |
| 198 | | return extracted_data |
| 199 | | |
| 200 | | except KeyboardInterrupt: |
| 201 | | sys.exit('\n') |
| | 114 | podcastdir = os.path.join(config.VPODCAST_DIR, pcdir[0].strip().replace(' ', '_').lower()) |
| | 115 | if not os.path.isdir(podcastdir): |
| | 116 | os.makedirs(podcastdir) |
| 238 | | url = p.link |
| 239 | | name = p.title |
| 240 | | if url != 'ERROR': |
| 241 | | isYT = url.find('youtube.com') |
| 242 | | isMC = url.find('metacafe.com') |
| 243 | | if isYT == -1 and isMC == -1: |
| 244 | | file_ext = '.avi' |
| 245 | | else: |
| 246 | | file_ext = '.flv' |
| 247 | | |
| 248 | | filename = config.VPODCAST_DIR + '/' + arg[0] + '/' + name + file_ext |
| 249 | | podcast_items += [menu.MenuItem(_(p.title), \ |
| 250 | | action=VPVideoItem(filename, url, self), arg=None, image=image)] |
| 251 | | |
| 252 | | popup.destroy() |
| 253 | | if (len(podcast_items) == 0): |
| 254 | | podcast_items += [menu.MenuItem(_('No Podcast locations found'), |
| 255 | | menwu.goto_prev_page, 0)] |
| | 157 | |
| | 158 | podcast_items = [] |
| | 159 | for item in feed.entries: |
| | 160 | #pprint(item) #DJW |
| | 161 | try: |
| | 162 | item_title = p.rss_item_title(item) |
| | 163 | item_image = p.rss_item_image(item) |
| | 164 | item_link = p.rss_item_link(item) |
| | 165 | isYT = item_link.find('youtube.com') |
| | 166 | isMC = item_link.find('metacafe.com') |
| | 167 | name = item_title.strip().replace(' ', '_').lower() |
| | 168 | file_ext = isYT >= 0 and config.YOUTUBE_FORMAT == '18' and '.mp4' \ |
| | 169 | or isYT >= 0 and config.YOUTUBE_FORMAT == '17' and '.3gp' \ |
| | 170 | or isMC >= 0 and '.flv' \ |
| | 171 | or '.avi' |
| | 172 | filename = os.path.join(config.VPODCAST_DIR, podcastdir, name+file_ext) |
| | 173 | podcast_items += [ menu.MenuItem(item_title, action=VPVideoItem(filename, item_link, self), |
| | 174 | arg=None, image=image) ] |
| | 175 | except PodcastException: |
| | 176 | pass |
| | 177 | if not podcast_items: |
| | 178 | podcast_items += [menu.MenuItem(_('No Podcast locations found'), menuw.goto_prev_page, 0)] |
| | 179 | finally: |
| | 180 | popup.destroy() |
| | 181 | |
| 268 | | for location in config.VPODCAST_LOCATIONS: |
| 269 | | url = location[1] |
| 270 | | image_path = config.VPODCAST_DIR + '/' + location[0] + '/' + 'cover.jpg' |
| 271 | | if self.check_logo(image_path): |
| 272 | | p = podcast() |
| 273 | | p.open_rss(url) |
| 274 | | p.rss_title() |
| 275 | | name = p.rss_title |
| 276 | | try: |
| 277 | | image_url = p.rss_image |
| 278 | | self.download(image_url, image_path) |
| 279 | | except: |
| 280 | | _debug_('No image in RSS', DINFO) |
| 281 | | |
| 282 | | if (len(config.VPODCAST_DIR) == 0): |
| 283 | | podcast_items += [menu.MenuItem(_('Set VPODCAST_DIR in local_conf.py'), menwu.goto_prev_page, 0)] |
| 284 | | |
| 285 | | podcast_menu_items += [menu.MenuItem(_(location[0]), action=self.create_podcast_submenu, \ |
| 286 | | arg=location, image=image_path)] |
| | 195 | for dir, url in config.VPODCAST_LOCATIONS: |
| | 196 | podcastdir = dir.strip().replace(' ', '_').lower() |
| | 197 | image_path = os.path.join(config.VPODCAST_DIR, podcastdir, 'cover.jpg') |
| | 198 | #XXX here we are only downloading the images, but this slows down the plug-in lots |
| | 199 | #if self.check_logo(image_path): |
| | 200 | # p = Podcast() |
| | 201 | # p.open_rss(url) |
| | 202 | # p.rss_title() |
| | 203 | # #XXX name = p.rss_title |
| | 204 | # if p.rss_imageurl: |
| | 205 | # try: |
| | 206 | # image_url = p.rss_imageurl |
| | 207 | # self.download(image_url, image_path) |
| | 208 | # except Exception, why: |
| | 209 | # _debug_(why, DWARNING) |
| | 210 | |
| | 211 | podcast_menu_items += [ menu.MenuItem(dir, action=self.create_podcast_submenu, |
| | 212 | arg=(dir, url), image=image_path) ] |
| | 263 | if self.rss.feed.has_key('title'): |
| | 264 | return self.rss.feed.title.encode(self.encoding) |
| | 265 | return None |
| | 266 | |
| | 267 | |
| | 268 | @benchmark(benchmarking) |
| | 269 | def rss_description(self): |
| | 270 | if self.rss.feed.has_key('description'): |
| | 271 | return self.rss.feed.description.encode(self.encoding) |
| | 272 | return None |
| | 273 | |
| | 274 | |
| | 275 | @benchmark(benchmarking) |
| | 276 | def rss_image(self): |
| | 277 | if self.rss.feed.has_key('image') and self.rss.feed.image.has_key('url'): |
| | 278 | return self.rss.feed.image.url |
| | 279 | return None |
| | 280 | |
| | 281 | |
| | 282 | @benchmark(benchmarking) |
| | 283 | def rss_item_title(self, item): |
| | 284 | """ get the item's title """ |
| | 285 | self.title = item.title.encode(self.encoding) |
| | 286 | self.title = re.sub('(/)', '_', self.title) |
| | 287 | return self.title |
| | 288 | |
| | 289 | |
| | 290 | @benchmark(benchmarking) |
| | 291 | def rss_item_link(self, item): |
| | 292 | """ get the item's link """ |
| | 293 | self.link = item.link.encode(self.encoding) |
| | 294 | return self.link |
| | 295 | |
| | 296 | |
| | 297 | @benchmark(benchmarking) |
| | 298 | def rss_item_image(self, item): |
| | 299 | """ get the item's image """ |
| | 300 | # Search for image |
| | 301 | #img_pattern = '<img src="(.*?)" align=' |
| | 302 | img_pattern = 'img src="(.*?)"' |
| 326 | | self.rss_title = self.rss.feed.title.encode(self.encoding) |
| 327 | | |
| 328 | | #self.rss_date = self.rss.feed.date |
| 329 | | except: |
| 330 | | _debug_('No title in rss feed', DINFO) |
| 331 | | self.rss_title = None |
| 332 | | |
| 333 | | try: |
| 334 | | self.rss_description = self.rss.feed.description.encode(self.encoding) |
| 335 | | except: |
| 336 | | _debug_('No description in rss feed', DINFO) |
| 337 | | |
| 338 | | try: |
| 339 | | self.rss_image = self.rss.feed.image.url |
| 340 | | except: |
| 341 | | self.rss_image = None |
| 342 | | |
| 343 | | |
| 344 | | def rss_count(self): |
| 345 | | self.rss.count = len(self.rss.entries) |
| 346 | | |
| 347 | | |
| 348 | | def rss_item(self, item=0): |
| 349 | | |
| 350 | | try: |
| 351 | | self.title = self.rss.entries[item].title.encode(self.encoding) |
| 352 | | self.title = re.sub('(/)','_',self.title) |
| 353 | | description_all = self.rss.entries[item].description |
| 354 | | self.link = self.rss.entries[item].link |
| 355 | | # Search for image |
| 356 | | #img_pattern = '<img src="(.*?)" align=' |
| 357 | | img_pattern = 'img src="(.*?)"' |
| 358 | | try: |
| 359 | | self.image = re.search(img_pattern, description_all).group(1) |
| 360 | | except: |
| 361 | | self.image = None |
| 362 | | |
| 363 | | except: |
| 364 | | pass |
| 365 | | |
| 366 | | if self.link == None: |
| 367 | | self.link = 'ERROR' |
| | 304 | self.image = re.search(img_pattern, item.description).group(1) |
| | 305 | except Exception, why: |
| | 306 | self.image = None |
| | 307 | return self.image |
| | 308 | |
| | 309 | |
| | 310 | |
| | 311 | class VPVideoItem(VideoItem): |
| | 312 | """ |
| | 313 | Video podcast video item |
| | 314 | """ |
| | 315 | @benchmark(benchmarking) |
| | 316 | def __init__(self, name, url, parent): |
| | 317 | """ Initialise the VPVideoItem class """ |
| | 318 | _debug_('VPVideoItem.__init__(name=%r, url=%r, parent=%r)' % (name, url, parent), 2) |
| | 319 | VideoItem.__init__(self, name, parent) |
| | 320 | self.vp_url = url |
| | 321 | |
| | 322 | |
| | 323 | @benchmark(benchmarking) |
| | 324 | def play(self, arg=None, menuw=None): |
| | 325 | """ |
| | 326 | Play this Podcast |
| | 327 | """ |
| | 328 | self.download_url = self.vp_url |
| | 329 | if not os.path.exists(self.filename): |
| | 330 | background = BGDownload(self.download_url, self.filename) |
| | 331 | background.start() |
| | 332 | popup = PopupBox(text=_('Buffering podcast...')) |
| | 333 | popup.show() |
| | 334 | size = 0 |
| | 335 | for i in range(int(config.VPODCAST_BUFFERING_TIME)): |
| | 336 | if os.path.exists(self.filename): |
| | 337 | mode, ino, dev, nlink, uid, gui, size, atime, mtime, ctime = os.stat(self.filename) |
| | 338 | if size > config.VPODCAST_BUFFERING_SIZE: |
| | 339 | break |
| | 340 | time.sleep(0.5) |
| | 341 | else: |
| | 342 | if size < 120 * 1024: # a bit arbitary, needs to be bigger than a web page |
| | 343 | popup.destroy() |
| | 344 | AlertBox(text=_('Cannot download %s') % self.filename).show() |
| | 345 | return |
| | 346 | popup.destroy() |
| | 347 | |
| | 348 | # call the play funuction of VideoItem |
| | 349 | VideoItem.play(self, menuw=menuw, arg=arg) |
| 383 | | file = urllib2.urlopen(self.url) |
| 384 | | info = file.info() |
| 385 | | save = open(self.savefile, 'wb') |
| 386 | | chunkSize = 25 |
| 387 | | totalBytes = int(info['Content-Length']) |
| 388 | | downloadBytes = 0 |
| 389 | | bytesLeft = totalBytes |
| 390 | | while bytesLeft > 0: |
| 391 | | chunk = file.read(chunkSize) |
| 392 | | readBytes = len(chunk) |
| 393 | | downloadBytes += readBytes |
| 394 | | bytesLeft -= readBytes |
| 395 | | save.write(chunk) |
| 396 | | except Exception, why: |
| 397 | | _debug_('Cannot download "%s": %s' % (self.url, why), DWARNING) |
| | 370 | fd = youtube.FileDownloader({ |
| | 371 | 'usenetrc': False, |
| | 372 | 'username': config.YOUTUBE_USERNAME, |
| | 373 | 'password': config.YOUTUBE_PASSWORD, |
| | 374 | 'quiet': True, |
| | 375 | 'forceurl': False, |
| | 376 | 'forcetitle': False, |
| | 377 | 'simulate': False, |
| | 378 | 'format': config.YOUTUBE_FORMAT, |
| | 379 | #'outtmpl': u'%(stitle)s-%(id)s.%(ext)s', |
| | 380 | 'outtmpl': self.savefile, |
| | 381 | 'ignoreerrors': False, |
| | 382 | 'ratelimit': None, |
| | 383 | }) |
| | 384 | fd.add_info_extractor(self.youtube_pl_ie) |
| | 385 | fd.add_info_extractor(self.metacafe_ie) |
| | 386 | fd.add_info_extractor(self.youtube_ie) |
| | 387 | retcode = fd.download([self.url]) |
| | 388 | _debug_('youtube download "%s": %s' % (self.url, retcode), DINFO) |
| | 389 | except youtube.DownloadError: |
| | 390 | # Not sure about this code |
| | 391 | try: |
| | 392 | file = urllib2.urlopen(self.url) |
| | 393 | info = file.info() |
| | 394 | save = open(self.savefile, 'wb') |
| | 395 | chunkSize = 25 |
| | 396 | totalBytes = int(info['Content-Length']) |
| | 397 | downloadBytes = 0 |
| | 398 | bytesLeft = totalBytes |
| | 399 | while bytesLeft > 0: |
| | 400 | chunk = file.read(chunkSize) |
| | 401 | readBytes = len(chunk) |
| | 402 | downloadBytes += readBytes |
| | 403 | bytesLeft -= readBytes |
| | 404 | save.write(chunk) |
| | 405 | except Exception, why: |
| | 406 | _debug_('Cannot download "%s": %s' % (self.url, why), DWARNING) |