teamcoco.py (7310B)
1 # coding: utf-8 2 from __future__ import unicode_literals 3 4 import json 5 6 from .turner import TurnerBaseIE 7 from ..utils import ( 8 determine_ext, 9 ExtractorError, 10 int_or_none, 11 mimetype2ext, 12 parse_duration, 13 parse_iso8601, 14 qualities, 15 ) 16 17 18 class TeamcocoIE(TurnerBaseIE): 19 _VALID_URL = r'https?://(?:\w+\.)?teamcoco\.com/(?P<id>([^/]+/)*[^/?#]+)' 20 _TESTS = [ 21 { 22 'url': 'http://teamcoco.com/video/mary-kay-remote', 23 'md5': '55d532f81992f5c92046ad02fec34d7d', 24 'info_dict': { 25 'id': '80187', 26 'ext': 'mp4', 27 'title': 'Conan Becomes A Mary Kay Beauty Consultant', 28 'description': 'Mary Kay is perhaps the most trusted name in female beauty, so of course Conan is a natural choice to sell their products.', 29 'duration': 495.0, 30 'upload_date': '20140402', 31 'timestamp': 1396407600, 32 } 33 }, { 34 'url': 'http://teamcoco.com/video/louis-ck-interview-george-w-bush', 35 'md5': 'cde9ba0fa3506f5f017ce11ead928f9a', 36 'info_dict': { 37 'id': '19705', 38 'ext': 'mp4', 39 'description': 'Louis C.K. got starstruck by George W. Bush, so what? Part one.', 40 'title': 'Louis C.K. Interview Pt. 1 11/3/11', 41 'duration': 288, 42 'upload_date': '20111104', 43 'timestamp': 1320405840, 44 } 45 }, { 46 'url': 'http://teamcoco.com/video/timothy-olyphant-drinking-whiskey', 47 'info_dict': { 48 'id': '88748', 49 'ext': 'mp4', 50 'title': 'Timothy Olyphant Raises A Toast To “Justified”', 51 'description': 'md5:15501f23f020e793aeca761205e42c24', 52 'upload_date': '20150415', 53 'timestamp': 1429088400, 54 }, 55 'params': { 56 'skip_download': True, # m3u8 downloads 57 } 58 }, { 59 'url': 'http://teamcoco.com/video/full-episode-mon-6-1-joel-mchale-jake-tapper-and-musical-guest-courtney-barnett?playlist=x;eyJ0eXBlIjoidGFnIiwiaWQiOjl9', 60 'info_dict': { 61 'id': '89341', 62 'ext': 'mp4', 63 'title': 'Full Episode - Mon. 6/1 - Joel McHale, Jake Tapper, And Musical Guest Courtney Barnett', 64 'description': 'Guests: Joel McHale, Jake Tapper, And Musical Guest Courtney Barnett', 65 }, 66 'params': { 67 'skip_download': True, # m3u8 downloads 68 }, 69 'skip': 'This video is no longer available.', 70 }, { 71 'url': 'http://teamcoco.com/video/the-conan-audiencey-awards-for-04/25/18', 72 'only_matching': True, 73 }, { 74 'url': 'http://teamcoco.com/italy/conan-jordan-schlansky-hit-the-streets-of-florence', 75 'only_matching': True, 76 }, { 77 'url': 'http://teamcoco.com/haiti/conan-s-haitian-history-lesson', 78 'only_matching': True, 79 }, { 80 'url': 'http://teamcoco.com/israel/conan-hits-the-streets-beaches-of-tel-aviv', 81 'only_matching': True, 82 }, { 83 'url': 'https://conan25.teamcoco.com/video/ice-cube-kevin-hart-conan-share-lyft', 84 'only_matching': True, 85 } 86 ] 87 _RECORD_TEMPL = '''id 88 title 89 teaser 90 publishOn 91 thumb { 92 preview 93 } 94 tags { 95 name 96 } 97 duration 98 turnerMediaId 99 turnerMediaAuthToken''' 100 101 def _graphql_call(self, query_template, object_type, object_id): 102 find_object = 'find' + object_type 103 return self._download_json( 104 'https://teamcoco.com/graphql', object_id, data=json.dumps({ 105 'query': query_template % (find_object, object_id) 106 }).encode(), headers={ 107 'Content-Type': 'application/json', 108 })['data'][find_object] 109 110 def _real_extract(self, url): 111 display_id = self._match_id(url) 112 113 response = self._graphql_call('''{ 114 %%s(slug: "%%s") { 115 ... on RecordSlug { 116 record { 117 %s 118 } 119 } 120 ... on PageSlug { 121 child { 122 id 123 } 124 } 125 ... on NotFoundSlug { 126 status 127 } 128 } 129 }''' % self._RECORD_TEMPL, 'Slug', display_id) 130 if response.get('status'): 131 raise ExtractorError('This video is no longer available.', expected=True) 132 133 child = response.get('child') 134 if child: 135 record = self._graphql_call('''{ 136 %%s(id: "%%s") { 137 ... on Video { 138 %s 139 } 140 } 141 }''' % self._RECORD_TEMPL, 'Record', child['id']) 142 else: 143 record = response['record'] 144 video_id = record['id'] 145 146 info = { 147 'id': video_id, 148 'display_id': display_id, 149 'title': record['title'], 150 'thumbnail': record.get('thumb', {}).get('preview'), 151 'description': record.get('teaser'), 152 'duration': parse_duration(record.get('duration')), 153 'timestamp': parse_iso8601(record.get('publishOn')), 154 } 155 156 media_id = record.get('turnerMediaId') 157 if media_id: 158 self._initialize_geo_bypass({ 159 'countries': ['US'], 160 }) 161 info.update(self._extract_ngtv_info(media_id, { 162 'accessToken': record['turnerMediaAuthToken'], 163 'accessTokenType': 'jws', 164 })) 165 else: 166 video_sources = self._download_json( 167 'https://teamcoco.com/_truman/d/' + video_id, 168 video_id)['meta']['src'] 169 if isinstance(video_sources, dict): 170 video_sources = video_sources.values() 171 172 formats = [] 173 get_quality = qualities(['low', 'sd', 'hd', 'uhd']) 174 for src in video_sources: 175 if not isinstance(src, dict): 176 continue 177 src_url = src.get('src') 178 if not src_url: 179 continue 180 format_id = src.get('label') 181 ext = determine_ext(src_url, mimetype2ext(src.get('type'))) 182 if format_id == 'hls' or ext == 'm3u8': 183 # compat_urllib_parse.urljoin does not work here 184 if src_url.startswith('/'): 185 src_url = 'http://ht.cdn.turner.com/tbs/big/teamcoco' + src_url 186 formats.extend(self._extract_m3u8_formats( 187 src_url, video_id, 'mp4', m3u8_id=format_id, fatal=False)) 188 else: 189 if src_url.startswith('/mp4:protected/'): 190 # TODO Correct extraction for these files 191 continue 192 tbr = int_or_none(self._search_regex( 193 r'(\d+)k\.mp4', src_url, 'tbr', default=None)) 194 195 formats.append({ 196 'url': src_url, 197 'ext': ext, 198 'tbr': tbr, 199 'format_id': format_id, 200 'quality': get_quality(format_id), 201 }) 202 self._sort_formats(formats) 203 info['formats'] = formats 204 205 return info