Merge branch 'vgtv' of https://github.com/mrkolby/youtube-dl into mrkolby-vgtv
[youtube-dl] / youtube_dl / extractor / grooveshark.py
1 # coding: utf-8
2 from __future__ import unicode_literals
3
4 import time
5 import math
6 import os.path
7 import re
8
9
10 from .common import InfoExtractor
11 from ..utils import ExtractorError, compat_urllib_request, compat_html_parser
12
13 from ..utils import (
14     compat_urllib_parse,
15     compat_urlparse,
16 )
17
18
19 class GroovesharkHtmlParser(compat_html_parser.HTMLParser):
20     def __init__(self):
21         self._current_object = None
22         self.objects = []
23         compat_html_parser.HTMLParser.__init__(self)
24
25     def handle_starttag(self, tag, attrs):
26         attrs = dict((k, v) for k, v in attrs)
27         if tag == 'object':
28             self._current_object = {'attrs': attrs, 'params': []}
29         elif tag == 'param':
30             self._current_object['params'].append(attrs)
31
32     def handle_endtag(self, tag):
33         if tag == 'object':
34             self.objects.append(self._current_object)
35             self._current_object = None
36
37     @classmethod
38     def extract_object_tags(cls, html):
39         p = cls()
40         p.feed(html)
41         p.close()
42         return p.objects
43
44
45 class GroovesharkIE(InfoExtractor):
46     _VALID_URL = r'https?://(www\.)?grooveshark\.com/#!/s/([^/]+)/([^/]+)'
47     _TEST = {
48         'url': 'http://grooveshark.com/#!/s/Jolene+Tenth+Key+Remix+Ft+Will+Sessions/6SS1DW?src=5',
49         'md5': '7ecf8aefa59d6b2098517e1baa530023',
50         'info_dict': {
51             'id': '6SS1DW',
52             'title': 'Jolene (Tenth Key Remix ft. Will Sessions)',
53             'ext': 'mp3',
54             'duration': 227,
55         }
56     }
57
58     do_playerpage_request = True
59     do_bootstrap_request = True
60
61     def _parse_target(self, target):
62         uri = compat_urlparse.urlparse(target)
63         hash = uri.fragment[1:].split('?')[0]
64         token = os.path.basename(hash.rstrip('/'))
65         return (uri, hash, token)
66
67     def _build_bootstrap_url(self, target):
68         (uri, hash, token) = self._parse_target(target)
69         query = 'getCommunicationToken=1&hash=%s&%d' % (compat_urllib_parse.quote(hash, safe=''), self.ts)
70         return (compat_urlparse.urlunparse((uri.scheme, uri.netloc, '/preload.php', None, query, None)), token)
71
72     def _build_meta_url(self, target):
73         (uri, hash, token) = self._parse_target(target)
74         query = 'hash=%s&%d' % (compat_urllib_parse.quote(hash, safe=''), self.ts)
75         return (compat_urlparse.urlunparse((uri.scheme, uri.netloc, '/preload.php', None, query, None)), token)
76
77     def _build_stream_url(self, meta):
78         return compat_urlparse.urlunparse(('http', meta['streamKey']['ip'], '/stream.php', None, None, None))
79
80     def _build_swf_referer(self, target, obj):
81         (uri, _, _) = self._parse_target(target)
82         return compat_urlparse.urlunparse((uri.scheme, uri.netloc, obj['attrs']['data'], None, None, None))
83
84     def _transform_bootstrap(self, js):
85         return re.split('(?m)^\s*try\s*{', js)[0] \
86                  .split(' = ', 1)[1].strip().rstrip(';')
87
88     def _transform_meta(self, js):
89         return js.split('\n')[0].split('=')[1].rstrip(';')
90
91     def _get_meta(self, target):
92         (meta_url, token) = self._build_meta_url(target)
93         self.to_screen('Metadata URL: %s' % meta_url)
94
95         headers = {'Referer': compat_urlparse.urldefrag(target)[0]}
96         req = compat_urllib_request.Request(meta_url, headers=headers)
97         res = self._download_json(req, token,
98                                   transform_source=self._transform_meta)
99
100         if 'getStreamKeyWithSong' not in res:
101             raise ExtractorError(
102                 'Metadata not found. URL may be malformed, or Grooveshark API may have changed.')
103
104         if res['getStreamKeyWithSong'] is None:
105             raise ExtractorError(
106                 'Metadata download failed, probably due to Grooveshark anti-abuse throttling. Wait at least an hour before retrying from this IP.',
107                 expected=True)
108
109         return res['getStreamKeyWithSong']
110
111     def _get_bootstrap(self, target):
112         (bootstrap_url, token) = self._build_bootstrap_url(target)
113
114         headers = {'Referer': compat_urlparse.urldefrag(target)[0]}
115         req = compat_urllib_request.Request(bootstrap_url, headers=headers)
116         res = self._download_json(req, token, fatal=False,
117                                   note='Downloading player bootstrap data',
118                                   errnote='Unable to download player bootstrap data',
119                                   transform_source=self._transform_bootstrap)
120         return res
121
122     def _get_playerpage(self, target):
123         (_, _, token) = self._parse_target(target)
124
125         webpage = self._download_webpage(
126             target, token,
127             note='Downloading player page',
128             errnote='Unable to download player page',
129             fatal=False)
130
131         if webpage is not None:
132             # Search (for example German) error message
133             error_msg = self._html_search_regex(
134                 r'<div id="content">\s*<h2>(.*?)</h2>', webpage,
135                 'error message', default=None)
136             if error_msg is not None:
137                 error_msg = error_msg.replace('\n', ' ')
138                 raise ExtractorError('Grooveshark said: %s' % error_msg)
139
140         if webpage is not None:
141             o = GroovesharkHtmlParser.extract_object_tags(webpage)
142             return (webpage, [x for x in o if x['attrs']['id'] == 'jsPlayerEmbed'])
143
144         return (webpage, None)
145
146     def _real_initialize(self):
147         self.ts = int(time.time() * 1000)  # timestamp in millis
148
149     def _real_extract(self, url):
150         (target_uri, _, token) = self._parse_target(url)
151
152         # 1. Fill cookiejar by making a request to the player page
153         swf_referer = None
154         if self.do_playerpage_request:
155             (_, player_objs) = self._get_playerpage(url)
156             if player_objs is not None:
157                 swf_referer = self._build_swf_referer(url, player_objs[0])
158                 self.to_screen('SWF Referer: %s' % swf_referer)
159
160         # 2. Ask preload.php for swf bootstrap data to better mimic webapp
161         if self.do_bootstrap_request:
162             bootstrap = self._get_bootstrap(url)
163             self.to_screen('CommunicationToken: %s' % bootstrap['getCommunicationToken'])
164
165         # 3. Ask preload.php for track metadata.
166         meta = self._get_meta(url)
167
168         # 4. Construct stream request for track.
169         stream_url = self._build_stream_url(meta)
170         duration = int(math.ceil(float(meta['streamKey']['uSecs']) / 1000000))
171         post_dict = {'streamKey': meta['streamKey']['streamKey']}
172         post_data = compat_urllib_parse.urlencode(post_dict).encode('utf-8')
173         headers = {
174             'Content-Length': len(post_data),
175             'Content-Type': 'application/x-www-form-urlencoded'
176         }
177         if swf_referer is not None:
178             headers['Referer'] = swf_referer
179
180         return {
181             'id': token,
182             'title': meta['song']['Name'],
183             'http_method': 'POST',
184             'url': stream_url,
185             'ext': 'mp3',
186             'format': 'mp3 audio',
187             'duration': duration,
188             'http_post_data': post_data,
189             'http_headers': headers,
190         }