Merge branch 'ceskatelevizesrt' of https://github.com/oskar456/youtube-dl into oskar4...
[youtube-dl] / youtube_dl / extractor / common.py
1 from __future__ import unicode_literals
2
3 import base64
4 import datetime
5 import hashlib
6 import json
7 import netrc
8 import os
9 import re
10 import socket
11 import sys
12 import time
13 import xml.etree.ElementTree
14
15 from ..compat import (
16     compat_cookiejar,
17     compat_http_client,
18     compat_urllib_error,
19     compat_urllib_parse_urlparse,
20     compat_urlparse,
21     compat_str,
22 )
23 from ..utils import (
24     clean_html,
25     compiled_regex_type,
26     ExtractorError,
27     float_or_none,
28     int_or_none,
29     RegexNotFoundError,
30     sanitize_filename,
31     unescapeHTML,
32 )
33 _NO_DEFAULT = object()
34
35
36 class InfoExtractor(object):
37     """Information Extractor class.
38
39     Information extractors are the classes that, given a URL, extract
40     information about the video (or videos) the URL refers to. This
41     information includes the real video URL, the video title, author and
42     others. The information is stored in a dictionary which is then
43     passed to the YoutubeDL. The YoutubeDL processes this
44     information possibly downloading the video to the file system, among
45     other possible outcomes.
46
47     The type field determines the the type of the result.
48     By far the most common value (and the default if _type is missing) is
49     "video", which indicates a single video.
50
51     For a video, the dictionaries must include the following fields:
52
53     id:             Video identifier.
54     title:          Video title, unescaped.
55
56     Additionally, it must contain either a formats entry or a url one:
57
58     formats:        A list of dictionaries for each format available, ordered
59                     from worst to best quality.
60
61                     Potential fields:
62                     * url        Mandatory. The URL of the video file
63                     * ext        Will be calculated from url if missing
64                     * format     A human-readable description of the format
65                                  ("mp4 container with h264/opus").
66                                  Calculated from the format_id, width, height.
67                                  and format_note fields if missing.
68                     * format_id  A short description of the format
69                                  ("mp4_h264_opus" or "19").
70                                 Technically optional, but strongly recommended.
71                     * format_note Additional info about the format
72                                  ("3D" or "DASH video")
73                     * width      Width of the video, if known
74                     * height     Height of the video, if known
75                     * resolution Textual description of width and height
76                     * tbr        Average bitrate of audio and video in KBit/s
77                     * abr        Average audio bitrate in KBit/s
78                     * acodec     Name of the audio codec in use
79                     * asr        Audio sampling rate in Hertz
80                     * vbr        Average video bitrate in KBit/s
81                     * fps        Frame rate
82                     * vcodec     Name of the video codec in use
83                     * container  Name of the container format
84                     * filesize   The number of bytes, if known in advance
85                     * filesize_approx  An estimate for the number of bytes
86                     * player_url SWF Player URL (used for rtmpdump).
87                     * protocol   The protocol that will be used for the actual
88                                  download, lower-case.
89                                  "http", "https", "rtsp", "rtmp", "m3u8" or so.
90                     * preference Order number of this format. If this field is
91                                  present and not None, the formats get sorted
92                                  by this field, regardless of all other values.
93                                  -1 for default (order by other properties),
94                                  -2 or smaller for less than default.
95                                  < -1000 to hide the format (if there is
96                                     another one which is strictly better)
97                     * language_preference  Is this in the correct requested
98                                  language?
99                                  10 if it's what the URL is about,
100                                  -1 for default (don't know),
101                                  -10 otherwise, other values reserved for now.
102                     * quality    Order number of the video quality of this
103                                  format, irrespective of the file format.
104                                  -1 for default (order by other properties),
105                                  -2 or smaller for less than default.
106                     * source_preference  Order number for this video source
107                                   (quality takes higher priority)
108                                  -1 for default (order by other properties),
109                                  -2 or smaller for less than default.
110                     * http_referer  HTTP Referer header value to set.
111                     * http_method  HTTP method to use for the download.
112                     * http_headers  A dictionary of additional HTTP headers
113                                  to add to the request.
114                     * http_post_data  Additional data to send with a POST
115                                  request.
116     url:            Final video URL.
117     ext:            Video filename extension.
118     format:         The video format, defaults to ext (used for --get-format)
119     player_url:     SWF Player URL (used for rtmpdump).
120
121     The following fields are optional:
122
123     alt_title:      A secondary title of the video.
124     display_id      An alternative identifier for the video, not necessarily
125                     unique, but available before title. Typically, id is
126                     something like "4234987", title "Dancing naked mole rats",
127                     and display_id "dancing-naked-mole-rats"
128     thumbnails:     A list of dictionaries, with the following entries:
129                         * "url"
130                         * "width" (optional, int)
131                         * "height" (optional, int)
132                         * "resolution" (optional, string "{width}x{height"},
133                                         deprecated)
134     thumbnail:      Full URL to a video thumbnail image.
135     description:    Full video description.
136     uploader:       Full name of the video uploader.
137     timestamp:      UNIX timestamp of the moment the video became available.
138     upload_date:    Video upload date (YYYYMMDD).
139                     If not explicitly set, calculated from timestamp.
140     uploader_id:    Nickname or id of the video uploader.
141     location:       Physical location where the video was filmed.
142     subtitles:      The subtitle file contents as a dictionary in the format
143                     {language: subtitles}.
144     duration:       Length of the video in seconds, as an integer.
145     view_count:     How many users have watched the video on the platform.
146     like_count:     Number of positive ratings of the video
147     dislike_count:  Number of negative ratings of the video
148     comment_count:  Number of comments on the video
149     age_limit:      Age restriction for the video, as an integer (years)
150     webpage_url:    The url to the video webpage, if given to youtube-dl it
151                     should allow to get the same result again. (It will be set
152                     by YoutubeDL if it's missing)
153     categories:     A list of categories that the video falls in, for example
154                     ["Sports", "Berlin"]
155     is_live:        True, False, or None (=unknown). Whether this video is a
156                     live stream that goes on instead of a fixed-length video.
157
158     Unless mentioned otherwise, the fields should be Unicode strings.
159
160     Unless mentioned otherwise, None is equivalent to absence of information.
161
162
163     _type "playlist" indicates multiple videos.
164     There must be a key "entries", which is a list, an iterable, or a PagedList
165     object, each element of which is a valid dictionary by this specification.
166
167     Additionally, playlists can have "title" and "id" attributes with the same
168     semantics as videos (see above).
169
170
171     _type "multi_video" indicates that there are multiple videos that
172     form a single show, for examples multiple acts of an opera or TV episode.
173     It must have an entries key like a playlist and contain all the keys
174     required for a video at the same time.
175
176
177     _type "url" indicates that the video must be extracted from another
178     location, possibly by a different extractor. Its only required key is:
179     "url" - the next URL to extract.
180     The key "ie_key" can be set to the class name (minus the trailing "IE",
181     e.g. "Youtube") if the extractor class is known in advance.
182     Additionally, the dictionary may have any properties of the resolved entity
183     known in advance, for example "title" if the title of the referred video is
184     known ahead of time.
185
186
187     _type "url_transparent" entities have the same specification as "url", but
188     indicate that the given additional information is more precise than the one
189     associated with the resolved URL.
190     This is useful when a site employs a video service that hosts the video and
191     its technical metadata, but that video service does not embed a useful
192     title, description etc.
193
194
195     Subclasses of this one should re-define the _real_initialize() and
196     _real_extract() methods and define a _VALID_URL regexp.
197     Probably, they should also be added to the list of extractors.
198
199     Finally, the _WORKING attribute should be set to False for broken IEs
200     in order to warn the users and skip the tests.
201     """
202
203     _ready = False
204     _downloader = None
205     _WORKING = True
206
207     def __init__(self, downloader=None):
208         """Constructor. Receives an optional downloader."""
209         self._ready = False
210         self.set_downloader(downloader)
211
212     @classmethod
213     def suitable(cls, url):
214         """Receives a URL and returns True if suitable for this IE."""
215
216         # This does not use has/getattr intentionally - we want to know whether
217         # we have cached the regexp for *this* class, whereas getattr would also
218         # match the superclass
219         if '_VALID_URL_RE' not in cls.__dict__:
220             cls._VALID_URL_RE = re.compile(cls._VALID_URL)
221         return cls._VALID_URL_RE.match(url) is not None
222
223     @classmethod
224     def _match_id(cls, url):
225         if '_VALID_URL_RE' not in cls.__dict__:
226             cls._VALID_URL_RE = re.compile(cls._VALID_URL)
227         m = cls._VALID_URL_RE.match(url)
228         assert m
229         return m.group('id')
230
231     @classmethod
232     def working(cls):
233         """Getter method for _WORKING."""
234         return cls._WORKING
235
236     def initialize(self):
237         """Initializes an instance (authentication, etc)."""
238         if not self._ready:
239             self._real_initialize()
240             self._ready = True
241
242     def extract(self, url):
243         """Extracts URL information and returns it in list of dicts."""
244         self.initialize()
245         return self._real_extract(url)
246
247     def set_downloader(self, downloader):
248         """Sets the downloader for this IE."""
249         self._downloader = downloader
250
251     def _real_initialize(self):
252         """Real initialization process. Redefine in subclasses."""
253         pass
254
255     def _real_extract(self, url):
256         """Real extraction process. Redefine in subclasses."""
257         pass
258
259     @classmethod
260     def ie_key(cls):
261         """A string for getting the InfoExtractor with get_info_extractor"""
262         return cls.__name__[:-2]
263
264     @property
265     def IE_NAME(self):
266         return type(self).__name__[:-2]
267
268     def _request_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True):
269         """ Returns the response handle """
270         if note is None:
271             self.report_download_webpage(video_id)
272         elif note is not False:
273             if video_id is None:
274                 self.to_screen('%s' % (note,))
275             else:
276                 self.to_screen('%s: %s' % (video_id, note))
277         try:
278             return self._downloader.urlopen(url_or_request)
279         except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
280             if errnote is False:
281                 return False
282             if errnote is None:
283                 errnote = 'Unable to download webpage'
284             errmsg = '%s: %s' % (errnote, compat_str(err))
285             if fatal:
286                 raise ExtractorError(errmsg, sys.exc_info()[2], cause=err)
287             else:
288                 self._downloader.report_warning(errmsg)
289                 return False
290
291     def _download_webpage_handle(self, url_or_request, video_id, note=None, errnote=None, fatal=True):
292         """ Returns a tuple (page content as string, URL handle) """
293         # Strip hashes from the URL (#1038)
294         if isinstance(url_or_request, (compat_str, str)):
295             url_or_request = url_or_request.partition('#')[0]
296
297         urlh = self._request_webpage(url_or_request, video_id, note, errnote, fatal)
298         if urlh is False:
299             assert not fatal
300             return False
301         content = self._webpage_read_content(urlh, url_or_request, video_id, note, errnote, fatal)
302         return (content, urlh)
303
304     def _webpage_read_content(self, urlh, url_or_request, video_id, note=None, errnote=None, fatal=True, prefix=None):
305         content_type = urlh.headers.get('Content-Type', '')
306         webpage_bytes = urlh.read()
307         if prefix is not None:
308             webpage_bytes = prefix + webpage_bytes
309         m = re.match(r'[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+\s*;\s*charset=(.+)', content_type)
310         if m:
311             encoding = m.group(1)
312         else:
313             m = re.search(br'<meta[^>]+charset=[\'"]?([^\'")]+)[ /\'">]',
314                           webpage_bytes[:1024])
315             if m:
316                 encoding = m.group(1).decode('ascii')
317             elif webpage_bytes.startswith(b'\xff\xfe'):
318                 encoding = 'utf-16'
319             else:
320                 encoding = 'utf-8'
321         if self._downloader.params.get('dump_intermediate_pages', False):
322             try:
323                 url = url_or_request.get_full_url()
324             except AttributeError:
325                 url = url_or_request
326             self.to_screen('Dumping request to ' + url)
327             dump = base64.b64encode(webpage_bytes).decode('ascii')
328             self._downloader.to_screen(dump)
329         if self._downloader.params.get('write_pages', False):
330             try:
331                 url = url_or_request.get_full_url()
332             except AttributeError:
333                 url = url_or_request
334             basen = '%s_%s' % (video_id, url)
335             if len(basen) > 240:
336                 h = '___' + hashlib.md5(basen.encode('utf-8')).hexdigest()
337                 basen = basen[:240 - len(h)] + h
338             raw_filename = basen + '.dump'
339             filename = sanitize_filename(raw_filename, restricted=True)
340             self.to_screen('Saving request to ' + filename)
341             # Working around MAX_PATH limitation on Windows (see
342             # http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)
343             if os.name == 'nt':
344                 absfilepath = os.path.abspath(filename)
345                 if len(absfilepath) > 259:
346                     filename = '\\\\?\\' + absfilepath
347             with open(filename, 'wb') as outf:
348                 outf.write(webpage_bytes)
349
350         try:
351             content = webpage_bytes.decode(encoding, 'replace')
352         except LookupError:
353             content = webpage_bytes.decode('utf-8', 'replace')
354
355         if ('<title>Access to this site is blocked</title>' in content and
356                 'Websense' in content[:512]):
357             msg = 'Access to this webpage has been blocked by Websense filtering software in your network.'
358             blocked_iframe = self._html_search_regex(
359                 r'<iframe src="([^"]+)"', content,
360                 'Websense information URL', default=None)
361             if blocked_iframe:
362                 msg += ' Visit %s for more details' % blocked_iframe
363             raise ExtractorError(msg, expected=True)
364
365         return content
366
367     def _download_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True):
368         """ Returns the data of the page as a string """
369         res = self._download_webpage_handle(url_or_request, video_id, note, errnote, fatal)
370         if res is False:
371             return res
372         else:
373             content, _ = res
374             return content
375
376     def _download_xml(self, url_or_request, video_id,
377                       note='Downloading XML', errnote='Unable to download XML',
378                       transform_source=None, fatal=True):
379         """Return the xml as an xml.etree.ElementTree.Element"""
380         xml_string = self._download_webpage(
381             url_or_request, video_id, note, errnote, fatal=fatal)
382         if xml_string is False:
383             return xml_string
384         if transform_source:
385             xml_string = transform_source(xml_string)
386         return xml.etree.ElementTree.fromstring(xml_string.encode('utf-8'))
387
388     def _download_json(self, url_or_request, video_id,
389                        note='Downloading JSON metadata',
390                        errnote='Unable to download JSON metadata',
391                        transform_source=None,
392                        fatal=True):
393         json_string = self._download_webpage(
394             url_or_request, video_id, note, errnote, fatal=fatal)
395         if (not fatal) and json_string is False:
396             return None
397         return self._parse_json(
398             json_string, video_id, transform_source=transform_source, fatal=fatal)
399
400     def _parse_json(self, json_string, video_id, transform_source=None, fatal=True):
401         if transform_source:
402             json_string = transform_source(json_string)
403         try:
404             return json.loads(json_string)
405         except ValueError as ve:
406             errmsg = '%s: Failed to parse JSON ' % video_id
407             if fatal:
408                 raise ExtractorError(errmsg, cause=ve)
409             else:
410                 self.report_warning(errmsg + str(ve))
411
412     def report_warning(self, msg, video_id=None):
413         idstr = '' if video_id is None else '%s: ' % video_id
414         self._downloader.report_warning(
415             '[%s] %s%s' % (self.IE_NAME, idstr, msg))
416
417     def to_screen(self, msg):
418         """Print msg to screen, prefixing it with '[ie_name]'"""
419         self._downloader.to_screen('[%s] %s' % (self.IE_NAME, msg))
420
421     def report_extraction(self, id_or_name):
422         """Report information extraction."""
423         self.to_screen('%s: Extracting information' % id_or_name)
424
425     def report_download_webpage(self, video_id):
426         """Report webpage download."""
427         self.to_screen('%s: Downloading webpage' % video_id)
428
429     def report_age_confirmation(self):
430         """Report attempt to confirm age."""
431         self.to_screen('Confirming age')
432
433     def report_login(self):
434         """Report attempt to log in."""
435         self.to_screen('Logging in')
436
437     # Methods for following #608
438     @staticmethod
439     def url_result(url, ie=None, video_id=None):
440         """Returns a url that points to a page that should be processed"""
441         # TODO: ie should be the class used for getting the info
442         video_info = {'_type': 'url',
443                       'url': url,
444                       'ie_key': ie}
445         if video_id is not None:
446             video_info['id'] = video_id
447         return video_info
448
449     @staticmethod
450     def playlist_result(entries, playlist_id=None, playlist_title=None, playlist_description=None):
451         """Returns a playlist"""
452         video_info = {'_type': 'playlist',
453                       'entries': entries}
454         if playlist_id:
455             video_info['id'] = playlist_id
456         if playlist_title:
457             video_info['title'] = playlist_title
458         if playlist_description:
459             video_info['description'] = playlist_description
460         return video_info
461
462     def _search_regex(self, pattern, string, name, default=_NO_DEFAULT, fatal=True, flags=0, group=None):
463         """
464         Perform a regex search on the given string, using a single or a list of
465         patterns returning the first matching group.
466         In case of failure return a default value or raise a WARNING or a
467         RegexNotFoundError, depending on fatal, specifying the field name.
468         """
469         if isinstance(pattern, (str, compat_str, compiled_regex_type)):
470             mobj = re.search(pattern, string, flags)
471         else:
472             for p in pattern:
473                 mobj = re.search(p, string, flags)
474                 if mobj:
475                     break
476
477         if os.name != 'nt' and sys.stderr.isatty():
478             _name = '\033[0;34m%s\033[0m' % name
479         else:
480             _name = name
481
482         if mobj:
483             if group is None:
484                 # return the first matching group
485                 return next(g for g in mobj.groups() if g is not None)
486             else:
487                 return mobj.group(group)
488         elif default is not _NO_DEFAULT:
489             return default
490         elif fatal:
491             raise RegexNotFoundError('Unable to extract %s' % _name)
492         else:
493             self._downloader.report_warning('unable to extract %s; '
494                                             'please report this issue on http://yt-dl.org/bug' % _name)
495             return None
496
497     def _html_search_regex(self, pattern, string, name, default=_NO_DEFAULT, fatal=True, flags=0, group=None):
498         """
499         Like _search_regex, but strips HTML tags and unescapes entities.
500         """
501         res = self._search_regex(pattern, string, name, default, fatal, flags, group)
502         if res:
503             return clean_html(res).strip()
504         else:
505             return res
506
507     def _get_login_info(self):
508         """
509         Get the the login info as (username, password)
510         It will look in the netrc file using the _NETRC_MACHINE value
511         If there's no info available, return (None, None)
512         """
513         if self._downloader is None:
514             return (None, None)
515
516         username = None
517         password = None
518         downloader_params = self._downloader.params
519
520         # Attempt to use provided username and password or .netrc data
521         if downloader_params.get('username', None) is not None:
522             username = downloader_params['username']
523             password = downloader_params['password']
524         elif downloader_params.get('usenetrc', False):
525             try:
526                 info = netrc.netrc().authenticators(self._NETRC_MACHINE)
527                 if info is not None:
528                     username = info[0]
529                     password = info[2]
530                 else:
531                     raise netrc.NetrcParseError('No authenticators for %s' % self._NETRC_MACHINE)
532             except (IOError, netrc.NetrcParseError) as err:
533                 self._downloader.report_warning('parsing .netrc: %s' % compat_str(err))
534
535         return (username, password)
536
537     def _get_tfa_info(self):
538         """
539         Get the two-factor authentication info
540         TODO - asking the user will be required for sms/phone verify
541         currently just uses the command line option
542         If there's no info available, return None
543         """
544         if self._downloader is None:
545             return None
546         downloader_params = self._downloader.params
547
548         if downloader_params.get('twofactor', None) is not None:
549             return downloader_params['twofactor']
550
551         return None
552
553     # Helper functions for extracting OpenGraph info
554     @staticmethod
555     def _og_regexes(prop):
556         content_re = r'content=(?:"([^>]+?)"|\'([^>]+?)\')'
557         property_re = r'(?:name|property)=[\'"]og:%s[\'"]' % re.escape(prop)
558         template = r'<meta[^>]+?%s[^>]+?%s'
559         return [
560             template % (property_re, content_re),
561             template % (content_re, property_re),
562         ]
563
564     def _og_search_property(self, prop, html, name=None, **kargs):
565         if name is None:
566             name = 'OpenGraph %s' % prop
567         escaped = self._search_regex(self._og_regexes(prop), html, name, flags=re.DOTALL, **kargs)
568         if escaped is None:
569             return None
570         return unescapeHTML(escaped)
571
572     def _og_search_thumbnail(self, html, **kargs):
573         return self._og_search_property('image', html, 'thumbnail url', fatal=False, **kargs)
574
575     def _og_search_description(self, html, **kargs):
576         return self._og_search_property('description', html, fatal=False, **kargs)
577
578     def _og_search_title(self, html, **kargs):
579         return self._og_search_property('title', html, **kargs)
580
581     def _og_search_video_url(self, html, name='video url', secure=True, **kargs):
582         regexes = self._og_regexes('video') + self._og_regexes('video:url')
583         if secure:
584             regexes = self._og_regexes('video:secure_url') + regexes
585         return self._html_search_regex(regexes, html, name, **kargs)
586
587     def _og_search_url(self, html, **kargs):
588         return self._og_search_property('url', html, **kargs)
589
590     def _html_search_meta(self, name, html, display_name=None, fatal=False, **kwargs):
591         if display_name is None:
592             display_name = name
593         return self._html_search_regex(
594             r'''(?isx)<meta
595                     (?=[^>]+(?:itemprop|name|property)=(["\']?)%s\1)
596                     [^>]+content=(["\'])(?P<content>.*?)\1''' % re.escape(name),
597             html, display_name, fatal=fatal, group='content', **kwargs)
598
599     def _dc_search_uploader(self, html):
600         return self._html_search_meta('dc.creator', html, 'uploader')
601
602     def _rta_search(self, html):
603         # See http://www.rtalabel.org/index.php?content=howtofaq#single
604         if re.search(r'(?ix)<meta\s+name="rating"\s+'
605                      r'     content="RTA-5042-1996-1400-1577-RTA"',
606                      html):
607             return 18
608         return 0
609
610     def _media_rating_search(self, html):
611         # See http://www.tjg-designs.com/WP/metadata-code-examples-adding-metadata-to-your-web-pages/
612         rating = self._html_search_meta('rating', html)
613
614         if not rating:
615             return None
616
617         RATING_TABLE = {
618             'safe for kids': 0,
619             'general': 8,
620             '14 years': 14,
621             'mature': 17,
622             'restricted': 19,
623         }
624         return RATING_TABLE.get(rating.lower(), None)
625
626     def _twitter_search_player(self, html):
627         return self._html_search_meta('twitter:player', html,
628                                       'twitter card player')
629
630     def _sort_formats(self, formats):
631         if not formats:
632             raise ExtractorError('No video formats found')
633
634         def _formats_key(f):
635             # TODO remove the following workaround
636             from ..utils import determine_ext
637             if not f.get('ext') and 'url' in f:
638                 f['ext'] = determine_ext(f['url'])
639
640             preference = f.get('preference')
641             if preference is None:
642                 proto = f.get('protocol')
643                 if proto is None:
644                     proto = compat_urllib_parse_urlparse(f.get('url', '')).scheme
645
646                 preference = 0 if proto in ['http', 'https'] else -0.1
647                 if f.get('ext') in ['f4f', 'f4m']:  # Not yet supported
648                     preference -= 0.5
649
650             if f.get('vcodec') == 'none':  # audio only
651                 if self._downloader.params.get('prefer_free_formats'):
652                     ORDER = ['aac', 'mp3', 'm4a', 'webm', 'ogg', 'opus']
653                 else:
654                     ORDER = ['webm', 'opus', 'ogg', 'mp3', 'aac', 'm4a']
655                 ext_preference = 0
656                 try:
657                     audio_ext_preference = ORDER.index(f['ext'])
658                 except ValueError:
659                     audio_ext_preference = -1
660             else:
661                 if self._downloader.params.get('prefer_free_formats'):
662                     ORDER = ['flv', 'mp4', 'webm']
663                 else:
664                     ORDER = ['webm', 'flv', 'mp4']
665                 try:
666                     ext_preference = ORDER.index(f['ext'])
667                 except ValueError:
668                     ext_preference = -1
669                 audio_ext_preference = 0
670
671             return (
672                 preference,
673                 f.get('language_preference') if f.get('language_preference') is not None else -1,
674                 f.get('quality') if f.get('quality') is not None else -1,
675                 f.get('height') if f.get('height') is not None else -1,
676                 f.get('width') if f.get('width') is not None else -1,
677                 ext_preference,
678                 f.get('tbr') if f.get('tbr') is not None else -1,
679                 f.get('vbr') if f.get('vbr') is not None else -1,
680                 f.get('abr') if f.get('abr') is not None else -1,
681                 audio_ext_preference,
682                 f.get('fps') if f.get('fps') is not None else -1,
683                 f.get('filesize') if f.get('filesize') is not None else -1,
684                 f.get('filesize_approx') if f.get('filesize_approx') is not None else -1,
685                 f.get('source_preference') if f.get('source_preference') is not None else -1,
686                 f.get('format_id'),
687             )
688         formats.sort(key=_formats_key)
689
690     def http_scheme(self):
691         """ Either "http:" or "https:", depending on the user's preferences """
692         return (
693             'http:'
694             if self._downloader.params.get('prefer_insecure', False)
695             else 'https:')
696
697     def _proto_relative_url(self, url, scheme=None):
698         if url is None:
699             return url
700         if url.startswith('//'):
701             if scheme is None:
702                 scheme = self.http_scheme()
703             return scheme + url
704         else:
705             return url
706
707     def _sleep(self, timeout, video_id, msg_template=None):
708         if msg_template is None:
709             msg_template = '%(video_id)s: Waiting for %(timeout)s seconds'
710         msg = msg_template % {'video_id': video_id, 'timeout': timeout}
711         self.to_screen(msg)
712         time.sleep(timeout)
713
714     def _extract_f4m_formats(self, manifest_url, video_id):
715         manifest = self._download_xml(
716             manifest_url, video_id, 'Downloading f4m manifest',
717             'Unable to download f4m manifest')
718
719         formats = []
720         media_nodes = manifest.findall('{http://ns.adobe.com/f4m/1.0}media')
721         for i, media_el in enumerate(media_nodes):
722             tbr = int_or_none(media_el.attrib.get('bitrate'))
723             format_id = 'f4m-%d' % (i if tbr is None else tbr)
724             formats.append({
725                 'format_id': format_id,
726                 'url': manifest_url,
727                 'ext': 'flv',
728                 'tbr': tbr,
729                 'width': int_or_none(media_el.attrib.get('width')),
730                 'height': int_or_none(media_el.attrib.get('height')),
731             })
732         self._sort_formats(formats)
733
734         return formats
735
736     def _extract_m3u8_formats(self, m3u8_url, video_id, ext=None,
737                               entry_protocol='m3u8', preference=None):
738
739         formats = [{
740             'format_id': 'm3u8-meta',
741             'url': m3u8_url,
742             'ext': ext,
743             'protocol': 'm3u8',
744             'preference': -1,
745             'resolution': 'multiple',
746             'format_note': 'Quality selection URL',
747         }]
748
749         format_url = lambda u: (
750             u
751             if re.match(r'^https?://', u)
752             else compat_urlparse.urljoin(m3u8_url, u))
753
754         m3u8_doc = self._download_webpage(
755             m3u8_url, video_id,
756             note='Downloading m3u8 information',
757             errnote='Failed to download m3u8 information')
758         last_info = None
759         kv_rex = re.compile(
760             r'(?P<key>[a-zA-Z_-]+)=(?P<val>"[^"]+"|[^",]+)(?:,|$)')
761         for line in m3u8_doc.splitlines():
762             if line.startswith('#EXT-X-STREAM-INF:'):
763                 last_info = {}
764                 for m in kv_rex.finditer(line):
765                     v = m.group('val')
766                     if v.startswith('"'):
767                         v = v[1:-1]
768                     last_info[m.group('key')] = v
769             elif line.startswith('#') or not line.strip():
770                 continue
771             else:
772                 if last_info is None:
773                     formats.append({'url': format_url(line)})
774                     continue
775                 tbr = int_or_none(last_info.get('BANDWIDTH'), scale=1000)
776
777                 f = {
778                     'format_id': 'm3u8-%d' % (tbr if tbr else len(formats)),
779                     'url': format_url(line.strip()),
780                     'tbr': tbr,
781                     'ext': ext,
782                     'protocol': entry_protocol,
783                     'preference': preference,
784                 }
785                 codecs = last_info.get('CODECS')
786                 if codecs:
787                     # TODO: looks like video codec is not always necessarily goes first
788                     va_codecs = codecs.split(',')
789                     if va_codecs[0]:
790                         f['vcodec'] = va_codecs[0].partition('.')[0]
791                     if len(va_codecs) > 1 and va_codecs[1]:
792                         f['acodec'] = va_codecs[1].partition('.')[0]
793                 resolution = last_info.get('RESOLUTION')
794                 if resolution:
795                     width_str, height_str = resolution.split('x')
796                     f['width'] = int(width_str)
797                     f['height'] = int(height_str)
798                 formats.append(f)
799                 last_info = {}
800         self._sort_formats(formats)
801         return formats
802
803     # TODO: improve extraction
804     def _extract_smil_formats(self, smil_url, video_id):
805         smil = self._download_xml(
806             smil_url, video_id, 'Downloading SMIL file',
807             'Unable to download SMIL file')
808
809         base = smil.find('./head/meta').get('base')
810
811         formats = []
812         rtmp_count = 0
813         for video in smil.findall('./body/switch/video'):
814             src = video.get('src')
815             if not src:
816                 continue
817             bitrate = int_or_none(video.get('system-bitrate') or video.get('systemBitrate'), 1000)
818             width = int_or_none(video.get('width'))
819             height = int_or_none(video.get('height'))
820             proto = video.get('proto')
821             if not proto:
822                 if base:
823                     if base.startswith('rtmp'):
824                         proto = 'rtmp'
825                     elif base.startswith('http'):
826                         proto = 'http'
827             ext = video.get('ext')
828             if proto == 'm3u8':
829                 formats.extend(self._extract_m3u8_formats(src, video_id, ext))
830             elif proto == 'rtmp':
831                 rtmp_count += 1
832                 streamer = video.get('streamer') or base
833                 formats.append({
834                     'url': streamer,
835                     'play_path': src,
836                     'ext': 'flv',
837                     'format_id': 'rtmp-%d' % (rtmp_count if bitrate is None else bitrate),
838                     'tbr': bitrate,
839                     'width': width,
840                     'height': height,
841                 })
842         self._sort_formats(formats)
843
844         return formats
845
846     def _live_title(self, name):
847         """ Generate the title for a live video """
848         now = datetime.datetime.now()
849         now_str = now.strftime("%Y-%m-%d %H:%M")
850         return name + ' ' + now_str
851
852     def _int(self, v, name, fatal=False, **kwargs):
853         res = int_or_none(v, **kwargs)
854         if 'get_attr' in kwargs:
855             print(getattr(v, kwargs['get_attr']))
856         if res is None:
857             msg = 'Failed to extract %s: Could not parse value %r' % (name, v)
858             if fatal:
859                 raise ExtractorError(msg)
860             else:
861                 self._downloader.report_warning(msg)
862         return res
863
864     def _float(self, v, name, fatal=False, **kwargs):
865         res = float_or_none(v, **kwargs)
866         if res is None:
867             msg = 'Failed to extract %s: Could not parse value %r' % (name, v)
868             if fatal:
869                 raise ExtractorError(msg)
870             else:
871                 self._downloader.report_warning(msg)
872         return res
873
874     def _set_cookie(self, domain, name, value, expire_time=None):
875         cookie = compat_cookiejar.Cookie(
876             0, name, value, None, None, domain, None,
877             None, '/', True, False, expire_time, '', None, None, None)
878         self._downloader.cookiejar.set_cookie(cookie)
879
880
881 class SearchInfoExtractor(InfoExtractor):
882     """
883     Base class for paged search queries extractors.
884     They accept urls in the format _SEARCH_KEY(|all|[0-9]):{query}
885     Instances should define _SEARCH_KEY and _MAX_RESULTS.
886     """
887
888     @classmethod
889     def _make_valid_url(cls):
890         return r'%s(?P<prefix>|[1-9][0-9]*|all):(?P<query>[\s\S]+)' % cls._SEARCH_KEY
891
892     @classmethod
893     def suitable(cls, url):
894         return re.match(cls._make_valid_url(), url) is not None
895
896     def _real_extract(self, query):
897         mobj = re.match(self._make_valid_url(), query)
898         if mobj is None:
899             raise ExtractorError('Invalid search query "%s"' % query)
900
901         prefix = mobj.group('prefix')
902         query = mobj.group('query')
903         if prefix == '':
904             return self._get_n_results(query, 1)
905         elif prefix == 'all':
906             return self._get_n_results(query, self._MAX_RESULTS)
907         else:
908             n = int(prefix)
909             if n <= 0:
910                 raise ExtractorError('invalid download number %s for query "%s"' % (n, query))
911             elif n > self._MAX_RESULTS:
912                 self._downloader.report_warning('%s returns max %i results (you requested %i)' % (self._SEARCH_KEY, self._MAX_RESULTS, n))
913                 n = self._MAX_RESULTS
914             return self._get_n_results(query, n)
915
916     def _get_n_results(self, query, n):
917         """Get a specified number of results for a query"""
918         raise NotImplementedError("This method must be implemented by subclasses")
919
920     @property
921     def SEARCH_KEY(self):
922         return self._SEARCH_KEY