From 47c98633e6710f71bf8111aea4a63cd990c60e24 Mon Sep 17 00:00:00 2001 From: Justin Forest Date: Mon, 13 Oct 2008 17:28:44 +0400 Subject: [PATCH] YouTube integration. Links to YouTube profiles are converted to the corresponding RSS feeds (http://www.youtube.com/rssls), which aren't available with the standard feed discovery. Normal links to YouTube enclosures (*.swf) are now on-the-fly replaced with links to high quality MP4 videos. Apparently links to real enclosures are not permanent, so they can't be saved in the database. --- src/gpodder/cache.py | 11 ++++++++ src/gpodder/download.py | 15 +++++++--- src/gpodder/resolver.py | 73 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 src/gpodder/resolver.py diff --git a/src/gpodder/cache.py b/src/gpodder/cache.py index 70c881bb..2a41c2c3 100644 --- a/src/gpodder/cache.py +++ b/src/gpodder/cache.py @@ -28,9 +28,11 @@ import feedparser +import re import time import gpodder +from gpodder import resolver from gpodder.liblogger import log @@ -136,6 +138,15 @@ class Cache: found_alternate_feed = True break + # YouTube etc feed lookup (after the normal link lookup in case + # they provide a standard feed discovery mechanism in the future). + if not found_alternate_feed: + next = resolver.get_real_channel_url(url) + + if next is not None: + parsed_result = feedparser.parse(next, agent=self.user_agent, modified=modified, etag=etag) + found_alternate_feed = True + # We have not found a valid feed - abort here! if not found_alternate_feed: return (False, None) diff --git a/src/gpodder/download.py b/src/gpodder/download.py index d539cc6c..de7839dd 100644 --- a/src/gpodder/download.py +++ b/src/gpodder/download.py @@ -29,6 +29,7 @@ from gpodder.liblogger import log from gpodder.libgpodder import gl from gpodder import util from gpodder import services +from gpodder import resolver import gpodder import threading @@ -208,13 +209,19 @@ class DownloadThread(threading.Thread): return util.delete_file( self.tempname) - self.downloader.retrieve( self.episode.url, self.tempname, reporthook = self.status_updated) + (unused, headers) = self.downloader.retrieve( resolver.get_real_download_url(self.url), self.tempname, reporthook = self.status_updated) + + if 'content-type' in headers and headers['content-type'] != self.episode.mimetype: + log('Correcting mime type: %s => %s', self.episode.mimetype, headers['content-type']) + self.episode.mimetype = headers['content-type'] + # File names are constructed with regard to the mime type. + self.filename = self.episode.local_filename() + shutil.move( self.tempname, self.filename) - self.channel.addDownloadedItem( self.episode) - services.download_status_manager.download_completed(self.download_id) # Get the _real_ filesize once we actually have the file self.episode.length = os.path.getsize(self.filename) - self.episode.save() + self.channel.addDownloadedItem( self.episode) + services.download_status_manager.download_completed(self.download_id) # If a user command has been defined, execute the command setting some environment variables if len(gl.config.cmd_download_complete) > 0: diff --git a/src/gpodder/resolver.py b/src/gpodder/resolver.py new file mode 100644 index 00000000..4458afbc --- /dev/null +++ b/src/gpodder/resolver.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# +# gPodder - A media aggregator and podcast client +# Copyright (c) 2005-2008 Thomas Perl and the gPodder Team +# +# gPodder is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# gPodder is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# resolver.py -- YouTube and related magic +# Justin Forest 2008-10-13 +# +# TODO: +# +# * Channel covers. +# * Support for Vimeo, maybe blip.tv and others. + +import re +from gpodder.liblogger import log +from gpodder.util import proxy_request + +rr = {} + +def get_real_download_url(url, proxy=None): + if 'youtube-episode' not in rr: + rr['youtube-episode'] = re.compile('http://(?:[a-z]+\.)?youtube\.com/v/.*\.swf', re.IGNORECASE) + + if rr['youtube-episode'].match(url): + req = proxy_request(url, proxy) + + if 'location' in req.msg: + id, tag = (None, None) + + for part in req.msg['location'].split('&'): + if part.startswith('video_id='): + id = part[9:] + elif part.startswith('t='): + tag = part[2:] + + if id is not None and tag is not None: + next = 'http://www.youtube.com/get_video?video_id='+ id +'&t='+ tag +'&fmt=18' + log('YouTube link resolved: %s => %s', url, next) + return next + + return url + +def get_real_channel_url(url): + r = re.compile('http://(?:[a-z]+\.)?youtube\.com/user/([a-z0-9]+)', re.IGNORECASE) + m = r.match(url) + + if m is not None: + next = 'http://www.youtube.com/rss/user/'+ m.group(1) +'/videos.rss' + log('YouTube link resolved: %s => %s', url, next) + return next + + r = re.compile('http://(?:[a-z]+\.)?youtube\.com/profile?user=([a-z0-9]+)', re.IGNORECASE) + m = r.match(url) + + if m is not None: + next = 'http://www.youtube.com/rss/user/'+ m.group(1) +'/videos.rss' + log('YouTube link resolved: %s => %s', url, next) + return next + + return url -- 2.11.4.GIT