QML UI: Subscription UI and compatibility fixes
[gpodder.git] / src / gpodder / youtube.py
blob22bf30edd342d95e0e002b96fe0d96bfae9aa372
1 # -*- coding: utf-8 -*-
3 # gPodder - A media aggregator and podcast client
4 # Copyright (c) 2005-2011 Thomas Perl and the gPodder Team
6 # gPodder is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # gPodder is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 # gpodder.youtube - YouTube and related magic
20 # Justin Forest <justin.forest@gmail.com> 2008-10-13
24 import gpodder
26 from gpodder import util
27 from gpodder.liblogger import log
29 try:
30 import simplejson as json
31 except ImportError:
32 import json
34 import re
35 import urllib
37 # See http://en.wikipedia.org/wiki/YouTube#Quality_and_codecs
38 # Currently missing: the WebM 480p and 720 formats; 3GP profile
39 supported_formats = [
40 (37, '37/1920x1080/9/0/115', '1920x1080 (HD)'),
41 (22, '22/1280x720/9/0/115', '1280x720 (HD)'),
42 (35, '35/854x480/9/0/115', '854x480'),
43 (34, '34/640x360/9/0/115', '640x360'),
44 (18, '18/640x360/9/0/115', '640x360 (iPod)'),
45 (18, '18/480x360/9/0/115', '480x360 (iPod)'),
46 (5, '5/320x240/7/0/0', '320x240 (FLV)'),
49 class YouTubeError(Exception): pass
51 def get_real_download_url(url, preferred_fmt_id=None):
52 # Default fmt_id when none preferred
53 if preferred_fmt_id is None:
54 preferred_fmt_id = 18
56 # For Maemo 5, we force fmt_id 5 for performance reasons
57 if gpodder.ui.fremantle and not gpodder.ui.fermintle:
58 preferred_fmt_id = 5
60 vid = get_youtube_id(url)
61 if vid is not None:
62 page = None
63 url = 'http://www.youtube.com/watch?v=' + vid
65 while page is None:
66 req = util.http_request(url, method='GET')
67 if 'location' in req.msg:
68 url = req.msg['location']
69 else:
70 page = req.read()
72 # Try to find the best video format available for this video
73 # (http://forum.videohelp.com/topic336882-1800.html#1912972)
74 def find_urls(page):
75 r4 = re.search('.*"fmt_url_map"\:\s+"([^"]+)".*', page)
76 if r4 is not None:
77 fmt_url_map = r4.group(1)
78 for fmt_url_encoded in fmt_url_map.split(','):
79 fmt_url = urllib.unquote(fmt_url_encoded)
80 fmt_url = fmt_url.replace('\\/', '/').replace('\u0026', '&')
81 fmt_id, url = fmt_url.split('|', 2)
82 yield int(fmt_id), url
84 fmt_id_url_map = sorted(find_urls(page), reverse=True)
85 # Default to the highest fmt_id if we don't find a match below
86 if fmt_id_url_map:
87 default_fmt_id, default_url = fmt_id_url_map[0]
88 else:
89 raise YouTubeError('fmt_url_map not found for video ID "%s"' % vid)
91 formats_available = set(fmt_id for fmt_id, url in fmt_id_url_map)
92 fmt_id_url_map = dict(fmt_id_url_map)
94 if gpodder.ui.fremantle:
95 # This provides good quality video, seems to be always available
96 # and is playable fluently in Media Player
97 if preferred_fmt_id == 5:
98 fmt_id = 5
99 else:
100 fmt_id = 18
101 else:
102 # As a fallback, use fmt_id 18 (seems to be always available)
103 fmt_id = 18
105 # This will be set to True if the search below has already "seen"
106 # our preferred format, but has not yet found a suitable available
107 # format for the given video.
108 seen_preferred = False
110 for id, wanted, description in supported_formats:
111 # If we see our preferred format, accept formats below
112 if id == preferred_fmt_id:
113 seen_preferred = True
115 # If the format is available and preferred (or lower),
116 # use the given format for our fmt_id
117 if id in formats_available and seen_preferred:
118 log('Found available YouTube format: %s (fmt_id=%d)', \
119 description, id)
120 fmt_id = id
121 break
123 url = fmt_id_url_map.get(fmt_id, None)
124 if url is None:
125 url = default_url
127 return url
129 def get_youtube_id(url):
130 r = re.compile('http://(?:[a-z]+\.)?youtube\.com/v/(.*)\.swf', re.IGNORECASE).match(url)
131 if r is not None:
132 return r.group(1)
134 r = re.compile('http://(?:[a-z]+\.)?youtube\.com/watch\?v=([^&]*)', re.IGNORECASE).match(url)
135 if r is not None:
136 return r.group(1)
138 return None
140 def is_video_link(url):
141 return (get_youtube_id(url) is not None)
143 def get_real_channel_url(url):
144 r = re.compile('http://(?:[a-z]+\.)?youtube\.com/user/([a-z0-9]+)', re.IGNORECASE)
145 m = r.match(url)
147 if m is not None:
148 next = 'http://www.youtube.com/rss/user/'+ m.group(1) +'/videos.rss'
149 log('YouTube link resolved: %s => %s', url, next)
150 return next
152 r = re.compile('http://(?:[a-z]+\.)?youtube\.com/profile?user=([a-z0-9]+)', re.IGNORECASE)
153 m = r.match(url)
155 if m is not None:
156 next = 'http://www.youtube.com/rss/user/'+ m.group(1) +'/videos.rss'
157 log('YouTube link resolved: %s => %s', url, next)
158 return next
160 return url
162 def get_real_cover(url):
163 r = re.compile('http://www\.youtube\.com/rss/user/([^/]+)/videos\.rss', \
164 re.IGNORECASE)
165 m = r.match(url)
167 if m is not None:
168 username = m.group(1)
169 api_url = 'http://gdata.youtube.com/feeds/api/users/%s?v=2' % username
170 data = util.urlopen(api_url).read()
171 match = re.search('<media:thumbnail url=[\'"]([^\'"]+)[\'"]/>', data)
172 if match is not None:
173 log('YouTube userpic for %s is: %s', url, match.group(1))
174 return match.group(1)
176 return None
178 def find_youtube_channels(string):
179 url = 'http://gdata.youtube.com/feeds/api/videos?alt=json&q=%s' % urllib.quote(string, '')
180 data = json.load(util.urlopen(url))
182 class FakeImporter(object):
183 def __init__(self):
184 self.items = []
186 result = FakeImporter()
188 seen_users = set()
189 for entry in data['feed']['entry']:
190 user = entry['author'][0]['name']['$t']
191 title = entry['title']['$t']
192 url = 'http://www.youtube.com/rss/user/%s/videos.rss' % user
193 if user not in seen_users:
194 result.items.append({
195 'title': user,
196 'url': url,
197 'description': title
199 seen_users.add(user)
201 return result