Merge remote-tracking branch 'jaimeMF/format_selection'
authorPhilipp Hagemeister <phihag@phihag.de>
Thu, 17 Oct 2013 22:17:03 +0000 (00:17 +0200)
committerPhilipp Hagemeister <phihag@phihag.de>
Thu, 17 Oct 2013 22:17:03 +0000 (00:17 +0200)
1  2 
youtube_dl/YoutubeDL.py
youtube_dl/__init__.py

diff --combined youtube_dl/YoutubeDL.py
index c8054544a60db20f041d22c5ee6e5405d935061a,a32e50772642adc046d6614577b8533584bf3745..f22a8bd0e044b9c10ecad56187a15a310a4c1d7d
@@@ -71,7 -71,6 +71,7 @@@ class YoutubeDL(object)
      logtostderr:       Log messages to stderr instead of stdout.
      writedescription:  Write the video description to a .description file
      writeinfojson:     Write the video description to a .info.json file
 +    writeannotations:  Write the video annotations to a .annotations.xml file
      writethumbnail:    Write the thumbnail image to a file
      writesubtitles:    Write the video subtitles to a file
      writeautomaticsub: Write the automatic subtitles to a file
          """ Report that the metadata file has been written """
          self.to_screen(u'[info] Video description metadata as JSON to: ' + infofn)
  
 +    def report_writeannotations(self, annofn):
 +        """ Report that the annotations file has been written. """
 +        self.to_screen(u'[info] Writing video annotations to: ' + annofn)
 +
      def report_file_already_downloaded(self, file_name):
          """Report file has already been fully downloaded."""
          try:
          result_type = ie_result.get('_type', 'video') # If not given we suppose it's a video, support the default old system
          if result_type == 'video':
              ie_result.update(extra_info)
-             if 'playlist' not in ie_result:
-                 # It isn't part of a playlist
-                 ie_result['playlist'] = None
-                 ie_result['playlist_index'] = None
-             if download:
-                 self.process_info(ie_result)
-             return ie_result
+             return self.process_video_result(ie_result)
          elif result_type == 'url':
              # We have to add extra_info to the results because it may be
              # contained in a playlist
          else:
              raise Exception('Invalid result type: %s' % result_type)
  
+     def process_video_result(self, info_dict, download=True):
+         assert info_dict.get('_type', 'video') == 'video'
+         if 'playlist' not in info_dict:
+             # It isn't part of a playlist
+             info_dict['playlist'] = None
+             info_dict['playlist_index'] = None
+         # This extractors handle format selection themselves
+         if info_dict['extractor'] in [u'youtube', u'Youku', u'YouPorn', u'mixcloud']:
+             self.process_info(info_dict)
+             return info_dict
+         # We now pick which formats have to be downloaded
+         if info_dict.get('formats') is None:
+             # There's only one format available
+             formats = [info_dict]
+         else:
+             formats = info_dict['formats']
+         # We check that all the formats have the format and format_id fields
+         for (i, format) in enumerate(formats):
+             if format.get('format') is None:
+                 if format.get('height') is not None:
+                     if format.get('width') is not None:
+                         format_desc = u'%sx%s' % (format['width'], format['height'])
+                     else:
+                         format_desc = u'%sp' % format['height']
+                 else:
+                     format_desc = '???'
+                 format['format'] = format_desc
+             if format.get('format_id') is None:
+                 format['format_id'] = compat_str(i)
+         if self.params.get('listformats', None):
+             self.list_formats(info_dict)
+             return
+         format_limit = self.params.get('format_limit', None)
+         if format_limit:
+             formats = [f for f in formats if f['format_id'] <= format_limit]
+         if self.params.get('prefer_free_formats'):
+             def _free_formats_key(f):
+                 try:
+                     ext_ord = [u'flv', u'mp4', u'webm'].index(f['ext'])
+                 except ValueError:
+                     ext_ord = -1
+                 # We only compare the extension if they have the same height and width
+                 return (f.get('height'), f.get('width'), ext_ord)
+             formats = sorted(formats, key=_free_formats_key)
+         req_format = self.params.get('format', 'best')
+         formats_to_download = []
+         if req_format == 'best' or req_format is None:
+             formats_to_download = [formats[-1]]
+         elif req_format == 'worst':
+             formats_to_download = [formats[0]]
+         # The -1 is for supporting YoutubeIE
+         elif req_format in ('-1', 'all'):
+             formats_to_download = formats
+         else:
+             # We can accept formats requestd in the format: 34/10/5, we pick
+             # the first that is availble, starting from left
+             req_formats = req_format.split('/')
+             for rf in req_formats:
+                 matches = filter(lambda f:f['format_id'] == rf ,formats)
+                 if matches:
+                     formats_to_download = [matches[0]]
+                     break
+         if not formats_to_download:
+             raise ExtractorError(u'requested format not available')
+         if download:
+             if len(formats_to_download) > 1:
+                 self.to_screen(u'[info] %s: downloading video in %s formats' % (info_dict['id'], len(formats_to_download)))
+             for format in formats_to_download:
+                 new_info = dict(info_dict)
+                 new_info.update(format)
+                 self.process_info(new_info)
+         # We update the info dict with the best quality format (backwards compatibility)
+         info_dict.update(formats_to_download[-1])
+         return info_dict
      def process_info(self, info_dict):
          """Process a single resolved IE result."""
  
                  self.report_error(u'Cannot write description file ' + descfn)
                  return
  
 +        if self.params.get('writeannotations', False):
 +            try:
 +               annofn = filename + u'.annotations.xml'
 +               self.report_writeannotations(annofn)
 +               with io.open(encodeFilename(annofn), 'w', encoding='utf-8') as annofile:
 +                   annofile.write(info_dict['annotations'])
 +            except (KeyError, TypeError):
 +                self.report_warning(u'There are no annotations to write.')
 +            except (OSError, IOError):
 +                 self.report_error(u'Cannot write annotations file: ' + annofn)
 +                 return
 +
          subtitles_are_requested = any([self.params.get('writesubtitles', False),
                                         self.params.get('writeautomaticsub')])
  
          vid_id = info_dict['extractor'] + u' ' + info_dict['id']
          with locked_file(fn, 'a', encoding='utf-8') as archive_file:
              archive_file.write(vid_id + u'\n')
