# Author: Danny Colligan
# Author: Benjamin Johnson
# Author: Vasyl' Vavrychuk
+# Author: Witold Baryluk
+# Author: Paweł Paprota
+# Author: Gergely Imreh
# License: Public domain code
import cookielib
import ctypes
import datetime
+import email.utils
+import gzip
import htmlentitydefs
import httplib
import locale
import re
import socket
import string
+import StringIO
import subprocess
import sys
import time
import urllib
import urllib2
+import zlib
# parse_qs was moved from the cgi module to the urlparse module recently.
try:
from cgi import parse_qs
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',
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:2.0b10) Gecko/20100101 Firefox/4.0b10',
'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',
stream = open(filename, open_mode)
return (stream, filename)
+def timeconvert(timestr):
+ """Convert RFC 2822 defined time string into system timestamp"""
+ timestamp = None
+ timetuple = email.utils.parsedate_tz(timestr)
+ if timetuple is not None:
+ timestamp = email.utils.mktime_tz(timetuple)
+ return timestamp
+
class DownloadError(Exception):
"""Download Error exception.
self.downloaded = downloaded
self.expected = expected
+class YoutubeDLHandler(urllib2.HTTPHandler):
+ """Handler for HTTP requests and responses.
+
+ This class, when installed with an OpenerDirector, automatically adds
+ the standard headers to every HTTP request and handles gzipped and
+ deflated responses from web servers. If compression is to be avoided in
+ a particular request, the original request in the program code only has
+ to include the HTTP header "Youtubedl-No-Compression", which will be
+ removed before making the real request.
+
+ Part of this code was copied from:
+
+ http://techknack.net/python-urllib2-handlers/
+
+ Andrew Rowls, the author of that code, agreed to release it to the
+ public domain.
+ """
+
+ @staticmethod
+ def deflate(data):
+ try:
+ return zlib.decompress(data, -zlib.MAX_WBITS)
+ except zlib.error:
+ return zlib.decompress(data)
+
+ @staticmethod
+ def addinfourl_wrapper(stream, headers, url, code):
+ if hasattr(urllib2.addinfourl, 'getcode'):
+ return urllib2.addinfourl(stream, headers, url, code)
+ ret = urllib2.addinfourl(stream, headers, url)
+ ret.code = code
+ return ret
+
+ def http_request(self, req):
+ for h in std_headers:
+ if h in req.headers:
+ del req.headers[h]
+ req.add_header(h, std_headers[h])
+ if 'Youtubedl-no-compression' in req.headers:
+ if 'Accept-encoding' in req.headers:
+ del req.headers['Accept-encoding']
+ del req.headers['Youtubedl-no-compression']
+ return req
+
+ def http_response(self, req, resp):
+ old_resp = resp
+ # gzip
+ if resp.headers.get('Content-encoding', '') == 'gzip':
+ gz = gzip.GzipFile(fileobj=StringIO.StringIO(resp.read()), mode='r')
+ resp = self.addinfourl_wrapper(gz, old_resp.headers, old_resp.url, old_resp.code)
+ resp.msg = old_resp.msg
+ # deflate
+ if resp.headers.get('Content-encoding', '') == 'deflate':
+ gz = StringIO.StringIO(self.deflate(resp.read()))
+ resp = self.addinfourl_wrapper(gz, old_resp.headers, old_resp.url, old_resp.code)
+ resp.msg = old_resp.msg
+ return resp
+
class FileDownloader(object):
"""File Downloader class.
forcetitle: Force printing title.
forcethumbnail: Force printing thumbnail URL.
forcedescription: Force printing description.
+ forcefilename: Force printing final filename.
simulate: Do not download the video files.
format: Video format code.
format_limit: Highest quality format to try.
playlistend: Playlist item to end at.
logtostderr: Log messages to stderr instead of stdout.
consoletitle: Display progress in console window's titlebar.
+ nopart: Do not use temporary .part files.
+ updatetime: Use the Last-modified header to set output file timestamps.
"""
params = None
for dir in aggregate:
if not os.path.exists(dir):
os.mkdir(dir)
-
- @staticmethod
- def temp_name(filename):
- """Returns a temporary filename for the given filename."""
- if filename == u'-' or (os.path.exists(filename) and not os.path.isfile(filename)):
- return filename
- return filename + u'.part'
-
+
@staticmethod
def format_bytes(bytes):
if bytes is None:
speed = float(byte_counter) / elapsed
if speed > rate_limit:
time.sleep((byte_counter - rate_limit * (now - start_time)) / rate_limit)
-
+
+ def temp_name(self, filename):
+ """Returns a temporary filename for the given filename."""
+ if self.params.get('nopart', False) or filename == u'-' or \
+ (os.path.exists(filename) and not os.path.isfile(filename)):
+ return filename
+ return filename + u'.part'
+
+ def undo_temp_name(self, filename):
+ if filename.endswith(u'.part'):
+ return filename[:-len(u'.part')]
+ return filename
+
def try_rename(self, old_filename, new_filename):
try:
if old_filename == new_filename:
os.rename(old_filename, new_filename)
except (IOError, OSError), err:
self.trouble(u'ERROR: unable to rename file')
+
+ def try_utime(self, filename, last_modified_hdr):
+ """Try to set the last-modified time of the given file."""
+ if last_modified_hdr is None:
+ return
+ if not os.path.isfile(filename):
+ return
+ timestr = last_modified_hdr
+ if timestr is None:
+ return
+ filetime = timeconvert(timestr)
+ if filetime is None:
+ return
+ try:
+ os.utime(filename,(time.time(), filetime))
+ except:
+ pass
def report_destination(self, filename):
"""Report destination filename."""
"""Increment the ordinal that assigns a number to each file."""
self._num_downloads += 1
+ def prepare_filename(self, info_dict):
+ """Generate the output filename."""
+ try:
+ template_dict = dict(info_dict)
+ template_dict['epoch'] = unicode(long(time.time()))
+ template_dict['autonumber'] = unicode('%05d' % self._num_downloads)
+ filename = self.params['outtmpl'] % template_dict
+ return filename
+ except (ValueError, KeyError), err:
+ self.trouble(u'ERROR: invalid system charset or erroneous output template')
+ return None
+
def process_info(self, info_dict):
"""Process a single dictionary returned by an InfoExtractor."""
+ filename = self.prepare_filename(info_dict)
# Do nothing else if in simulate mode
if self.params.get('simulate', False):
# Forced printings
print info_dict['thumbnail'].encode(preferredencoding(), 'xmlcharrefreplace')
if self.params.get('forcedescription', False) and 'description' in info_dict:
print info_dict['description'].encode(preferredencoding(), 'xmlcharrefreplace')
+ if self.params.get('forcefilename', False) and filename is not None:
+ print filename.encode(preferredencoding(), 'xmlcharrefreplace')
return
- try:
- template_dict = dict(info_dict)
- template_dict['epoch'] = unicode(long(time.time()))
- template_dict['autonumber'] = unicode('%05d' % self._num_downloads)
- filename = self.params['outtmpl'] % template_dict
- except (ValueError, KeyError), err:
- self.trouble(u'ERROR: invalid system charset or erroneous output template')
+ 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')
def _do_download(self, filename, url, player_url):
# Check file already present
- if self.params.get('continuedl', False) and os.path.isfile(filename):
+ if self.params.get('continuedl', False) and os.path.isfile(filename) and not self.params.get('nopart', False):
self.report_file_already_downloaded(filename)
return True
tmpfilename = self.temp_name(filename)
stream = None
open_mode = 'wb'
- basic_request = urllib2.Request(url, None, std_headers)
- request = urllib2.Request(url, None, std_headers)
+
+ # Do not include the Accept-Encoding header
+ headers = {'Youtubedl-no-compression': 'True'}
+ basic_request = urllib2.Request(url, None, headers)
+ request = urllib2.Request(url, None, headers)
# Establish possible resume length
if os.path.isfile(tmpfilename):
if stream is None:
try:
(stream, tmpfilename) = sanitize_open(tmpfilename, open_mode)
+ filename = self.undo_temp_name(tmpfilename)
self.report_destination(filename)
except (OSError, IOError), err:
self.trouble(u'ERROR: unable to open for writing: %s' % str(err))
if data_len is not None and byte_counter != data_len:
raise ContentTooShortError(byte_counter, long(data_len))
self.try_rename(tmpfilename, filename)
+
+ # Update file modification time
+ if self.params.get('updatetime', True):
+ self.try_utime(filename, data.info().get('last-modified', None))
+
return True
class InfoExtractor(object):
class YoutubeIE(InfoExtractor):
"""Information extractor for youtube.com."""
- _VALID_URL = r'^((?:https?://)?(?:youtu\.be/|(?:\w+\.)?youtube(?:-nocookie)?\.com/(?:(?:v/)|(?:(?:watch(?:_popup)?(?:\.php)?)?(?:\?|#!?)(?:.+&)?v=))))?([0-9A-Za-z_-]+)(?(1).+)?$'
+ _VALID_URL = r'^((?:https?://)?(?:youtu\.be/|(?:\w+\.)?youtube(?:-nocookie)?\.com/)(?:(?:(?:v|embed)/)|(?:(?:watch(?:_popup)?(?:\.php)?)?(?:\?|#!?)(?:.+&)?v=)))?([0-9A-Za-z_-]+)(?(1).+)?$'
_LANG_URL = r'http://www.youtube.com/?hl=en&persist_hl=1&gl=US&persist_gl=1&opt_out_ackd=1'
_LOGIN_URL = 'https://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'
# Listed in order of quality
- _available_formats = ['38', '37', '22', '45', '35', '34', '43', '18', '6', '5', '17', '13']
+ _available_formats = ['38', '37', '45', '22', '43', '35', '34', '18', '6', '5', '17', '13']
_video_extensions = {
'13': '3gp',
'17': 'mp4',
return
# Set language
- request = urllib2.Request(self._LANG_URL, None, std_headers)
+ request = urllib2.Request(self._LANG_URL)
try:
self.report_lang()
urllib2.urlopen(request).read()
'username': username,
'password': password,
}
- request = urllib2.Request(self._LOGIN_URL, urllib.urlencode(login_form), std_headers)
+ request = urllib2.Request(self._LOGIN_URL, urllib.urlencode(login_form))
try:
self.report_login()
login_results = urllib2.urlopen(request).read()
'next_url': '/',
'action_confirm': 'Confirm',
}
- request = urllib2.Request(self._AGE_URL, urllib.urlencode(age_form), std_headers)
+ request = urllib2.Request(self._AGE_URL, urllib.urlencode(age_form))
try:
self.report_age_confirmation()
age_results = urllib2.urlopen(request).read()
# Get video webpage
self.report_video_webpage_download(video_id)
- request = urllib2.Request('http://www.youtube.com/watch?v=%s&gl=US&hl=en&has_verified=1' % video_id, None, std_headers)
+ request = urllib2.Request('http://www.youtube.com/watch?v=%s&gl=US&hl=en&has_verified=1' % video_id)
try:
video_webpage = urllib2.urlopen(request).read()
except (urllib2.URLError, httplib.HTTPException, socket.error), err:
for el_type in ['&el=embedded', '&el=detailpage', '&el=vevo', '']:
video_info_url = ('http://www.youtube.com/get_video_info?&video_id=%s%s&ps=default&eurl=&gl=US&hl=en'
% (video_id, el_type))
- request = urllib2.Request(video_info_url, None, std_headers)
+ request = urllib2.Request(video_info_url)
try:
video_info_webpage = urllib2.urlopen(request).read()
video_info = parse_qs(video_info_webpage)
def _real_initialize(self):
# Retrieve disclaimer
- request = urllib2.Request(self._DISCLAIMER, None, std_headers)
+ request = urllib2.Request(self._DISCLAIMER)
try:
self.report_disclaimer()
disclaimer = urllib2.urlopen(request).read()
'filters': '0',
'submit': "Continue - I'm over 18",
}
- request = urllib2.Request(self._FILTER_POST, urllib.urlencode(disclaimer_form), std_headers)
+ request = urllib2.Request(self._FILTER_POST, urllib.urlencode(disclaimer_form))
try:
self.report_age_confirmation()
disclaimer = urllib2.urlopen(request).read()
video_title = mobj.group(1).decode('utf-8')
video_title = sanitize_title(video_title)
- mobj = re.search(r'(?im)<div class="dmco_html owner">.*?<a class="name" href="/.+?">(.+?)</a>', webpage)
+ mobj = re.search(r'(?im)<Attribute name="owner">(.+?)</Attribute>', webpage)
if mobj is None:
self._downloader.trouble(u'ERROR: unable to extract uploader nickname')
return
while True:
self.report_download_page(query, pagenum)
result_url = self._TEMPLATE_URL % (urllib.quote_plus(query), pagenum)
- request = urllib2.Request(result_url, None, std_headers)
+ request = urllib2.Request(result_url)
try:
page = urllib2.urlopen(request).read()
except (urllib2.URLError, httplib.HTTPException, socket.error), err:
while True:
self.report_download_page(query, pagenum)
result_url = self._TEMPLATE_URL % (urllib.quote_plus(query), pagenum)
- request = urllib2.Request(result_url, None, std_headers)
+ request = urllib2.Request(result_url)
try:
page = urllib2.urlopen(request).read()
except (urllib2.URLError, httplib.HTTPException, socket.error), err:
while True:
self.report_download_page(query, pagenum)
result_url = self._TEMPLATE_URL % (urllib.quote_plus(query), pagenum)
- request = urllib2.Request(result_url, None, std_headers)
+ request = urllib2.Request(result_url)
try:
page = urllib2.urlopen(request).read()
except (urllib2.URLError, httplib.HTTPException, socket.error), err:
class YoutubePlaylistIE(InfoExtractor):
"""Information Extractor for YouTube playlists."""
- _VALID_URL = r'(?:http://)?(?:\w+\.)?youtube.com/(?:(?:view_play_list|my_playlists)\?.*?p=|user/.*?/user/)([^&]+).*'
- _TEMPLATE_URL = 'http://www.youtube.com/view_play_list?p=%s&page=%s&gl=US&hl=en'
+ _VALID_URL = r'(?:http://)?(?:\w+\.)?youtube.com/(?:(?:view_play_list|my_playlists|artist)\?.*?(p|a)=|user/.*?/user/|p/|user/.*?#[pg]/c/)([0-9A-Za-z]+)(?:/.*?/([0-9A-Za-z_-]+))?.*'
+ _TEMPLATE_URL = 'http://www.youtube.com/%s?%s=%s&page=%s&gl=US&hl=en'
_VIDEO_INDICATOR = r'/watch\?v=(.+?)&'
_MORE_PAGES_INDICATOR = r'(?m)>\s*Next\s*</a>'
_youtube_ie = None
self._downloader.trouble(u'ERROR: invalid url: %s' % url)
return
+ # Single video case
+ if mobj.group(3) is not None:
+ self._youtube_ie.extract(mobj.group(3))
+ return
+
# Download playlist pages
- playlist_id = mobj.group(1)
+ # prefix is 'p' as default for playlists but there are other types that need extra care
+ playlist_prefix = mobj.group(1)
+ if playlist_prefix == 'a':
+ playlist_access = 'artist'
+ else:
+ playlist_prefix = 'p'
+ playlist_access = 'view_play_list'
+ playlist_id = mobj.group(2)
video_ids = []
pagenum = 1
while True:
self.report_download_page(playlist_id, pagenum)
- request = urllib2.Request(self._TEMPLATE_URL % (playlist_id, pagenum), None, std_headers)
+ request = urllib2.Request(self._TEMPLATE_URL % (playlist_access, playlist_prefix, playlist_id, pagenum))
try:
page = urllib2.urlopen(request).read()
except (urllib2.URLError, httplib.HTTPException, socket.error), err:
class YoutubeUserIE(InfoExtractor):
"""Information Extractor for YouTube users."""
- _VALID_URL = r'(?:http://)?(?:\w+\.)?youtube.com/user/(.*)'
+ _VALID_URL = r'(?:(?:(?:http://)?(?:\w+\.)?youtube.com/user/)|ytuser:)([A-Za-z0-9_-]+)'
_TEMPLATE_URL = 'http://gdata.youtube.com/feeds/api/users/%s'
- _VIDEO_INDICATOR = r'http://gdata.youtube.com/feeds/api/videos/(.*)' # XXX Fix this.
+ _GDATA_PAGE_SIZE = 50
+ _GDATA_URL = 'http://gdata.youtube.com/feeds/api/users/%s/uploads?max-results=%d&start-index=%d'
+ _VIDEO_INDICATOR = r'/watch\?v=(.+?)&'
_youtube_ie = None
def __init__(self, youtube_ie, downloader=None):
def suitable(url):
return (re.match(YoutubeUserIE._VALID_URL, url) is not None)
- def report_download_page(self, username):
+ def report_download_page(self, username, start_index):
"""Report attempt to download user page."""
- self._downloader.to_screen(u'[youtube] user %s: Downloading page ' % (username))
+ self._downloader.to_screen(u'[youtube] user %s: Downloading video ids from %d to %d' %
+ (username, start_index, start_index + self._GDATA_PAGE_SIZE))
def _real_initialize(self):
self._youtube_ie.initialize()
self._downloader.trouble(u'ERROR: invalid url: %s' % url)
return
- # Download user page
username = mobj.group(1)
+
+ # Download video ids using YouTube Data API. Result size per
+ # query is limited (currently to 50 videos) so we need to query
+ # page by page until there are no video ids - it means we got
+ # all of them.
+
video_ids = []
- pagenum = 1
+ pagenum = 0
- self.report_download_page(username)
- request = urllib2.Request(self._TEMPLATE_URL % (username), None, std_headers)
- try:
- page = urllib2.urlopen(request).read()
- except (urllib2.URLError, httplib.HTTPException, socket.error), err:
- self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err))
- return
+ while True:
+ start_index = pagenum * self._GDATA_PAGE_SIZE + 1
+ self.report_download_page(username, start_index)
+
+ request = urllib2.Request(self._GDATA_URL % (username, self._GDATA_PAGE_SIZE, start_index))
+
+ try:
+ page = urllib2.urlopen(request).read()
+ except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+ self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err))
+ return
- # Extract video identifiers
- ids_in_page = []
+ # Extract video identifiers
+ ids_in_page = []
- for mobj in re.finditer(self._VIDEO_INDICATOR, page):
- if mobj.group(1) not in ids_in_page:
- ids_in_page.append(mobj.group(1))
- video_ids.extend(ids_in_page)
+ for mobj in re.finditer(self._VIDEO_INDICATOR, page):
+ if mobj.group(1) not in ids_in_page:
+ ids_in_page.append(mobj.group(1))
+ video_ids.extend(ids_in_page)
+
+ # A little optimization - if current page is not
+ # "full", ie. does not contain PAGE_SIZE video ids then
+ # we can assume that this page is the last one - there
+ # are no more ids on further pages - no need to query
+ # again.
+
+ if len(ids_in_page) < self._GDATA_PAGE_SIZE:
+ break
+
+ pagenum += 1
+
+ all_ids_count = len(video_ids)
playliststart = self._downloader.params.get('playliststart', 1) - 1
playlistend = self._downloader.params.get('playlistend', -1)
- video_ids = video_ids[playliststart:playlistend]
- for id in video_ids:
- self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id)
- return
+ if playlistend == -1:
+ video_ids = video_ids[playliststart:]
+ else:
+ video_ids = video_ids[playliststart:playlistend]
+
+ self._downloader.to_screen("[youtube] user %s: Collected %d video ids (downloading %d of them)" %
+ (username, all_ids_count, len(video_ids)))
+
+ for video_id in video_ids:
+ self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % video_id)
+
class DepositFilesIE(InfoExtractor):
"""Information extractor for depositfiles.com"""
# Retrieve file webpage with 'Free download' button pressed
free_download_indication = { 'gateway_result' : '1' }
- request = urllib2.Request(url, urllib.urlencode(free_download_indication), std_headers)
+ request = urllib2.Request(url, urllib.urlencode(free_download_indication))
try:
self.report_download_webpage(file_id)
webpage = urllib2.urlopen(request).read()
except UnavailableVideoError, err:
self._downloader.trouble(u'ERROR: unable to download file')
+class FacebookIE(InfoExtractor):
+ """Information Extractor for Facebook"""
+
+ _VALID_URL = r'^(?:https?://)?(?:\w+\.)?facebook.com/video/video.php\?(?:.*?)v=(?P<ID>\d+)(?:.*)'
+ _LOGIN_URL = 'https://login.facebook.com/login.php?m&next=http%3A%2F%2Fm.facebook.com%2Fhome.php&'
+ _NETRC_MACHINE = 'facebook'
+ _available_formats = ['highqual', 'lowqual']
+ _video_extensions = {
+ 'highqual': 'mp4',
+ 'lowqual': 'mp4',
+ }
+
+ def __init__(self, downloader=None):
+ InfoExtractor.__init__(self, downloader)
+
+ @staticmethod
+ def suitable(url):
+ return (re.match(FacebookIE._VALID_URL, url) is not None)
+
+ def _reporter(self, message):
+ """Add header and report message."""
+ self._downloader.to_screen(u'[facebook] %s' % message)
+
+ def report_login(self):
+ """Report attempt to log in."""
+ self._reporter(u'Logging in')
+
+ def report_video_webpage_download(self, video_id):
+ """Report attempt to download video webpage."""
+ self._reporter(u'%s: Downloading video webpage' % video_id)
+
+ def report_information_extraction(self, video_id):
+ """Report attempt to extract video information."""
+ self._reporter(u'%s: Extracting video information' % video_id)
+
+ def _parse_page(self, video_webpage):
+ """Extract video information from page"""
+ # General data
+ data = {'title': r'class="video_title datawrap">(.*?)</',
+ 'description': r'<div class="datawrap">(.*?)</div>',
+ 'owner': r'\("video_owner_name", "(.*?)"\)',
+ 'upload_date': r'data-date="(.*?)"',
+ 'thumbnail': r'\("thumb_url", "(?P<THUMB>.*?)"\)',
+ }
+ video_info = {}
+ for piece in data.keys():
+ mobj = re.search(data[piece], video_webpage)
+ if mobj is not None:
+ video_info[piece] = urllib.unquote_plus(mobj.group(1).decode("unicode_escape"))
+
+ # Video urls
+ video_urls = {}
+ for fmt in self._available_formats:
+ mobj = re.search(r'\("%s_src\", "(.+?)"\)' % fmt, video_webpage)
+ if mobj is not None:
+ # URL is in a Javascript segment inside an escaped Unicode format within
+ # the generally utf-8 page
+ video_urls[fmt] = urllib.unquote_plus(mobj.group(1).decode("unicode_escape"))
+ video_info['video_urls'] = video_urls
+
+ return video_info
+
+ def _real_initialize(self):
+ if self._downloader is None:
+ return
+
+ useremail = None
+ password = None
+ downloader_params = self._downloader.params
+
+ # Attempt to use provided username and password or .netrc data
+ if downloader_params.get('username', None) is not None:
+ useremail = downloader_params['username']
+ password = downloader_params['password']
+ elif downloader_params.get('usenetrc', False):
+ try:
+ info = netrc.netrc().authenticators(self._NETRC_MACHINE)
+ if info is not None:
+ useremail = info[0]
+ password = info[2]
+ else:
+ raise netrc.NetrcParseError('No authenticators for %s' % self._NETRC_MACHINE)
+ except (IOError, netrc.NetrcParseError), err:
+ self._downloader.to_stderr(u'WARNING: parsing .netrc: %s' % str(err))
+ return
+
+ if useremail is None:
+ return
+
+ # Log in
+ login_form = {
+ 'email': useremail,
+ 'pass': password,
+ 'login': 'Log+In'
+ }
+ request = urllib2.Request(self._LOGIN_URL, urllib.urlencode(login_form))
+ try:
+ self.report_login()
+ login_results = urllib2.urlopen(request).read()
+ if re.search(r'<form(.*)name="login"(.*)</form>', login_results) is not None:
+ self._downloader.to_stderr(u'WARNING: unable to log in: bad username/password, or exceded login rate limit (~3/min). Check credentials or wait.')
+ return
+ except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+ self._downloader.to_stderr(u'WARNING: unable to log in: %s' % str(err))
+ return
+
+ def _real_extract(self, url):
+ mobj = re.match(self._VALID_URL, url)
+ if mobj is None:
+ self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
+ return
+ video_id = mobj.group('ID')
+
+ # Get video webpage
+ self.report_video_webpage_download(video_id)
+ request = urllib2.Request('https://www.facebook.com/video/video.php?v=%s' % video_id)
+ try:
+ page = urllib2.urlopen(request)
+ video_webpage = page.read()
+ except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+ self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % str(err))
+ return
+
+ # Start extracting information
+ self.report_information_extraction(video_id)
+
+ # Extract information
+ video_info = self._parse_page(video_webpage)
+
+ # uploader
+ if 'owner' not in video_info:
+ self._downloader.trouble(u'ERROR: unable to extract uploader nickname')
+ return
+ video_uploader = video_info['owner']
+
+ # title
+ if 'title' not in video_info:
+ self._downloader.trouble(u'ERROR: unable to extract video title')
+ return
+ video_title = video_info['title']
+ video_title = video_title.decode('utf-8')
+ video_title = sanitize_title(video_title)
+
+ # simplified title
+ simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
+ simple_title = simple_title.strip(ur'_')
+
+ # thumbnail image
+ if 'thumbnail' not in video_info:
+ self._downloader.trouble(u'WARNING: unable to extract video thumbnail')
+ video_thumbnail = ''
+ else:
+ video_thumbnail = video_info['thumbnail']
+
+ # upload date
+ upload_date = u'NA'
+ if 'upload_date' in video_info:
+ upload_time = video_info['upload_date']
+ timetuple = email.utils.parsedate_tz(upload_time)
+ if timetuple is not None:
+ try:
+ upload_date = time.strftime('%Y%m%d', timetuple[0:9])
+ except:
+ pass
+
+ # description
+ video_description = 'No description available.'
+ if (self._downloader.params.get('forcedescription', False) and
+ 'description' in video_info):
+ video_description = video_info['description']
+
+ url_map = video_info['video_urls']
+ if len(url_map.keys()) > 0:
+ # Decide which formats to download
+ req_format = self._downloader.params.get('format', None)
+ 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):]
+ else:
+ format_list = self._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')
+ return
+ if req_format is None:
+ video_url_list = [(existing_formats[0], url_map[existing_formats[0]])] # Best quality
+ elif req_format == '-1':
+ video_url_list = [(f, url_map[f]) for f in existing_formats] # All formats
+ else:
+ # 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
+
+ for format_param, video_real_url in video_url_list:
+
+ # At this point we have a new video
+ self._downloader.increment_downloads()
+
+ # Extension
+ video_extension = self._video_extensions.get(format_param, 'mp4')
+
+ # Find the video URL in fmt_url_map or conn paramters
+ try:
+ # Process video information
+ self._downloader.process_info({
+ 'id': video_id.decode('utf-8'),
+ 'url': video_real_url.decode('utf-8'),
+ 'uploader': video_uploader.decode('utf-8'),
+ 'upload_date': upload_date,
+ 'title': video_title,
+ 'stitle': simple_title,
+ 'ext': video_extension.decode('utf-8'),
+ 'format': (format_param is None and u'NA' or format_param.decode('utf-8')),
+ 'thumbnail': video_thumbnail.decode('utf-8'),
+ 'description': video_description.decode('utf-8'),
+ 'player_url': None,
+ })
+ except UnavailableVideoError, err:
+ self._downloader.trouble(u'\nERROR: unable to download video')
+
class PostProcessor(object):
"""Post Processor class.
import getpass
import optparse
- # Function to update the program file with the latest version from bitbucket.org
+ # Function to update the program file with the latest version from the repository.
def update_self(downloader, filename):
# Note: downloader only used for options
- if not os.access (filename, os.W_OK):
+ if not os.access(filename, os.W_OK):
sys.exit('ERROR: no write permissions on %s' % filename)
downloader.to_screen('Updating to latest stable version...')
- latest_url = 'http://github.com/rg3/youtube-dl/raw/master/LATEST_VERSION'
- latest_version = urllib.urlopen(latest_url).read().strip()
- prog_url = 'http://github.com/rg3/youtube-dl/raw/%s/youtube-dl' % latest_version
- newcontent = urllib.urlopen(prog_url).read()
- stream = open(filename, 'w')
- stream.write(newcontent)
- stream.close()
+ try:
+ latest_url = 'http://github.com/rg3/youtube-dl/raw/master/LATEST_VERSION'
+ latest_version = urllib.urlopen(latest_url).read().strip()
+ prog_url = 'http://github.com/rg3/youtube-dl/raw/%s/youtube-dl' % latest_version
+ newcontent = urllib.urlopen(prog_url).read()
+ except (IOError, OSError), err:
+ sys.exit('ERROR: unable to download latest version')
+ try:
+ stream = open(filename, 'w')
+ stream.write(newcontent)
+ stream.close()
+ except (IOError, OSError), err:
+ sys.exit('ERROR: unable to overwrite current version')
downloader.to_screen('Updated to version %s' % latest_version)
# Parse command line
parser = optparse.OptionParser(
usage='Usage: %prog [options] url...',
- version='2010.12.09',
+ version='2011.01.30',
conflict_handler='resolve',
)
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)
+ 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',
verbosity.add_option('-e', '--get-title',
action='store_true', dest='gettitle', help='simulate, quiet but print title', default=False)
verbosity.add_option('--get-thumbnail',
- action='store_true', dest='getthumbnail', help='simulate, quiet but print thumbnail URL', default=False)
+ action='store_true', dest='getthumbnail',
+ help='simulate, quiet but print thumbnail URL', default=False)
verbosity.add_option('--get-description',
- action='store_true', dest='getdescription', help='simulate, quiet but print video description', default=False)
+ action='store_true', dest='getdescription',
+ help='simulate, quiet but print video description', default=False)
+ verbosity.add_option('--get-filename',
+ action='store_true', dest='getfilename',
+ help='simulate, quiet but print output filename', 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)
+ action='store_true', dest='consoletitle',
+ help='display progress in console titlebar', default=False)
parser.add_option_group(verbosity)
filesystem = optparse.OptionGroup(parser, 'Filesystem Options')
filesystem.add_option('-l', '--literal',
action='store_true', dest='useliteral', help='use literal title in file name', default=False)
filesystem.add_option('-A', '--auto-number',
- action='store_true', dest='autonumber', help='number downloaded files starting from 00000', default=False)
+ action='store_true', dest='autonumber',
+ help='number downloaded files starting from 00000', default=False)
filesystem.add_option('-o', '--output',
dest='outtmpl', metavar='TEMPLATE', help='output filename template')
filesystem.add_option('-a', '--batch-file',
action='store_true', dest='continue_dl', help='resume partially downloaded files', default=False)
filesystem.add_option('--cookies',
dest='cookiefile', metavar='FILE', help='file to dump cookie jar to')
+ filesystem.add_option('--no-part',
+ action='store_true', dest='nopart', help='do not use .part files', default=False)
+ filesystem.add_option('--no-mtime',
+ action='store_false', dest='updatetime',
+ help='do not use the Last-modified header to set the file modification time', default=True)
parser.add_option_group(filesystem)
(opts, args) = parser.parse_args()
# General configuration
cookie_processor = urllib2.HTTPCookieProcessor(jar)
- urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler()))
- urllib2.install_opener(urllib2.build_opener(cookie_processor))
+ urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler(), cookie_processor, YoutubeDLHandler()))
socket.setdefaulttimeout(300) # 5 minutes should be enough (famous last words)
# Batch file verification
yahoo_ie = YahooIE()
yahoo_search_ie = YahooSearchIE(yahoo_ie)
deposit_files_ie = DepositFilesIE()
+ facebook_ie = FacebookIE()
generic_ie = GenericIE()
# File downloader
'usenetrc': opts.usenetrc,
'username': opts.username,
'password': opts.password,
- 'quiet': (opts.quiet or opts.geturl or opts.gettitle or opts.getthumbnail or opts.getdescription),
+ 'quiet': (opts.quiet or opts.geturl or opts.gettitle or opts.getthumbnail or opts.getdescription or opts.getfilename),
'forceurl': opts.geturl,
'forcetitle': opts.gettitle,
'forcethumbnail': opts.getthumbnail,
'forcedescription': opts.getdescription,
- 'simulate': (opts.simulate or opts.geturl or opts.gettitle or opts.getthumbnail or opts.getdescription),
+ 'forcefilename': opts.getfilename,
+ 'simulate': (opts.simulate or opts.geturl or opts.gettitle or opts.getthumbnail or opts.getdescription or opts.getfilename),
'format': opts.format,
'format_limit': opts.format_limit,
'outtmpl': ((opts.outtmpl is not None and opts.outtmpl.decode(preferredencoding()))
'playlistend': opts.playlistend,
'logtostderr': opts.outtmpl == '-',
'consoletitle': opts.consoletitle,
+ 'nopart': opts.nopart,
+ 'updatetime': opts.updatetime,
})
fd.add_info_extractor(youtube_search_ie)
fd.add_info_extractor(youtube_pl_ie)
fd.add_info_extractor(yahoo_ie)
fd.add_info_extractor(yahoo_search_ie)
fd.add_info_extractor(deposit_files_ie)
+ fd.add_info_extractor(facebook_ie)
# This must come last since it's the
# fallback if none of the others work