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
28 from gpodder
import model
29 from gpodder
import util
32 # For Python < 2.6, we use the "simplejson" add-on module
33 # XXX: Mark as dependency
34 import simplejson
as json
36 # Python 2.6 already ships with a nice "json" module
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).
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.
65 msg
= email
.message_from_string(s
)
67 value
= msg
.get_param(param
, header
=header
)
68 decoded_list
= email
.Header
.decode_header(value
)
70 for part
, encoding
in decoded_list
:
72 value
.append(part
.decode(encoding
))
74 value
.append(unicode(part
))
75 return u
''.join(value
)
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.
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
))
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
):
102 self
.cache
= json
.load(open(self
.cache_file
, 'r'))
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
]
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
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."""
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'])
138 url
= track
['download_url']
139 if url
not in self
.cache
:
141 self
.cache
[url
] = get_metadata(url
)
144 filesize
, filetype
, filename
= self
.cache
[url
]
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)),
159 class SoundcloudFeed(object):
160 URL_REGEX
= re
.compile('http://([a-z]+\.)?soundcloud\.com/([^/]+)', re
.I
)
163 def handle_url(cls
, url
):
164 m
= cls
.URL_REGEX
.match(url
)
166 subdomain
, username
= m
.groups()
169 def __init__(self
, username
):
170 self
.username
= username
171 self
.sc_user
= SoundcloudUser(username
)
174 return _('%s on Soundcloud') % self
.username
177 return self
.sc_user
.get_coverart()
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
]
190 episode
= model
.PodcastEpisode(channel
)
191 episode
.update_from_dict(track
)
196 # Register our URL handler
197 model
.register_custom_handler(SoundcloudFeed
)