Only skip download if files exists; convert audio
[youtube-dl.git] / youtube_dl / __init__.py
index fe0fe98..d02178c 100755 (executable)
@@ -18,7 +18,7 @@ __author__  = (
        )
 
 __license__ = 'Public Domain'
-__version__ = '2011.11.23'
+__version__ = '2011.12.15'
 
 UPDATE_URL = 'https://raw.github.com/rg3/youtube-dl/master/youtube-dl'
 
@@ -317,6 +317,10 @@ class PostProcessingError(Exception):
        """
        pass
 
+class MaxDownloadsReached(Exception):
+       """ --max-downloads limit has been reached. """
+       pass
+
 
 class UnavailableVideoError(Exception):
        """Unavailable Format exception.
@@ -730,8 +734,7 @@ class FileDownloader(object):
                max_downloads = self.params.get('max_downloads')
                if max_downloads is not None:
                        if self._num_downloads > int(max_downloads):
-                               self.to_screen(u'[download] Maximum number of downloads reached. Skipping ' + info_dict['title'])
-                               return
+                               raise MaxDownloadsReached()
 
                filename = self.prepare_filename(info_dict)
                
@@ -756,10 +759,6 @@ class FileDownloader(object):
                if filename is None:
                        return
 
-               if self.params.get('nooverwrites', False) and os.path.exists(filename):
-                       self.to_stderr(u'WARNING: file exists and will be skipped')
-                       return
-
                try:
                        dn = os.path.dirname(filename)
                        if dn != '' and not os.path.exists(dn):
@@ -801,16 +800,19 @@ class FileDownloader(object):
                                return
 
                if not self.params.get('skip_download', False):
