Compare commits

...

16 Commits

Author SHA1 Message Date
Ricardo Garcia
dd24ff44ab Bump version number 2010-10-31 11:24:48 +01:00
Ricardo Garcia
304a4d85ea Modify _MORE_PAGES_INDICATOR for searches (fixes issue #41) 2010-10-31 11:24:48 +01:00
Ricardo Garcia
d899774377 Accept my_playlists syntax for playlists 2010-10-31 11:24:48 +01:00
Ricardo Garcia
fade05990c Do not specify any format as a last option for the -b flag 2010-10-31 11:24:48 +01:00
Ricardo Garcia
e5b1604882 Set INTERNAL version number 2010-10-31 11:24:48 +01:00
Ricardo Garcia
0c8beb43f2 Bump version number 2010-10-31 11:24:44 +01:00
Ricardo Garcia
71b7300e63 Use get_video_info to work around captcha problems (fixes issue #31) 2010-10-31 11:24:44 +01:00
Ricardo Garcia
8497c36d5a Fix minor problem with size formatting method 2010-10-31 11:24:44 +01:00
Ricardo Garcia
110cd3462e Update User-agent string 2010-10-31 11:24:43 +01:00
Ricardo Garcia
18963a36b0 Fix metacafe.com code due to recent changes in the site 2010-10-31 11:24:43 +01:00
Ricardo Garcia
df1ceb1fd9 Include format 5 in best quality list 2010-10-31 11:24:43 +01:00
Ricardo Garcia
7eb0e89742 Properly encode messages sent to stderr (fixes issue #34) 2010-10-31 11:24:43 +01:00
Ricardo Garcia
8b07dec5f6 Bump version number 2010-10-31 11:24:40 +01:00
Ricardo Garcia
113e5266cc Modify "more pages" check in YouTube playlist (fixes issue #29) 2010-10-31 11:24:40 +01:00
Ricardo Garcia
55e7c75e12 Delay opening file until there is data to write
Fixes issue #19.
2010-10-31 11:24:40 +01:00
Ricardo Garcia
ff21a710ae Restore INTERNAL version number 2010-10-31 11:24:36 +01:00
2 changed files with 91 additions and 73 deletions

View File

@@ -1 +1 @@
2009.05.30
2009.09.08

View File

@@ -19,7 +19,7 @@ import urllib
import urllib2
std_headers = {
'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.8) Gecko/2009032609 Firefox/3.0.8',
'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2',
'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
'Accept': 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
'Accept-Language': 'en-us,en;q=0.5',
@@ -143,10 +143,12 @@ class FileDownloader(object):
def format_bytes(bytes):
if bytes is None:
return 'N/A'
if bytes == 0:
if type(bytes) is str:
bytes = float(bytes)
if bytes == 0.0:
exponent = 0
else:
exponent = long(math.log(float(bytes), 1024.0))
exponent = long(math.log(bytes, 1024.0))
suffix = 'bkMGTPEZY'[exponent]
converted = float(bytes) / float(1024**exponent)
return '%.2f%s' % (converted, suffix)
@@ -227,7 +229,7 @@ class FileDownloader(object):
def to_stderr(self, message):
"""Print message to stderr."""
print >>sys.stderr, message
print >>sys.stderr, message.encode(locale.getpreferredencoding())
def fixed_template(self):
"""Checks if the output template is fixed."""
@@ -305,11 +307,10 @@ class FileDownloader(object):
template_dict = dict(info_dict)
template_dict['epoch'] = unicode(long(time.time()))
filename = self.params['outtmpl'] % template_dict
self.report_destination(filename)
except (ValueError, KeyError), err:
self.trouble('ERROR: invalid output template or system charset: %s' % str(err))
if self.params['nooverwrites'] and os.path.exists(filename):
self.to_stderr('WARNING: file exists: %s; skipping' % filename)
self.to_stderr(u'WARNING: file exists: %s; skipping' % filename)
return
try:
@@ -319,17 +320,8 @@ class FileDownloader(object):
return
try:
outstream = open(filename, 'ab')
success = self._do_download(filename, info_dict['url'])
except (OSError, IOError), err:
self.trouble('ERROR: unable to open for writing: %s' % str(err))
return
try:
self._do_download(outstream, info_dict['url'])
outstream.close()
except (OSError, IOError), err:
outstream.close()
os.remove(filename)
raise UnavailableFormatError
except (urllib2.URLError, httplib.HTTPException, socket.error), err:
self.trouble('ERROR: unable to download video data: %s' % str(err))
@@ -338,11 +330,12 @@ class FileDownloader(object):
self.trouble('ERROR: content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
return
try:
self.post_process(filename, info_dict)
except (PostProcessingError), err:
self.trouble('ERROR: postprocessing: %s' % str(err))
return
if success:
try:
self.post_process(filename, info_dict)
except (PostProcessingError), err:
self.trouble('ERROR: postprocessing: %s' % str(err))
return
def download(self, url_list):
"""Download a given list of URLs."""
@@ -379,32 +372,36 @@ class FileDownloader(object):
if info is None:
break
def _do_download(self, stream, url):
def _do_download(self, filename, url):
stream = None
open_mode = 'ab'
basic_request = urllib2.Request(url, None, std_headers)
request = urllib2.Request(url, None, std_headers)
# Resume transfer if filesize is non-zero
resume_len = stream.tell()
# Attempt to resume download with "continuedl" option
if os.path.isfile(filename):
resume_len = os.path.getsize(filename)
else:
resume_len = 0
if self.params['continuedl'] and resume_len != 0:
self.report_resuming_byte(resume_len)
request.add_header('Range','bytes=%d-' % resume_len)
else:
stream.close()
stream = open(stream.name,'wb')
# Establish connection
try:
data = urllib2.urlopen(request)
except urllib2.HTTPError, e:
if not e.code == 416: # 416 is 'Requested range not satisfiable'
except (urllib2.HTTPError, ), err:
if err.code != 416: # 416 is 'Requested range not satisfiable'
raise
data = urllib2.urlopen(basic_request)
content_length = data.info()['Content-Length']
if content_length is not None and long(content_length) == resume_len:
self.report_file_already_downloaded(stream.name)
return
self.report_file_already_downloaded(filename)
return True
else:
self.report_unable_to_resume()
stream.close()
stream = open(stream.name,'wb')
open_mode = 'wb'
data_len = data.info().get('Content-length', None)
data_len_str = self.format_bytes(data_len)
@@ -412,12 +409,6 @@ class FileDownloader(object):
block_size = 1024
start = time.time()
while True:
# 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)
self.report_progress(percent_str, data_len_str, speed_str, eta_str)
# Download and write
before = time.time()
data_block = data.read(block_size)
@@ -426,15 +417,31 @@ class FileDownloader(object):
if data_block_len == 0:
break
byte_counter += data_block_len
# Open file just in time
if stream is None:
try:
stream = open(filename, open_mode)
self.report_destination(filename)
except (OSError, IOError), err:
self.trouble('ERROR: unable to open for writing: %s' % str(err))
return False
stream.write(data_block)
block_size = self.best_block_size(after - before, data_block_len)
# 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)
self.report_progress(percent_str, data_len_str, speed_str, eta_str)
# Apply rate limit
self.slow_down(start, byte_counter)
self.report_finish()
if data_len is not None and str(byte_counter) != data_len:
raise ContentTooShortError(byte_counter, long(data_len))
return True
class InfoExtractor(object):
"""Information Extractor class.
@@ -505,7 +512,7 @@ class YoutubeIE(InfoExtractor):
_LOGIN_URL = 'http://www.youtube.com/signup?next=/&gl=US&hl=en'
_AGE_URL = 'http://www.youtube.com/verify_age?next_url=/&gl=US&hl=en'
_NETRC_MACHINE = 'youtube'
_available_formats = ['22', '35', '18', '17', '13'] # listed in order of priority for -b flag
_available_formats = ['22', '35', '18', '5', '17', '13', None] # listed in order of priority for -b flag
_video_extensions = {
'13': '3gp',
'17': 'mp4',
@@ -552,9 +559,9 @@ class YoutubeIE(InfoExtractor):
"""Report attempt to confirm age."""
self._downloader.to_stdout(u'[youtube] Confirming age')
def report_webpage_download(self, video_id):
"""Report attempt to download webpage."""
self._downloader.to_stdout(u'[youtube] %s: Downloading video webpage' % video_id)
def report_video_info_webpage_download(self, video_id):
"""Report attempt to download video info webpage."""
self._downloader.to_stdout(u'[youtube] %s: Downloading video info webpage' % video_id)
def report_information_extraction(self, video_id):
"""Report attempt to extract video information."""
@@ -660,42 +667,51 @@ class YoutubeIE(InfoExtractor):
# Extension
video_extension = self._video_extensions.get(format_param, 'flv')
# Normalize URL, including format
normalized_url = 'http://www.youtube.com/watch?v=%s&gl=US&hl=en' % video_id
if format_param is not None:
normalized_url = '%s&fmt=%s' % (normalized_url, format_param)
request = urllib2.Request(normalized_url, None, std_headers)
# Get video info
video_info_url = 'http://www.youtube.com/get_video_info?&video_id=%s&el=detailpage&ps=default&eurl=&gl=US&hl=en' % video_id
request = urllib2.Request(video_info_url, None, std_headers)
try:
self.report_webpage_download(video_id)
video_webpage = urllib2.urlopen(request).read()
self.report_video_info_webpage_download(video_id)
video_info_webpage = urllib2.urlopen(request).read()
except (urllib2.URLError, httplib.HTTPException, socket.error), err:
self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % str(err))
self._downloader.trouble(u'ERROR: unable to download video info webpage: %s' % str(err))
return
self.report_information_extraction(video_id)
# "t" param
mobj = re.search(r', "t": "([^"]+)"', video_webpage)
mobj = re.search(r'(?m)&token=([^&]+)(?:&|$)', video_info_webpage)
if mobj is None:
self._downloader.trouble(u'ERROR: unable to extract "t" parameter')
# Attempt to see if YouTube has issued an error message
mobj = re.search(r'(?m)&reason=([^&]+)(?:&|$)', video_info_webpage)
if mobj is None:
self._downloader.trouble(u'ERROR: unable to extract "t" parameter for unknown reason')
stream = open('reportme-ydl-%s.dat' % time.time(), 'wb')
stream.write(video_info_webpage)
stream.close()
else:
reason = urllib.unquote_plus(mobj.group(1))
self._downloader.trouble(u'ERROR: YouTube said: %s' % reason.decode('utf-8'))
return
video_real_url = 'http://www.youtube.com/get_video?video_id=%s&t=%s&el=detailpage&ps=' % (video_id, mobj.group(1))
token = urllib.unquote(mobj.group(1))
video_real_url = 'http://www.youtube.com/get_video?video_id=%s&t=%s&eurl=&el=detailpage&ps=default&gl=US&hl=en' % (video_id, token)
if format_param is not None:
video_real_url = '%s&fmt=%s' % (video_real_url, format_param)
self.report_video_url(video_id, video_real_url)
# uploader
mobj = re.search(r"var watchUsername = '([^']+)';", video_webpage)
mobj = re.search(r'(?m)&author=([^&]+)(?:&|$)', video_info_webpage)
if mobj is None:
self._downloader.trouble(u'ERROR: unable to extract uploader nickname')
return
video_uploader = mobj.group(1)
video_uploader = urllib.unquote(mobj.group(1))
# title
mobj = re.search(r'(?im)<title>YouTube - ([^<]*)</title>', video_webpage)
mobj = re.search(r'(?m)&title=([^&]+)(?:&|$)', video_info_webpage)
if mobj is None:
self._downloader.trouble(u'ERROR: unable to extract video title')
return
video_title = mobj.group(1).decode('utf-8')
video_title = urllib.unquote(mobj.group(1))
video_title = video_title.decode('utf-8')
video_title = re.sub(ur'(?u)&(.+?);', self.htmlentity_transform, video_title)
video_title = video_title.replace(os.sep, u'%')
@@ -816,19 +832,21 @@ class MetacafeIE(InfoExtractor):
# Extract URL, uploader and title from webpage
self.report_extraction(video_id)
mobj = re.search(r'(?m)&mediaURL=(http.*?\.flv)', webpage)
mobj = re.search(r'(?m)&mediaURL=([^&]+)', webpage)
if mobj is None:
self._downloader.trouble(u'ERROR: unable to extract media URL')
return
mediaURL = urllib.unquote(mobj.group(1))
mobj = re.search(r'(?m)&gdaKey=(.*?)&', webpage)
if mobj is None:
self._downloader.trouble(u'ERROR: unable to extract gdaKey')
return
gdaKey = mobj.group(1)
#mobj = re.search(r'(?m)&gdaKey=(.*?)&', webpage)
#if mobj is None:
# self._downloader.trouble(u'ERROR: unable to extract gdaKey')
# return
#gdaKey = mobj.group(1)
#
#video_url = '%s?__gda__=%s' % (mediaURL, gdaKey)
video_url = '%s?__gda__=%s' % (mediaURL, gdaKey)
video_url = mediaURL
mobj = re.search(r'(?im)<title>(.*) - Video</title>', webpage)
if mobj is None:
@@ -861,7 +879,7 @@ class YoutubeSearchIE(InfoExtractor):
_VALID_QUERY = r'ytsearch(\d+|all)?:[\s\S]+'
_TEMPLATE_URL = 'http://www.youtube.com/results?search_query=%s&page=%s&gl=US&hl=en'
_VIDEO_INDICATOR = r'href="/watch\?v=.+?"'
_MORE_PAGES_INDICATOR = r'>Next</a>'
_MORE_PAGES_INDICATOR = r'(?m)>\s*Next\s*</a>'
_youtube_ie = None
_max_youtube_results = 1000
@@ -938,7 +956,7 @@ class YoutubeSearchIE(InfoExtractor):
self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id)
return
if self._MORE_PAGES_INDICATOR not in page:
if re.search(self._MORE_PAGES_INDICATOR, page) is None:
for id in video_ids:
self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id)
return
@@ -948,10 +966,10 @@ class YoutubeSearchIE(InfoExtractor):
class YoutubePlaylistIE(InfoExtractor):
"""Information Extractor for YouTube playlists."""
_VALID_URL = r'(?:http://)?(?:\w+\.)?youtube.com/view_play_list\?p=(.+)'
_VALID_URL = r'(?:http://)?(?:\w+\.)?youtube.com/(?:view_play_list|my_playlists)\?.*?p=([^&]+).*'
_TEMPLATE_URL = 'http://www.youtube.com/view_play_list?p=%s&page=%s&gl=US&hl=en'
_VIDEO_INDICATOR = r'/watch\?v=(.+?)&'
_MORE_PAGES_INDICATOR = r'/view_play_list?p=%s&amp;page=%s'
_MORE_PAGES_INDICATOR = r'/view_play_list?p=%s&page=%s'
_youtube_ie = None
def __init__(self, youtube_ie, downloader=None):
@@ -997,7 +1015,7 @@ class YoutubePlaylistIE(InfoExtractor):
ids_in_page.append(mobj.group(1))
video_ids.extend(ids_in_page)
if (self._MORE_PAGES_INDICATOR % (playlist_id, pagenum + 1)) not in page:
if (self._MORE_PAGES_INDICATOR % (playlist_id.upper(), pagenum + 1)) not in page:
break
pagenum = pagenum + 1
@@ -1066,7 +1084,7 @@ if __name__ == '__main__':
# Parse command line
parser = optparse.OptionParser(
usage='Usage: %prog [options] url...',
version='2009.05.30',
version='2009.09.08',
conflict_handler='resolve',
)