+     def list_formats(self, info_dict):
+         formats_s = []
+         for format in info_dict.get('formats', [info_dict]):
+             formats_s.append("%s\t:\t%s\t[%s]" % (format['format_id'],
+                                                 format['ext'],
+                                                 format.get('format', '???'),
+                                                 )
+                             )
+         if len(formats_s) != 1:
+             formats_s[0]  += ' (worst)'
+             formats_s[-1] += ' (best)'
+         formats_s = "\n".join(formats_s)
+         self.to_screen(u"[info] Available formats for %s:\nformat code\textension\n%s" % (info_dict['id'], formats_s)) 
diff --combined youtube_dl/__init__.py
index 3efa5dfd1e5f43c39b12b241df62592707a39a3b,bc8e97250e18a9f52ed48a2cafe3ee48348cef05..cd642ce3b5a58aca93e4fb6cc7c65d3a540cdc35
@@@ -31,7 -31,6 +31,7 @@@ __authors__  = 
      'Huarong Huo',
      'Ismael Mejía',
      'Steffan \'Ruirize\' James',
 +    'Andras Elso',
  )
  
  __license__ = 'Public Domain'
@@@ -47,43 -46,17 +47,43 @@@ import shle
  import socket
  import subprocess
  import sys
 -import warnings
 +import traceback
  import platform
  
  
 -from .utils import *
 +from .utils import (
 +    compat_cookiejar,
 +    compat_print,
 +    compat_str,
 +    compat_urllib_request,
 +    DateRange,
 +    decodeOption,
 +    determine_ext,
 +    DownloadError,
 +    get_cachedir,
 +    make_HTTPS_handler,
 +    MaxDownloadsReached,
 +    platform_name,
 +    preferredencoding,
 +    SameFileError,
 +    std_headers,
 +    write_string,
 +    YoutubeDLHandler,
 +)
  from .update import update_self
  from .version import __version__
 -from .FileDownloader import *
 +from .FileDownloader import (
 +    FileDownloader,
 +)
  from .extractor import gen_extractors
  from .YoutubeDL import YoutubeDL
 -from .PostProcessor import *
 +from .PostProcessor import (
 +    FFmpegMetadataPP,
 +    FFmpegVideoConvertor,
 +    FFmpegExtractAudioPP,
 +    FFmpegEmbedSubtitlePP,
 +)
 +
  
  def parseOpts(overrideArguments=None):
      def _readOptions(filename_bytes):
  
  
      video_format.add_option('-f', '--format',
-             action='store', dest='format', metavar='FORMAT',
+             action='store', dest='format', metavar='FORMAT', default='best',
              help='video format code, specifiy the order of preference using slashes: "-f 22/17/18". "-f mp4" and "-f flv" are also supported')
      video_format.add_option('--all-formats',
              action='store_const', dest='format', help='download all available video formats', const='all')
              help='languages of the subtitles to download (optional) separated by commas, use IETF language tags like \'en,pt\'')
  
      downloader.add_option('-r', '--rate-limit',
 -            dest='ratelimit', metavar='LIMIT', help='maximum download rate (e.g. 50k or 44.6m)')
 +            dest='ratelimit', metavar='LIMIT', help='maximum download rate in bytes per second (e.g. 50K or 4.2M)')
      downloader.add_option('-R', '--retries',
              dest='retries', metavar='RETRIES', help='number of retries (default is %default)', default=10)
      downloader.add_option('--buffer-size',
 -            dest='buffersize', metavar='SIZE', help='size of download buffer (e.g. 1024 or 16k) (default is %default)', default="1024")
 +            dest='buffersize', metavar='SIZE', help='size of download buffer (e.g. 1024 or 16K) (default is %default)', default="1024")
      downloader.add_option('--no-resize-buffer',
              action='store_true', dest='noresizebuffer',
              help='do not automatically adjust the buffer size. By default, the buffer size is automatically resized from an initial value of SIZE.', default=False)
      filesystem.add_option('--write-info-json',
              action='store_true', dest='writeinfojson',
              help='write video metadata to a .info.json file', default=False)
 +    filesystem.add_option('--write-annotations',
 +            action='store_true', dest='writeannotations',
 +            help='write video annotations to a .annotation file', default=False)
      filesystem.add_option('--write-thumbnail',
              action='store_true', dest='writethumbnail',
              help='write thumbnail image to disk', default=False)
@@@ -631,7 -601,6 +631,7 @@@ def _real_main(argv=None)
          'nopart': opts.nopart,
          'updatetime': opts.updatetime,
          'writedescription': opts.writedescription,
 +        'writeannotations': opts.writeannotations,
          'writeinfojson': opts.writeinfojson,
          'writethumbnail': opts.writethumbnail,
          'writesubtitles': opts.writesubtitles,
      if opts.cookiefile is not None:
          try:
              jar.save()
 -        except (IOError, OSError) as err:
 +        except (IOError, OSError):
              sys.exit(u'ERROR: unable to save cookie jar')
  
      sys.exit(retcode)