Merge branch 'master' of https://github.com/rg3/youtube-dl
[youtube-dl] / youtube_dl / FileDownloader.py
index c471cc16081d5ff40cd0898ba0149f346d6c574e..be9e4918ec0983eaf8e188a46acf090ff8deb4de 100644 (file)
@@ -4,12 +4,14 @@
 from __future__ import absolute_import
 
 import math
+import io
 import os
 import re
 import socket
 import subprocess
 import sys
 import time
+import traceback
 
 if os.name == 'nt':
     import ctypes
@@ -78,6 +80,7 @@ class FileDownloader(object):
     writeinfojson:     Write the video description to a .info.json file
     writesubtitles:    Write the video subtitles to a .srt file
     subtitleslang:     Language of the subtitles to download
+    test:              Download only first bytes to test the downloader.
     """
 
     params = None
@@ -207,15 +210,22 @@ class FileDownloader(object):
         """Checks if the output template is fixed."""
         return (re.search(u'(?u)%\\(.+?\\)s', self.params['outtmpl']) is None)
 
-    def trouble(self, message=None):
+    def trouble(self, message=None, tb=None):
         """Determine action to take when a download problem appears.
 
         Depending on if the downloader has been configured to ignore
         download errors or not, this method may throw an exception or
         not when errors are found, after printing the message.
+
+        tb, if given, is additional traceback information.
         """
         if message is not None:
             self.to_stderr(message)
+        if self.params.get('verbose'):
+            if tb is None:
+                tb_data = traceback.format_list(traceback.extract_stack())
+                tb = u''.join(tb_data)
+            self.to_stderr(tb)
         if not self.params.get('ignoreerrors', False):
             raise DownloadError(message)
         self._download_retcode = 1
@@ -416,11 +426,8 @@ class FileDownloader(object):
             try:
                 descfn = filename + u'.description'
                 self.report_writedescription(descfn)
-                descfile = open(encodeFilename(descfn), 'wb')
-                try:
-                    descfile.write(info_dict['description'].encode('utf-8'))
-                finally:
-                    descfile.close()
+                with io.open(encodeFilename(descfn), 'w', encoding='utf-8') as descfile:
+                    descfile.write(info_dict['description'])
             except (OSError, IOError):
                 self.trouble(u'ERROR: Cannot write description file ' + descfn)
                 return
@@ -431,11 +438,8 @@ class FileDownloader(object):
             try:
                 srtfn = filename.rsplit('.', 1)[0] + u'.srt'
                 self.report_writesubtitles(srtfn)
-                srtfile = open(encodeFilename(srtfn), 'wb')
-                try:
-                    srtfile.write(info_dict['subtitles'].encode('utf-8'))
-                finally:
-                    srtfile.close()
+                with io.open(encodeFilename(srtfn), 'w', encoding='utf-8') as srtfile:
+                    srtfile.write(info_dict['subtitles'])
             except (OSError, IOError):
                 self.trouble(u'ERROR: Cannot write subtitles file ' + descfn)
                 return
@@ -444,17 +448,8 @@ class FileDownloader(object):
             infofn = filename + u'.info.json'
             self.report_writeinfojson(infofn)
             try:
-                json.dump
-            except (NameError,AttributeError):
-                self.trouble(u'ERROR: No JSON encoder found. Update to Python 2.6+, setup a json module, or leave out --write-info-json.')
-                return
-            try:
-                infof = open(encodeFilename(infofn), 'wb')
-                try:
-                    json_info_dict = dict((k,v) for k,v in info_dict.iteritems() if not k in ('urlhandle',))
-                    json.dump(json_info_dict, infof)
-                finally:
-                    infof.close()
+                json_info_dict = dict((k, v) for k,v in info_dict.items() if not k in ['urlhandle'])
+                write_json_file(json_info_dict, encodeFilename(infofn))
             except (OSError, IOError):
                 self.trouble(u'ERROR: Cannot write metadata to JSON file ' + infofn)
                 return
@@ -495,14 +490,28 @@ class FileDownloader(object):
 
                 # Warn if the _WORKING attribute is False
                 if not ie.working():
-                    self.trouble(u'WARNING: the program functionality for this site has been marked as broken, '
-                                 u'and will probably not work. If you want to go on, use the -i option.')
+                    self.to_stderr(u'WARNING: the program functionality for this site has been marked as broken, '
+                                   u'and will probably not work. If you want to go on, use the -i option.')
 
                 # Suitable InfoExtractor found
                 suitable_found = True
 
                 # Extract information from URL and process it
-                videos = ie.extract(url)
+                try:
+                    videos = ie.extract(url)
+                except ExtractorError as de: # An error we somewhat expected
+                    self.trouble(u'ERROR: ' + compat_str(de), de.format_traceback())
+                    break
+                except Exception as e:
+                    if self.params.get('ignoreerrors', False):
+                        self.trouble(u'ERROR: ' + compat_str(e), tb=compat_str(traceback.format_exc()))
+                        break
+                    else:
+                        raise
+
+                if len(videos or []) > 1 and self.fixed_template():
+                    raise SameFileError(self.params['outtmpl'])
+
                 for video in videos or []:
                     video['extractor'] = ie.IE_NAME
                     try:
@@ -528,7 +537,7 @@ class FileDownloader(object):
             if info is None:
                 break
 
-    def _download_with_rtmpdump(self, filename, url, player_url):
+    def _download_with_rtmpdump(self, filename, url, player_url, page_url):
         self.report_destination(filename)
         tmpfilename = self.temp_name(filename)
 
@@ -542,7 +551,11 @@ class FileDownloader(object):
         # Download using rtmpdump. rtmpdump returns exit code 2 when
         # the connection was interrumpted and resuming appears to be
         # possible. This is part of rtmpdump's normal usage, AFAIK.
-        basic_args = ['rtmpdump', '-q'] + [[], ['-W', player_url]][player_url is not None] + ['-r', url, '-o', tmpfilename]
+        basic_args = ['rtmpdump', '-q', '-r', url, '-o', tmpfilename]
+        if player_url is not None:
+            basic_args += ['-W', player_url]
+        if page_url is not None:
+            basic_args += ['--pageUrl', page_url]
         args = basic_args + [[], ['-e', '-k', '1']][self.params.get('continuedl', False)]
         if self.params.get('verbose', False):
             try:
@@ -575,7 +588,6 @@ class FileDownloader(object):
 
     def _do_download(self, filename, info_dict):
         url = info_dict['url']
-        player_url = info_dict.get('player_url', None)
 
         # Check file already present
         if self.params.get('continuedl', False) and os.path.isfile(encodeFilename(filename)) and not self.params.get('nopart', False):
@@ -584,7 +596,9 @@ class FileDownloader(object):
 
         # Attempt to download using rtmpdump
         if url.startswith('rtmp'):
-            return self._download_with_rtmpdump(filename, url, player_url)
+            return self._download_with_rtmpdump(filename, url,
+                                                info_dict.get('player_url', None),
+                                                info_dict.get('page_url', None))
 
         tmpfilename = self.temp_name(filename)
         stream = None
@@ -594,6 +608,9 @@ class FileDownloader(object):
         basic_request = compat_urllib_request.Request(url, None, headers)
         request = compat_urllib_request.Request(url, None, headers)
 
+        if self.params.get('test', False):
+            request.add_header('Range','bytes=0-10240')
+
         # Establish possible resume length
         if os.path.isfile(encodeFilename(tmpfilename)):
             resume_len = os.path.getsize(encodeFilename(tmpfilename))