diff options
-rw-r--r-- | LATEST_VERSION | 2 | ||||
-rw-r--r-- | Makefile | 11 | ||||
-rwxr-xr-x | youtube-dl | bin | 40663 -> 42225 bytes | |||
-rwxr-xr-x | youtube-dl.exe | bin | 3989787 -> 0 bytes | |||
-rw-r--r-- | youtube_dl/InfoExtractors.py | 288 | ||||
-rw-r--r-- | youtube_dl/__init__.py | 5 | ||||
-rw-r--r-- | youtube_dl/utils.py | 2 |
7 files changed, 300 insertions, 8 deletions
diff --git a/LATEST_VERSION b/LATEST_VERSION index 0c8dc502a..88c5427e0 100644 --- a/LATEST_VERSION +++ b/LATEST_VERSION @@ -1 +1 @@ -2012.02.27 +2012.09.27 @@ -1,4 +1,9 @@ -all: youtube-dl youtube-dl.exe README.md youtube-dl.1 youtube-dl.bash-completion LATEST_VERSION +all: youtube-dl README.md youtube-dl.1 youtube-dl.bash-completion LATEST_VERSION + +# TODO: re-add youtube-dl.exe, and make sure it's 1. safe and 2. doesn't need sudo + +clean: + rm -f youtube-dl youtube-dl.exe youtube-dl.1 LATEST_VERSION PREFIX=/usr/local install: youtube-dl youtube-dl.1 youtube-dl.bash-completion @@ -6,13 +11,15 @@ install: youtube-dl youtube-dl.1 youtube-dl.bash-completion install -m 644 --owner root --group root youtube-dl.1 $(PREFIX)/man/man1 install -m 644 --owner root --group root youtube-dl.bash-completion /etc/bash_completion.d/youtube-dl -.PHONY: all install +.PHONY: all clean install README.md youtube-dl.bash-completion +# TODO un-phone README.md and youtube-dl.bash_completion by reading from .in files and generating from them youtube-dl: youtube_dl/*.py zip --quiet --junk-paths youtube-dl youtube_dl/*.py echo '#!/usr/bin/env python' > youtube-dl cat youtube-dl.zip >> youtube-dl rm youtube-dl.zip + chmod a+x youtube-dl youtube-dl.exe: youtube_dl/*.py bash devscripts/wine-py2exe.sh build_exe.py diff --git a/youtube-dl b/youtube-dl Binary files differindex 82d465c46..9c78766ff 100755 --- a/youtube-dl +++ b/youtube-dl diff --git a/youtube-dl.exe b/youtube-dl.exe Binary files differdeleted file mode 100755 index b3016aeb6..000000000 --- a/youtube-dl.exe +++ /dev/null diff --git a/youtube_dl/InfoExtractors.py b/youtube_dl/InfoExtractors.py index 0835c447c..ff5c47726 100644 --- a/youtube_dl/InfoExtractors.py +++ b/youtube_dl/InfoExtractors.py @@ -13,6 +13,8 @@ import urllib import urllib2 import email.utils import xml.etree.ElementTree +import random +import math from urlparse import parse_qs try: @@ -95,7 +97,7 @@ class InfoExtractor(object): class YoutubeIE(InfoExtractor): """Information extractor for youtube.com.""" - _VALID_URL = r'^((?:https?://)?(?:youtu\.be/|(?:\w+\.)?youtube(?:-nocookie)?\.com/)(?!view_play_list|my_playlists|artist|playlist)(?:(?:(?:v|embed|e)/)|(?:(?:watch(?:_popup)?(?:\.php)?)?(?:\?|#!?)(?:.+&)?v=))?)?([0-9A-Za-z_-]+)(?(1).+)?$' + _VALID_URL = r'^((?:https?://)?(?:youtu\.be/|(?:\w+\.)?youtube(?:-nocookie)?\.com/|tube\.majestyc\.net/)(?!view_play_list|my_playlists|artist|playlist)(?:(?:(?:v|embed|e)/)|(?:(?: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' @@ -402,7 +404,7 @@ class YoutubeIE(InfoExtractor): url_data_strs = video_info['url_encoded_fmt_stream_map'][0].split(',') url_data = [parse_qs(uds) for uds in url_data_strs] url_data = filter(lambda ud: 'itag' in ud and 'url' in ud, url_data) - url_map = dict((ud['itag'][0], ud['url'][0]) for ud in url_data) + url_map = dict((ud['itag'][0], ud['url'][0] + '&signature=' + ud['sig'][0]) for ud in url_data) format_limit = self._downloader.params.get('format_limit', None) available_formats = self._available_formats_prefer_free if self._downloader.params.get('prefer_free_formats', False) else self._available_formats @@ -1622,6 +1624,98 @@ class YoutubeUserIE(InfoExtractor): self._downloader.download(['http://www.youtube.com/watch?v=%s' % video_id]) +class BlipTVUserIE(InfoExtractor): + """Information Extractor for blip.tv users.""" + + _VALID_URL = r'(?:(?:(?:https?://)?(?:\w+\.)?blip\.tv/)|bliptvuser:)([^/]+)/*$' + _PAGE_SIZE = 12 + IE_NAME = u'blip.tv:user' + + def __init__(self, downloader=None): + InfoExtractor.__init__(self, downloader) + + def report_download_page(self, username, pagenum): + """Report attempt to download user page.""" + self._downloader.to_screen(u'[%s] user %s: Downloading video ids from page %d' % + (self.IE_NAME, username, pagenum)) + + def _real_extract(self, url): + # Extract username + mobj = re.match(self._VALID_URL, url) + if mobj is None: + self._downloader.trouble(u'ERROR: invalid url: %s' % url) + return + + username = mobj.group(1) + + page_base = 'http://m.blip.tv/pr/show_get_full_episode_list?users_id=%s&lite=0&esi=1' + + request = urllib2.Request(url) + + try: + page = urllib2.urlopen(request).read().decode('utf-8') + mobj = re.search(r'data-users-id="([^"]+)"', page) + page_base = page_base % mobj.group(1) + except (urllib2.URLError, httplib.HTTPException, socket.error), err: + self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err)) + return + + + # Download video ids using BlipTV Ajax calls. Result size per + # query is limited (currently to 12 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 + + while True: + self.report_download_page(username, pagenum) + + request = urllib2.Request( page_base + "&page=" + str(pagenum) ) + + try: + page = urllib2.urlopen(request).read().decode('utf-8') + 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 = [] + + for mobj in re.finditer(r'href="/([^"]+)"', page): + if mobj.group(1) not in ids_in_page: + ids_in_page.append(unescapeHTML(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._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) + + if playlistend == -1: + video_ids = video_ids[playliststart:] + else: + video_ids = video_ids[playliststart:playlistend] + + self._downloader.to_screen(u"[%s] user %s: Collected %d video ids (downloading %d of them)" % + (self.IE_NAME, username, all_ids_count, len(video_ids))) + + for video_id in video_ids: + self._downloader.download([u'http://blip.tv/'+video_id]) + + class DepositFilesIE(InfoExtractor): """Information extractor for depositfiles.com""" @@ -1920,7 +2014,7 @@ class BlipTVIE(InfoExtractor): else: cchar = '?' json_url = url + cchar + 'skin=json&version=2&no_wrap=1' - request = urllib2.Request(json_url) + request = urllib2.Request(json_url.encode('utf-8')) self.report_extraction(mobj.group(1)) info = None try: @@ -1978,6 +2072,7 @@ class BlipTVIE(InfoExtractor): self._downloader.trouble(u'ERROR: unable to parse video information: %s' % repr(err)) return + std_headers['User-Agent'] = 'iTunes/10.6.1' return [info] @@ -2862,3 +2957,190 @@ class MTVIE(InfoExtractor): } return [info] + + +class YoukuIE(InfoExtractor): + + _VALID_URL = r'(?:http://)?v\.youku\.com/v_show/id_(?P<ID>[A-Za-z0-9]+)\.html' + IE_NAME = u'Youku' + + def __init__(self, downloader=None): + InfoExtractor.__init__(self, downloader) + + def report_download_webpage(self, file_id): + """Report webpage download.""" + self._downloader.to_screen(u'[Youku] %s: Downloading webpage' % file_id) + + def report_extraction(self, file_id): + """Report information extraction.""" + self._downloader.to_screen(u'[Youku] %s: Extracting information' % file_id) + + def _gen_sid(self): + nowTime = int(time.time() * 1000) + random1 = random.randint(1000,1998) + random2 = random.randint(1000,9999) + + return "%d%d%d" %(nowTime,random1,random2) + + def _get_file_ID_mix_string(self, seed): + mixed = [] + source = list("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/\:._-1234567890") + seed = float(seed) + for i in range(len(source)): + seed = (seed * 211 + 30031 ) % 65536 + index = math.floor(seed / 65536 * len(source) ) + mixed.append(source[int(index)]) + source.remove(source[int(index)]) + #return ''.join(mixed) + return mixed + + def _get_file_id(self, fileId, seed): + mixed = self._get_file_ID_mix_string(seed) + ids = fileId.split('*') + realId = [] + for ch in ids: + if ch: + realId.append(mixed[int(ch)]) + return ''.join(realId) + + 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') + + info_url = 'http://v.youku.com/player/getPlayList/VideoIDS/' + video_id + + request = urllib2.Request(info_url, None, std_headers) + try: + self.report_download_webpage(video_id) + jsondata = urllib2.urlopen(request).read() + except (urllib2.URLError, httplib.HTTPException, socket.error) as err: + self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err)) + return + + self.report_extraction(video_id) + try: + config = json.loads(jsondata) + + video_title = config['data'][0]['title'] + seed = config['data'][0]['seed'] + + format = self._downloader.params.get('format', None) + supported_format = config['data'][0]['streamfileids'].keys() + + if format is None or format == 'best': + if 'hd2' in supported_format: + format = 'hd2' + else: + format = 'flv' + ext = u'flv' + elif format == 'worst': + format = 'mp4' + ext = u'mp4' + else: + format = 'flv' + ext = u'flv' + + + fileid = config['data'][0]['streamfileids'][format] + seg_number = len(config['data'][0]['segs'][format]) + + keys=[] + for i in xrange(seg_number): + keys.append(config['data'][0]['segs'][format][i]['k']) + + #TODO check error + #youku only could be viewed from mainland china + except: + self._downloader.trouble(u'ERROR: unable to extract info section') + return + + files_info=[] + sid = self._gen_sid() + fileid = self._get_file_id(fileid, seed) + + #column 8,9 of fileid represent the segment number + #fileid[7:9] should be changed + for index, key in enumerate(keys): + + temp_fileid = '%s%02X%s' % (fileid[0:8], index, fileid[10:]) + download_url = 'http://f.youku.com/player/getFlvPath/sid/%s_%02X/st/flv/fileid/%s?k=%s' % (sid, index, temp_fileid, key) + + info = { + 'id': '%s_part%02d' % (video_id, index), + 'url': download_url, + 'uploader': None, + 'title': video_title, + 'ext': ext, + 'format': u'NA' + } + files_info.append(info) + + return files_info + + +class XNXXIE(InfoExtractor): + """Information extractor for xnxx.com""" + + _VALID_URL = r'^http://video\.xnxx\.com/video([0-9]+)/(.*)' + IE_NAME = u'xnxx' + VIDEO_URL_RE = r'flv_url=(.*?)&' + VIDEO_TITLE_RE = r'<title>(.*?)\s+-\s+XNXX.COM' + VIDEO_THUMB_RE = r'url_bigthumb=(.*?)&' + + def report_webpage(self, video_id): + """Report information extraction""" + self._downloader.to_screen(u'[%s] %s: Downloading webpage' % (self.IE_NAME, video_id)) + + def report_extraction(self, video_id): + """Report information extraction""" + self._downloader.to_screen(u'[%s] %s: Extracting information' % (self.IE_NAME, video_id)) + + 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(1).decode('utf-8') + + self.report_webpage(video_id) + + # Get webpage content + try: + webpage = urllib2.urlopen(url).read() + except (urllib2.URLError, httplib.HTTPException, socket.error), err: + self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % err) + return + + result = re.search(self.VIDEO_URL_RE, webpage) + if result is None: + self._downloader.trouble(u'ERROR: unable to extract video url') + return + video_url = urllib.unquote(result.group(1).decode('utf-8')) + + result = re.search(self.VIDEO_TITLE_RE, webpage) + if result is None: + self._downloader.trouble(u'ERROR: unable to extract video title') + return + video_title = result.group(1).decode('utf-8') + + result = re.search(self.VIDEO_THUMB_RE, webpage) + if result is None: + self._downloader.trouble(u'ERROR: unable to extract video thumbnail') + return + video_thumbnail = result.group(1).decode('utf-8') + + info = {'id': video_id, + 'url': video_url, + 'uploader': None, + 'upload_date': None, + 'title': video_title, + 'ext': 'flv', + 'format': 'flv', + 'thumbnail': video_thumbnail, + 'description': None, + 'player_url': None} + + return [info] diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py index f10822db1..5ed453bf7 100644 --- a/youtube_dl/__init__.py +++ b/youtube_dl/__init__.py @@ -19,7 +19,7 @@ __authors__ = ( ) __license__ = 'Public Domain' -__version__ = '2012.02.27' +__version__ = '2012.09.27' UPDATE_URL = 'https://raw.github.com/rg3/youtube-dl/master/youtube-dl' UPDATE_URL_VERSION = 'https://raw.github.com/rg3/youtube-dl/master/LATEST_VERSION' @@ -338,6 +338,7 @@ def gen_extractors(): YahooSearchIE(), DepositFilesIE(), FacebookIE(), + BlipTVUserIE(), BlipTVIE(), VimeoIE(), MyVideoIE(), @@ -350,6 +351,8 @@ def gen_extractors(): MixcloudIE(), StanfordOpenClassroomIE(), MTVIE(), + YoukuIE(), + XNXXIE(), GenericIE() ] diff --git a/youtube_dl/utils.py b/youtube_dl/utils.py index 2853ba50f..922e17ecc 100644 --- a/youtube_dl/utils.py +++ b/youtube_dl/utils.py @@ -223,7 +223,7 @@ def encodeFilename(s): assert type(s) == type(u'') - if sys.platform == 'win32' and sys.getwindowsversion().major >= 5: + if sys.platform == 'win32' and sys.getwindowsversion()[0] >= 5: # Pass u'' directly to use Unicode APIs on Windows 2000 and up # (Detecting Windows NT 4 is tricky because 'major >= 4' would # match Windows 9x series as well. Besides, NT 4 is obsolete.) |