Added --console-title to display download progress in console window title.
[youtube-dl.git] / youtube-dl
index e6177e3..d6aecea 100755 (executable)
@@ -6,6 +6,7 @@
 # Author: Vasyl' Vavrychuk
 # License: Public domain code
 import cookielib
+import ctypes
 import datetime
 import htmlentitydefs
 import httplib
@@ -33,6 +34,7 @@ std_headers = {
        'User-Agent': 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.12) Gecko/20101028 Firefox/3.6.12',
        'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+       'Accept-Encoding': 'gzip, deflate',
        'Accept-Language': 'en-us,en;q=0.5',
 }
 
@@ -207,6 +209,7 @@ class FileDownloader(object):
        playliststart:    Playlist item to start at.
        playlistend:      Playlist item to end at.
        logtostderr:      Log messages to stderr instead of stdout.
+       consoletitle:     Display progress in console window's titlebar.
        """
 
        params = None
@@ -331,6 +334,17 @@ class FileDownloader(object):
                """Print message to stderr."""
                print >>sys.stderr, message.encode(preferredencoding())
        
+       def to_cons_title(self, message):
+               """Set console/terminal window title to message."""
+               if not self.params.get('consoletitle', False):
+                       return
+               if os.name == 'nt' and ctypes.windll.kernel32.GetConsoleWindow():
+                       # c_wchar_p() might not be necessary if `message` is
+                       # already of type unicode()
+                       ctypes.windll.kernel32.SetConsoleTitleW(ctypes.c_wchar_p(message))
+               elif 'TERM' in os.environ:
+                       sys.stderr.write('\033]0;%s\007' % message.encode(preferredencoding()))
+
        def fixed_template(self):
                """Checks if the output template is fixed."""
                return (re.search(ur'(?u)%\(.+?\)s', self.params['outtmpl']) is None)
@@ -379,6 +393,8 @@ class FileDownloader(object):
                        return
                self.to_screen(u'\r[download] %s of %s at %s ETA %s' %
                                (percent_str, data_len_str, speed_str, eta_str), skip_eol=True)
+               self.to_cons_title(u'youtube-dl - %s of %s at %s ETA %s' %
+                               (percent_str.strip(), data_len_str.strip(), speed_str.strip(), eta_str.strip()))
 
        def report_resuming_byte(self, resume_len):
                """Report attempt to resume at given byte."""
@@ -606,8 +622,10 @@ class FileDownloader(object):
                        return False
 
                data_len = data.info().get('Content-length', None)
+               if data_len is not None:
+                       data_len = long(data_len) + resume_len
                data_len_str = self.format_bytes(data_len)
-               byte_counter = 0
+               byte_counter = 0 + resume_len
                block_size = 1024
                start = time.time()
                while True:
@@ -615,10 +633,9 @@ class FileDownloader(object):
                        before = time.time()
                        data_block = data.read(block_size)
                        after = time.time()
-                       data_block_len = len(data_block)
-                       if data_block_len == 0:
+                       if len(data_block) == 0:
                                break
-                       byte_counter += data_block_len
+                       byte_counter += len(data_block)
 
                        # Open file just in time
                        if stream is None:
@@ -633,20 +650,20 @@ class FileDownloader(object):
                        except (IOError, OSError), err:
                                self.trouble(u'\nERROR: unable to write data: %s' % str(err))
                                return False
-                       block_size = self.best_block_size(after - before, data_block_len)
+                       block_size = self.best_block_size(after - before, len(data_block))
 
                        # Progress message
                        percent_str = self.calc_percent(byte_counter, data_len)
-                       eta_str = self.calc_eta(start, time.time(), data_len, byte_counter)
-                       speed_str = self.calc_speed(start, time.time(), byte_counter)
+                       eta_str = self.calc_eta(start, time.time(), data_len - resume_len, byte_counter - resume_len)
+                       speed_str = self.calc_speed(start, time.time(), byte_counter - resume_len)
                        self.report_progress(percent_str, data_len_str, speed_str, eta_str)
 
                        # Apply rate limit
-                       self.slow_down(start, byte_counter)
+                       self.slow_down(start, byte_counter - resume_len)
 
                stream.close()
                self.report_finish()
-               if data_len is not None and str(byte_counter) != data_len:
+               if data_len is not None and byte_counter != data_len:
                        raise ContentTooShortError(byte_counter, long(data_len))
                self.try_rename(tmpfilename, filename)
                return True
@@ -944,8 +961,7 @@ class YoutubeIE(InfoExtractor):
                video_token = urllib.unquote_plus(video_info['token'][0])
 
                # Decide which formats to download
-               requested_format = self._downloader.params.get('format', None)
-               get_video_template = 'http://www.youtube.com/get_video?video_id=%s&t=%s&eurl=&el=&ps=&asv=&fmt=%%s' % (video_id, video_token)
+               req_format = self._downloader.params.get('format', None)
 
                if 'fmt_url_map' in video_info:
                        url_map = dict(tuple(pair.split('|')) for pair in video_info['fmt_url_map'][0].split(','))
@@ -958,12 +974,16 @@ class YoutubeIE(InfoExtractor):
                        if len(existing_formats) == 0:
                                self._downloader.trouble(u'ERROR: no known formats available for video')
                                return
-                       if requested_format is None:
+                       if req_format is None:
                                video_url_list = [(existing_formats[0], url_map[existing_formats[0]])] # Best quality
-                       elif requested_format == '-1':
+                       elif req_format == '-1':
                                video_url_list = [(f, url_map[f]) for f in existing_formats] # All formats
                        else:
-                               video_url_list = [(requested_format, get_video_template % requested_format)] # Specific format
+                               # Specific format
+                               if req_format not in url_map:
+                                       self._downloader.trouble(u'ERROR: requested format not available')
+                                       return
+                               video_url_list = [(req_format, url_map[req_format])] # Specific format
 
                elif 'conn' in video_info and video_info['conn'][0].startswith('rtmp'):
                        self.report_rtmp_download()
@@ -997,7 +1017,7 @@ class YoutubeIE(InfoExtractor):
                                        'player_url':   player_url,
                                })
                        except UnavailableVideoError, err:
-                               self._downloader.trouble(u'ERROR: unable to download video (format may not be available)')
+                               self._downloader.trouble(u'\nERROR: unable to download video')
 
 
 class MetacafeIE(InfoExtractor):
@@ -1142,7 +1162,7 @@ class MetacafeIE(InfoExtractor):
                                'player_url':   None,
                        })
                except UnavailableVideoError:
-                       self._downloader.trouble(u'ERROR: unable to download video')
+                       self._downloader.trouble(u'\nERROR: unable to download video')
 
 
 class DailymotionIE(InfoExtractor):
@@ -1231,7 +1251,7 @@ class DailymotionIE(InfoExtractor):
                                'player_url':   None,
                        })
                except UnavailableVideoError:
-                       self._downloader.trouble(u'ERROR: unable to download video')
+                       self._downloader.trouble(u'\nERROR: unable to download video')
 
 class GoogleIE(InfoExtractor):
        """Information extractor for video.google.com."""
@@ -1341,7 +1361,7 @@ class GoogleIE(InfoExtractor):
                                'player_url':   None,
                        })
                except UnavailableVideoError:
-                       self._downloader.trouble(u'ERROR: unable to download video')
+                       self._downloader.trouble(u'\nERROR: unable to download video')
 
 
 class PhotobucketIE(InfoExtractor):
@@ -1423,7 +1443,7 @@ class PhotobucketIE(InfoExtractor):
                                'player_url':   None,
                        })
                except UnavailableVideoError:
-                       self._downloader.trouble(u'ERROR: unable to download video')
+                       self._downloader.trouble(u'\nERROR: unable to download video')
 
 
 class YahooIE(InfoExtractor):
@@ -1581,7 +1601,7 @@ class YahooIE(InfoExtractor):
                                'player_url':   None,
                        })
                except UnavailableVideoError:
-                       self._downloader.trouble(u'ERROR: unable to download video')
+                       self._downloader.trouble(u'\nERROR: unable to download video')
 
 
 class GenericIE(InfoExtractor):
@@ -1682,7 +1702,7 @@ class GenericIE(InfoExtractor):
                                'player_url':   None,
                        })
                except UnavailableVideoError, err:
-                       self._downloader.trouble(u'ERROR: unable to download video')
+                       self._downloader.trouble(u'\nERROR: unable to download video')
 
 
 class YoutubeSearchIE(InfoExtractor):
@@ -2252,6 +2272,8 @@ if __name__ == '__main__':
                                dest='playliststart', metavar='NUMBER', help='playlist video to start at (default is 1)', default=1)
                parser.add_option('--playlist-end',
                                dest='playlistend', metavar='NUMBER', help='playlist video to end at (default is last)', default=-1)
+               parser.add_option('--dump-user-agent',
+                               action='store_true', dest='dump_user_agent', help='display the current browser identification', default=False)
 
                authentication = optparse.OptionGroup(parser, 'Authentication Options')
                authentication.add_option('-u', '--username',
@@ -2265,14 +2287,10 @@ if __name__ == '__main__':
                video_format = optparse.OptionGroup(parser, 'Video Format Options')
                video_format.add_option('-f', '--format',
                                action='store', dest='format', metavar='FORMAT', help='video format code')
-               video_format.add_option('-m', '--mobile-version',
-                               action='store_const', dest='format', help='alias for -f 17', const='17')
                video_format.add_option('--all-formats',
                                action='store_const', dest='format', help='download all available video formats', const='-1')
                video_format.add_option('--max-quality',
                                action='store', dest='format_limit', metavar='FORMAT', help='highest quality format to download')
-               video_format.add_option('-b', '--best-quality',
-                               action='store_true', dest='bestquality', help='download the best video quality (DEPRECATED)')
                parser.add_option_group(video_format)
 
                verbosity = optparse.OptionGroup(parser, 'Verbosity / Simulation Options')
@@ -2290,6 +2308,8 @@ if __name__ == '__main__':
                                action='store_true', dest='getdescription', help='simulate, quiet but print video description', default=False)
                verbosity.add_option('--no-progress',
                                action='store_true', dest='noprogress', help='do not print progress bar', default=False)
+               verbosity.add_option('--console-title',
+                               action='store_true', dest='consoletitle', help='display progress in console titlebar', default=False)
                parser.add_option_group(verbosity)
 
                filesystem = optparse.OptionGroup(parser, 'Filesystem Options')
@@ -2324,6 +2344,11 @@ if __name__ == '__main__':
                        except (IOError, OSError), err:
                                sys.exit(u'ERROR: unable to open cookie file')
 
+               # Dump user agent
+               if opts.dump_user_agent:
+                       print std_headers['User-Agent']
+                       sys.exit(0)
+
                # General configuration
                cookie_processor = urllib2.HTTPCookieProcessor(jar)
                urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler()))
@@ -2346,8 +2371,6 @@ if __name__ == '__main__':
                all_urls = batchurls + args
 
                # Conflicting, missing and erroneous options
-               if opts.bestquality:
-                       print >>sys.stderr, u'\nWARNING: -b/--best-quality IS DEPRECATED AS IT IS THE DEFAULT BEHAVIOR NOW\n'
                if opts.usenetrc and (opts.username is not None or opts.password is not None):
                        parser.error(u'using .netrc conflicts with giving username/password')
                if opts.password is not None and opts.username is None:
@@ -2428,6 +2451,7 @@ if __name__ == '__main__':
                        'playliststart': opts.playliststart,
                        'playlistend': opts.playlistend,
                        'logtostderr': opts.outtmpl == '-',
+                       'consoletitle': opts.consoletitle,
                        })
                fd.add_info_extractor(youtube_search_ie)
                fd.add_info_extractor(youtube_pl_ie)