Cleaned-up and re-designed preferences dialog
[gpodder.git] / src / gpodder / soundcloud.py
blobd7cb8267d2f1530d0b44251afca7369e26c539b7
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
4 # gPodder - A media aggregator and podcast client
5 # Copyright (c) 2005-2010 Thomas Perl and the gPodder Team
7 # gPodder is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # gPodder is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 # Soundcloud.com API client module for gPodder
22 # Thomas Perl <thpinfo.com>; 2009-11-03
24 import gpodder
26 _ = gpodder.gettext
28 from gpodder import model
29 from gpodder import util
31 try:
32 # For Python < 2.6, we use the "simplejson" add-on module
33 # XXX: Mark as dependency
34 import simplejson as json
35 except ImportError:
36 # Python 2.6 already ships with a nice "json" module
37 import json
39 import os
40 import urllib2
41 import sys
42 import time
44 import re
45 import email
46 import email.Header
49 def soundcloud_parsedate(s):
50 """Parse a string into a unix timestamp
52 Only strings provided by Soundcloud's API are
53 parsed with this function (2009/11/03 13:37:00).
54 """
55 m = re.match(r'(\d{4})/(\d{2})/(\d{2}) (\d{2}):(\d{2}):(\d{2})', s)
56 return time.mktime([int(x) for x in m.groups()]+[0, 0, -1])
58 def get_param(s, param='filename', header='content-disposition'):
59 """Get a parameter from a string of headers
61 By default, this gets the "filename" parameter of
62 the content-disposition header. This works fine
63 for downloads from Soundcloud.
64 """
65 msg = email.message_from_string(s)
66 if header in msg:
67 value = msg.get_param(param, header=header)
68 decoded_list = email.Header.decode_header(value)
69 value = []
70 for part, encoding in decoded_list:
71 if encoding:
72 value.append(part.decode(encoding))
73 else:
74 value.append(unicode(part))
75 return u''.join(value)
77 return None
79 def get_metadata(url):
80 """Get file download metadata
82 Returns a (size, type, name) from the given download
83 URL. Will use the network connection to determine the
84 metadata via the HTTP header fields.
85 """
86 track_fp = util.urlopen(url)
87 headers = track_fp.info()
88 filesize = headers['content-length'] or '0'
89 filetype = headers['content-type'] or 'application/octet-stream'
90 headers_s = '\n'.join('%s:%s'%(k,v) for k, v in headers.items())
91 filename = get_param(headers_s) or os.path.basename(os.path.dirname(url))
92 track_fp.close()
93 return filesize, filetype, filename
96 class SoundcloudUser(object):
97 def __init__(self, username):
98 self.username = username
99 self.cache_file = os.path.join(gpodder.home, 'soundcloud.cache')
100 if os.path.exists(self.cache_file):
101 try:
102 self.cache = json.load(open(self.cache_file, 'r'))
103 except:
104 self.cache = {}
105 else:
106 self.cache = {}
108 def commit_cache(self):
109 json.dump(self.cache, open(self.cache_file, 'w'))
111 def get_coverart(self):
112 key = ':'.join((self.username, 'avatar_url'))
113 if key in self.cache:
114 return self.cache[key]
116 image = None
117 try:
118 json_url = 'http://api.soundcloud.com/users/%s.json' % self.username
119 user_info = json.load(util.urlopen(json_url))
120 image = user_info.get('avatar_url', None)
121 self.cache[key] = image
122 finally:
123 self.commit_cache()
125 return image
127 def get_tracks(self):
128 """Get a generator of tracks from a SC user
130 The generator will give you a dictionary for every
131 track it can find for its user."""
132 try:
133 json_url = 'http://api.soundcloud.com/users/%s/tracks.json' % self.username
134 tracks = (track for track in json.load(util.urlopen(json_url)) \
135 if track['downloadable'])
137 for track in tracks:
138 url = track['download_url']
139 if url not in self.cache:
140 try:
141 self.cache[url] = get_metadata(url)
142 except:
143 continue
144 filesize, filetype, filename = self.cache[url]
146 yield {
147 'title': track.get('title', track.get('permalink', _('Unknown track'))),
148 'link': track.get('permalink_url', 'http://soundcloud.com/'+self.username),
149 'description': track.get('description', _('No description available')),
150 'url': track['download_url'],
151 'length': int(filesize),
152 'mimetype': filetype,
153 'guid': track.get('permalink', track.get('id')),
154 'pubDate': soundcloud_parsedate(track.get('created_at', None)),
156 finally:
157 self.commit_cache()
159 class SoundcloudFeed(object):
160 URL_REGEX = re.compile('http://([a-z]+\.)?soundcloud\.com/([^/]+)', re.I)
162 @classmethod
163 def handle_url(cls, url):
164 m = cls.URL_REGEX.match(url)
165 if m is not None:
166 subdomain, username = m.groups()
167 return cls(username)
169 def __init__(self, username):
170 self.username = username
171 self.sc_user = SoundcloudUser(username)
173 def get_title(self):
174 return _('%s on Soundcloud') % self.username
176 def get_image(self):
177 return self.sc_user.get_coverart()
179 def get_link(self):
180 return 'http://soundcloud.com/%s' % self.username
182 def get_description(self):
183 return _('Tracks published by %s on Soundcloud.') % self.username
185 def get_new_episodes(self, channel, guids):
186 tracks = [t for t in self.sc_user.get_tracks() \
187 if t['guid'] not in guids]
189 for track in tracks:
190 episode = model.PodcastEpisode(channel)
191 episode.update_from_dict(track)
192 episode.save()
194 return len(tracks)
196 # Register our URL handler
197 model.register_custom_handler(SoundcloudFeed)