|  | @@ -5,6 +5,7 @@ from __future__ import absolute_import, unicode_literals
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  import collections
 | 
	
		
			
				|  |  |  import contextlib
 | 
	
		
			
				|  |  | +import copy
 | 
	
		
			
				|  |  |  import datetime
 | 
	
		
			
				|  |  |  import errno
 | 
	
		
			
				|  |  |  import fileinput
 | 
	
	
		
			
				|  | @@ -1051,9 +1052,9 @@ class YoutubeDL(object):
 | 
	
		
			
				|  |  |              if isinstance(selector, list):
 | 
	
		
			
				|  |  |                  fs = [_build_selector_function(s) for s in selector]
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                def selector_function(formats):
 | 
	
		
			
				|  |  | +                def selector_function(ctx):
 | 
	
		
			
				|  |  |                      for f in fs:
 | 
	
		
			
				|  |  | -                        for format in f(formats):
 | 
	
		
			
				|  |  | +                        for format in f(ctx):
 | 
	
		
			
				|  |  |                              yield format
 | 
	
		
			
				|  |  |                  return selector_function
 | 
	
		
			
				|  |  |              elif selector.type == GROUP:
 | 
	
	
		
			
				|  | @@ -1061,17 +1062,17 @@ class YoutubeDL(object):
 | 
	
		
			
				|  |  |              elif selector.type == PICKFIRST:
 | 
	
		
			
				|  |  |                  fs = [_build_selector_function(s) for s in selector.selector]
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                def selector_function(formats):
 | 
	
		
			
				|  |  | +                def selector_function(ctx):
 | 
	
		
			
				|  |  |                      for f in fs:
 | 
	
		
			
				|  |  | -                        picked_formats = list(f(formats))
 | 
	
		
			
				|  |  | +                        picked_formats = list(f(ctx))
 | 
	
		
			
				|  |  |                          if picked_formats:
 | 
	
		
			
				|  |  |                              return picked_formats
 | 
	
		
			
				|  |  |                      return []
 | 
	
		
			
				|  |  |              elif selector.type == SINGLE:
 | 
	
		
			
				|  |  |                  format_spec = selector.selector
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                def selector_function(formats):
 | 
	
		
			
				|  |  | -                    formats = list(formats)
 | 
	
		
			
				|  |  | +                def selector_function(ctx):
 | 
	
		
			
				|  |  | +                    formats = list(ctx['formats'])
 | 
	
		
			
				|  |  |                      if not formats:
 | 
	
		
			
				|  |  |                          return
 | 
	
		
			
				|  |  |                      if format_spec == 'all':
 | 
	
	
		
			
				|  | @@ -1084,9 +1085,10 @@ class YoutubeDL(object):
 | 
	
		
			
				|  |  |                              if f.get('vcodec') != 'none' and f.get('acodec') != 'none']
 | 
	
		
			
				|  |  |                          if audiovideo_formats:
 | 
	
		
			
				|  |  |                              yield audiovideo_formats[format_idx]
 | 
	
		
			
				|  |  | -                        # for audio only (soundcloud) or video only (imgur) urls, select the best/worst audio format
 | 
	
		
			
				|  |  | -                        elif (all(f.get('acodec') != 'none' for f in formats) or
 | 
	
		
			
				|  |  | -                              all(f.get('vcodec') != 'none' for f in formats)):
 | 
	
		
			
				|  |  | +                        # for extractors with incomplete formats (audio only (soundcloud)
 | 
	
		
			
				|  |  | +                        # or video only (imgur)) we will fallback to best/worst
 | 
	
		
			
				|  |  | +                        # {video,audio}-only format
 | 
	
		
			
				|  |  | +                        elif ctx['incomplete_formats']:
 | 
	
		
			
				|  |  |                              yield formats[format_idx]
 | 
	
		
			
				|  |  |                      elif format_spec == 'bestaudio':
 | 
	
		
			
				|  |  |                          audio_formats = [
 | 
	
	
		
			
				|  | @@ -1160,17 +1162,18 @@ class YoutubeDL(object):
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  |                  video_selector, audio_selector = map(_build_selector_function, selector.selector)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                def selector_function(formats):
 | 
	
		
			
				|  |  | -                    formats = list(formats)
 | 
	
		
			
				|  |  | -                    for pair in itertools.product(video_selector(formats), audio_selector(formats)):
 | 
	
		
			
				|  |  | +                def selector_function(ctx):
 | 
	
		
			
				|  |  | +                    for pair in itertools.product(
 | 
	
		
			
				|  |  | +                            video_selector(copy.deepcopy(ctx)), audio_selector(copy.deepcopy(ctx))):
 | 
	
		
			
				|  |  |                          yield _merge(pair)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              filters = [self._build_format_filter(f) for f in selector.filters]
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            def final_selector(formats):
 | 
	
		
			
				|  |  | +            def final_selector(ctx):
 | 
	
		
			
				|  |  | +                ctx_copy = copy.deepcopy(ctx)
 | 
	
		
			
				|  |  |                  for _filter in filters:
 | 
	
		
			
				|  |  | -                    formats = list(filter(_filter, formats))
 | 
	
		
			
				|  |  | -                return selector_function(formats)
 | 
	
		
			
				|  |  | +                    ctx_copy['formats'] = list(filter(_filter, ctx_copy['formats']))
 | 
	
		
			
				|  |  | +                return selector_function(ctx_copy)
 | 
	
		
			
				|  |  |              return final_selector
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          stream = io.BytesIO(format_spec.encode('utf-8'))
 | 
	
	
		
			
				|  | @@ -1377,7 +1380,35 @@ class YoutubeDL(object):
 | 
	
		
			
				|  |  |              req_format_list.append('best')
 | 
	
		
			
				|  |  |              req_format = '/'.join(req_format_list)
 | 
	
		
			
				|  |  |          format_selector = self.build_format_selector(req_format)
 | 
	
		
			
				|  |  | -        formats_to_download = list(format_selector(formats))
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        # While in format selection we may need to have an access to the original
 | 
	
		
			
				|  |  | +        # format set in order to calculate some metrics or do some processing.
 | 
	
		
			
				|  |  | +        # For now we need to be able to guess whether original formats provided
 | 
	
		
			
				|  |  | +        # by extractor are incomplete or not (i.e. whether extractor provides only
 | 
	
		
			
				|  |  | +        # video-only or audio-only formats) for proper formats selection for
 | 
	
		
			
				|  |  | +        # extractors with such incomplete formats (see
 | 
	
		
			
				|  |  | +        # https://github.com/rg3/youtube-dl/pull/5556).
 | 
	
		
			
				|  |  | +        # Since formats may be filtered during format selection and may not match
 | 
	
		
			
				|  |  | +        # the original formats the results may be incorrect. Thus original formats
 | 
	
		
			
				|  |  | +        # or pre-calculated metrics should be passed to format selection routines
 | 
	
		
			
				|  |  | +        # as well.
 | 
	
		
			
				|  |  | +        # We will pass a context object containing all necessary additional data
 | 
	
		
			
				|  |  | +        # instead of just formats.
 | 
	
		
			
				|  |  | +        # This fixes incorrect format selection issue (see
 | 
	
		
			
				|  |  | +        # https://github.com/rg3/youtube-dl/issues/10083).
 | 
	
		
			
				|  |  | +        incomplete_formats = all(
 | 
	
		
			
				|  |  | +            # All formats are video-only or
 | 
	
		
			
				|  |  | +            f.get('vcodec') != 'none' and f.get('acodec') == 'none' or
 | 
	
		
			
				|  |  | +            # all formats are audio-only
 | 
	
		
			
				|  |  | +            f.get('vcodec') == 'none' and f.get('acodec') != 'none'
 | 
	
		
			
				|  |  | +            for f in formats)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        ctx = {
 | 
	
		
			
				|  |  | +            'formats': formats,
 | 
	
		
			
				|  |  | +            'incomplete_formats': incomplete_formats,
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        formats_to_download = list(format_selector(ctx))
 | 
	
		
			
				|  |  |          if not formats_to_download:
 | 
	
		
			
				|  |  |              raise ExtractorError('requested format not available',
 | 
	
		
			
				|  |  |                                   expected=True)
 |