Merge branch 'patch-2' of https://github.com/steven7851/youtube-dl into steven7851...
[youtube-dl] / youtube_dl / extractor / douyutv.py
1 # coding: utf-8
2 from __future__ import unicode_literals
3
4 import hashlib
5 import time
6 import uuid
7 from .common import InfoExtractor
8 from ..utils import (ExtractorError, unescapeHTML)
9 from ..compat import (compat_str, compat_basestring, compat_urllib_parse_urlencode)
10
11
12 class DouyuTVIE(InfoExtractor):
13     IE_DESC = '斗鱼'
14     _VALID_URL = r'https?://(?:www\.)?douyu(?:tv)?\.com/(?P<id>[A-Za-z0-9]+)'
15     _TESTS = [{
16         'url': 'http://www.douyutv.com/iseven',
17         'info_dict': {
18             'id': '17732',
19             'display_id': 'iseven',
20             'ext': 'flv',
21             'title': 're:^清晨醒脑!T-ara根本停不下来! [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
22             'description': 're:.*m7show@163\.com.*',
23             'thumbnail': 're:^https?://.*\.jpg$',
24             'uploader': '7师傅',
25             'is_live': True,
26         },
27         'params': {
28             'skip_download': True,
29         },
30     }, {
31         'url': 'http://www.douyutv.com/85982',
32         'info_dict': {
33             'id': '85982',
34             'display_id': '85982',
35             'ext': 'flv',
36             'title': 're:^小漠从零单排记!——CSOL2躲猫猫 [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
37             'description': 'md5:746a2f7a253966a06755a912f0acc0d2',
38             'thumbnail': 're:^https?://.*\.jpg$',
39             'uploader': 'douyu小漠',
40             'is_live': True,
41         },
42         'params': {
43             'skip_download': True,
44         },
45         'skip': 'Room not found',
46     }, {
47         'url': 'http://www.douyutv.com/17732',
48         'info_dict': {
49             'id': '17732',
50             'display_id': '17732',
51             'ext': 'flv',
52             'title': 're:^清晨醒脑!T-ara根本停不下来! [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
53             'description': 're:.*m7show@163\.com.*',
54             'thumbnail': 're:^https?://.*\.jpg$',
55             'uploader': '7师傅',
56             'is_live': True,
57         },
58         'params': {
59             'skip_download': True,
60         },
61     }, {
62         'url': 'http://www.douyu.com/xiaocang',
63         'only_matching': True,
64     }]
65
66     def _real_extract(self, url):
67         video_id = self._match_id(url)
68
69         if video_id.isdigit():
70             room_id = video_id
71         else:
72             page = self._download_webpage(url, video_id)
73             room_id = self._html_search_regex(
74                 r'"room_id"\s*:\s*(\d+),', page, 'room id')
75
76         room_url = 'http://m.douyu.com/html5/live?roomId=%s' % room_id
77         room_content = self._download_webpage(room_url, video_id)
78         room_json = self._parse_json(room_content, video_id, fatal=False)
79
80         room = room_json['data']
81
82         show_status = room.get('show_status')
83         # 1 = live, 2 = offline
84         if show_status == '2':
85             raise ExtractorError(
86                 'Live stream is offline', expected=True)
87
88         flv_json = None
89         # Douyu API sometimes returns error "Unable to load the requested class: eticket_redis_cache"
90         # Retry with different parameters - same parameters cause same errors
91         for i in range(5):
92             tt = int(time.time() / 60)
93             did = uuid.uuid4().hex.upper()
94
95             # Decompile core.swf in webpage by ffdec "Search SWFs in memory"
96             # core.swf is encrypted originally, but ffdec can dump memory to get the decrypted one
97             # If API changes in the future, just use this way to update
98             sign_content = '{room_id}{did}A12Svb&%1UUmf@hC{tt}'.format(room_id = room_id, did = did, tt = tt)
99             sign = hashlib.md5((sign_content).encode('utf-8')).hexdigest()
100
101             payload = {'cdn': 'ws', 'rate': '0', 'tt': tt, 'did': did, 'sign': sign}
102             flv_data = compat_urllib_parse_urlencode(payload)
103
104             flv_request_url = 'http://www.douyu.com/lapi/live/getPlay/%s' % room_id
105             flv_content = self._download_webpage(flv_request_url, video_id, data=flv_data,
106                 headers={'Content-Type': 'application/x-www-form-urlencoded'})
107             try:
108                 flv_json = self._parse_json(flv_content, video_id, fatal=False)
109             except ExtractorError:
110                 # Wait some time before retrying to get a different time() value
111                 self._sleep(1, video_id, msg_template='%(video_id)s: Error occurs. '
112                                                       'Waiting for %(timeout)s seconds before retrying')
113                 continue
114             else:
115                 break
116         if flv_json is None:
117             raise ExtractorError('Unable to fetch API result')
118
119         flv = flv_json['data']
120
121         error_code = flv_json.get('error', 0)
122         if error_code is not 0:
123             error_desc = 'Server reported error %i' % error_code
124             if isinstance(flv, (compat_str, compat_basestring)):
125                 error_desc += ': ' + flv
126             raise ExtractorError(error_desc, expected=True)
127
128         base_url = flv['rtmp_url']
129         live_path = flv['rtmp_live']
130
131         video_url = '%s/%s' % (base_url, live_path)
132
133         title = self._live_title(unescapeHTML(room['room_name']))
134         description = room.get('notice')
135         thumbnail = room.get('room_src')
136         uploader = room.get('nickname')
137
138         return {
139             'id': room_id,
140             'display_id': video_id,
141             'url': video_url,
142             'title': title,
143             'description': description,
144             'thumbnail': thumbnail,
145             'uploader': uploader,
146             'is_live': True,
147         }