metacafe.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. from __future__ import unicode_literals
  2. import re
  3. from .common import InfoExtractor
  4. from ..compat import (
  5. compat_parse_qs,
  6. compat_urllib_parse_unquote,
  7. )
  8. from ..utils import (
  9. determine_ext,
  10. ExtractorError,
  11. int_or_none,
  12. urlencode_postdata,
  13. get_element_by_attribute,
  14. mimetype2ext,
  15. )
  16. class MetacafeIE(InfoExtractor):
  17. _VALID_URL = r'https?://(?:www\.)?metacafe\.com/watch/(?P<video_id>[^/]+)/(?P<display_id>[^/?#]+)'
  18. _DISCLAIMER = 'http://www.metacafe.com/family_filter/'
  19. _FILTER_POST = 'http://www.metacafe.com/f/index.php?inputType=filter&controllerGroup=user'
  20. IE_NAME = 'metacafe'
  21. _TESTS = [
  22. # Youtube video
  23. {
  24. 'add_ie': ['Youtube'],
  25. 'url': 'http://metacafe.com/watch/yt-_aUehQsCQtM/the_electric_company_short_i_pbs_kids_go/',
  26. 'info_dict': {
  27. 'id': '_aUehQsCQtM',
  28. 'ext': 'mp4',
  29. 'upload_date': '20090102',
  30. 'title': 'The Electric Company | "Short I" | PBS KIDS GO!',
  31. 'description': 'md5:2439a8ef6d5a70e380c22f5ad323e5a8',
  32. 'uploader': 'PBS',
  33. 'uploader_id': 'PBS'
  34. }
  35. },
  36. # Normal metacafe video
  37. {
  38. 'url': 'http://www.metacafe.com/watch/11121940/news_stuff_you_wont_do_with_your_playstation_4/',
  39. 'md5': '6e0bca200eaad2552e6915ed6fd4d9ad',
  40. 'info_dict': {
  41. 'id': '11121940',
  42. 'ext': 'mp4',
  43. 'title': 'News: Stuff You Won\'t Do with Your PlayStation 4',
  44. 'uploader': 'ign',
  45. 'description': 'Sony released a massive FAQ on the PlayStation Blog detailing the PS4\'s capabilities and limitations.',
  46. },
  47. 'skip': 'Page is temporarily unavailable.',
  48. },
  49. # metacafe video with family filter
  50. {
  51. 'url': 'http://www.metacafe.com/watch/2155630/adult_art_by_david_hart_156/',
  52. 'md5': 'b06082c5079bbdcde677a6291fbdf376',
  53. 'info_dict': {
  54. 'id': '2155630',
  55. 'ext': 'mp4',
  56. 'title': 'Adult Art By David Hart #156',
  57. 'uploader': 'hartistry',
  58. 'description': 'Adult Art By David Hart. All the Art Works presented here are not in the possession of the American Artist, David John Hart. The paintings are in collections worldwide of individuals, countries, art museums, foundations and charities.',
  59. }
  60. },
  61. # AnyClip video
  62. {
  63. 'url': 'http://www.metacafe.com/watch/an-dVVXnuY7Jh77J/the_andromeda_strain_1971_stop_the_bomb_part_3/',
  64. 'info_dict': {
  65. 'id': 'an-dVVXnuY7Jh77J',
  66. 'ext': 'mp4',
  67. 'title': 'The Andromeda Strain (1971): Stop the Bomb Part 3',
  68. 'uploader': 'AnyClip',
  69. 'description': 'md5:cbef0460d31e3807f6feb4e7a5952e5b',
  70. },
  71. },
  72. # age-restricted video
  73. {
  74. 'url': 'http://www.metacafe.com/watch/5186653/bbc_internal_christmas_tape_79_uncensored_outtakes_etc/',
  75. 'md5': '98dde7c1a35d02178e8ab7560fe8bd09',
  76. 'info_dict': {
  77. 'id': '5186653',
  78. 'ext': 'mp4',
  79. 'title': 'BBC INTERNAL Christmas Tape \'79 - UNCENSORED Outtakes, Etc.',
  80. 'uploader': 'Dwayne Pipe',
  81. 'description': 'md5:950bf4c581e2c059911fa3ffbe377e4b',
  82. 'age_limit': 18,
  83. },
  84. },
  85. # cbs video
  86. {
  87. 'url': 'http://www.metacafe.com/watch/cb-8VD4r_Zws8VP/open_this_is_face_the_nation_february_9/',
  88. 'info_dict': {
  89. 'id': '8VD4r_Zws8VP',
  90. 'ext': 'flv',
  91. 'title': 'Open: This is Face the Nation, February 9',
  92. 'description': 'md5:8a9ceec26d1f7ed6eab610834cc1a476',
  93. 'duration': 96,
  94. 'uploader': 'CBSI-NEW',
  95. 'upload_date': '20140209',
  96. 'timestamp': 1391959800,
  97. },
  98. 'params': {
  99. # rtmp download
  100. 'skip_download': True,
  101. },
  102. },
  103. # Movieclips.com video
  104. {
  105. 'url': 'http://www.metacafe.com/watch/mv-Wy7ZU/my_week_with_marilyn_do_you_love_me/',
  106. 'info_dict': {
  107. 'id': 'mv-Wy7ZU',
  108. 'ext': 'mp4',
  109. 'title': 'My Week with Marilyn - Do You Love Me?',
  110. 'description': 'From the movie My Week with Marilyn - Colin (Eddie Redmayne) professes his love to Marilyn (Michelle Williams) and gets her to promise to return to set and finish the movie.',
  111. 'uploader': 'movie_trailers',
  112. 'duration': 176,
  113. },
  114. 'params': {
  115. 'skip_download': 'requires rtmpdump',
  116. }
  117. }
  118. ]
  119. def report_disclaimer(self):
  120. self.to_screen('Retrieving disclaimer')
  121. def _confirm_age(self):
  122. # Retrieve disclaimer
  123. self.report_disclaimer()
  124. self._download_webpage(self._DISCLAIMER, None, False, 'Unable to retrieve disclaimer')
  125. # Confirm age
  126. self.report_age_confirmation()
  127. self._download_webpage(
  128. self._FILTER_POST, None, False, 'Unable to confirm age',
  129. data=urlencode_postdata({
  130. 'filters': '0',
  131. 'submit': "Continue - I'm over 18",
  132. }), headers={
  133. 'Content-Type': 'application/x-www-form-urlencoded',
  134. })
  135. def _real_extract(self, url):
  136. # Extract id and simplified title from URL
  137. video_id, display_id = re.match(self._VALID_URL, url).groups()
  138. # the video may come from an external site
  139. m_external = re.match(r'^(\w{2})-(.*)$', video_id)
  140. if m_external is not None:
  141. prefix, ext_id = m_external.groups()
  142. # Check if video comes from YouTube
  143. if prefix == 'yt':
  144. return self.url_result('http://www.youtube.com/watch?v=%s' % ext_id, 'Youtube')
  145. # CBS videos use theplatform.com
  146. if prefix == 'cb':
  147. return self.url_result('theplatform:%s' % ext_id, 'ThePlatform')
  148. # self._confirm_age()
  149. # AnyClip videos require the flashversion cookie so that we get the link
  150. # to the mp4 file
  151. headers = {}
  152. headers['Cookie'] = 'user=%7B%22ffilter%22%3Afalse%7D;';
  153. if video_id.startswith('an-'):
  154. headers['Cookie'] += ' flashVersion=0;'
  155. # Retrieve video webpage to extract further information
  156. webpage = self._download_webpage(url, video_id, headers=headers)
  157. error = get_element_by_attribute(
  158. 'class', 'notfound-page-title', webpage)
  159. if error:
  160. raise ExtractorError(error, expected=True)
  161. video_title = self._html_search_meta(
  162. ['og:title', 'twitter:title'], webpage, 'title', default=None) or self._search_regex(r'<h1>(.*?)</h1>', webpage, 'title')
  163. # Extract URL, uploader and title from webpage
  164. self.report_extraction(video_id)
  165. video_url = None
  166. mobj = re.search(r'(?m)&(?:media|video)URL=([^&]+)', webpage)
  167. if mobj is not None:
  168. mediaURL = compat_urllib_parse_unquote(mobj.group(1))
  169. video_ext = determine_ext(mediaURL)
  170. # Extract gdaKey if available
  171. mobj = re.search(r'(?m)&gdaKey=(.*?)&', webpage)
  172. if mobj is None:
  173. video_url = mediaURL
  174. else:
  175. gdaKey = mobj.group(1)
  176. video_url = '%s?__gda__=%s' % (mediaURL, gdaKey)
  177. if video_url is None:
  178. mobj = re.search(r'<video src="([^"]+)"', webpage)
  179. if mobj:
  180. video_url = mobj.group(1)
  181. video_ext = 'mp4'
  182. if video_url is None:
  183. flashvars = self._search_regex(
  184. r' name="flashvars" value="(.*?)"', webpage, 'flashvars',
  185. default=None)
  186. if flashvars:
  187. vardict = compat_parse_qs(flashvars)
  188. if 'mediaData' not in vardict:
  189. raise ExtractorError('Unable to extract media URL')
  190. mobj = re.search(
  191. r'"mediaURL":"(?P<mediaURL>http.*?)",(.*?)"key":"(?P<key>.*?)"', vardict['mediaData'][0])
  192. if mobj is None:
  193. raise ExtractorError('Unable to extract media URL')
  194. mediaURL = mobj.group('mediaURL').replace('\\/', '/')
  195. video_url = '%s?__gda__=%s' % (mediaURL, mobj.group('key'))
  196. video_ext = determine_ext(video_url)
  197. if video_url is None:
  198. player_url = self._search_regex(
  199. r"swfobject\.embedSWF\('([^']+)'",
  200. webpage, 'config URL', default=None)
  201. if player_url:
  202. config_url = self._search_regex(
  203. r'config=(.+)$', player_url, 'config URL')
  204. config_doc = self._download_xml(
  205. config_url, video_id,
  206. note='Downloading video config')
  207. smil_url = config_doc.find('.//properties').attrib['smil_file']
  208. smil_doc = self._download_xml(
  209. smil_url, video_id,
  210. note='Downloading SMIL document')
  211. base_url = smil_doc.find('./head/meta').attrib['base']
  212. video_url = []
  213. for vn in smil_doc.findall('.//video'):
  214. br = int(vn.attrib['system-bitrate'])
  215. play_path = vn.attrib['src']
  216. video_url.append({
  217. 'format_id': 'smil-%d' % br,
  218. 'url': base_url,
  219. 'play_path': play_path,
  220. 'page_url': url,
  221. 'player_url': player_url,
  222. 'ext': play_path.partition(':')[0],
  223. })
  224. if video_url is None:
  225. flashvars = self._parse_json(self._search_regex(
  226. r'flashvars\s*=\s*({.*});', webpage, 'flashvars',
  227. default=None), video_id, fatal=False)
  228. if flashvars:
  229. video_url = []
  230. for source in flashvars.get('sources'):
  231. source_url = source.get('src')
  232. if not source_url:
  233. continue
  234. ext = mimetype2ext(source.get('type')) or determine_ext(source_url)
  235. if ext == 'm3u8':
  236. video_url.extend(self._extract_m3u8_formats(
  237. source_url, video_id, 'mp4',
  238. 'm3u8_native', m3u8_id='hls', fatal=False))
  239. else:
  240. video_url.append({
  241. 'url': source_url,
  242. 'ext': ext,
  243. })
  244. if video_url is None:
  245. raise ExtractorError('Unsupported video type')
  246. description = self._html_search_meta(
  247. ['og:description', 'twitter:description', 'description'],
  248. webpage, 'title', fatal=False)
  249. thumbnail = self._html_search_meta(
  250. ['og:image', 'twitter:image'], webpage, 'title', fatal=False)
  251. video_uploader = self._html_search_regex(
  252. r'submitter=(.*?);|googletag\.pubads\(\)\.setTargeting\("(?:channel|submiter)","([^"]+)"\);',
  253. webpage, 'uploader nickname', fatal=False)
  254. duration = int_or_none(
  255. self._html_search_meta('video:duration', webpage, default=None))
  256. age_limit = (
  257. 18
  258. if re.search(r'(?:"contentRating":|"rating",)"restricted"', webpage)
  259. else 0)
  260. if isinstance(video_url, list):
  261. formats = video_url
  262. else:
  263. formats = [{
  264. 'url': video_url,
  265. 'ext': video_ext,
  266. }]
  267. self._sort_formats(formats)
  268. return {
  269. 'id': video_id,
  270. 'display_id': display_id,
  271. 'description': description,
  272. 'uploader': video_uploader,
  273. 'title': video_title,
  274. 'thumbnail': thumbnail,
  275. 'age_limit': age_limit,
  276. 'formats': formats,
  277. 'duration': duration,
  278. }