-                       try:
-                               success = self._do_download(filename, info_dict)
-                       except (OSError, IOError), err:
-                               raise UnavailableVideoError
-                       except (urllib2.URLError, httplib.HTTPException, socket.error), err:
-                               self.trouble(u'ERROR: unable to download video data: %s' % str(err))
-                               return
-                       except (ContentTooShortError, ), err:
-                               self.trouble(u'ERROR: content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
-                               return
+                       if self.params.get('nooverwrites', False) and os.path.exists(filename):
+                               success = True
+                       else:
+                               try:
+                                       success = self._do_download(filename, info_dict)
+                               except (OSError, IOError), err:
+                                       raise UnavailableVideoError
+                               except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+                                       self.trouble(u'ERROR: unable to download video data: %s' % str(err))
+                                       return
+                               except (ContentTooShortError, ), err:
+                                       self.trouble(u'ERROR: content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
+                                       return
        
                        if success:
                                try:
@@ -1118,6 +1120,7 @@ class YoutubeIE(InfoExtractor):
        _NETRC_MACHINE = 'youtube'
        # Listed in order of quality
        _available_formats = ['38', '37', '22', '45', '35', '44', '34', '18', '43', '6', '5', '17', '13']
+       _available_formats_prefer_free = ['38', '37', '45', '22', '44', '35', '43', '34', '18', '6', '5', '17', '13']
        _video_extensions = {
                '13': '3gp',
                '17': 'mp4',
@@ -1367,10 +1370,11 @@ class YoutubeIE(InfoExtractor):
                        url_map = dict((ud['itag'][0], ud['url'][0]) for ud in url_data)
 
                        format_limit = self._downloader.params.get('format_limit', None)
-                       if format_limit is not None and format_limit in self._available_formats:
-                               format_list = self._available_formats[self._available_formats.index(format_limit):]
+                       available_formats = self._available_formats_prefer_free if self._downloader.params.get('prefer_free_formats', False) else self._available_formats
+                       if format_limit is not None and format_limit in available_formats:
+                               format_list = available_formats[available_formats.index(format_limit):]
                        else:
-                               format_list = self._available_formats
+                               format_list = available_formats
                        existing_formats = [x for x in format_list if x in url_map]
                        if len(existing_formats) == 0:
                                self._downloader.trouble(u'ERROR: no known formats available for video')
@@ -1586,6 +1590,8 @@ class DailymotionIE(InfoExtractor):
                self._downloader.to_screen(u'[dailymotion] %s: Extracting information' % video_id)
 
        def _real_extract(self, url):
+               htmlParser = HTMLParser.HTMLParser()
+               
                # Extract id and simplified title from URL
                mobj = re.match(self._VALID_URL, url)
                if mobj is None:
@@ -1596,7 +1602,6 @@ class DailymotionIE(InfoExtractor):
                self._downloader.increment_downloads()
                video_id = mobj.group(1)
 
-               simple_title = mobj.group(2).decode('utf-8')
                video_extension = 'flv'
 
                # Retrieve video webpage to extract further information
@@ -1626,12 +1631,13 @@ class DailymotionIE(InfoExtractor):
 
                video_url = mediaURL
 
-               mobj = re.search(r'(?im)<title>\s*(.+)\s*-\s*Video\s+Dailymotion</title>', webpage)
+               mobj = re.search(r'<meta property="og:title" content="(?P<title>[^"]*)" />', webpage)
                if mobj is None:
                        self._downloader.trouble(u'ERROR: unable to extract title')
                        return
-               video_title = mobj.group(1).decode('utf-8')
+               video_title = htmlParser.unescape(mobj.group('title')).decode('utf-8')
                video_title = sanitize_title(video_title)
+               simple_title = _simplify_title(video_title)
 
                mobj = re.search(r'(?im)<span class="owner[^\"]+?">[^<]+?<a [^>]+?>([^<]+?)</a></span>', webpage)
                if mobj is None:
@@ -3965,8 +3971,13 @@ class FFmpegExtractAudioPP(PostProcessor):
                        return None
 
                more_opts = []
-               if self._preferredcodec == 'best' or self._preferredcodec == filecodec:
-                       if filecodec in ['aac', 'mp3', 'vorbis']:
+               if self._preferredcodec == 'best' or self._preferredcodec == filecodec or (self._preferredcodec == 'm4a' and filecodec == 'aac'):
+                       if self._preferredcodec == 'm4a' and filecodec == 'aac':
+                               # Lossless, but in another container
+                               acodec = 'copy'
+                               extension = self._preferredcodec
+                               more_opts = ['-absf', 'aac_adtstoasc']
+                       elif filecodec in ['aac', 'mp3', 'vorbis']:
                                # Lossless if possible
                                acodec = 'copy'
                                extension = filecodec
@@ -3983,13 +3994,15 @@ class FFmpegExtractAudioPP(PostProcessor):
                                        more_opts += ['-ab', self._preferredquality]
                else:
                        # We convert the audio (lossy)
-                       acodec = {'mp3': 'libmp3lame', 'aac': 'aac', 'vorbis': 'libvorbis'}[self._preferredcodec]
+                       acodec = {'mp3': 'libmp3lame', 'aac': 'aac', 'm4a': 'aac', 'vorbis': 'libvorbis'}[self._preferredcodec]
                        extension = self._preferredcodec
                        more_opts = []
                        if self._preferredquality is not None:
                                more_opts += ['-ab', self._preferredquality]
                        if self._preferredcodec == 'aac':
                                more_opts += ['-f', 'adts']
+                       if self._preferredcodec == 'm4a':
+                               more_opts += ['-absf', 'aac_adtstoasc']
                        if self._preferredcodec == 'vorbis':
                                extension = 'ogg'
 
@@ -4165,6 +4178,8 @@ def parseOpts():
                        action='store', dest='format', metavar='FORMAT', help='video format code')
        video_format.add_option('--all-formats',
                        action='store_const', dest='format', help='download all available video formats', const='all')
+       video_format.add_option('--prefer-free-formats',
+                       action='store_true', dest='prefer_free_formats', default=False, help='prefer free video formats unless a specific one is requested')
        video_format.add_option('--max-quality',
                        action='store', dest='format_limit', metavar='FORMAT', help='highest quality format to download')
        video_format.add_option('-F', '--list-formats',
@@ -4236,7 +4251,7 @@ def parseOpts():
        postproc.add_option('--extract-audio', action='store_true', dest='extractaudio', default=False,
                        help='convert video files to audio-only files (requires ffmpeg and ffprobe)')
        postproc.add_option('--audio-format', metavar='FORMAT', dest='audioformat', default='best',
-                       help='"best", "aac", "vorbis" or "mp3"; best by default')
+                       help='"best", "aac", "vorbis", "mp3", or "m4a"; best by default')
        postproc.add_option('--audio-quality', metavar='QUALITY', dest='audioquality', default='128K',
                        help='ffmpeg audio bitrate specification, 128k by default')
        postproc.add_option('-k', '--keep-video', action='store_true', dest='keepvideo', default=False,
@@ -4382,7 +4397,7 @@ def _real_main():
        except (TypeError, ValueError), err:
                parser.error(u'invalid playlist end number specified')
        if opts.extractaudio:
-               if opts.audioformat not in ['best', 'aac', 'mp3', 'vorbis']:
+               if opts.audioformat not in ['best', 'aac', 'mp3', 'vorbis', 'm4a']:
                        parser.error(u'invalid audio format specified')
 
        # File downloader
@@ -4429,6 +4444,7 @@ def _real_main():
                'matchtitle': opts.matchtitle,
                'rejecttitle': opts.rejecttitle,
                'max_downloads': opts.max_downloads,
+               'prefer_free_formats': opts.prefer_free_formats,
                })
        for extractor in extractors:
                fd.add_info_extractor(extractor)
@@ -4447,7 +4463,12 @@ def _real_main():
                        parser.error(u'you must provide at least one URL')
                else:
                        sys.exit()
-       retcode = fd.download(all_urls)
+       
+       try:
+               retcode = fd.download(all_urls)
+       except MaxDownloadsReached:
+               fd.to_screen(u'--max-download limit reached, aborting.')
+               retcode = 101
 
        # Dump cookie jar if requested
        if opts.cookiefile is not None: