From 56c7366547462ecec0536df58971249a8a870ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Mon, 8 Jul 2013 15:14:27 +0200 Subject: [PATCH 001/122] YoutubeIE: reuse instances of InfoExtractors (closes #998) When a IE is added to the list, it's also added to a dictionary. When a IE is requested it first looks in the dictionary and if there's no instance it will create a new one. That way _real_initialize is only called once for each IE, saving time if it needs to login for example. --- youtube_dl/YoutubeDL.py | 18 +++++++++++++++--- youtube_dl/extractor/common.py | 5 +++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py index d3281fed2..cd3d6ea7b 100644 --- a/youtube_dl/YoutubeDL.py +++ b/youtube_dl/YoutubeDL.py @@ -97,6 +97,7 @@ class YoutubeDL(object): def __init__(self, params): """Create a FileDownloader object with the given options.""" self._ies = [] + self._ies_instances = {} self._pps = [] self._progress_hooks = [] self._download_retcode = 0 @@ -111,8 +112,21 @@ class YoutubeDL(object): def add_info_extractor(self, ie): """Add an InfoExtractor object to the end of the list.""" self._ies.append(ie) + self._ies_instances[ie.ie_key()] = ie ie.set_downloader(self) + def get_info_extractor(self, ie_key): + """ + Get an instance of an IE with name ie_key, it will try to get one from + the _ies list, if there's no instance it will create a new one and add + it to the extractor list. + """ + ie = self._ies_instances.get(ie_key) + if ie is None: + ie = get_info_extractor(ie_key)() + self.add_info_extractor(ie) + return ie + def add_default_info_extractors(self): """ Add the InfoExtractors returned by gen_extractors to the end of the list @@ -294,9 +308,7 @@ class YoutubeDL(object): ''' if ie_key: - ie = get_info_extractor(ie_key)() - ie.set_downloader(self) - ies = [ie] + ies = [self.get_info_extractor(ie_key)] else: ies = self._ies diff --git a/youtube_dl/extractor/common.py b/youtube_dl/extractor/common.py index 1d98222ce..236c7b12c 100644 --- a/youtube_dl/extractor/common.py +++ b/youtube_dl/extractor/common.py @@ -106,6 +106,11 @@ class InfoExtractor(object): """Real extraction process. Redefine in subclasses.""" pass + @classmethod + def ie_key(cls): + """A string for getting the InfoExtractor with get_info_extractor""" + return cls.__name__[:-2] + @property def IE_NAME(self): return type(self).__name__[:-2] From c4a91be726bd2892931a061ef6703b9bfce2a2d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Wed, 26 Jun 2013 00:02:15 +0200 Subject: [PATCH 002/122] Save subtitles using the same code for all the options --- youtube_dl/YoutubeDL.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py index c76f1118e..4a8cafdb4 100644 --- a/youtube_dl/YoutubeDL.py +++ b/youtube_dl/YoutubeDL.py @@ -483,25 +483,13 @@ class YoutubeDL(object): self.report_error(u'Cannot write description file ' + descfn) return - if (self.params.get('writesubtitles', False) or self.params.get('writeautomaticsub')) and 'subtitles' in info_dict and info_dict['subtitles']: + subtitles_are_requested = any([self.params.get('writesubtitles', False), + self.params.get('writeautomaticsub'), + self.params.get('allsubtitles', False)]) + + if subtitles_are_requested and 'subtitles' in info_dict and info_dict['subtitles']: # subtitles download errors are already managed as troubles in relevant IE # that way it will silently go on when used with unsupporting IE - subtitle = info_dict['subtitles'][0] - (sub_error, sub_lang, sub) = subtitle - sub_format = self.params.get('subtitlesformat') - if sub_error: - self.report_warning("Some error while getting the subtitles") - else: - try: - sub_filename = filename.rsplit('.', 1)[0] + u'.' + sub_lang + u'.' + sub_format - self.report_writesubtitles(sub_filename) - with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile: - subfile.write(sub) - except (OSError, IOError): - self.report_error(u'Cannot write subtitles file ' + descfn) - return - - if self.params.get('allsubtitles', False) and 'subtitles' in info_dict and info_dict['subtitles']: subtitles = info_dict['subtitles'] sub_format = self.params.get('subtitlesformat') for subtitle in subtitles: From 5d51a883c2049e0186074ded9405b01f79470d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Wed, 26 Jun 2013 11:03:44 +0200 Subject: [PATCH 003/122] Use a dictionary for storing the subtitles The errors while getting the subtitles are reported as warnings, if no subtitles are found return and empty dict. --- test/test_youtube_subtitles.py | 26 ++++++------- youtube_dl/YoutubeDL.py | 23 +++++------ youtube_dl/extractor/common.py | 3 +- youtube_dl/extractor/youtube.py | 67 +++++++++++++++++---------------- 4 files changed, 59 insertions(+), 60 deletions(-) diff --git a/test/test_youtube_subtitles.py b/test/test_youtube_subtitles.py index 86e09c9b1..fe0eac680 100644 --- a/test/test_youtube_subtitles.py +++ b/test/test_youtube_subtitles.py @@ -35,47 +35,47 @@ class TestYoutubeSubtitles(unittest.TestCase): DL.params['writesubtitles'] = True IE = YoutubeIE(DL) info_dict = IE.extract('QRS8MkLhQmM') - sub = info_dict[0]['subtitles'][0] - self.assertEqual(md5(sub[2]), '4cd9278a35ba2305f47354ee13472260') + sub = info_dict[0]['subtitles']['en'] + self.assertEqual(md5(sub), '4cd9278a35ba2305f47354ee13472260') def test_youtube_subtitles_it(self): DL = FakeYDL() DL.params['writesubtitles'] = True DL.params['subtitleslang'] = 'it' IE = YoutubeIE(DL) info_dict = IE.extract('QRS8MkLhQmM') - sub = info_dict[0]['subtitles'][0] - self.assertEqual(md5(sub[2]), '164a51f16f260476a05b50fe4c2f161d') + sub = info_dict[0]['subtitles']['it'] + self.assertEqual(md5(sub), '164a51f16f260476a05b50fe4c2f161d') def test_youtube_onlysubtitles(self): DL = FakeYDL() DL.params['writesubtitles'] = True DL.params['onlysubtitles'] = True IE = YoutubeIE(DL) info_dict = IE.extract('QRS8MkLhQmM') - sub = info_dict[0]['subtitles'][0] - self.assertEqual(md5(sub[2]), '4cd9278a35ba2305f47354ee13472260') + sub = info_dict[0]['subtitles']['en'] + self.assertEqual(md5(sub), '4cd9278a35ba2305f47354ee13472260') def test_youtube_allsubtitles(self): DL = FakeYDL() DL.params['allsubtitles'] = True IE = YoutubeIE(DL) info_dict = IE.extract('QRS8MkLhQmM') subtitles = info_dict[0]['subtitles'] - self.assertEqual(len(subtitles), 13) + self.assertEqual(len(subtitles.keys()), 13) def test_youtube_subtitles_sbv_format(self): DL = FakeYDL() DL.params['writesubtitles'] = True DL.params['subtitlesformat'] = 'sbv' IE = YoutubeIE(DL) info_dict = IE.extract('QRS8MkLhQmM') - sub = info_dict[0]['subtitles'][0] - self.assertEqual(md5(sub[2]), '13aeaa0c245a8bed9a451cb643e3ad8b') + sub = info_dict[0]['subtitles']['en'] + self.assertEqual(md5(sub), '13aeaa0c245a8bed9a451cb643e3ad8b') def test_youtube_subtitles_vtt_format(self): DL = FakeYDL() DL.params['writesubtitles'] = True DL.params['subtitlesformat'] = 'vtt' IE = YoutubeIE(DL) info_dict = IE.extract('QRS8MkLhQmM') - sub = info_dict[0]['subtitles'][0] - self.assertEqual(md5(sub[2]), '356cdc577fde0c6783b9b822e7206ff7') + sub = info_dict[0]['subtitles']['en'] + self.assertEqual(md5(sub), '356cdc577fde0c6783b9b822e7206ff7') def test_youtube_list_subtitles(self): DL = FakeYDL() DL.params['listsubtitles'] = True @@ -88,8 +88,8 @@ class TestYoutubeSubtitles(unittest.TestCase): DL.params['subtitleslang'] = 'it' IE = YoutubeIE(DL) info_dict = IE.extract('8YoUxe5ncPo') - sub = info_dict[0]['subtitles'][0] - self.assertTrue(sub[2] is not None) + sub = info_dict[0]['subtitles']['it'] + self.assertTrue(sub is not None) if __name__ == '__main__': unittest.main() diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py index 4a8cafdb4..be6ceafcc 100644 --- a/youtube_dl/YoutubeDL.py +++ b/youtube_dl/YoutubeDL.py @@ -492,19 +492,16 @@ class YoutubeDL(object): # that way it will silently go on when used with unsupporting IE subtitles = info_dict['subtitles'] sub_format = self.params.get('subtitlesformat') - for subtitle in subtitles: - (sub_error, sub_lang, sub) = subtitle - if sub_error: - self.report_warning("Some error while getting the subtitles") - else: - try: - sub_filename = filename.rsplit('.', 1)[0] + u'.' + sub_lang + u'.' + sub_format - self.report_writesubtitles(sub_filename) - with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile: - subfile.write(sub) - except (OSError, IOError): - self.report_error(u'Cannot write subtitles file ' + descfn) - return + for sub_lang in subtitles.keys(): + sub = subtitles[sub_lang] + try: + sub_filename = filename.rsplit('.', 1)[0] + u'.' + sub_lang + u'.' + sub_format + self.report_writesubtitles(sub_filename) + with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile: + subfile.write(sub) + except (OSError, IOError): + self.report_error(u'Cannot write subtitles file ' + descfn) + return if self.params.get('writeinfojson', False): infofn = filename + u'.info.json' diff --git a/youtube_dl/extractor/common.py b/youtube_dl/extractor/common.py index da50abfc1..e2e192bef 100644 --- a/youtube_dl/extractor/common.py +++ b/youtube_dl/extractor/common.py @@ -47,7 +47,8 @@ class InfoExtractor(object): uploader_id: Nickname or id of the video uploader. location: Physical location of the video. player_url: SWF Player URL (used for rtmpdump). - subtitles: The subtitle file contents. + subtitles: The subtitle file contents as a dictionary in the format + {language: subtitles}. view_count: How many users have watched the video on the platform. urlhandle: [internal] The urlHandle to be used to download the file, like returned by urllib.request.urlopen diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py index 12e8fc25d..78500b0f7 100644 --- a/youtube_dl/extractor/youtube.py +++ b/youtube_dl/extractor/youtube.py @@ -209,11 +209,13 @@ class YoutubeIE(InfoExtractor): try: sub_list = compat_urllib_request.urlopen(request).read().decode('utf-8') except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err: - return (u'unable to download video subtitles: %s' % compat_str(err), None) + self._downloader.report_warning(u'unable to download video subtitles: %s' % compat_str(err)) + return {} sub_lang_list = re.findall(r'name="([^"]*)"[^>]+lang_code="([\w\-]+)"', sub_list) sub_lang_list = dict((l[1], l[0]) for l in sub_lang_list) if not sub_lang_list: - return (u'video doesn\'t have subtitles', None) + self._downloader.report_warning(u'video doesn\'t have subtitles') + return {} return sub_lang_list def _list_available_subtitles(self, video_id): @@ -222,8 +224,7 @@ class YoutubeIE(InfoExtractor): def _request_subtitle(self, sub_lang, sub_name, video_id, format): """ - Return tuple: - (error_message, sub_lang, sub) + Return the subtitle as a string or None if they are not found """ self.report_video_subtitles_request(video_id, sub_lang, format) params = compat_urllib_parse.urlencode({ @@ -236,10 +237,12 @@ class YoutubeIE(InfoExtractor): try: sub = compat_urllib_request.urlopen(url).read().decode('utf-8') except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err: - return (u'unable to download video subtitles: %s' % compat_str(err), None, None) + self._downloader.report_warning(u'unable to download video subtitles for %s: %s' % (sub_lang, compat_str(err))) + return if not sub: - return (u'Did not fetch video subtitles', None, None) - return (None, sub_lang, sub) + self._downloader.report_warning(u'Did not fetch video subtitles') + return + return sub def _request_automatic_caption(self, video_id, webpage): """We need the webpage for getting the captions url, pass it as an @@ -250,7 +253,8 @@ class YoutubeIE(InfoExtractor): mobj = re.search(r';ytplayer.config = ({.*?});', webpage) err_msg = u'Couldn\'t find automatic captions for "%s"' % sub_lang if mobj is None: - return [(err_msg, None, None)] + self._downloader.report_warning(err_msg) + return {} player_config = json.loads(mobj.group(1)) try: args = player_config[u'args'] @@ -265,19 +269,20 @@ class YoutubeIE(InfoExtractor): }) subtitles_url = caption_url + '&' + params sub = self._download_webpage(subtitles_url, video_id, u'Downloading automatic captions') - return [(None, sub_lang, sub)] + return {sub_lang: sub} except KeyError: - return [(err_msg, None, None)] + self._downloader.report_warning(err_msg) + return {} def _extract_subtitle(self, video_id): """ - Return a list with a tuple: - [(error_message, sub_lang, sub)] + Return a dictionary: {language: subtitles} or {} if the subtitles + couldn't be found """ sub_lang_list = self._get_available_subtitles(video_id) sub_format = self._downloader.params.get('subtitlesformat') - if isinstance(sub_lang_list,tuple): #There was some error, it didn't get the available subtitles - return [(sub_lang_list[0], None, None)] + if not sub_lang_list: #There was some error, it didn't get the available subtitles + return {} if self._downloader.params.get('subtitleslang', False): sub_lang = self._downloader.params.get('subtitleslang') elif 'en' in sub_lang_list: @@ -285,20 +290,28 @@ class YoutubeIE(InfoExtractor): else: sub_lang = list(sub_lang_list.keys())[0] if not sub_lang in sub_lang_list: - return [(u'no closed captions found in the specified language "%s"' % sub_lang, None, None)] + self._downloader.report_warning(u'no closed captions found in the specified language "%s"' % sub_lang) + return {} subtitle = self._request_subtitle(sub_lang, sub_lang_list[sub_lang].encode('utf-8'), video_id, sub_format) - return [subtitle] + if subtitle: + self.to_screen('sub %s' % subtitle[:20]) + return {sub_lang: subtitle} + else: + return {} def _extract_all_subtitles(self, video_id): + """ + Return a dicitonary: {language: subtitles} or {} if the subtitles + couldn't be found + """ sub_lang_list = self._get_available_subtitles(video_id) sub_format = self._downloader.params.get('subtitlesformat') - if isinstance(sub_lang_list,tuple): #There was some error, it didn't get the available subtitles - return [(sub_lang_list[0], None, None)] - subtitles = [] + subtitles = {} for sub_lang in sub_lang_list: subtitle = self._request_subtitle(sub_lang, sub_lang_list[sub_lang].encode('utf-8'), video_id, sub_format) - subtitles.append(subtitle) + if subtitle: + subtitles[sub_lang] = subtitle return subtitles def _print_formats(self, formats): @@ -523,23 +536,11 @@ class YoutubeIE(InfoExtractor): if self._downloader.params.get('writesubtitles', False): video_subtitles = self._extract_subtitle(video_id) - if video_subtitles: - (sub_error, sub_lang, sub) = video_subtitles[0] - if sub_error: - self._downloader.report_warning(sub_error) - - if self._downloader.params.get('writeautomaticsub', False): + elif self._downloader.params.get('writeautomaticsub', False): video_subtitles = self._request_automatic_caption(video_id, video_webpage) - (sub_error, sub_lang, sub) = video_subtitles[0] - if sub_error: - self._downloader.report_warning(sub_error) if self._downloader.params.get('allsubtitles', False): video_subtitles = self._extract_all_subtitles(video_id) - for video_subtitle in video_subtitles: - (sub_error, sub_lang, sub) = video_subtitle - if sub_error: - self._downloader.report_warning(sub_error) if self._downloader.params.get('listsubtitles', False): self._list_available_subtitles(video_id) From 88ae5991cd777f05b437dbe7b4399f1ff25d6b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Wed, 26 Jun 2013 11:39:34 +0200 Subject: [PATCH 004/122] YoutubeIE: use the same function for getting the subtitles for the "--write-sub" and "--all-sub" options --- youtube_dl/extractor/youtube.py | 46 +++++++++++---------------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py index 78500b0f7..30036524f 100644 --- a/youtube_dl/extractor/youtube.py +++ b/youtube_dl/extractor/youtube.py @@ -273,8 +273,8 @@ class YoutubeIE(InfoExtractor): except KeyError: self._downloader.report_warning(err_msg) return {} - - def _extract_subtitle(self, video_id): + + def _extract_subtitles(self, video_id): """ Return a dictionary: {language: subtitles} or {} if the subtitles couldn't be found @@ -283,30 +283,17 @@ class YoutubeIE(InfoExtractor): sub_format = self._downloader.params.get('subtitlesformat') if not sub_lang_list: #There was some error, it didn't get the available subtitles return {} - if self._downloader.params.get('subtitleslang', False): - sub_lang = self._downloader.params.get('subtitleslang') - elif 'en' in sub_lang_list: - sub_lang = 'en' - else: - sub_lang = list(sub_lang_list.keys())[0] - if not sub_lang in sub_lang_list: - self._downloader.report_warning(u'no closed captions found in the specified language "%s"' % sub_lang) - return {} - - subtitle = self._request_subtitle(sub_lang, sub_lang_list[sub_lang].encode('utf-8'), video_id, sub_format) - if subtitle: - self.to_screen('sub %s' % subtitle[:20]) - return {sub_lang: subtitle} - else: - return {} - - def _extract_all_subtitles(self, video_id): - """ - Return a dicitonary: {language: subtitles} or {} if the subtitles - couldn't be found - """ - sub_lang_list = self._get_available_subtitles(video_id) - sub_format = self._downloader.params.get('subtitlesformat') + if self._downloader.params.get('writesubtitles', False): + if self._downloader.params.get('subtitleslang', False): + sub_lang = self._downloader.params.get('subtitleslang') + elif 'en' in sub_lang_list: + sub_lang = 'en' + else: + sub_lang = list(sub_lang_list.keys())[0] + if not sub_lang in sub_lang_list: + self._downloader.report_warning(u'no closed captions found in the specified language "%s"' % sub_lang) + return {} + sub_lang_list = {sub_lang: sub_lang_list[sub_lang]} subtitles = {} for sub_lang in sub_lang_list: subtitle = self._request_subtitle(sub_lang, sub_lang_list[sub_lang].encode('utf-8'), video_id, sub_format) @@ -534,14 +521,11 @@ class YoutubeIE(InfoExtractor): # subtitles video_subtitles = None - if self._downloader.params.get('writesubtitles', False): - video_subtitles = self._extract_subtitle(video_id) + if self._downloader.params.get('writesubtitles', False) or self._downloader.params.get('allsubtitles', False): + video_subtitles = self._extract_subtitles(video_id) elif self._downloader.params.get('writeautomaticsub', False): video_subtitles = self._request_automatic_caption(video_id, video_webpage) - if self._downloader.params.get('allsubtitles', False): - video_subtitles = self._extract_all_subtitles(video_id) - if self._downloader.params.get('listsubtitles', False): self._list_available_subtitles(video_id) return From 2f799533ae680dc788c8b4f6ce41272cf89689cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Sat, 29 Jun 2013 22:11:18 +0200 Subject: [PATCH 005/122] YoutubeIE: don't crash when trying to get automatic captions if the videos has standard subtitles. --- youtube_dl/extractor/youtube.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py index 30036524f..2b03226f6 100644 --- a/youtube_dl/extractor/youtube.py +++ b/youtube_dl/extractor/youtube.py @@ -270,7 +270,9 @@ class YoutubeIE(InfoExtractor): subtitles_url = caption_url + '&' + params sub = self._download_webpage(subtitles_url, video_id, u'Downloading automatic captions') return {sub_lang: sub} - except KeyError: + # An extractor error can be raise by the download process if there are + # no automatic captions but there are subtitles + except (KeyError, ExtractorError): self._downloader.report_warning(err_msg) return {} From 6804038d065e0eeffd9fca2dc55b3262a9191c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Sat, 20 Jul 2013 12:59:47 +0200 Subject: [PATCH 006/122] Don't try to write the subtitles if it's None --- youtube_dl/YoutubeDL.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py index be6ceafcc..e69d844b8 100644 --- a/youtube_dl/YoutubeDL.py +++ b/youtube_dl/YoutubeDL.py @@ -494,6 +494,8 @@ class YoutubeDL(object): sub_format = self.params.get('subtitlesformat') for sub_lang in subtitles.keys(): sub = subtitles[sub_lang] + if sub is None: + continue try: sub_filename = filename.rsplit('.', 1)[0] + u'.' + sub_lang + u'.' + sub_format self.report_writesubtitles(sub_filename) From 6d3a7d03e14fcbc704bf30d305fb95c5829e55a6 Mon Sep 17 00:00:00 2001 From: huohuarong Date: Fri, 2 Aug 2013 15:26:11 +0800 Subject: [PATCH 007/122] fix bug: kankan extractor not support http://vod.kankan.com/v/70/70309.shtml --- youtube_dl/extractor/kankan.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/youtube_dl/extractor/kankan.py b/youtube_dl/extractor/kankan.py index 8537ba584..445d46501 100644 --- a/youtube_dl/extractor/kankan.py +++ b/youtube_dl/extractor/kankan.py @@ -21,8 +21,10 @@ class KankanIE(InfoExtractor): video_id = mobj.group('id') webpage = self._download_webpage(url, video_id) - title = self._search_regex(r'G_TITLE=[\'"](.+?)[\'"]', webpage, u'video title') - gcid = self._search_regex(r'lurl:[\'"]http://.+?/.+?/(.+?)/', webpage, u'gcid') + title = self._search_regex(r'(?:G_TITLE=|G_MOVIE_TITLE = )[\'"](.+?)[\'"]', webpage, u'video title') + surls = re.search(r'surls:\[\'.+?\'\]|lurl:\'.+?\.flv\'', webpage).group(0) + gcids = re.findall(r"http://.+?/.+?/(.+?)/", surls) + gcid = gcids[-1] video_info_page = self._download_webpage('http://p2s.cl.kankan.com/getCdnresource_flv?gcid=%s' % gcid, video_id, u'Downloading video url info') From 6624a2b07dafad4de895b4e84f4595214817518d Mon Sep 17 00:00:00 2001 From: huohuarong Date: Fri, 2 Aug 2013 17:58:46 +0800 Subject: [PATCH 008/122] add an extractor for tv.sohu.com --- youtube_dl/extractor/__init__.py | 1 + youtube_dl/extractor/sohu.py | 97 ++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 youtube_dl/extractor/sohu.py diff --git a/youtube_dl/extractor/__init__.py b/youtube_dl/extractor/__init__.py index c20172a53..3a08d676f 100644 --- a/youtube_dl/extractor/__init__.py +++ b/youtube_dl/extractor/__init__.py @@ -55,6 +55,7 @@ from .redtube import RedTubeIE from .ringtv import RingTVIE from .roxwel import RoxwelIE from .sina import SinaIE +from .sohu import SohuIE from .soundcloud import SoundcloudIE, SoundcloudSetIE from .spiegel import SpiegelIE from .stanfordoc import StanfordOpenClassroomIE diff --git a/youtube_dl/extractor/sohu.py b/youtube_dl/extractor/sohu.py new file mode 100644 index 000000000..830814221 --- /dev/null +++ b/youtube_dl/extractor/sohu.py @@ -0,0 +1,97 @@ +# encoding: utf-8 + +import re +import json +import time +import logging +import urllib2 + +from .common import InfoExtractor +from ..utils import compat_urllib_request + + +class SohuIE(InfoExtractor): + _VALID_URL = r'https?://tv\.sohu\.com/\d+?/n(?P\d+)\.shtml.*?' + + _TEST = { + u'url': u'http://tv.sohu.com/20130724/n382479172.shtml#super', + u'file': u'382479172.flv', + u'md5': u'cc84eed6b6fbf0f2f9a8d3cb9da1939b', + u'info_dict': { + u'title': u'The Illest - Far East Movement Riff Raff', + }, + } + + def _clearn_html(self, string): + tags = re.findall(r'<.+?>', string) + for t in tags: + string = string.replace(t, ' ') + for i in range(2): + spaces = re.findall(r'\s+', string) + for s in spaces: + string = string.replace(s, ' ') + string = string.strip() + return string + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + video_id = mobj.group('id') + webpage = self._download_webpage(url, video_id) + pattern = r'

\n*?(.+?)\n*?

' + compiled = re.compile(pattern, re.DOTALL) + title = self._search_regex(compiled, webpage, u'video title').strip('\t\n') + title = self._clearn_html(title) + pattern = re.compile(r'var vid="(\d+)"') + result = re.search(pattern, webpage) + if not result: + logging.info('[Sohu] could not get vid') + return None + vid = result.group(1) + logging.info('vid: %s' % vid) + base_url_1 = 'http://hot.vrs.sohu.com/vrs_flash.action?vid=' + url_1 = base_url_1 + vid + logging.info('json url: %s' % url_1) + json_1 = json.loads(urllib2.urlopen(url_1).read()) + # get the highest definition video vid and json infomation. + vids = [] + qualities = ('oriVid', 'superVid', 'highVid', 'norVid') + for vid_name in qualities: + vids.append(json_1['data'][vid_name]) + clearest_vid = 0 + for i, v in enumerate(vids): + if v != 0: + clearest_vid = v + logging.info('quality definition: %s' % qualities[i][:-3]) + break + if not clearest_vid: + logging.warning('could not find valid clearest_vid') + return None + if vid != clearest_vid: + url_1 = '%s%d' % (base_url_1, clearest_vid) + logging.info('highest definition json url: %s' % url_1) + json_1 = json.loads(urllib2.urlopen(url_1).read()) + allot = json_1['allot'] + prot = json_1['prot'] + clipsURL = json_1['data']['clipsURL'] + su = json_1['data']['su'] + num_of_parts = json_1['data']['totalBlocks'] + logging.info('Total parts: %d' % num_of_parts) + base_url_3 = 'http://allot/?prot=prot&file=clipsURL[i]&new=su[i]' + files_info = [] + for i in range(num_of_parts): + middle_url = 'http://%s/?prot=%s&file=%s&new=%s' % (allot, prot, clipsURL[i], su[i]) + logging.info('middle url part %d: %s' % (i, middle_url)) + middle_info = urllib2.urlopen(middle_url).read().split('|') + middle_part_1 = middle_info[0] + download_url = '%s%s?key=%s' % (middle_info[0], su[i], middle_info[3]) + + info = { + 'id': '%s_part%02d' % (video_id, i + 1), + 'title': title, + 'url': download_url, + 'ext': 'mp4', + } + files_info.append(info) + time.sleep(1) + + return files_info From 4ec929dc9b55a2588b4a27e64871c5bfa900bf37 Mon Sep 17 00:00:00 2001 From: huohuarong Date: Sat, 3 Aug 2013 10:29:58 +0800 Subject: [PATCH 009/122] use ..utils/clean_html() --- youtube_dl/extractor/sohu.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/youtube_dl/extractor/sohu.py b/youtube_dl/extractor/sohu.py index 830814221..cf0ab5478 100644 --- a/youtube_dl/extractor/sohu.py +++ b/youtube_dl/extractor/sohu.py @@ -7,7 +7,7 @@ import logging import urllib2 from .common import InfoExtractor -from ..utils import compat_urllib_request +from ..utils import compat_urllib_request, clean_html class SohuIE(InfoExtractor): @@ -22,16 +22,6 @@ class SohuIE(InfoExtractor): }, } - def _clearn_html(self, string): - tags = re.findall(r'<.+?>', string) - for t in tags: - string = string.replace(t, ' ') - for i in range(2): - spaces = re.findall(r'\s+', string) - for s in spaces: - string = string.replace(s, ' ') - string = string.strip() - return string def _real_extract(self, url): mobj = re.match(self._VALID_URL, url) @@ -40,7 +30,7 @@ class SohuIE(InfoExtractor): pattern = r'

\n*?(.+?)\n*?

' compiled = re.compile(pattern, re.DOTALL) title = self._search_regex(compiled, webpage, u'video title').strip('\t\n') - title = self._clearn_html(title) + title = clean_html(title) pattern = re.compile(r'var vid="(\d+)"') result = re.search(pattern, webpage) if not result: @@ -93,5 +83,8 @@ class SohuIE(InfoExtractor): } files_info.append(info) time.sleep(1) - + if num_of_parts == 1: + info = files_info[0] + info['id'] = video_id + return info return files_info From 968b5e0112a83f2a4637226d4d743b394ebed038 Mon Sep 17 00:00:00 2001 From: Dan Church Date: Sun, 4 Aug 2013 12:45:24 -0500 Subject: [PATCH 010/122] Add some verbosity when reporting finished downloads For example: [download] Resuming download at byte 1868140 [download] Destination: Entry #1-Bn59FJ4HrmU.flv [download] 100% of 3.27MiB in 4s This format is meant to somewhat mirror the behavior of wget(1) when reporting finished downloads: 100%[==================>] 54,836,682 788KB/s in 74s 2013-08-04 12:32:05 (728 KB/s) - 'google-chrome-stable_current_x86_64.rpm' saved [54836682/54836682] --- youtube_dl/FileDownloader.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/youtube_dl/FileDownloader.py b/youtube_dl/FileDownloader.py index ea6b9d626..ab06533c0 100644 --- a/youtube_dl/FileDownloader.py +++ b/youtube_dl/FileDownloader.py @@ -230,12 +230,14 @@ class FileDownloader(object): """Report it was impossible to resume download.""" self.to_screen(u'[download] Unable to resume') - def report_finish(self): + def report_finish(self, data_len_str, tot_time): """Report download finished.""" if self.params.get('noprogress', False): self.to_screen(u'[download] Download completed') else: - self.to_screen(u'') + clear_line = (u'\x1b[K' if sys.stderr.isatty() and os.name != 'nt' else u'') + self.to_screen(u'\r%s[download] 100%% of %s in %ss' % + (clear_line, data_len_str, int(tot_time))) def _download_with_rtmpdump(self, filename, url, player_url, page_url, play_path, tc_url): self.report_destination(filename) @@ -538,7 +540,7 @@ class FileDownloader(object): self.report_error(u'Did not get any data blocks') return False stream.close() - self.report_finish() + self.report_finish(data_len_str, (time.time() - start)) if data_len is not None and byte_counter != data_len: raise ContentTooShortError(byte_counter, int(data_len)) self.try_rename(tmpfilename, filename) From b5a6d408181c118bf51382f486a2492643ed74ec Mon Sep 17 00:00:00 2001 From: huohuarong Date: Mon, 5 Aug 2013 22:51:54 +0800 Subject: [PATCH 011/122] fix parse title bug --- youtube_dl/extractor/sohu.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/youtube_dl/extractor/sohu.py b/youtube_dl/extractor/sohu.py index cf0ab5478..cd049b6f0 100644 --- a/youtube_dl/extractor/sohu.py +++ b/youtube_dl/extractor/sohu.py @@ -27,10 +27,10 @@ class SohuIE(InfoExtractor): mobj = re.match(self._VALID_URL, url) video_id = mobj.group('id') webpage = self._download_webpage(url, video_id) - pattern = r'

\n*?(.+?)\n*?

' + pattern = r'(.+?)' compiled = re.compile(pattern, re.DOTALL) - title = self._search_regex(compiled, webpage, u'video title').strip('\t\n') - title = clean_html(title) + title = self._search_regex(compiled, webpage, u'video title') + title = clean_html(title).split('-')[0].strip() pattern = re.compile(r'var vid="(\d+)"') result = re.search(pattern, webpage) if not result: @@ -41,7 +41,8 @@ class SohuIE(InfoExtractor): base_url_1 = 'http://hot.vrs.sohu.com/vrs_flash.action?vid=' url_1 = base_url_1 + vid logging.info('json url: %s' % url_1) - json_1 = json.loads(urllib2.urlopen(url_1).read()) + webpage = self._download_webpage(url_1, vid) + json_1 = json.loads(webpage) # get the highest definition video vid and json infomation. vids = [] qualities = ('oriVid', 'superVid', 'highVid', 'norVid') From 461cead4f788f6a69902f350b9143a5e1588b57d Mon Sep 17 00:00:00 2001 From: tsantala Date: Tue, 6 Aug 2013 04:34:24 +0300 Subject: [PATCH 012/122] changes --- youtube_dl/extractor/AddAnime.py | 54 ++++++++++++++++++++++++++++++++ youtube_dl/extractor/__init__.py | 2 ++ 2 files changed, 56 insertions(+) create mode 100644 youtube_dl/extractor/AddAnime.py diff --git a/youtube_dl/extractor/AddAnime.py b/youtube_dl/extractor/AddAnime.py new file mode 100644 index 000000000..43b0b24fe --- /dev/null +++ b/youtube_dl/extractor/AddAnime.py @@ -0,0 +1,54 @@ +import re + +from .common import InfoExtractor +from ..utils import ( + ExtractorError, +) +from bs4 import BeautifulSoup + + +class AddAnimeIE(InfoExtractor): + + _VALID_URL = r'^(?:http?://)?(?:\w+\.)?add-anime\.net/watch_video.php\?(?:.*?)v=(?P[\w_]+)(?:.*)' + IE_NAME = u'AddAnime' + _TEST = { + u'url': u'http://www.add-anime.net/watch_video.php?v=24MR3YO5SAS9', + u'file': u'137499050692ced.flv', + u'md5': u'0813c2430bea7a46bf13acf3406992f4', + u'info_dict': { + u"description": u"One Piece 606", + u"uploader": u"mugiwaraQ8", + u"title": u"One Piece 606" + } + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + if mobj is None: + raise ExtractorError(u'Invalid URL: %s' % url) + + video_id = mobj.group('video_id') + + webpage = self._download_webpage(url, video_id) + + video_url = self._search_regex(r'var normal_video_file = "(.*?)",', + webpage, u'video URL') + + video_title = self._og_search_title(webpage) + + video_description = self._og_search_description(webpage) + + soup = BeautifulSoup(webpage) + + video_uploader= soup.find("meta", {"author":""})['content'] + + info = { + 'id': video_id, + 'url': video_url, + 'ext': 'flv', + 'title': video_title, + 'description': video_description, + 'uploader': video_uploader + } + + return [info] diff --git a/youtube_dl/extractor/__init__.py b/youtube_dl/extractor/__init__.py index 84c02c2ed..28dcb2cc4 100644 --- a/youtube_dl/extractor/__init__.py +++ b/youtube_dl/extractor/__init__.py @@ -1,3 +1,5 @@ + +from .AddAnime import AddAnimeIE from .archiveorg import ArchiveOrgIE from .ard import ARDIE from .arte import ArteTvIE From d5b00ee6e0ba70fd5d87752e8772fc1c39e4bd59 Mon Sep 17 00:00:00 2001 From: huohuarong Date: Tue, 6 Aug 2013 10:26:57 +0800 Subject: [PATCH 013/122] improve sohu extractor --- youtube_dl/extractor/sohu.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/youtube_dl/extractor/sohu.py b/youtube_dl/extractor/sohu.py index cd049b6f0..24fc3a5d7 100644 --- a/youtube_dl/extractor/sohu.py +++ b/youtube_dl/extractor/sohu.py @@ -31,6 +31,7 @@ class SohuIE(InfoExtractor): compiled = re.compile(pattern, re.DOTALL) title = self._search_regex(compiled, webpage, u'video title') title = clean_html(title).split('-')[0].strip() + self.to_screen('Title: %s' % title) pattern = re.compile(r'var vid="(\d+)"') result = re.search(pattern, webpage) if not result: @@ -70,6 +71,7 @@ class SohuIE(InfoExtractor): base_url_3 = 'http://allot/?prot=prot&file=clipsURL[i]&new=su[i]' files_info = [] for i in range(num_of_parts): + self.to_screen('Geting json infomation of part %s/%s' % (i + 1, num_of_parts)) middle_url = 'http://%s/?prot=%s&file=%s&new=%s' % (allot, prot, clipsURL[i], su[i]) logging.info('middle url part %d: %s' % (i, middle_url)) middle_info = urllib2.urlopen(middle_url).read().split('|') From f3bcebb1d2ebf6a69f06b72e1e365bc76970e1e2 Mon Sep 17 00:00:00 2001 From: rzhxeo Date: Fri, 9 Aug 2013 18:36:01 +0200 Subject: [PATCH 014/122] add an aes implementation --- youtube_dl/aes.py | 200 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 youtube_dl/aes.py diff --git a/youtube_dl/aes.py b/youtube_dl/aes.py new file mode 100644 index 000000000..2fa9238e3 --- /dev/null +++ b/youtube_dl/aes.py @@ -0,0 +1,200 @@ +__all__ = ['aes_encrypt', 'key_expansion', 'aes_ctr_decrypt', 'aes_decrypt_text'] + +import base64 +from math import ceil + +BLOCK_SIZE_BYTES = 16 + +def aes_ctr_decrypt(data, key, counter): + """ + Decrypt with aes in counter mode + + @param {int[]} data cipher + @param {int[]} key 16/24/32-Byte cipher key + @param {instance} counter Instance whose next_value function (@returns {int[]} 16-Byte block) + returns the next counter block + @returns {int[]} decrypted data + """ + expanded_key = key_expansion(key) + block_count = int(ceil(float(len(data)) / BLOCK_SIZE_BYTES)) + + decrypted_data=[] + for i in range(block_count): + counter_block = counter.next_value() + block = data[i*BLOCK_SIZE_BYTES : (i+1)*BLOCK_SIZE_BYTES] + block += [0]*(BLOCK_SIZE_BYTES - len(block)) + + cipher_counter_block = aes_encrypt(counter_block, expanded_key) + decrypted_data += xor(block, cipher_counter_block) + decrypted_data = decrypted_data[:len(data)] + + return decrypted_data + +def key_expansion(data): + """ + Generate key schedule + + @param {int[]} data 16/24/32-Byte cipher key + @returns {int[]} 176/208/240-Byte expanded key + """ + data = data[:] # copy + rcon_iteration = 1 + key_size_bytes = len(data) + expanded_key_size_bytes = (key_size_bytes/4 + 7) * BLOCK_SIZE_BYTES + + while len(data) < expanded_key_size_bytes: + temp = data[-4:] + temp = key_schedule_core(temp, rcon_iteration) + rcon_iteration += 1 + data += xor(temp, data[-key_size_bytes : 4-key_size_bytes]) + + for _ in range(3): + temp = data[-4:] + data += xor(temp, data[-key_size_bytes : 4-key_size_bytes]) + + if key_size_bytes == 32: + temp = data[-4:] + temp = sub_bytes(temp) + data += xor(temp, data[-key_size_bytes : 4-key_size_bytes]) + + for _ in range(3 if key_size_bytes == 32 else 2 if key_size_bytes == 24 else 0): + temp = data[-4:] + data += xor(temp, data[-key_size_bytes : 4-key_size_bytes]) + data = data[:expanded_key_size_bytes] + + return data + +def aes_encrypt(data, expanded_key): + """ + Encrypt one block with aes + + @param {int[]} data 16-Byte state + @param {int[]} expanded_key 176/208/240-Byte expanded key + @returns {int[]} 16-Byte cipher + """ + rounds = len(expanded_key) / BLOCK_SIZE_BYTES - 1 + + data = xor(data, expanded_key[:BLOCK_SIZE_BYTES]) + for i in range(1, rounds+1): + data = sub_bytes(data) + data = shift_rows(data) + if i != rounds: + data = mix_columns(data) + data = xor(data, expanded_key[i*BLOCK_SIZE_BYTES : (i+1)*BLOCK_SIZE_BYTES]) + + return data + +def aes_decrypt_text(data, password, key_size_bytes): + """ + Decrypt text + - The first 8 Bytes of decoded 'data' are the 8 high Bytes of the counter + - The cipher key is retrieved by encrypting the first 16 Byte of 'password' + with the first 'key_size_bytes' Bytes from 'password' (if necessary filled with 0's) + - Mode of operation is 'counter' + + @param {str} data Base64 encoded string + @param {str,unicode} password Password (will be encoded with utf-8) + @param {int} key_size_bytes Possible values: 16 for 128-Bit, 24 for 192-Bit or 32 for 256-Bit + @returns {str} Decrypted data + """ + NONCE_LENGTH_BYTES = 8 + + data = map(lambda c: ord(c), base64.b64decode(data)) + password = map(lambda c: ord(c), password.encode('utf-8')) + + key = password[:key_size_bytes] + [0]*(key_size_bytes - len(password)) + key = aes_encrypt(key[:BLOCK_SIZE_BYTES], key_expansion(key)) * (key_size_bytes / BLOCK_SIZE_BYTES) + + nonce = data[:NONCE_LENGTH_BYTES] + cipher = data[NONCE_LENGTH_BYTES:] + + class Counter: + __value = nonce + [0]*(BLOCK_SIZE_BYTES - NONCE_LENGTH_BYTES) + def next_value(self): + temp = self.__value + self.__value = inc(self.__value) + return temp + + decrypted_data = aes_ctr_decrypt(cipher, key, Counter()) + plaintext = ''.join(map(lambda x: chr(x), decrypted_data)) + + return plaintext + +RCON = (0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36) +SBOX = (0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, + 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, + 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, + 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, + 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, + 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, + 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, + 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, + 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, + 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, + 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, + 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, + 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, + 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, + 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, + 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16) +MIX_COLUMN_MATRIX = ((2,3,1,1), + (1,2,3,1), + (1,1,2,3), + (3,1,1,2)) + +def sub_bytes(data): + return map(lambda x: SBOX[x], data) + +def rotate(data): + return data[1:] + [data[0]] + +def key_schedule_core(data, rcon_iteration): + data = rotate(data) + data = sub_bytes(data) + data[0] = data[0] ^ RCON[rcon_iteration] + + return data + +def xor(data1, data2): + return map(lambda (x,y): x^y, zip(data1, data2)) + +def mix_column(data): + data_mixed = [] + for row in range(4): + mixed = 0 + for column in range(4): + addend = data[column] + if MIX_COLUMN_MATRIX[row][column] in (2,3): + addend <<= 1 + if addend > 0xff: + addend &= 0xff + addend ^= 0x1b + if MIX_COLUMN_MATRIX[row][column] == 3: + addend ^= data[column] + mixed ^= addend & 0xff + data_mixed.append(mixed) + return data_mixed + +def mix_columns(data): + data_mixed = [] + for i in range(4): + column = data[i*4 : (i+1)*4] + data_mixed += mix_column(column) + return data_mixed + +def shift_rows(data): + data_shifted = [] + for column in range(4): + for row in range(4): + data_shifted.append( data[((column + row) & 0b11) * 4 + row] ) + return data_shifted + +def inc(data): + data = data[:] # copy + for i in range(len(data)-1,-1,-1): + if data[i] == 255: + data[i] = 0 + else: + data[i] = data[i] + 1 + break + return data From 97b3656c2e37e45d556816b8f1f15c20d14f1acd Mon Sep 17 00:00:00 2001 From: rzhxeo Date: Fri, 9 Aug 2013 18:37:33 +0200 Subject: [PATCH 015/122] YoupornIE: Add support for hd videos and update Test --- youtube_dl/extractor/youporn.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/youtube_dl/extractor/youporn.py b/youtube_dl/extractor/youporn.py index d1156bf42..cc9c37027 100644 --- a/youtube_dl/extractor/youporn.py +++ b/youtube_dl/extractor/youporn.py @@ -12,14 +12,16 @@ from ..utils import ( unescapeHTML, unified_strdate, ) - +from ..aes import ( + aes_decrypt_text +) class YouPornIE(InfoExtractor): _VALID_URL = r'^(?:https?://)?(?:\w+\.)?youporn\.com/watch/(?P[0-9]+)/(?P[^/]+)' _TEST = { u'url': u'http://www.youporn.com/watch/505835/sex-ed-is-it-safe-to-masturbate-daily/', u'file': u'505835.mp4', - u'md5': u'c37ddbaaa39058c76a7e86c6813423c1', + u'md5': u'71ec5fcfddacf80f495efa8b6a8d9a89', u'info_dict': { u"upload_date": u"20101221", u"description": u"Love & Sex Answers: http://bit.ly/DanAndJenn -- Is It Unhealthy To Masturbate Daily?", @@ -75,6 +77,14 @@ class YouPornIE(InfoExtractor): # Get all of the links from the page LINK_RE = r'(?s)<a href="(?P<url>[^"]+)">' links = re.findall(LINK_RE, download_list_html) + + # Get link of hd video + encrypted_video_url = self._html_search_regex(r'var encryptedURL = \'(?P<encrypted_video_url>[a-zA-Z0-9+/]+={0,2})\';', + webpage, u'encrypted_video_url') + video_url = unicode( aes_decrypt_text(encrypted_video_url, video_title, 32), 'utf-8') + if video_url.split('/')[6].split('_')[0] == u'720p': # only add if 720p to avoid duplicates + links = [video_url] + links + if(len(links) == 0): raise ExtractorError(u'ERROR: no known formats available for video') From e3a88568b0457f18a03a2a894287a03f7cc2dbbe Mon Sep 17 00:00:00 2001 From: Yasoob <yasoob@yasoob.(none)> Date: Sun, 11 Aug 2013 22:23:05 +0500 Subject: [PATCH 016/122] Added an IE for hark.com --- youtube_dl/extractor/__init__.py | 1 + youtube_dl/extractor/hark.py | 35 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 youtube_dl/extractor/hark.py diff --git a/youtube_dl/extractor/__init__.py b/youtube_dl/extractor/__init__.py index 84c02c2ed..ca5664577 100644 --- a/youtube_dl/extractor/__init__.py +++ b/youtube_dl/extractor/__init__.py @@ -29,6 +29,7 @@ from .gametrailers import GametrailersIE from .generic import GenericIE from .googleplus import GooglePlusIE from .googlesearch import GoogleSearchIE +from .hark import HarkIE from .hotnewhiphop import HotNewHipHopIE from .howcast import HowcastIE from .hypem import HypemIE diff --git a/youtube_dl/extractor/hark.py b/youtube_dl/extractor/hark.py new file mode 100644 index 000000000..ab0a69697 --- /dev/null +++ b/youtube_dl/extractor/hark.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +import re + +from .common import InfoExtractor +from ..utils import determine_ext + +class HarkIE(InfoExtractor): + _VALID_URL = r'https?://www\.hark\.com/clips/(.+?)-.+' + _TEST = { + u'url': u'http://www.hark.com/clips/mmbzyhkgny-obama-beyond-the-afghan-theater-we-only-target-al-qaeda-on-may-23-2013', + u'file': u'mmbzyhkgny.mp3', + u'md5': u'6783a58491b47b92c7c1af5a77d4cbee', + u'info_dict': { + u"title": u"Obama: 'Beyond The Afghan Theater, We Only Target Al Qaeda' On May 23, 2013 ", + } + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + video_id = mobj.group(1) + embed_url = "http://www.hark.com/clips/%s/homepage_embed" %(video_id) + webpage = self._download_webpage(embed_url, video_id) + + final_url = self._search_regex(r'src="(.+?).mp3"', + webpage, 'video url')+'.mp3' + title = self._html_search_regex(r'<title>(.+?)', + webpage, 'video title').replace(' Sound Clip and Quote - Hark','').replace( + 'Sound Clip , Quote, MP3, and Ringtone - Hark','') + + return {'id': video_id, + 'url' : final_url, + 'title': title, + 'ext': determine_ext(final_url), + } From 2b9213cdc1fd02da75a905e8838e09738b2bc6b8 Mon Sep 17 00:00:00 2001 From: Emilien Kenler Date: Mon, 12 Aug 2013 10:48:40 +0200 Subject: [PATCH 017/122] Update generator Signed-off-by: Emilien Kenler --- devscripts/youtube_genalgo.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/devscripts/youtube_genalgo.py b/devscripts/youtube_genalgo.py index 31d6ec952..edad4c7ba 100644 --- a/devscripts/youtube_genalgo.py +++ b/devscripts/youtube_genalgo.py @@ -11,21 +11,24 @@ tests = [ # 90 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[]}|:;?/>.<'`", "mrtyuioplkjhgfdsazxcvbne1234567890QWER[YUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={`]}|"), + # 89 + ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[]}|:;?/>.<'", + "/?;:|}<[{=+-_)(*&^%$#@!MqBVCXZASDFGHJKLPOIUYTREWQ0987654321mnbvcxzasdfghjklpoiuyt"), # 88 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[]}|:;?/>.<", "J:|}][{=+-_)(*&;%$#@>MNBVCXZASDFGH^KLPOIUYTREWQ0987654321mnbvcxzasdfghrklpoiuytej"), - # 87 - vflART1Nf 2013/07/24 + # 87 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$^&*()_-+={[]}|:;?/>.<", - "tyuioplkjhgfdsazxcv"), - # 86 - vflm_D8eE 2013/07/31 + "/?;:|}][{=+-_)(*&^$#@!MNBVCXZArDFGHJKLPOIUY.<", - ">.1}|[{=+-_)(*&^%$#@!MNBVCXZASDFGHJK.<"), # 85 - vflSAFCP9 2013/07/19 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[};?/>.<", "ertyuiqplkjhgfdsazx$vbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#<%^&*()_-+={[};?/c"), # 84 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[};?>.<", - "<.>?;}[{=+-_)(*&^%$#@!MNBVCXZASDFGHJKLPOIUYTREWe098765432rmnbvcxzasdfghjklpoiuyt1"), + "<.>?;}[{=+-_)(*&^%$#@!MNBVCXZASDFGHJKLPOIUYTREWQ09876543q1mnbvcxzasdfghjklpoiuew2"), # 83 - vflTWC9KW 2013/08/01 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!#$%^&*()_+={[};?/>.<", "qwertyuioplkjhg>dsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!#$%^&*()_+={[};?/f"), From 5a27ecdd2ec83ba6e1069428c4c0fb3bd61f638c Mon Sep 17 00:00:00 2001 From: kkalpakloglou Date: Fri, 16 Aug 2013 23:54:09 +0300 Subject: [PATCH 018/122] Update AddAnime.py --- youtube_dl/extractor/AddAnime.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/youtube_dl/extractor/AddAnime.py b/youtube_dl/extractor/AddAnime.py index 43b0b24fe..a312fa97e 100644 --- a/youtube_dl/extractor/AddAnime.py +++ b/youtube_dl/extractor/AddAnime.py @@ -1,11 +1,6 @@ import re from .common import InfoExtractor -from ..utils import ( - ExtractorError, -) -from bs4 import BeautifulSoup - class AddAnimeIE(InfoExtractor): @@ -17,7 +12,6 @@ class AddAnimeIE(InfoExtractor): u'md5': u'0813c2430bea7a46bf13acf3406992f4', u'info_dict': { u"description": u"One Piece 606", - u"uploader": u"mugiwaraQ8", u"title": u"One Piece 606" } } @@ -31,24 +25,27 @@ class AddAnimeIE(InfoExtractor): webpage = self._download_webpage(url, video_id) - video_url = self._search_regex(r'var normal_video_file = "(.*?)",', - webpage, u'video URL') + + def find_between( webpage, first, last ): + try: + start = webpage.index( first ) + len( first ) + end = webpage.index( last, start ) + return webpage[start:end] + except ValueError: + return "" + + video_url = find_between( webpage, "var normal_video_file = '", "';" ) video_title = self._og_search_title(webpage) video_description = self._og_search_description(webpage) - - soup = BeautifulSoup(webpage) - - video_uploader= soup.find("meta", {"author":""})['content'] info = { 'id': video_id, 'url': video_url, 'ext': 'flv', 'title': video_title, - 'description': video_description, - 'uploader': video_uploader + 'description': video_description } return [info] From dbda1b51473ddc452d75bc1e98b3edabf4a7f5e8 Mon Sep 17 00:00:00 2001 From: rzhxeo Date: Sun, 18 Aug 2013 08:15:18 +0200 Subject: [PATCH 019/122] Add RTLnow extractor Supports http://rtl2now.rtl2.de and http://rtl-now.rtl.de --- youtube_dl/extractor/rtlnow.py | 88 ++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 youtube_dl/extractor/rtlnow.py diff --git a/youtube_dl/extractor/rtlnow.py b/youtube_dl/extractor/rtlnow.py new file mode 100644 index 000000000..15f01a2e2 --- /dev/null +++ b/youtube_dl/extractor/rtlnow.py @@ -0,0 +1,88 @@ +# encoding: utf-8 +import re + +from .common import InfoExtractor +from ..utils import ExtractorError + +class RTLnowIE(InfoExtractor): + """Information Extractor for RTL(2)now""" + _VALID_URL = r'(?:http://)?(?P(?Prtl(?:(?P2)|-)now\.rtl(?(is_rtl2)2|)\.de/)[a-zA-Z0-9-]+/[a-zA-Z0-9-]+\.php\?(?:container_id|film_id)=(?P[0-9]+)&player=1(?:&season=[0-9]+)?(?:&.*)?)' + _TESTS = [{ + u'url': u'http://rtl-now.rtl.de/ahornallee/folge-1.php?film_id=90419&player=1&season=1', + u'file': u'90419.flv', + u'info_dict': { + u'upload_date': u'20070416', + u'title': u'Ahornallee - Folge 1 - Der Einzug', + u'description': u'Folge 1 - Der Einzug', + }, + u'params': { + u'skip_download': True, + }, + }, + { + u'url': u'http://rtl2now.rtl2.de/aerger-im-revier/episode-15-teil-1.php?film_id=69756&player=1&season=2&index=5', + u'file': u'69756.flv', + u'info_dict': { + u'upload_date': u'20120519', + u'title': u'Ärger im Revier - Ein junger Ladendieb, ein handfester Streit...', + u'description': u'Ärger im Revier - Ein junger Ladendieb, ein handfester Streit u.a.', + u'thumbnail': u'http://autoimg.static-fra.de/rtl2now/219850/1500x1500/image2.jpg', + }, + u'params': { + u'skip_download': True, + }, + },] + + def _real_extract(self,url): + mobj = re.match(self._VALID_URL, url) + + webpage_url = u'http://' + mobj.group('url') + video_page_url = u'http://' + mobj.group('base_url') + video_id = mobj.group(u'video_id') + + webpage = self._download_webpage(webpage_url, video_id) + video_title = self._html_search_regex(r'(?P<title>[^<]+)', + webpage, u'title') + playerdata_url = self._html_search_regex(r'\'playerdata\': \'(?P[^\']+)\'', + webpage, u'playerdata_url') + + playerdata = self._download_webpage(playerdata_url, video_id) + mobj = re.search(r'<!\[CDATA\[(?P<description>.+?)\s+- (?:Sendung )?vom (?P<upload_date_d>[0-9]{2})\.(?P<upload_date_m>[0-9]{2})\.(?:(?P<upload_date_Y>[0-9]{4})|(?P<upload_date_y>[0-9]{2})) [0-9]{2}:[0-9]{2} Uhr\]\]>', playerdata) + if mobj: + video_description = mobj.group(u'description') + if mobj.group('upload_date_Y'): + video_upload_date = mobj.group('upload_date_Y') + else: + video_upload_date = u'20' + mobj.group('upload_date_y') + video_upload_date += mobj.group('upload_date_m')+mobj.group('upload_date_d') + else: + video_description = None + video_upload_date = None + self._downloader.report_warning(u'Unable to extract description and upload date') + + # Thumbnail: not every video has an thumbnail + mobj = re.search(r'', webpage) + if mobj: + video_thumbnail = mobj.group(u'thumbnail') + else: + video_thumbnail = None + + mobj = re.search(r']+>rtmpe://(?:[^/]+/){2})(?P[^\]]+)\]\]>', playerdata) + if mobj is None: + raise ExtractorError(u'Unable to extract media URL') + video_url = mobj.group(u'url') + video_play_path = u'mp4:' + mobj.group(u'play_path') + video_player_url = video_page_url + u'includes/vodplayer.swf' + + return [{ + 'id': video_id, + 'url': video_url, + 'play_path': video_play_path, + 'page_url': video_page_url, + 'player_url': video_player_url, + 'ext': 'flv', + 'title': video_title, + 'description': video_description, + 'upload_date': video_upload_date, + 'thumbnail': video_thumbnail, + }] From 01b32990da992a5f271532e7408f4c6d546e162c Mon Sep 17 00:00:00 2001 From: rzhxeo Date: Sun, 18 Aug 2013 08:16:53 +0200 Subject: [PATCH 020/122] Add RTLnow extractor --- youtube_dl/extractor/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/youtube_dl/extractor/__init__.py b/youtube_dl/extractor/__init__.py index 84c02c2ed..5bb44e764 100644 --- a/youtube_dl/extractor/__init__.py +++ b/youtube_dl/extractor/__init__.py @@ -56,6 +56,7 @@ from .rbmaradio import RBMARadioIE from .redtube import RedTubeIE from .ringtv import RingTVIE from .roxwel import RoxwelIE +from .rtlnow import RTLnowIE from .sina import SinaIE from .soundcloud import SoundcloudIE, SoundcloudSetIE from .spiegel import SpiegelIE From bda2c49d75e9cd34e8ece2fa3e5375365a84f290 Mon Sep 17 00:00:00 2001 From: Emilien Kenler Date: Sun, 18 Aug 2013 11:10:39 +0200 Subject: [PATCH 021/122] Update algo - see #1254 Signed-off-by: Emilien Kenler --- devscripts/youtube_genalgo.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/devscripts/youtube_genalgo.py b/devscripts/youtube_genalgo.py index edad4c7ba..dca963e8f 100644 --- a/devscripts/youtube_genalgo.py +++ b/devscripts/youtube_genalgo.py @@ -19,19 +19,19 @@ tests = [ "J:|}][{=+-_)(*&;%$#@>MNBVCXZASDFGH^KLPOIUYTREWQ0987654321mnbvcxzasdfghrklpoiuytej"), # 87 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$^&*()_-+={[]}|:;?/>.<", - "/?;:|}][{=+-_)(*&^$#@!MNBVCXZArDFGHJKLPOIUY.<"), # 86 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[|};?/>.<", "yuioplkjhgfdsazecvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[|};?/>.<"), - # 85 - vflSAFCP9 2013/07/19 + # 85 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[};?/>.<", - "ertyuiqplkjhgfdsazx$vbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#<%^&*()_-+={[};?/c"), + ".>/?;}[{=+-_)(*&^%$#@!MNBVCXZASDFGHJKLPOIUYTREWQ0q876543r1mnbvcx9asdfghjklpoiuyt2"), # 84 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[};?>.<", "<.>?;}[{=+-_)(*&^%$#@!MNBVCXZASDFGHJKLPOIUYTREWQ09876543q1mnbvcxzasdfghjklpoiuew2"), - # 83 - vflTWC9KW 2013/08/01 + # 83 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!#$%^&*()_+={[};?/>.<", - "qwertyuioplkjhg>dsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!#$%^&*()_+={[};?/f"), + ".>/?;}[{=+_)(*&^%<#!MNBVCXZASPFGHJKLwOIUYTREWQ0987654321mnbvcxzasdfghjklpoiuytreq"), # 82 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKHGFDSAZXCVBNM!@#$%^&*(-+={[};?/>.<", "Q>/?;}[{=+-(*<^%$#@!MNBVCXZASDFGHKLPOIUY8REWT0q&7654321mnbvcxzasdfghjklpoiuytrew9"), From 943f7f7a399c6fb3006eb2bd68070f28a272171f Mon Sep 17 00:00:00 2001 From: Pierre Rudloff Date: Sun, 18 Aug 2013 16:11:47 +0200 Subject: [PATCH 022/122] Download videos from jeuxvideo.com --- youtube-dl | Bin 3445 -> 173825 bytes youtube_dl/extractor/__init__.py | 1 + youtube_dl/extractor/jeuxvideo.py | 33 ++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 youtube_dl/extractor/jeuxvideo.py diff --git a/youtube-dl b/youtube-dl index e3eb8774caa258ddbaa51323938fbd36c4fc170d..ae8cc98e33b73af6d7cb35fef44dc9e7f5a3ebbd 100755 GIT binary patch literal 173825 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%0D42;5lvZJw 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

)&tdVq)c(n#}UD)NxZ+>3Q6cNmrg0rW=`%BA-$ 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 XF9Gom4b2Yt703g;^A`ZbC$#?q&q@*K literal 3445 zcmbVOdvDuD692pU6yp>ajEG1SN zkH;R0Oy=l16*r||&a|cDTB99iwEE17*->GCgxOYmD>K`V*^ zST}-V06PD+fz(%_S{QUDJB<+3-D9Nhj&Mk#;$Fmu*c-lt#jy-h0{zGSu z;=5IPFsO;|?o|KCe6(Yv^-9rt>(9U@$bI(9vy+s8fs35- zu!DnaOq1e^yaIukYcLX;I_CzVG#aY{q(In8g<#4pdiR1}=QmWo-;^45WmOS$iW46_-EykA5swW1*s#@yONOd4* zTRS?~|G_?>Qq&FQWB~NHe2b6aJe>S6P)!Pg9YHjN1U6<|tv_2xntXWo>gD^Z)rXtk z>E_wyI~I5OIDaA$ikw?mOKga5E)C7m}P;#SS}-;_#s0Uu{?>xY`F{; z82a;Q5vN(=$IF0z&|l7zWn5$hV`&^0AqGqI=NR$@!zE`diW2(g8FH4)eU4eUD6)_^ z(l{jxvxIS$&Dng8ewgMg^BK!A4lpdp_(d89i`-A3is7kv8X^t$yu(aWzzQ*O^9N zn!%Zji+jg_ObsWz9#{#x3+chT3c0p#_W*ot@2QQW^2mffdd>)Rj)nVyz4h}cjrKVX zgl)8d0LFF>D~mvl03##lXljGG2QO0^-b%5yN=IcB!a+i->h8pj2b!a*|8?D)ellsb zvCErn{U~S@eo-V!@tIfUBhM$j zyI(s##KC#E8VdE9b`cAw%%y`?k%Dcm>jKG*$J0S z=?7h}Z?i{&skg!X&}`d^9X;rHsW(6Jl|4MN?Tf4pW>JyaSKewSTd6kdE~P1nLPDLo==9nF(C+$%f8xu~nEwBuO7gYCt*lYXrtu`K-)Q`= XXg)ZDx7{cKgcG0|aWYnP6T^Q26dqrA diff --git a/youtube_dl/extractor/__init__.py b/youtube_dl/extractor/__init__.py index 84c02c2ed..b9bd3a429 100644 --- a/youtube_dl/extractor/__init__.py +++ b/youtube_dl/extractor/__init__.py @@ -36,6 +36,7 @@ from .ign import IGNIE, OneUPIE from .ina import InaIE from .infoq import InfoQIE from .instagram import InstagramIE +from .jeuxvideo import JeuxVideoIE from .jukebox import JukeboxIE from .justintv import JustinTVIE from .kankan import KankanIE diff --git a/youtube_dl/extractor/jeuxvideo.py b/youtube_dl/extractor/jeuxvideo.py new file mode 100644 index 000000000..d74a1c9b4 --- /dev/null +++ b/youtube_dl/extractor/jeuxvideo.py @@ -0,0 +1,33 @@ +import json +import re + +from .common import InfoExtractor + +class JeuxVideoIE(InfoExtractor): + _VALID_URL = r'http://.*?\.jeuxvideo\.com/.*/(.*?)-\d+\.htm' + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + title = re.match(self._VALID_URL, url).group(1) + webpage = self._download_webpage(url, title) + m_download = re.search(r'', webpage) + + xml_link = m_download.group(1) + + id = re.search(r'http://www.jeuxvideo.com/config/\w+/0011/(.*?)/\d+_player\.xml', xml_link).group(1) + + xml_config = self._download_webpage(xml_link, title, + 'Downloading XML config') + info = re.search(r'(.*?)', + xml_config, re.MULTILINE|re.DOTALL).group(1) + info = json.loads(info)['versions'][0] + + video_url = 'http://video720.jeuxvideo.com/' + info['file'] + + track_info = {'id':id, + 'title' : title, + 'ext' : 'mp4', + 'url' : video_url + } + + return [track_info] From ea55b2a4cac1d56c578380b6bcb21b5fbc496a57 Mon Sep 17 00:00:00 2001 From: rzhxeo Date: Mon, 19 Aug 2013 08:57:36 +0200 Subject: [PATCH 023/122] Add VOXnow to RTLnow extractor --- youtube_dl/extractor/rtlnow.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/youtube_dl/extractor/rtlnow.py b/youtube_dl/extractor/rtlnow.py index 15f01a2e2..d993a990a 100644 --- a/youtube_dl/extractor/rtlnow.py +++ b/youtube_dl/extractor/rtlnow.py @@ -5,8 +5,8 @@ from .common import InfoExtractor from ..utils import ExtractorError class RTLnowIE(InfoExtractor): - """Information Extractor for RTL(2)now""" - _VALID_URL = r'(?:http://)?(?P(?Prtl(?:(?P2)|-)now\.rtl(?(is_rtl2)2|)\.de/)[a-zA-Z0-9-]+/[a-zA-Z0-9-]+\.php\?(?:container_id|film_id)=(?P[0-9]+)&player=1(?:&season=[0-9]+)?(?:&.*)?)' + """Information Extractor for RTLnow, RTL2now and VOXnow""" + _VALID_URL = r'(?:http://)?(?P(?Prtl(?:(?P2)|-)now\.rtl(?(is_rtl2)2|)\.de/|(?:www\.)?voxnow\.de/)[a-zA-Z0-9-]+/[a-zA-Z0-9-]+\.php\?(?:container_id|film_id)=(?P[0-9]+)&player=1(?:&season=[0-9]+)?(?:&.*)?)' _TESTS = [{ u'url': u'http://rtl-now.rtl.de/ahornallee/folge-1.php?film_id=90419&player=1&season=1', u'file': u'90419.flv', @@ -31,7 +31,19 @@ class RTLnowIE(InfoExtractor): u'params': { u'skip_download': True, }, - },] + }, + { + u'url': u'www.voxnow.de/voxtours/suedafrika-reporter-ii.php?film_id=13883&player=1&season=17', + u'file': u'13883.flv', + u'info_dict': { + u'upload_date': u'20090627', + u'title': u'Voxtours - Südafrika-Reporter II', + u'description': u'Südafrika-Reporter II', + }, + u'params': { + u'skip_download': True, + }, + }] def _real_extract(self,url): mobj = re.match(self._VALID_URL, url) From d741e55a423a09c40b3c5e19551f432a050353d7 Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Mon, 19 Aug 2013 10:27:42 +0200 Subject: [PATCH 024/122] [youtube] Support watch_popup URLs (Fixes #1275) --- test/test_all_urls.py | 1 + youtube_dl/extractor/youtube.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_all_urls.py b/test/test_all_urls.py index c73d0e467..c54faa380 100644 --- a/test/test_all_urls.py +++ b/test/test_all_urls.py @@ -50,6 +50,7 @@ class TestAllURLsMatching(unittest.TestCase): self.assertEqual(YoutubeIE()._extract_id('http://www.youtube.com/watch?&v=BaW_jenozKc'), 'BaW_jenozKc') self.assertEqual(YoutubeIE()._extract_id('https://www.youtube.com/watch?&v=BaW_jenozKc'), 'BaW_jenozKc') self.assertEqual(YoutubeIE()._extract_id('https://www.youtube.com/watch?feature=player_embedded&v=BaW_jenozKc'), 'BaW_jenozKc') + self.assertEqual(YoutubeIE()._extract_id('https://www.youtube.com/watch_popup?v=BaW_jenozKc'), 'BaW_jenozKc') def test_no_duplicates(self): ies = gen_extractors() diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py index f74718950..843a973ca 100644 --- a/youtube_dl/extractor/youtube.py +++ b/youtube_dl/extractor/youtube.py @@ -141,7 +141,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): (?: # the various things that can precede the ID: (?:(?:v|embed|e)/) # v/ or embed/ or e/ |(?: # or the v= param in all its forms - (?:watch|movie(?:_popup)?(?:\.php)?)? # preceding watch(_popup|.php) or nothing (like /?v=xxxx) + (?:(?:watch|movie)(?:_popup)?(?:\.php)?)? # preceding watch(_popup|.php) or nothing (like /?v=xxxx) (?:\?|\#!?) # the params delimiter ? or # or #! (?:.*?&)? # any other preceding param (like /?s=tuff&v=xxxx) v= From 836a086ce9d48338444f010f690119a9a3998517 Mon Sep 17 00:00:00 2001 From: Allan Zhou Date: Mon, 19 Aug 2013 18:22:25 -0700 Subject: [PATCH 025/122] Add YouTube DASH formats to YouTubeIE --- youtube_dl/extractor/youtube.py | 96 ++++++++++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 6 deletions(-) diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py index 843a973ca..248105d7f 100644 --- a/youtube_dl/extractor/youtube.py +++ b/youtube_dl/extractor/youtube.py @@ -155,11 +155,19 @@ class YoutubeIE(YoutubeBaseInfoExtractor): # Listed in order of quality _available_formats = ['38', '37', '46', '22', '45', '35', '44', '34', '18', '43', '6', '5', '17', '13', '95', '94', '93', '92', '132', '151', - '85', '84', '102', '83', '101', '82', '100', + '85', '84', '102', '83', '101', '82', '100', # 3D + '138', '137', '136', '135', '134', '133', '160', # Dash video mp4 + '141', '140', '139', # Dash auido mp4 + '248', '247', '246', '245', '244', '243', '242', # Dash video webm + '172', '171', # Dash audio webm ] _available_formats_prefer_free = ['38', '46', '37', '45', '22', '44', '35', '43', '34', '18', '6', '5', '17', '13', '95', '94', '93', '92', '132', '151', '85', '102', '84', '101', '83', '100', '82', + '248', '247', '246', '245', '244', '243', '242', # Dash video webm + '172', '171', # Dash audio webm + '138', '137', '136', '135', '134', '133', '160', # Dash video mp4 + '141', '140', '139', # Dash auido mp4 ] _video_extensions = { '13': '3gp', @@ -181,7 +189,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): '100': 'webm', '101': 'webm', '102': 'webm', - + # videos that use m3u8 '92': 'mp4', '93': 'mp4', @@ -190,6 +198,29 @@ class YoutubeIE(YoutubeBaseInfoExtractor): '96': 'mp4', '132': 'mp4', '151': 'mp4', + + # Dash mp4 + '133': 'mp4', + '134': 'mp4', + '135': 'mp4', + '136': 'mp4', + '137': 'mp4', + '138': 'mp4', + '139': 'mp4', + '140': 'mp4', + '141': 'mp4', + '160': 'mp4', + + # Dash webm + '171': 'webm', + '172': 'webm', + '242': 'webm', + '243': 'webm', + '244': 'webm', + '245': 'webm', + '246': 'webm', + '247': 'webm', + '248': 'webm', } _video_dimensions = { '5': '240x400', @@ -217,11 +248,58 @@ class YoutubeIE(YoutubeBaseInfoExtractor): '96': '1080p', '100': '360p', '101': '480p', - '102': '720p', + '102': '720p', '132': '240p', '151': '72p', + '133': '240p', + '134': '360p', + '135': '480p', + '136': '720p', + '137': '1080p', + '138': '>1080p', + '139': '48k', + '140': '128k', + '141': '256k', + '160': '192p', + '171': '128k', + '172': '256k', + '242': '240p', + '243': '360p', + '244': '480p', + '245': '480p', + '246': '480p', + '247': '720p', + '248': '1080p', } - _3d_itags = ['85', '84', '102', '83', '101', '82', '100'] + _special_itags = { + '82': '3D', + '83': '3D', + '84': '3D', + '85': '3D', + '100': '3D', + '101': '3D', + '102': '3D', + '133': 'DASH Video', + '134': 'DASH Video', + '135': 'DASH Video', + '136': 'DASH Video', + '137': 'DASH Video', + '138': 'DASH Video', + '139': 'DASH Audio', + '140': 'DASH Audio', + '141': 'DASH Audio', + '160': 'DASH Video', + '171': 'DASH Audio', + '172': 'DASH Audio', + '242': 'DASH Video', + '243': 'DASH Video', + '244': 'DASH Video', + '245': 'DASH Video', + '246': 'DASH Video', + '247': 'DASH Video', + '248': 'DASH Video', + } + IE_NAME = u'youtube' _TESTS = [ { @@ -472,7 +550,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): for x in formats: print('%s\t:\t%s\t[%s]%s' %(x, self._video_extensions.get(x, 'flv'), self._video_dimensions.get(x, '???'), - ' (3D)' if x in self._3d_itags else '')) + ' ('+self._special_itags[x]+')' if x in self._special_itags else '')) def _extract_id(self, url): mobj = re.match(self._VALID_URL, url, re.VERBOSE) @@ -699,6 +777,12 @@ class YoutubeIE(YoutubeBaseInfoExtractor): if m_s is not None: self.to_screen(u'%s: Encrypted signatures detected.' % video_id) video_info['url_encoded_fmt_stream_map'] = [args['url_encoded_fmt_stream_map']] + m_s = re.search(r'[&,]s=', args['adaptive_fmts']) + if m_s is not None: + video_info['url_encoded_fmt_stream_map'][0] += ','+args['adaptive_fmts'] + else: + video_info['url_encoded_fmt_stream_map'][0] += ','+video_info['adaptive_fmts'][0] + except ValueError: pass @@ -758,7 +842,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): video_format = '{0} - {1}{2}'.format(format_param if format_param else video_extension, self._video_dimensions.get(format_param, '???'), - ' (3D)' if format_param in self._3d_itags else '') + ' ('+self._special_itags[format_param]+')' if format_param in self._special_itags else '') results.append({ 'id': video_id, From 211fbc1328edda1752fce9dc5ed604b98f9dc865 Mon Sep 17 00:00:00 2001 From: Allan Zhou Date: Mon, 19 Aug 2013 18:57:55 -0700 Subject: [PATCH 026/122] fix failed tests --- youtube_dl/extractor/youtube.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py index 248105d7f..bdd399d3e 100644 --- a/youtube_dl/extractor/youtube.py +++ b/youtube_dl/extractor/youtube.py @@ -777,10 +777,10 @@ class YoutubeIE(YoutubeBaseInfoExtractor): if m_s is not None: self.to_screen(u'%s: Encrypted signatures detected.' % video_id) video_info['url_encoded_fmt_stream_map'] = [args['url_encoded_fmt_stream_map']] - m_s = re.search(r'[&,]s=', args['adaptive_fmts']) - if m_s is not None: + m_s = re.search(r'[&,]s=', args['adaptive_fmts'] if 'adaptive_fmts' in args else '') + if m_s is not None and 'adaptive_fmts' in args: video_info['url_encoded_fmt_stream_map'][0] += ','+args['adaptive_fmts'] - else: + elif 'adaptive_fmts' in video_info: video_info['url_encoded_fmt_stream_map'][0] += ','+video_info['adaptive_fmts'][0] except ValueError: From 87f78946a56d19fe3696725fe7329767fd910320 Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Wed, 21 Aug 2013 03:50:56 +0200 Subject: [PATCH 027/122] [collegehumor] Allow old-style videos (Fixes #1285) --- youtube_dl/extractor/collegehumor.py | 52 ++++++++++++++++++---------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/youtube_dl/extractor/collegehumor.py b/youtube_dl/extractor/collegehumor.py index 30b9c7549..8d4c93d6d 100644 --- a/youtube_dl/extractor/collegehumor.py +++ b/youtube_dl/extractor/collegehumor.py @@ -4,6 +4,7 @@ import xml.etree.ElementTree from .common import InfoExtractor from ..utils import ( compat_urllib_parse_urlparse, + determine_ext, ExtractorError, ) @@ -12,7 +13,7 @@ from ..utils import ( class CollegeHumorIE(InfoExtractor): _VALID_URL = r'^(?:https?://)?(?:www\.)?collegehumor\.com/(video|embed|e)/(?P[0-9]+)/?(?P.*)$' - _TEST = { + _TESTS = [{ u'url': u'http://www.collegehumor.com/video/6902724/comic-con-cosplay-catastrophe', u'file': u'6902724.mp4', u'md5': u'1264c12ad95dca142a9f0bf7968105a0', @@ -20,7 +21,16 @@ class CollegeHumorIE(InfoExtractor): u'title': u'Comic-Con Cosplay Catastrophe', u'description': u'Fans get creative this year at San Diego. Too creative. And yes, that\'s really Joss Whedon.', }, - } + }, + { + u'url': u'http://www.collegehumor.com/video/3505939/font-conference', + u'file': u'3505939.mp4', + u'md5': u'c51ca16b82bb456a4397987791a835f5', + u'info_dict': { + u'title': u'Font Conference', + u'description': u'This video wasn\'t long enough, so we made it double-spaced.', + }, + }] def _real_extract(self, url): mobj = re.match(self._VALID_URL, url) @@ -49,25 +59,29 @@ class CollegeHumorIE(InfoExtractor): info['description'] = videoNode.findall('./description')[0].text info['title'] = videoNode.findall('./caption')[0].text info['thumbnail'] = videoNode.findall('./thumbnail')[0].text - manifest_url = videoNode.findall('./file')[0].text + next_url = videoNode.findall('./file')[0].text except IndexError: raise ExtractorError(u'Invalid metadata XML file') - manifest_url += '?hdcore=2.10.3' - manifestXml = self._download_webpage(manifest_url, video_id, - u'Downloading XML manifest', - u'Unable to download video info XML') + if next_url.endswith(u'manifest.f4m'): + manifest_url = next_url + '?hdcore=2.10.3' + manifestXml = self._download_webpage(manifest_url, video_id, + u'Downloading XML manifest', + u'Unable to download video info XML') - adoc = xml.etree.ElementTree.fromstring(manifestXml) - try: - media_node = adoc.findall('./{http://ns.adobe.com/f4m/1.0}media')[0] - node_id = media_node.attrib['url'] - video_id = adoc.findall('./{http://ns.adobe.com/f4m/1.0}id')[0].text - except IndexError as err: - raise ExtractorError(u'Invalid manifest file') + adoc = xml.etree.ElementTree.fromstring(manifestXml) + try: + media_node = adoc.findall('./{http://ns.adobe.com/f4m/1.0}media')[0] + node_id = media_node.attrib['url'] + video_id = adoc.findall('./{http://ns.adobe.com/f4m/1.0}id')[0].text + except IndexError as err: + raise ExtractorError(u'Invalid manifest file') + url_pr = compat_urllib_parse_urlparse(info['thumbnail']) + info['url'] = url_pr.scheme + '://' + url_pr.netloc + video_id[:-2].replace('.csmil','').replace(',','') + info['ext'] = 'mp4' + else: + # Old-style direct links + info['url'] = next_url + info['ext'] = determine_ext(info['url']) - url_pr = compat_urllib_parse_urlparse(info['thumbnail']) - - info['url'] = url_pr.scheme + '://' + url_pr.netloc + video_id[:-2].replace('.csmil','').replace(',','') - info['ext'] = 'mp4' - return [info] + return info From 79cb25776f46e0b9b1e95052fbd84a59440fa34f Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Wed, 21 Aug 2013 04:06:46 +0200 Subject: [PATCH 028/122] Cache suitable regular expressions This speeds up TestAllURLsMatching.test_no_duplicates by about 8000% at the cost of minimal memory overhead. --- youtube_dl/extractor/common.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/youtube_dl/extractor/common.py b/youtube_dl/extractor/common.py index da50abfc1..8009c2d85 100644 --- a/youtube_dl/extractor/common.py +++ b/youtube_dl/extractor/common.py @@ -77,7 +77,13 @@ class InfoExtractor(object): @classmethod def suitable(cls, url): """Receives a URL and returns True if suitable for this IE.""" - return re.match(cls._VALID_URL, url) is not None + + # This does not use has/getattr intentionally - we want to know whether + # we have cached the regexp for *this* class, whereas getattr would also + # match the superclass + if '_VALID_URL_RE' not in cls.__dict__: + cls._VALID_URL_RE = re.compile(cls._VALID_URL) + return cls._VALID_URL_RE.match(url) is not None @classmethod def working(cls): From 3093468977e5c04d7f39016bbe983c483e47707f Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Wed, 21 Aug 2013 04:31:57 +0200 Subject: [PATCH 029/122] [generic] Ignore stupid HTTP servers (#1284) --- youtube_dl/extractor/generic.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/youtube_dl/extractor/generic.py b/youtube_dl/extractor/generic.py index b633e896c..1c468f8f6 100644 --- a/youtube_dl/extractor/generic.py +++ b/youtube_dl/extractor/generic.py @@ -107,8 +107,13 @@ class GenericIE(InfoExtractor): return new_url def _real_extract(self, url): - new_url = self._test_redirect(url) - if new_url: return [self.url_result(new_url)] + try: + new_url = self._test_redirect(url) + if new_url: + return [self.url_result(new_url)] + except compat_urllib_error.HTTPError: + # This may be a stupid server that doesn't like HEAD, our UA, or so + pass video_id = url.split('/')[-1] try: From 7fea7156cb41d4706059174f1fd00faa02278c8c Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Wed, 21 Aug 2013 04:32:22 +0200 Subject: [PATCH 030/122] [generic] support HTML5 video --- youtube_dl/extractor/generic.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/youtube_dl/extractor/generic.py b/youtube_dl/extractor/generic.py index 1c468f8f6..da016f7ee 100644 --- a/youtube_dl/extractor/generic.py +++ b/youtube_dl/extractor/generic.py @@ -149,6 +149,9 @@ class GenericIE(InfoExtractor): # We only look in og:video if the MIME type is a video, don't try if it's a Flash player: if m_video_type is not None: mobj = re.search(r'.*? Date: Wed, 21 Aug 2013 04:33:57 +0200 Subject: [PATCH 031/122] release 2013.08.21 --- youtube_dl/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtube_dl/version.py b/youtube_dl/version.py index 8c93a275c..58e26bc49 100644 --- a/youtube_dl/version.py +++ b/youtube_dl/version.py @@ -1,2 +1,2 @@ -__version__ = '2013.08.17' +__version__ = '2013.08.21' From 739674cd77d6a6c7025878701939d987fac5b446 Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Wed, 21 Aug 2013 05:24:58 +0200 Subject: [PATCH 032/122] [rtlnow] Add support for error message for queries from outside of Germany --- youtube_dl/extractor/rtlnow.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/youtube_dl/extractor/rtlnow.py b/youtube_dl/extractor/rtlnow.py index d993a990a..2f134e6a7 100644 --- a/youtube_dl/extractor/rtlnow.py +++ b/youtube_dl/extractor/rtlnow.py @@ -2,7 +2,10 @@ import re from .common import InfoExtractor -from ..utils import ExtractorError +from ..utils import ( + clean_html, + ExtractorError, +) class RTLnowIE(InfoExtractor): """Information Extractor for RTLnow, RTL2now and VOXnow""" @@ -18,6 +21,7 @@ class RTLnowIE(InfoExtractor): u'params': { u'skip_download': True, }, + u'skip': u'Only works from Germany', }, { u'url': u'http://rtl2now.rtl2.de/aerger-im-revier/episode-15-teil-1.php?film_id=69756&player=1&season=2&index=5', @@ -31,6 +35,7 @@ class RTLnowIE(InfoExtractor): u'params': { u'skip_download': True, }, + u'skip': u'Only works from Germany', }, { u'url': u'www.voxnow.de/voxtours/suedafrika-reporter-ii.php?film_id=13883&player=1&season=17', @@ -53,6 +58,14 @@ class RTLnowIE(InfoExtractor): video_id = mobj.group(u'video_id') webpage = self._download_webpage(webpage_url, video_id) + + note_m = re.search(r'''(?sx) + (.*?) + ''', webpage) + if note_m: + msg = clean_html(note_m.group(1)) + raise ExtractorError(msg) + video_title = self._html_search_regex(r'(?P<title>[^<]+)', webpage, u'title') playerdata_url = self._html_search_regex(r'\'playerdata\': \'(?P[^\']+)\'', From 6c3e6e88d3aaaea64ca3d96c005da654c89c8a3a Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Wed, 21 Aug 2013 05:44:19 +0200 Subject: [PATCH 033/122] Allow hours in ETA display (Fixes #1280) --- youtube_dl/FileDownloader.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/youtube_dl/FileDownloader.py b/youtube_dl/FileDownloader.py index ea6b9d626..217c4a52f 100644 --- a/youtube_dl/FileDownloader.py +++ b/youtube_dl/FileDownloader.py @@ -79,9 +79,13 @@ class FileDownloader(object): rate = float(current) / dif eta = int((float(total) - float(current)) / rate) (eta_mins, eta_secs) = divmod(eta, 60) - if eta_mins > 99: - return '--:--' - return '%02d:%02d' % (eta_mins, eta_secs) + (eta_hours, eta_mins) = divmod(eta_mins, 60) + if eta_hours > 99: + return '--:--:--' + if eta_hours == 0: + return '%02d:%02d' % (eta_mins, eta_secs) + else: + return '%02d:%02d:%02d' % (eta_hours, eta_mins, eta_secs) @staticmethod def calc_speed(start, now, bytes): From cde846b3d3f59029fc07ecd97e49cfae050af3c9 Mon Sep 17 00:00:00 2001 From: Allan Zhou Date: Tue, 20 Aug 2013 21:42:49 -0700 Subject: [PATCH 034/122] fix code style --- youtube_dl/extractor/youtube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py index bdd399d3e..1cd2d40f1 100644 --- a/youtube_dl/extractor/youtube.py +++ b/youtube_dl/extractor/youtube.py @@ -777,7 +777,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): if m_s is not None: self.to_screen(u'%s: Encrypted signatures detected.' % video_id) video_info['url_encoded_fmt_stream_map'] = [args['url_encoded_fmt_stream_map']] - m_s = re.search(r'[&,]s=', args['adaptive_fmts'] if 'adaptive_fmts' in args else '') + m_s = re.search(r'[&,]s=', args.get('adaptive_fmts', u'')) if m_s is not None and 'adaptive_fmts' in args: video_info['url_encoded_fmt_stream_map'][0] += ','+args['adaptive_fmts'] elif 'adaptive_fmts' in video_info: From b7a68384078ec0d97fb3c8e4a3100e9c60f340d0 Mon Sep 17 00:00:00 2001 From: Allan Zhou Date: Tue, 20 Aug 2013 21:57:32 -0700 Subject: [PATCH 035/122] address review comment --- youtube_dl/extractor/youtube.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py index 1cd2d40f1..e573b021d 100644 --- a/youtube_dl/extractor/youtube.py +++ b/youtube_dl/extractor/youtube.py @@ -778,7 +778,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor): self.to_screen(u'%s: Encrypted signatures detected.' % video_id) video_info['url_encoded_fmt_stream_map'] = [args['url_encoded_fmt_stream_map']] m_s = re.search(r'[&,]s=', args.get('adaptive_fmts', u'')) - if m_s is not None and 'adaptive_fmts' in args: + if 'url_encoded_fmt_stream_map' not in video_info or not video_info['url_encoded_fmt_stream_map']: + video_info['url_encoded_fmt_stream_map'] = [''] + if m_s is not None: video_info['url_encoded_fmt_stream_map'][0] += ','+args['adaptive_fmts'] elif 'adaptive_fmts' in video_info: video_info['url_encoded_fmt_stream_map'][0] += ','+video_info['adaptive_fmts'][0] From 37b6d5f684d409365bbac6d3f2b8074b57e643a8 Mon Sep 17 00:00:00 2001 From: Allan Zhou Date: Tue, 20 Aug 2013 23:51:05 -0700 Subject: [PATCH 036/122] fix hls test --- youtube_dl/extractor/youtube.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py index e573b021d..1599dd484 100644 --- a/youtube_dl/extractor/youtube.py +++ b/youtube_dl/extractor/youtube.py @@ -778,13 +778,16 @@ class YoutubeIE(YoutubeBaseInfoExtractor): self.to_screen(u'%s: Encrypted signatures detected.' % video_id) video_info['url_encoded_fmt_stream_map'] = [args['url_encoded_fmt_stream_map']] m_s = re.search(r'[&,]s=', args.get('adaptive_fmts', u'')) - if 'url_encoded_fmt_stream_map' not in video_info or not video_info['url_encoded_fmt_stream_map']: - video_info['url_encoded_fmt_stream_map'] = [''] if m_s is not None: - video_info['url_encoded_fmt_stream_map'][0] += ','+args['adaptive_fmts'] + if 'url_encoded_fmt_stream_map' in video_info: + video_info['url_encoded_fmt_stream_map'][0] += ',' + args['adaptive_fmts'] + else: + video_info['url_encoded_fmt_stream_map'] = [args['adaptive_fmts']] elif 'adaptive_fmts' in video_info: - video_info['url_encoded_fmt_stream_map'][0] += ','+video_info['adaptive_fmts'][0] - + if 'url_encoded_fmt_stream_map' in video_info: + video_info['url_encoded_fmt_stream_map'][0] += ',' + video_info['adaptive_fmts'][0] + else: + video_info['url_encoded_fmt_stream_map'] = video_info['adaptive_fmts'] except ValueError: pass From a91b954bb4571b766f4bc01dfbe0be870a1b0a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Wed, 21 Aug 2013 13:48:19 +0200 Subject: [PATCH 037/122] [vimeo] extract information for Vimeo Pro videos from http://player.vimeo.com/video/{video_id} (fixes #1197) For some videos https://vimeo.com/{video_id} doesn't work --- youtube_dl/extractor/vimeo.py | 41 ++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/youtube_dl/extractor/vimeo.py b/youtube_dl/extractor/vimeo.py index cc9c8d018..512e06e2a 100644 --- a/youtube_dl/extractor/vimeo.py +++ b/youtube_dl/extractor/vimeo.py @@ -20,18 +20,31 @@ class VimeoIE(InfoExtractor): _VALID_URL = r'(?Phttps?://)?(?:(?:www|player)\.)?vimeo(?Ppro)?\.com/(?:(?:(?:groups|album)/[^/]+)|(?:.*?)/)?(?Pplay_redirect_hls\?clip_id=)?(?:videos?/)?(?P[0-9]+)(?:[?].*)?$' _NETRC_MACHINE = 'vimeo' IE_NAME = u'vimeo' - _TEST = { - u'url': u'http://vimeo.com/56015672', - u'file': u'56015672.mp4', - u'md5': u'8879b6cc097e987f02484baf890129e5', - u'info_dict': { - u"upload_date": u"20121220", - u"description": u"This is a test case for youtube-dl.\nFor more information, see github.com/rg3/youtube-dl\nTest chars: \u2605 \" ' \u5e78 / \\ \u00e4 \u21ad \U0001d550", - u"uploader_id": u"user7108434", - u"uploader": u"Filippo Valsorda", - u"title": u"youtube-dl test video - \u2605 \" ' \u5e78 / \\ \u00e4 \u21ad \U0001d550" - } - } + _TESTS = [ + { + u'url': u'http://vimeo.com/56015672', + u'file': u'56015672.mp4', + u'md5': u'8879b6cc097e987f02484baf890129e5', + u'info_dict': { + u"upload_date": u"20121220", + u"description": u"This is a test case for youtube-dl.\nFor more information, see github.com/rg3/youtube-dl\nTest chars: \u2605 \" ' \u5e78 / \\ \u00e4 \u21ad \U0001d550", + u"uploader_id": u"user7108434", + u"uploader": u"Filippo Valsorda", + u"title": u"youtube-dl test video - \u2605 \" ' \u5e78 / \\ \u00e4 \u21ad \U0001d550", + }, + }, + { + u'url': u'http://vimeopro.com/openstreetmapus/state-of-the-map-us-2013/video/68093876', + u'file': u'68093876.mp4', + u'md5': u'3b5ca6aa22b60dfeeadf50b72e44ed82', + u'note': u'Vimeo Pro video (#1197)', + u'info_dict': { + u'uploader_id': u'openstreetmapus', + u'uploader': u'OpenStreetMap US', + u'title': u'Andy Allan - Putting the Carto into OpenStreetMap Cartography', + }, + }, + ] def _login(self): (username, password) = self._get_login_info() @@ -83,7 +96,9 @@ class VimeoIE(InfoExtractor): video_id = mobj.group('id') if not mobj.group('proto'): url = 'https://' + url - if mobj.group('direct_link') or mobj.group('pro'): + elif mobj.group('pro'): + url = 'http://player.vimeo.com/video/' + video_id + elif mobj.group('direct_link'): url = 'https://vimeo.com/' + video_id # Retrieve video webpage to extract further information From 668de34c6bbe48f574f23ad898fa904a7c1ad84b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Wed, 21 Aug 2013 17:06:37 +0200 Subject: [PATCH 038/122] [soundcloud] Support widget urls (fixes #1252) --- youtube_dl/extractor/soundcloud.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/youtube_dl/extractor/soundcloud.py b/youtube_dl/extractor/soundcloud.py index 7c9f1c6b6..5f3a5540d 100644 --- a/youtube_dl/extractor/soundcloud.py +++ b/youtube_dl/extractor/soundcloud.py @@ -4,6 +4,7 @@ import re from .common import InfoExtractor from ..utils import ( compat_str, + compat_urlparse, ExtractorError, unified_strdate, @@ -22,6 +23,7 @@ class SoundcloudIE(InfoExtractor): _VALID_URL = r'''^(?:https?://)? (?:(?:(?:www\.)?soundcloud\.com/([\w\d-]+)/([\w\d-]+)/?(?:[?].*)?$) |(?:api\.soundcloud\.com/tracks/(?P\d+)) + |(?Pw.soundcloud.com/player/?.*?url=.*) ) ''' IE_NAME = u'soundcloud' @@ -79,6 +81,9 @@ class SoundcloudIE(InfoExtractor): if track_id is not None: info_json_url = 'http://api.soundcloud.com/tracks/' + track_id + '.json?client_id=' + self._CLIENT_ID full_title = track_id + elif mobj.group('widget'): + query = compat_urlparse.parse_qs(compat_urlparse.urlparse(url).query) + return self.url_result(query['url'][0], ie='Soundcloud') else: # extract uploader (which is in the url) uploader = mobj.group(1) From 75340ee3838580b9ac763db57b5a2b419a286718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Wed, 21 Aug 2013 18:20:03 +0200 Subject: [PATCH 039/122] [vevo] Fix urls with a query (#1258) --- youtube_dl/extractor/vevo.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/youtube_dl/extractor/vevo.py b/youtube_dl/extractor/vevo.py index 14abd58e8..70408c4f0 100644 --- a/youtube_dl/extractor/vevo.py +++ b/youtube_dl/extractor/vevo.py @@ -11,14 +11,14 @@ class VevoIE(InfoExtractor): Accepts urls from vevo.com or in the format 'vevo:{id}' (currently used by MTVIE) """ - _VALID_URL = r'((http://www.vevo.com/watch/.*?/.*?/)|(vevo:))(?P.*)$' + _VALID_URL = r'((http://www.vevo.com/watch/.*?/.*?/)|(vevo:))(?P.*?)(\?|$)' _TEST = { u'url': u'http://www.vevo.com/watch/hurts/somebody-to-die-for/GB1101300280', u'file': u'GB1101300280.mp4', u'md5': u'06bea460acb744eab74a9d7dcb4bfd61', u'info_dict': { - u"upload_date": u"20130624", - u"uploader": u"Hurts", + u"upload_date": u"20130624", + u"uploader": u"Hurts", u"title": u"Somebody to Die For" } } From e0cfeb2ea7c2c9c597e974d13716425b0d4565c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Wed, 21 Aug 2013 18:58:25 +0200 Subject: [PATCH 040/122] [funnyordie] fix extraction of video url and title --- youtube_dl/extractor/funnyordie.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/youtube_dl/extractor/funnyordie.py b/youtube_dl/extractor/funnyordie.py index 67a7e5f76..4508f0dfa 100644 --- a/youtube_dl/extractor/funnyordie.py +++ b/youtube_dl/extractor/funnyordie.py @@ -21,17 +21,14 @@ class FunnyOrDieIE(InfoExtractor): video_id = mobj.group('id') webpage = self._download_webpage(url, video_id) - video_url = self._html_search_regex(r']*>\s*]*>\s*(?P.*?)</h1>", - r'<title>(?P<title>[^<]+?)'), webpage, 'title', flags=re.DOTALL) - info = { 'id': video_id, 'url': video_url, 'ext': 'mp4', - 'title': title, + 'title': self._og_search_title(webpage), 'description': self._og_search_description(webpage), } return [info] From 683e98a8a4adeda8339ba167baedd4a2b89dc026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Wed, 21 Aug 2013 19:20:27 +0200 Subject: [PATCH 041/122] [statigram] change test video The old one cannot be accessed. --- youtube_dl/extractor/statigram.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/youtube_dl/extractor/statigram.py b/youtube_dl/extractor/statigram.py index b8e6b3bf9..1ea4a9f2f 100644 --- a/youtube_dl/extractor/statigram.py +++ b/youtube_dl/extractor/statigram.py @@ -5,13 +5,13 @@ from .common import InfoExtractor class StatigramIE(InfoExtractor): _VALID_URL = r'(?:http://)?(?:www\.)?statigr\.am/p/([^/]+)' _TEST = { - u'url': u'http://statigr.am/p/484091715184808010_284179915', - u'file': u'484091715184808010_284179915.mp4', - u'md5': u'deda4ff333abe2e118740321e992605b', + u'url': u'http://statigr.am/p/522207370455279102_24101272', + u'file': u'522207370455279102_24101272.mp4', + u'md5': u'6eb93b882a3ded7c378ee1d6884b1814', u'info_dict': { - u"uploader_id": u"videoseconds", - u"title": u"Instagram photo by @videoseconds" - } + u'uploader_id': u'aguynamedpatrick', + u'title': u'Instagram photo by @aguynamedpatrick (Patrick Janelle)', + }, } def _real_extract(self, url): From 45ed795cb0aada683963b74bd001a872edc6b06b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Wed, 21 Aug 2013 19:25:54 +0200 Subject: [PATCH 042/122] [youtube] update uploader name for a test video: 'IconaPop' has changed to 'Icona Pop' --- youtube_dl/extractor/youtube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py index 843a973ca..4c9bab459 100644 --- a/youtube_dl/extractor/youtube.py +++ b/youtube_dl/extractor/youtube.py @@ -255,7 +255,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): u"upload_date": u"20120506", u"title": u"Icona Pop - I Love It (feat. Charli XCX) [OFFICIAL VIDEO]", u"description": u"md5:b085c9804f5ab69f4adea963a2dceb3c", - u"uploader": u"IconaPop", + u"uploader": u"Icona Pop", u"uploader_id": u"IconaPop" } }, From d81aef3adf76f67661264a773389baf8a458bf45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Wed, 21 Aug 2013 21:51:58 +0200 Subject: [PATCH 043/122] Add an extractor for tv.slashdot.org (closes #1192) It uses the ooyala platform, so it just extracts the ooyala url. --- youtube_dl/extractor/__init__.py | 1 + youtube_dl/extractor/slashdot.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 youtube_dl/extractor/slashdot.py diff --git a/youtube_dl/extractor/__init__.py b/youtube_dl/extractor/__init__.py index 5bb44e764..d836a22b5 100644 --- a/youtube_dl/extractor/__init__.py +++ b/youtube_dl/extractor/__init__.py @@ -58,6 +58,7 @@ from .ringtv import RingTVIE from .roxwel import RoxwelIE from .rtlnow import RTLnowIE from .sina import SinaIE +from .slashdot import SlashdotIE from .soundcloud import SoundcloudIE, SoundcloudSetIE from .spiegel import SpiegelIE from .stanfordoc import StanfordOpenClassroomIE diff --git a/youtube_dl/extractor/slashdot.py b/youtube_dl/extractor/slashdot.py new file mode 100644 index 000000000..2cba53076 --- /dev/null +++ b/youtube_dl/extractor/slashdot.py @@ -0,0 +1,23 @@ +import re + +from .common import InfoExtractor + + +class SlashdotIE(InfoExtractor): + _VALID_URL = r'https?://tv.slashdot.org/video/\?embed=(?P.*?)(&|$)' + + _TEST = { + u'url': u'http://tv.slashdot.org/video/?embed=JscHMzZDplD0p-yNLOzTfzC3Q3xzJaUz', + u'file': u'JscHMzZDplD0p-yNLOzTfzC3Q3xzJaUz.mp4', + u'md5': u'd2222e7a4a4c1541b3e0cf732fb26735', + u'info_dict': { + u'title': u' Meet the Stampede Supercomputing Cluster\'s Administrator', + }, + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + video_id = mobj.group('id') + webpage = self._download_webpage(url, video_id) + ooyala_url = self._search_regex(r'', u'', playlist_snippet) + playlist_html = u'' + playlist_cleaned + u'' + + size_cache = {} + + doc = xml.etree.ElementTree.fromstring(playlist_html) + playlist = [] + for li in doc.findall('./div/ul/li'): + title = li.find('.//h3').text + video_id = movie + '-' + re.sub(r'[^a-zA-Z0-9]', '', title).lower() + thumbnail = li.find('.//img').attrib['src'] + + date_el = li.find('.//p') + upload_date = None + m = re.search(r':\s?(?P[0-9]{2})/(?P[0-9]{2})/(?P[0-9]{2})', date_el.text) + if m: + upload_date = u'20' + m.group('year') + m.group('month') + m.group('day') + runtime_el = date_el.find('./br') + m = re.search(r':\s?(?P[0-9]+):(?P[0-9]{1,2})', runtime_el.tail) + duration = None + if m: + duration = 60 * int(m.group('minutes')) + int(m.group('seconds')) + + formats = [] + for formats_el in li.findall('.//li/a'): + if formats_el.attrib['class'] != 'OverlayPanel': + continue + target = formats_el.attrib['target'] + + format_code = formats_el.text + if 'Automatic' in format_code: + continue + + size_q = formats_el.attrib['href'] + size_id = size_q.rpartition('#videos-')[2] + if size_id not in size_cache: + size_url = url + size_q + sizepage_html = self._download_webpage( + size_url, movie, + note=u'Downloading size info %s' % size_id, + errnote=u'Error while downloading size info %s' % size_id, + ) + _doc = xml.etree.ElementTree.fromstring(sizepage_html) + size_cache[size_id] = _doc + + sizepage_doc = size_cache[size_id] + links = sizepage_doc.findall('.//{http://www.w3.org/1999/xhtml}ul/{http://www.w3.org/1999/xhtml}li/{http://www.w3.org/1999/xhtml}a') + for vid_a in links: + href = vid_a.get('href') + if not href.endswith(target): + continue + detail_q = href.partition('#')[0] + detail_url = url + '/' + detail_q + + m = re.match(r'includes/(?P[^/]+)/', detail_q) + detail_id = m.group('detail_id') + + detail_html = self._download_webpage( + detail_url, movie, + note=u'Downloading detail %s %s' % (detail_id, size_id), + errnote=u'Error while downloading detail %s %s' % (detail_id, size_id) + ) + detail_doc = xml.etree.ElementTree.fromstring(detail_html) + movie_link_el = detail_doc.find('.//{http://www.w3.org/1999/xhtml}a') + assert movie_link_el.get('class') == 'movieLink' + movie_link = movie_link_el.get('href').partition('?')[0].replace('_', '_h') + ext = determine_ext(movie_link) + assert ext == 'mov' + + formats.append({ + 'format': format_code, + 'ext': ext, + 'url': movie_link, + }) + + info = { + '_type': 'video', + 'id': video_id, + 'title': title, + 'formats': formats, + 'title': title, + 'duration': duration, + 'thumbnail': thumbnail, + 'upload_date': upload_date, + 'uploader_id': uploader_id, + 'user_agent': 'QuickTime compatible (youtube-dl)', + } + # TODO: Remove when #980 has been merged + info['url'] = formats[-1]['url'] + info['ext'] = formats[-1]['ext'] + + playlist.append(info) + + return { + '_type': 'playlist', + 'id': movie, + 'entries': playlist, + } From 0e283428f777a23de3c5a522aa283f87cda1b40a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Wed, 28 Aug 2013 10:18:39 +0200 Subject: [PATCH 091/122] HTTPError is in urllib.error in Python 3, not in http.error --- youtube_dl/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtube_dl/utils.py b/youtube_dl/utils.py index f78b5fe78..e6fa634a7 100644 --- a/youtube_dl/utils.py +++ b/youtube_dl/utils.py @@ -61,7 +61,7 @@ except ImportError: # Python 2 import httplib as compat_http_client try: - from http.error import HTTPError as compat_HTTPError + from urllib.error import HTTPError as compat_HTTPError except ImportError: # Python 2 from urllib2 import HTTPError as compat_HTTPError From a1bb0f8773e0fff787ffe7bd1729073f3385d2ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Wed, 28 Aug 2013 10:20:37 +0200 Subject: [PATCH 092/122] [cnn] remove debug print call. --- youtube_dl/extractor/cnn.py | 1 - 1 file changed, 1 deletion(-) diff --git a/youtube_dl/extractor/cnn.py b/youtube_dl/extractor/cnn.py index 4338bd180..a79f881cd 100644 --- a/youtube_dl/extractor/cnn.py +++ b/youtube_dl/extractor/cnn.py @@ -33,7 +33,6 @@ class CNNIE(InfoExtractor): path = mobj.group('path') page_title = mobj.group('title') info_url = u'http://cnn.com/video/data/3.0/%s/index.xml' % path - print(info_url) info_xml = self._download_webpage(info_url, page_title) info = xml.etree.ElementTree.fromstring(info_xml.encode('utf-8')) From 3e223834d9f358bc7cb1c3748dc63d1ab40d9b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Wed, 28 Aug 2013 10:26:44 +0200 Subject: [PATCH 093/122] [youtube] update algo for length 88, thanks to @Ramhack (fixes #1328) --- devscripts/youtube_genalgo.py | 4 ++-- youtube_dl/extractor/youtube.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/devscripts/youtube_genalgo.py b/devscripts/youtube_genalgo.py index 6f1d6ef99..917e8f79d 100644 --- a/devscripts/youtube_genalgo.py +++ b/devscripts/youtube_genalgo.py @@ -14,9 +14,9 @@ tests = [ # 89 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[]}|:;?/>.<'", "/?;:|}<[{=+-_)(*&^%$#@!MqBVCXZASDFGHJKLPOIUYTREWQ0987654321mnbvcxzasdfghjklpoiuyt"), - # 88 + # 88 - vflapUV9V 2013/08/28 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[]}|:;?/>.<", - "J:|}][{=+-_)(*&;%$#@>MNBVCXZASDFGH^KLPOIUYTREWQ0987654321mnbvcxzasdfghrklpoiuytej"), + "ioplkjhgfdsazxcvbnm12<4567890QWERTYUIOZLKJHGFDSAeXCVBNM!@#$%^&*()_-+={[]}|:;?/>.3"), # 87 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$^&*()_-+={[]}|:;?/>.<", "uioplkjhgfdsazxcvbnm1t34567890QWE2TYUIOPLKJHGFDSAZXCVeNM!@#$^&*()_-+={[]}|:;?/>.<"), diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py index af01c9da0..8e486afd0 100644 --- a/youtube_dl/extractor/youtube.py +++ b/youtube_dl/extractor/youtube.py @@ -419,7 +419,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): elif len(s) == 89: return s[84:78:-1] + s[87] + s[77:60:-1] + s[0] + s[59:3:-1] elif len(s) == 88: - return s[48] + s[81:67:-1] + s[82] + s[66:62:-1] + s[85] + s[61:48:-1] + s[67] + s[47:12:-1] + s[3] + s[11:3:-1] + s[2] + s[12] + return s[7:28] + s[87] + s[29:45] + s[55] + s[46:55] + s[2] + s[56:87] + s[28] elif len(s) == 87: return s[6:27] + s[4] + s[28:39] + s[27] + s[40:59] + s[2] + s[60:] elif len(s) == 86: From 4f5f18acb93ea2bf70f80c7f76e6bb6b8dee3fbf Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Wed, 28 Aug 2013 10:28:16 +0200 Subject: [PATCH 094/122] [addanime] add file --- youtube_dl/extractor/addanime.py | 76 ++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 youtube_dl/extractor/addanime.py diff --git a/youtube_dl/extractor/addanime.py b/youtube_dl/extractor/addanime.py new file mode 100644 index 000000000..46db8262f --- /dev/null +++ b/youtube_dl/extractor/addanime.py @@ -0,0 +1,76 @@ +import ast +import re + +from .common import InfoExtractor +from ..utils import ( + compat_HTTPError, + compat_str, + compat_urllib_parse, + compat_urllib_parse_urlparse, + + ExtractorError, +) + + +class AddAnimeIE(InfoExtractor): + + _VALID_URL = r'^http://(?:\w+\.)?add-anime\.net/watch_video.php\?(?:.*?)v=(?P[\w_]+)(?:.*)' + IE_NAME = u'AddAnime' + _TEST = { + u'url': u'http://www.add-anime.net/watch_video.php?v=24MR3YO5SAS9', + u'file': u'24MR3YO5SAS9.flv', + u'md5': u'1036a0e0cd307b95bd8a8c3a5c8cfaf1', + u'info_dict': { + u"description": u"One Piece 606", + u"title": u"One Piece 606" + } + } + + def _real_extract(self, url): + try: + mobj = re.match(self._VALID_URL, url) + video_id = mobj.group('video_id') + webpage = self._download_webpage(url, video_id) + except ExtractorError as ee: + if not isinstance(ee.cause, compat_HTTPError): + raise + + redir_webpage = ee.cause.read().decode('utf-8') + action = self._search_regex( + r'

', + redir_webpage, u'redirect vc value') + av = re.search( + r'a\.value = ([0-9]+)[+]([0-9]+)[*]([0-9]+);', + redir_webpage) + if av is None: + raise ExtractorError(u'Cannot find redirect math task') + av_res = int(av.group(1)) + int(av.group(2)) * int(av.group(3)) + + parsed_url = compat_urllib_parse_urlparse(url) + av_val = av_res + len(parsed_url.netloc) + confirm_url = ( + parsed_url.scheme + u'://' + parsed_url.netloc + + action + '?' + + compat_urllib_parse.urlencode({ + 'jschl_vc': vc, 'jschl_answer': compat_str(av_val)})) + self._download_webpage( + confirm_url, video_id, + note=u'Confirming after redirect') + webpage = self._download_webpage(url, video_id) + + video_url = self._search_regex(r"var normal_video_file = '(.*?)';", + webpage, u'video file URL') + video_title = self._og_search_title(webpage) + video_description = self._og_search_description(webpage) + + return { + '_type': 'video', + 'id': video_id, + 'url': video_url, + 'ext': 'flv', + 'title': video_title, + 'description': video_description + } From af8bd6a82d140e5a776185707a9b21d5b8a9fe52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Wed, 28 Aug 2013 10:55:31 +0200 Subject: [PATCH 095/122] Show the time taken to download in the same format as the ETA --- youtube_dl/FileDownloader.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/youtube_dl/FileDownloader.py b/youtube_dl/FileDownloader.py index 4f6a23835..7c5ac4bc2 100644 --- a/youtube_dl/FileDownloader.py +++ b/youtube_dl/FileDownloader.py @@ -63,6 +63,17 @@ class FileDownloader(object): converted = float(bytes) / float(1024 ** exponent) return '%.2f%s' % (converted, suffix) + @staticmethod + def format_seconds(seconds): + (mins, secs) = divmod(seconds, 60) + (hours, eta_mins) = divmod(mins, 60) + if hours > 99: + return '--:--:--' + if hours == 0: + return '%02d:%02d' % (mins, secs) + else: + return '%02d:%02d:%02d' % (hours, mins, secs) + @staticmethod def calc_percent(byte_counter, data_len): if data_len is None: @@ -78,14 +89,7 @@ class FileDownloader(object): return '--:--' rate = float(current) / dif eta = int((float(total) - float(current)) / rate) - (eta_mins, eta_secs) = divmod(eta, 60) - (eta_hours, eta_mins) = divmod(eta_mins, 60) - if eta_hours > 99: - return '--:--:--' - if eta_hours == 0: - return '%02d:%02d' % (eta_mins, eta_secs) - else: - return '%02d:%02d:%02d' % (eta_hours, eta_mins, eta_secs) + return FileDownloader.format_seconds(eta) @staticmethod def calc_speed(start, now, bytes): @@ -240,8 +244,8 @@ class FileDownloader(object): self.to_screen(u'[download] Download completed') else: clear_line = (u'\x1b[K' if sys.stderr.isatty() and os.name != 'nt' else u'') - self.to_screen(u'\r%s[download] 100%% of %s in %ss' % - (clear_line, data_len_str, int(tot_time))) + self.to_screen(u'\r%s[download] 100%% of %s in %s' % + (clear_line, data_len_str, self.format_seconds(tot_time))) def _download_with_rtmpdump(self, filename, url, player_url, page_url, play_path, tc_url): self.report_destination(filename) From aa3e950764337ef9800c936f4de89b31c00dfcf5 Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Wed, 28 Aug 2013 11:57:13 +0200 Subject: [PATCH 096/122] Tolerate junk at the end of gzip-compressed content (#1268) --- youtube_dl/utils.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/youtube_dl/utils.py b/youtube_dl/utils.py index e6fa634a7..be788cf5a 100644 --- a/youtube_dl/utils.py +++ b/youtube_dl/utils.py @@ -628,8 +628,23 @@ class YoutubeDLHandler(compat_urllib_request.HTTPHandler): old_resp = resp # gzip if resp.headers.get('Content-encoding', '') == 'gzip': - gz = gzip.GzipFile(fileobj=io.BytesIO(resp.read()), mode='r') - resp = self.addinfourl_wrapper(gz, old_resp.headers, old_resp.url, old_resp.code) + content = resp.read() + gz = gzip.GzipFile(fileobj=io.BytesIO(content), mode='rb') + try: + uncompressed = io.BytesIO(gz.read()) + except IOError as original_ioerror: + # There may be junk add the end of the file + # See http://stackoverflow.com/q/4928560/35070 for details + for i in range(1, 1024): + try: + gz = gzip.GzipFile(fileobj=io.BytesIO(content[:-i]), mode='rb') + uncompressed = io.BytesIO(gz.read()) + except IOError: + continue + break + else: + raise original_ioerror + resp = self.addinfourl_wrapper(uncompressed, old_resp.headers, old_resp.url, old_resp.code) resp.msg = old_resp.msg # deflate if resp.headers.get('Content-encoding', '') == 'deflate': From ae3531adf926998d42d1fb52453491c85e33b5f0 Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Wed, 28 Aug 2013 12:04:44 +0200 Subject: [PATCH 097/122] [generic] Fix URL concatenation When the url is something like http://example.org/foo/bar?x=y and the added is file/video.mp4 , we want http://example.org/foo/file/video.mp4 Fixes #1268. --- youtube_dl/extractor/generic.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/youtube_dl/extractor/generic.py b/youtube_dl/extractor/generic.py index d034a11bb..bfc9bff49 100644 --- a/youtube_dl/extractor/generic.py +++ b/youtube_dl/extractor/generic.py @@ -166,7 +166,12 @@ class GenericIE(InfoExtractor): if video_url.startswith('//'): video_url = compat_urllib_parse_urlparse(url).scheme + ':' + video_url if '://' not in video_url: - video_url = url + ('' if url.endswith('/') else '/') + video_url + up = compat_urllib_parse_urlparse(url) + if video_url.startswith('/'): + video_url = up.scheme + '://' + up.netloc + video_url + else: # relative path + video_url = (up.scheme + '://' + up.netloc + + up.path.rpartition('/')[0] + '/' + video_url) video_id = os.path.basename(video_url) # here's a fun little line of code for you: From edde6c56ac20af57d7fd494810834125bbd3728d Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Wed, 28 Aug 2013 12:14:45 +0200 Subject: [PATCH 098/122] Print playpath with --get-url (Fixes #1334) --- youtube_dl/YoutubeDL.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py index 3fc4ec378..d5f7c81eb 100644 --- a/youtube_dl/YoutubeDL.py +++ b/youtube_dl/YoutubeDL.py @@ -448,7 +448,8 @@ class YoutubeDL(object): if self.params.get('forceid', False): compat_print(info_dict['id']) if self.params.get('forceurl', False): - compat_print(info_dict['url']) + # For RTMP URLs, also include the playpath + compat_print(info_dict['url'] + info_dict.get('play_path', u'')) if self.params.get('forcethumbnail', False) and 'thumbnail' in info_dict: compat_print(info_dict['thumbnail']) if self.params.get('forcedescription', False) and 'description' in info_dict: From a5caba1eb02665cdc982d6be4a933aafd79243de Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Wed, 28 Aug 2013 12:47:27 +0200 Subject: [PATCH 099/122] [generic] simply use urljoin --- youtube_dl/extractor/generic.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/youtube_dl/extractor/generic.py b/youtube_dl/extractor/generic.py index bfc9bff49..dc4dea4ad 100644 --- a/youtube_dl/extractor/generic.py +++ b/youtube_dl/extractor/generic.py @@ -7,8 +7,8 @@ from .common import InfoExtractor from ..utils import ( compat_urllib_error, compat_urllib_parse, - compat_urllib_parse_urlparse, compat_urllib_request, + compat_urlparse, ExtractorError, ) @@ -163,15 +163,7 @@ class GenericIE(InfoExtractor): raise ExtractorError(u'Invalid URL: %s' % url) video_url = compat_urllib_parse.unquote(mobj.group(1)) - if video_url.startswith('//'): - video_url = compat_urllib_parse_urlparse(url).scheme + ':' + video_url - if '://' not in video_url: - up = compat_urllib_parse_urlparse(url) - if video_url.startswith('/'): - video_url = up.scheme + '://' + up.netloc + video_url - else: # relative path - video_url = (up.scheme + '://' + up.netloc + - up.path.rpartition('/')[0] + '/' + video_url) + video_url = compat_urlparse.urljoin(url, video_url) video_id = os.path.basename(video_url) # here's a fun little line of code for you: From ce6a696e4d964aeb27de46a31a899b28d7ca7754 Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Wed, 28 Aug 2013 12:47:38 +0200 Subject: [PATCH 100/122] Remove unused imports --- youtube_dl/extractor/addanime.py | 1 - youtube_dl/extractor/appletrailers.py | 1 - youtube_dl/extractor/trilulilu.py | 3 --- youtube_dl/extractor/wat.py | 1 - 4 files changed, 6 deletions(-) diff --git a/youtube_dl/extractor/addanime.py b/youtube_dl/extractor/addanime.py index 46db8262f..82a785a19 100644 --- a/youtube_dl/extractor/addanime.py +++ b/youtube_dl/extractor/addanime.py @@ -1,4 +1,3 @@ -import ast import re from .common import InfoExtractor diff --git a/youtube_dl/extractor/appletrailers.py b/youtube_dl/extractor/appletrailers.py index b3bdb2955..8b191c196 100644 --- a/youtube_dl/extractor/appletrailers.py +++ b/youtube_dl/extractor/appletrailers.py @@ -4,7 +4,6 @@ import xml.etree.ElementTree from .common import InfoExtractor from ..utils import ( determine_ext, - ExtractorError, ) diff --git a/youtube_dl/extractor/trilulilu.py b/youtube_dl/extractor/trilulilu.py index 1c46156c7..f278951ba 100644 --- a/youtube_dl/extractor/trilulilu.py +++ b/youtube_dl/extractor/trilulilu.py @@ -3,9 +3,6 @@ import re import xml.etree.ElementTree from .common import InfoExtractor -from ..utils import ( - ExtractorError, -) class TriluliluIE(InfoExtractor): diff --git a/youtube_dl/extractor/wat.py b/youtube_dl/extractor/wat.py index 7d228edac..29c25f0e3 100644 --- a/youtube_dl/extractor/wat.py +++ b/youtube_dl/extractor/wat.py @@ -6,7 +6,6 @@ import re from .common import InfoExtractor from ..utils import ( - compat_urllib_parse, unified_strdate, ) From 67b22dd03686d9e360d87a7751de74b321d3f231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Wed, 28 Aug 2013 12:51:22 +0200 Subject: [PATCH 101/122] Add extractors for video.mit.edu and techtv.mit.edu (closes #1327) video.mit.edu just embeds the videos from techtv.mit.edu --- youtube_dl/extractor/__init__.py | 1 + youtube_dl/extractor/mit.py | 76 ++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 youtube_dl/extractor/mit.py diff --git a/youtube_dl/extractor/__init__.py b/youtube_dl/extractor/__init__.py index c76b99a81..21e9e5d37 100644 --- a/youtube_dl/extractor/__init__.py +++ b/youtube_dl/extractor/__init__.py @@ -50,6 +50,7 @@ from .keek import KeekIE from .liveleak import LiveLeakIE from .livestream import LivestreamIE from .metacafe import MetacafeIE +from .mit import TechTVMITIE, MITIE from .mixcloud import MixcloudIE from .mtv import MTVIE from .muzu import MuzuTVIE diff --git a/youtube_dl/extractor/mit.py b/youtube_dl/extractor/mit.py new file mode 100644 index 000000000..d09d03e36 --- /dev/null +++ b/youtube_dl/extractor/mit.py @@ -0,0 +1,76 @@ +import re +import json + +from .common import InfoExtractor +from ..utils import ( + clean_html, + get_element_by_id, +) + + +class TechTVMITIE(InfoExtractor): + IE_NAME = u'techtv.mit.edu' + _VALID_URL = r'https?://techtv\.mit\.edu/(videos|embeds)/(?P\d+)' + + _TEST = { + u'url': u'http://techtv.mit.edu/videos/25418-mit-dna-learning-center-set', + u'file': u'25418.mp4', + u'md5': u'1f8cb3e170d41fd74add04d3c9330e5f', + u'info_dict': { + u'title': u'MIT DNA Learning Center Set', + u'description': u'md5:82313335e8a8a3f243351ba55bc1b474', + }, + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + video_id = mobj.group('id') + webpage = self._download_webpage( + 'http://techtv.mit.edu/videos/%s' % video_id, video_id) + embed_page = self._download_webpage( + 'http://techtv.mit.edu/embeds/%s/' % video_id, video_id, + note=u'Downloading embed page') + + base_url = self._search_regex(r'ipadUrl: \'(.+?cloudfront.net/)', + embed_page, u'base url') + formats_json = self._search_regex(r'bitrates: (\[.+?\])', embed_page, + u'video formats') + formats = json.loads(formats_json) + formats = sorted(formats, key=lambda f: f['bitrate']) + + title = get_element_by_id('edit-title', webpage) + description = clean_html(get_element_by_id('edit-description', webpage)) + thumbnail = self._search_regex(r'playlist:.*?url: \'(.+?)\'', + embed_page, u'thumbnail', flags=re.DOTALL) + + return {'id': video_id, + 'title': title, + 'url': base_url + formats[-1]['url'].replace('mp4:', ''), + 'ext': 'mp4', + 'description': description, + 'thumbnail': thumbnail, + } + + +class MITIE(TechTVMITIE): + IE_NAME = u'video.mit.edu' + _VALID_URL = r'https?://video\.mit\.edu/watch/(?P[^/]+)' + + _TEST = { + u'url': u'http://video.mit.edu/watch/the-government-is-profiling-you-13222/', + u'file': u'21783.mp4', + u'md5': u'7db01d5ccc1895fc5010e9c9e13648da', + u'info_dict': { + u'title': u'The Government is Profiling You', + u'description': u'md5:ad5795fe1e1623b73620dbfd47df9afd', + }, + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + page_title = mobj.group('title') + webpage = self._download_webpage(url, page_title) + self.to_screen('%s: Extracting %s url' % (page_title, TechTVMITIE.IE_NAME)) + embed_url = self._search_regex(r'<iframe .*?src="(.+?)"', webpage, + u'embed url') + return self.url_result(embed_url, ie='TechTVMIT') From c496ca96e7639e5dd0020074b7ada18c2bd4ae3e Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister <phihag@phihag.de> Date: Wed, 28 Aug 2013 12:57:10 +0200 Subject: [PATCH 102/122] Fix platform name in Python 2 with --verbose (Closes #1228) --- youtube_dl/__init__.py | 3 ++- youtube_dl/utils.py | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py index bc6a6d180..b33a18a26 100644 --- a/youtube_dl/__init__.py +++ b/youtube_dl/__init__.py @@ -45,6 +45,7 @@ import sys import warnings import platform + from .utils import * from .update import update_self from .version import __version__ @@ -611,7 +612,7 @@ def _real_main(argv=None): sys.exc_clear() except: pass - sys.stderr.write(u'[debug] Python version %s - %s' %(platform.python_version(), platform.platform()) + u'\n') + sys.stderr.write(u'[debug] Python version %s - %s' %(platform.python_version(), platform_name()) + u'\n') sys.stderr.write(u'[debug] Proxy map: ' + str(proxy_handler.proxies) + u'\n') ydl.add_default_info_extractors() diff --git a/youtube_dl/utils.py b/youtube_dl/utils.py index be788cf5a..64ab30910 100644 --- a/youtube_dl/utils.py +++ b/youtube_dl/utils.py @@ -1,19 +1,20 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import datetime +import email.utils import errno import gzip import io import json import locale import os +import platform import re +import socket import sys import traceback import zlib -import email.utils -import socket -import datetime try: import urllib.request as compat_urllib_request @@ -732,3 +733,13 @@ class DateRange(object): return self.start <= date <= self.end def __str__(self): return '%s - %s' % ( self.start.isoformat(), self.end.isoformat()) + + +def platform_name(): + """ Returns the platform name as a compat_str """ + res = platform.platform() + if isinstance(res, bytes): + res = res.decode(preferredencoding()) + + assert isinstance(res, compat_str) + return res From 8ae97d76eee1bf9e9098797db3be2d7b816196b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= <jaime.marquinez.ferrandiz@gmail.com> Date: Wed, 28 Aug 2013 13:37:31 +0200 Subject: [PATCH 103/122] PostProcessingError holds the message in the 'msg' property, not in 'message' (fixes #1323) Causes DeprecationWarning: http://www.python.org/dev/peps/pep-0352/ --- youtube_dl/PostProcessor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/youtube_dl/PostProcessor.py b/youtube_dl/PostProcessor.py index c02ed7148..ae56d2082 100644 --- a/youtube_dl/PostProcessor.py +++ b/youtube_dl/PostProcessor.py @@ -137,7 +137,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor): try: FFmpegPostProcessor.run_ffmpeg(self, path, out_path, opts) except FFmpegPostProcessorError as err: - raise AudioConversionError(err.message) + raise AudioConversionError(err.msg) def run(self, information): path = information['filepath'] @@ -207,7 +207,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor): except: etype,e,tb = sys.exc_info() if isinstance(e, AudioConversionError): - msg = u'audio conversion failed: ' + e.message + msg = u'audio conversion failed: ' + e.msg else: msg = u'error running ' + (self._exes['avconv'] and 'avconv' or 'ffmpeg') raise PostProcessingError(msg) From f143d86ad2fc0633d8e2da598cf21e73ff0f2872 Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister <phihag@phihag.de> Date: Wed, 28 Aug 2013 13:59:08 +0200 Subject: [PATCH 104/122] [sohu] Handle encoding, and fix tests --- youtube_dl/extractor/common.py | 9 ++- youtube_dl/extractor/sohu.py | 133 ++++++++++++++++----------------- 2 files changed, 72 insertions(+), 70 deletions(-) diff --git a/youtube_dl/extractor/common.py b/youtube_dl/extractor/common.py index 77a13aea5..a2986cebe 100644 --- a/youtube_dl/extractor/common.py +++ b/youtube_dl/extractor/common.py @@ -145,12 +145,17 @@ class InfoExtractor(object): urlh = self._request_webpage(url_or_request, video_id, note, errnote) content_type = urlh.headers.get('Content-Type', '') + webpage_bytes = urlh.read() m = re.match(r'[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+\s*;\s*charset=(.+)', content_type) if m: encoding = m.group(1) else: - encoding = 'utf-8' - webpage_bytes = urlh.read() + m = re.search(br'<meta[^>]+charset="?([^"]+)[ /">]', + webpage_bytes[:1024]) + if m: + encoding = m.group(1).decode('ascii') + else: + encoding = 'utf-8' if self._downloader.params.get('dump_intermediate_pages', False): try: url = url_or_request.get_full_url() diff --git a/youtube_dl/extractor/sohu.py b/youtube_dl/extractor/sohu.py index 24fc3a5d7..77bb0a8dc 100644 --- a/youtube_dl/extractor/sohu.py +++ b/youtube_dl/extractor/sohu.py @@ -1,13 +1,10 @@ # encoding: utf-8 -import re import json -import time -import logging -import urllib2 +import re from .common import InfoExtractor -from ..utils import compat_urllib_request, clean_html +from ..utils import ExtractorError class SohuIE(InfoExtractor): @@ -15,79 +12,79 @@ class SohuIE(InfoExtractor): _TEST = { u'url': u'http://tv.sohu.com/20130724/n382479172.shtml#super', - u'file': u'382479172.flv', - u'md5': u'cc84eed6b6fbf0f2f9a8d3cb9da1939b', + u'file': u'382479172.mp4', + u'md5': u'bde8d9a6ffd82c63a1eefaef4eeefec7', u'info_dict': { - u'title': u'The Illest - Far East Movement Riff Raff', + u'title': u'MV:Far East Movement《The Illest》', }, } - def _real_extract(self, url): + + def _fetch_data(vid_id): + base_data_url = u'http://hot.vrs.sohu.com/vrs_flash.action?vid=' + data_url = base_data_url + str(vid_id) + data_json = self._download_webpage( + data_url, video_id, + note=u'Downloading JSON data for ' + str(vid_id)) + return json.loads(data_json) + mobj = re.match(self._VALID_URL, url) video_id = mobj.group('id') - webpage = self._download_webpage(url, video_id) - pattern = r'<title>(.+?)' - compiled = re.compile(pattern, re.DOTALL) - title = self._search_regex(compiled, webpage, u'video title') - title = clean_html(title).split('-')[0].strip() - self.to_screen('Title: %s' % title) - pattern = re.compile(r'var vid="(\d+)"') - result = re.search(pattern, webpage) - if not result: - logging.info('[Sohu] could not get vid') - return None - vid = result.group(1) - logging.info('vid: %s' % vid) - base_url_1 = 'http://hot.vrs.sohu.com/vrs_flash.action?vid=' - url_1 = base_url_1 + vid - logging.info('json url: %s' % url_1) - webpage = self._download_webpage(url_1, vid) - json_1 = json.loads(webpage) - # get the highest definition video vid and json infomation. - vids = [] - qualities = ('oriVid', 'superVid', 'highVid', 'norVid') - for vid_name in qualities: - vids.append(json_1['data'][vid_name]) - clearest_vid = 0 - for i, v in enumerate(vids): - if v != 0: - clearest_vid = v - logging.info('quality definition: %s' % qualities[i][:-3]) - break - if not clearest_vid: - logging.warning('could not find valid clearest_vid') - return None - if vid != clearest_vid: - url_1 = '%s%d' % (base_url_1, clearest_vid) - logging.info('highest definition json url: %s' % url_1) - json_1 = json.loads(urllib2.urlopen(url_1).read()) - allot = json_1['allot'] - prot = json_1['prot'] - clipsURL = json_1['data']['clipsURL'] - su = json_1['data']['su'] - num_of_parts = json_1['data']['totalBlocks'] - logging.info('Total parts: %d' % num_of_parts) - base_url_3 = 'http://allot/?prot=prot&file=clipsURL[i]&new=su[i]' - files_info = [] - for i in range(num_of_parts): - self.to_screen('Geting json infomation of part %s/%s' % (i + 1, num_of_parts)) - middle_url = 'http://%s/?prot=%s&file=%s&new=%s' % (allot, prot, clipsURL[i], su[i]) - logging.info('middle url part %d: %s' % (i, middle_url)) - middle_info = urllib2.urlopen(middle_url).read().split('|') - middle_part_1 = middle_info[0] - download_url = '%s%s?key=%s' % (middle_info[0], su[i], middle_info[3]) - info = { + webpage = self._download_webpage(url, video_id) + raw_title = self._html_search_regex(r'(?s)(.+?)', + webpage, u'video title') + title = raw_title.partition('-')[0].strip() + + vid = self._html_search_regex(r'var vid="(\d+)"', webpage, + u'video path') + data = _fetch_data(vid) + + QUALITIES = ('ori', 'super', 'high', 'nor') + vid_ids = [data['data'][q + 'Vid'] + for q in QUALITIES + if data['data'][q + 'Vid'] != 0] + if not vid_ids: + raise ExtractorError(u'No formats available for this video') + + # For now, we just pick the highest available quality + vid_id = vid_ids[-1] + + format_data = data if vid == vid_id else _fetch_data(vid_id) + part_count = format_data['data']['totalBlocks'] + allot = format_data['allot'] + prot = format_data['prot'] + clipsURL = format_data['data']['clipsURL'] + su = format_data['data']['su'] + + playlist = [] + for i in range(part_count): + part_url = ('http://%s/?prot=%s&file=%s&new=%s' % + (allot, prot, clipsURL[i], su[i])) + part_str = self._download_webpage( + part_url, video_id, + note=u'Downloading part %d of %d' % (i+1, part_count)) + + part_info = part_str.split('|') + video_url = '%s%s?key=%s' % (part_info[0], su[i], part_info[3]) + + video_info = { 'id': '%s_part%02d' % (video_id, i + 1), 'title': title, - 'url': download_url, + 'url': video_url, 'ext': 'mp4', } - files_info.append(info) - time.sleep(1) - if num_of_parts == 1: - info = files_info[0] + playlist.append(video_info) + + if len(playlist) == 1: + info = playlist[0] info['id'] = video_id - return info - return files_info + else: + info = { + '_type': 'playlist', + 'entries': playlist, + 'id': video_id, + } + + return info From 48ea9cea77e7ea24ee867027f03ca37dd1b935d8 Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Wed, 28 Aug 2013 14:28:55 +0200 Subject: [PATCH 105/122] Allow changes to run under Python 3 --- youtube_dl/aes.py | 18 ++++++++++-------- youtube_dl/extractor/youporn.py | 12 ++++++++---- youtube_dl/utils.py | 10 ++++++++++ 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/youtube_dl/aes.py b/youtube_dl/aes.py index 2fa9238e3..278f8bb82 100644 --- a/youtube_dl/aes.py +++ b/youtube_dl/aes.py @@ -3,6 +3,8 @@ __all__ = ['aes_encrypt', 'key_expansion', 'aes_ctr_decrypt', 'aes_decrypt_text' import base64 from math import ceil +from .utils import bytes_to_intlist + BLOCK_SIZE_BYTES = 16 def aes_ctr_decrypt(data, key, counter): @@ -16,7 +18,7 @@ def aes_ctr_decrypt(data, key, counter): @returns {int[]} decrypted data """ expanded_key = key_expansion(key) - block_count = int(ceil(float(len(data)) / BLOCK_SIZE_BYTES)) + block_count = int(ceil(float(len(data)) // BLOCK_SIZE_BYTES)) decrypted_data=[] for i in range(block_count): @@ -40,7 +42,7 @@ def key_expansion(data): data = data[:] # copy rcon_iteration = 1 key_size_bytes = len(data) - expanded_key_size_bytes = (key_size_bytes/4 + 7) * BLOCK_SIZE_BYTES + expanded_key_size_bytes = (key_size_bytes // 4 + 7) * BLOCK_SIZE_BYTES while len(data) < expanded_key_size_bytes: temp = data[-4:] @@ -72,7 +74,7 @@ def aes_encrypt(data, expanded_key): @param {int[]} expanded_key 176/208/240-Byte expanded key @returns {int[]} 16-Byte cipher """ - rounds = len(expanded_key) / BLOCK_SIZE_BYTES - 1 + rounds = len(expanded_key) // BLOCK_SIZE_BYTES - 1 data = xor(data, expanded_key[:BLOCK_SIZE_BYTES]) for i in range(1, rounds+1): @@ -99,11 +101,11 @@ def aes_decrypt_text(data, password, key_size_bytes): """ NONCE_LENGTH_BYTES = 8 - data = map(lambda c: ord(c), base64.b64decode(data)) - password = map(lambda c: ord(c), password.encode('utf-8')) + data = bytes_to_intlist(base64.b64decode(data)) + password = bytes_to_intlist(password.encode('utf-8')) key = password[:key_size_bytes] + [0]*(key_size_bytes - len(password)) - key = aes_encrypt(key[:BLOCK_SIZE_BYTES], key_expansion(key)) * (key_size_bytes / BLOCK_SIZE_BYTES) + key = aes_encrypt(key[:BLOCK_SIZE_BYTES], key_expansion(key)) * (key_size_bytes // BLOCK_SIZE_BYTES) nonce = data[:NONCE_LENGTH_BYTES] cipher = data[NONCE_LENGTH_BYTES:] @@ -143,7 +145,7 @@ MIX_COLUMN_MATRIX = ((2,3,1,1), (3,1,1,2)) def sub_bytes(data): - return map(lambda x: SBOX[x], data) + return [SBOX[x] for x in data] def rotate(data): return data[1:] + [data[0]] @@ -156,7 +158,7 @@ def key_schedule_core(data, rcon_iteration): return data def xor(data1, data2): - return map(lambda (x,y): x^y, zip(data1, data2)) + return [x^y for x, y in zip(data1, data2)] def mix_column(data): data_mixed = [] diff --git a/youtube_dl/extractor/youporn.py b/youtube_dl/extractor/youporn.py index cc9c37027..19360e273 100644 --- a/youtube_dl/extractor/youporn.py +++ b/youtube_dl/extractor/youporn.py @@ -5,6 +5,7 @@ import sys from .common import InfoExtractor from ..utils import ( + compat_str, compat_urllib_parse_urlparse, compat_urllib_request, @@ -79,13 +80,16 @@ class YouPornIE(InfoExtractor): links = re.findall(LINK_RE, download_list_html) # Get link of hd video - encrypted_video_url = self._html_search_regex(r'var encryptedURL = \'(?P[a-zA-Z0-9+/]+={0,2})\';', + encrypted_video_url = self._html_search_regex( + r'var encrypted(?:Quality[0-9]+)?URL = \'(?P[a-zA-Z0-9+/]+={0,2})\';', webpage, u'encrypted_video_url') - video_url = unicode( aes_decrypt_text(encrypted_video_url, video_title, 32), 'utf-8') + video_url = aes_decrypt_text(encrypted_video_url, video_title, 32) + print(video_url) + assert isinstance(video_url, compat_str) if video_url.split('/')[6].split('_')[0] == u'720p': # only add if 720p to avoid duplicates links = [video_url] + links - if(len(links) == 0): + if not links: raise ExtractorError(u'ERROR: no known formats available for video') self.to_screen(u'Links found: %d' % len(links)) @@ -122,7 +126,7 @@ class YouPornIE(InfoExtractor): self._print_formats(formats) return - req_format = self._downloader.params.get('format', None) + req_format = self._downloader.params.get('format', 'best') self.to_screen(u'Format: %s' % req_format) if req_format is None or req_format == 'best': diff --git a/youtube_dl/utils.py b/youtube_dl/utils.py index 59eeaf4a8..07b40da6c 100644 --- a/youtube_dl/utils.py +++ b/youtube_dl/utils.py @@ -708,3 +708,13 @@ class DateRange(object): return self.start <= date <= self.end def __str__(self): return '%s - %s' % ( self.start.isoformat(), self.end.isoformat()) + + +def bytes_to_intlist(bs): + if not bs: + return [] + if isinstance(bs[0], int): # Python 3 + return list(bs) + else: + return [ord(c) for c in bs] + From 920ef0779b6bcd5131e237e5c2ca28361f6d45d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Sat, 20 Jul 2013 12:49:24 +0200 Subject: [PATCH 106/122] Hide the password and username in verbose mode (closes #1089) --- youtube_dl/__init__.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py index b33a18a26..431460c57 100644 --- a/youtube_dl/__init__.py +++ b/youtube_dl/__init__.py @@ -100,6 +100,16 @@ def parseOpts(overrideArguments=None): pass return None + def _hide_login_info(opts): + opts = list(opts) + for private_opt in ['-p', '--password', '-u', '--username']: + try: + i = opts.index(private_opt) + opts[i+1] = '' + except ValueError: + pass + return opts + max_width = 80 max_help_position = 80 @@ -358,9 +368,9 @@ def parseOpts(overrideArguments=None): argv = systemConf + userConf + commandLineConf opts, args = parser.parse_args(argv) if opts.verbose: - sys.stderr.write(u'[debug] System config: ' + repr(systemConf) + '\n') - sys.stderr.write(u'[debug] User config: ' + repr(userConf) + '\n') - sys.stderr.write(u'[debug] Command-line args: ' + repr(commandLineConf) + '\n') + sys.stderr.write(u'[debug] System config: ' + repr(_hide_login_info(systemConf)) + '\n') + sys.stderr.write(u'[debug] User config: ' + repr(_hide_login_info(userConf)) + '\n') + sys.stderr.write(u'[debug] Command-line args: ' + repr(_hide_login_info(commandLineConf)) + '\n') return parser, opts, args From cba892fa1fd6a7f1278e637c338921c5ae236840 Mon Sep 17 00:00:00 2001 From: rzhxeo Date: Wed, 28 Aug 2013 15:59:07 +0200 Subject: [PATCH 107/122] Add intlist_to_bytes to utils.py --- youtube_dl/utils.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/youtube_dl/utils.py b/youtube_dl/utils.py index 07b40da6c..ee8df6a5b 100644 --- a/youtube_dl/utils.py +++ b/youtube_dl/utils.py @@ -718,3 +718,10 @@ def bytes_to_intlist(bs): else: return [ord(c) for c in bs] +def intlist_to_bytes(xs): + if not xs: + return b'' + if isinstance(chr(0), bytes): # Python 2 + return ''.join([chr(x) for x in xs]) + else: + return bytes(xs) From 6e74bc41ca07bda56107cfff9ceb98d6f8d28e53 Mon Sep 17 00:00:00 2001 From: rzhxeo Date: Wed, 28 Aug 2013 16:01:43 +0200 Subject: [PATCH 108/122] Fix division bug in aes.py --- youtube_dl/aes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtube_dl/aes.py b/youtube_dl/aes.py index 278f8bb82..9913d59a4 100644 --- a/youtube_dl/aes.py +++ b/youtube_dl/aes.py @@ -18,7 +18,7 @@ def aes_ctr_decrypt(data, key, counter): @returns {int[]} decrypted data """ expanded_key = key_expansion(key) - block_count = int(ceil(float(len(data)) // BLOCK_SIZE_BYTES)) + block_count = int(ceil(float(len(data)) / BLOCK_SIZE_BYTES)) decrypted_data=[] for i in range(block_count): From 0012690aae977d76e9162e2334989498366a8e94 Mon Sep 17 00:00:00 2001 From: rzhxeo Date: Wed, 28 Aug 2013 16:03:35 +0200 Subject: [PATCH 109/122] Let aes_decrypt_text return bytes instead of unicode --- youtube_dl/aes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/youtube_dl/aes.py b/youtube_dl/aes.py index 9913d59a4..9a0c93fa6 100644 --- a/youtube_dl/aes.py +++ b/youtube_dl/aes.py @@ -3,7 +3,7 @@ __all__ = ['aes_encrypt', 'key_expansion', 'aes_ctr_decrypt', 'aes_decrypt_text' import base64 from math import ceil -from .utils import bytes_to_intlist +from .utils import bytes_to_intlist, intlist_to_bytes BLOCK_SIZE_BYTES = 16 @@ -118,7 +118,7 @@ def aes_decrypt_text(data, password, key_size_bytes): return temp decrypted_data = aes_ctr_decrypt(cipher, key, Counter()) - plaintext = ''.join(map(lambda x: chr(x), decrypted_data)) + plaintext = intlist_to_bytes(decrypted_data) return plaintext From 878e83c5a4c84c7abbf3484366e76fbe906c8947 Mon Sep 17 00:00:00 2001 From: rzhxeo Date: Wed, 28 Aug 2013 16:04:48 +0200 Subject: [PATCH 110/122] YoupornIE: Clean up extraction of hd video --- youtube_dl/extractor/youporn.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/youtube_dl/extractor/youporn.py b/youtube_dl/extractor/youporn.py index 19360e273..c85fd4b5a 100644 --- a/youtube_dl/extractor/youporn.py +++ b/youtube_dl/extractor/youporn.py @@ -5,7 +5,6 @@ import sys from .common import InfoExtractor from ..utils import ( - compat_str, compat_urllib_parse_urlparse, compat_urllib_request, @@ -79,14 +78,11 @@ class YouPornIE(InfoExtractor): LINK_RE = r'(?s)' links = re.findall(LINK_RE, download_list_html) - # Get link of hd video - encrypted_video_url = self._html_search_regex( - r'var encrypted(?:Quality[0-9]+)?URL = \'(?P[a-zA-Z0-9+/]+={0,2})\';', - webpage, u'encrypted_video_url') - video_url = aes_decrypt_text(encrypted_video_url, video_title, 32) - print(video_url) - assert isinstance(video_url, compat_str) - if video_url.split('/')[6].split('_')[0] == u'720p': # only add if 720p to avoid duplicates + # Get link of hd video if available + mobj = re.search(r'var encryptedQuality720URL = \'(?P[a-zA-Z0-9+/]+={0,2})\';', webpage) + if mobj != None: + encrypted_video_url = mobj.group(u'encrypted_video_url') + video_url = aes_decrypt_text(encrypted_video_url, video_title, 32).decode('utf-8') links = [video_url] + links if not links: From 2891932bf0a01acc025246438f890dca57f91c6b Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Wed, 28 Aug 2013 19:00:17 +0200 Subject: [PATCH 111/122] release 2013.08.28.1 --- youtube_dl/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtube_dl/version.py b/youtube_dl/version.py index 0b56e48dc..2ba75258d 100644 --- a/youtube_dl/version.py +++ b/youtube_dl/version.py @@ -1,2 +1,2 @@ -__version__ = '2013.08.28' +__version__ = '2013.08.28.1' From b5ba7b9dcfed5ded96c841a0ebbbf12132de838f Mon Sep 17 00:00:00 2001 From: Jeff Smith Date: Wed, 28 Aug 2013 14:00:59 -0500 Subject: [PATCH 112/122] Fix MIT extractor for Python 2.6 The HTML for the MIT page does not parse cleanly for Python 2.6 due to script tags within an actual script element. The offending piece is inside a comment block, so removing all such comment blocks fixes the parsing. --- youtube_dl/extractor/mit.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/youtube_dl/extractor/mit.py b/youtube_dl/extractor/mit.py index d09d03e36..52be9232f 100644 --- a/youtube_dl/extractor/mit.py +++ b/youtube_dl/extractor/mit.py @@ -25,23 +25,21 @@ class TechTVMITIE(InfoExtractor): def _real_extract(self, url): mobj = re.match(self._VALID_URL, url) video_id = mobj.group('id') - webpage = self._download_webpage( + raw_page = self._download_webpage( 'http://techtv.mit.edu/videos/%s' % video_id, video_id) - embed_page = self._download_webpage( - 'http://techtv.mit.edu/embeds/%s/' % video_id, video_id, - note=u'Downloading embed page') + clean_page = re.compile(u'', re.S).sub(u'', raw_page) base_url = self._search_regex(r'ipadUrl: \'(.+?cloudfront.net/)', - embed_page, u'base url') - formats_json = self._search_regex(r'bitrates: (\[.+?\])', embed_page, + raw_page, u'base url') + formats_json = self._search_regex(r'bitrates: (\[.+?\])', raw_page, u'video formats') formats = json.loads(formats_json) formats = sorted(formats, key=lambda f: f['bitrate']) - title = get_element_by_id('edit-title', webpage) - description = clean_html(get_element_by_id('edit-description', webpage)) + title = get_element_by_id('edit-title', clean_page) + description = clean_html(get_element_by_id('edit-description', clean_page)) thumbnail = self._search_regex(r'playlist:.*?url: \'(.+?)\'', - embed_page, u'thumbnail', flags=re.DOTALL) + raw_page, u'thumbnail', flags=re.DOTALL) return {'id': video_id, 'title': title, From 0d75ae2ce313c5738b2bdd9602ab3cc15e78810d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Thu, 29 Aug 2013 11:35:15 +0200 Subject: [PATCH 113/122] Fix detection of the webpage charset if it's declared using ' instead of " Like in "" --- youtube_dl/extractor/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtube_dl/extractor/common.py b/youtube_dl/extractor/common.py index a2986cebe..77726ee24 100644 --- a/youtube_dl/extractor/common.py +++ b/youtube_dl/extractor/common.py @@ -150,7 +150,7 @@ class InfoExtractor(object): if m: encoding = m.group(1) else: - m = re.search(br']+charset="?([^"]+)[ /">]', + m = re.search(br']+charset=[\'"]?([^\'")]+)[ /\'">]', webpage_bytes[:1024]) if m: encoding = m.group(1).decode('ascii') From b7052e508787e49aa1e141a15f16284bbf1f634b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Thu, 29 Aug 2013 12:15:45 +0200 Subject: [PATCH 114/122] Also print the field that fails if it is a md5 checksum --- test/test_download.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/test_download.py b/test/test_download.py index 21cb2e694..23a66254d 100644 --- a/test/test_download.py +++ b/test/test_download.py @@ -127,12 +127,11 @@ def generator(test_case): info_dict = json.load(infof) for (info_field, expected) in tc.get('info_dict', {}).items(): if isinstance(expected, compat_str) and expected.startswith('md5:'): - self.assertEqual(expected, 'md5:' + md5(info_dict.get(info_field))) + got = 'md5:' + md5(info_dict.get(info_field)) else: got = info_dict.get(info_field) - self.assertEqual( - expected, got, - u'invalid value for field %s, expected %r, got %r' % (info_field, expected, got)) + self.assertEqual(expected, got, + u'invalid value for field %s, expected %r, got %r' % (info_field, expected, got)) # If checkable fields are missing from the test case, print the info_dict test_info_dict = dict((key, value if not isinstance(value, compat_str) or len(value) < 250 else 'md5:' + md5(value)) From c7bf7366bc0d4d1c4fc9c81ee5d33bf3c3512aa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Thu, 29 Aug 2013 13:41:59 +0200 Subject: [PATCH 115/122] Update descriptions checksum for some test for Unistra and Youtube --- youtube_dl/extractor/unistra.py | 2 +- youtube_dl/extractor/youtube.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/youtube_dl/extractor/unistra.py b/youtube_dl/extractor/unistra.py index 5ba0a9061..516e18914 100644 --- a/youtube_dl/extractor/unistra.py +++ b/youtube_dl/extractor/unistra.py @@ -11,7 +11,7 @@ class UnistraIE(InfoExtractor): u'md5': u'736f605cfdc96724d55bb543ab3ced24', u'info_dict': { u'title': u'M!ss Yella', - u'description': u'md5:75e8439a3e2981cd5d4b6db232e8fdfc', + u'description': u'md5:104892c71bd48e55d70b902736b81bbf', }, } diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py index 8e486afd0..4038af256 100644 --- a/youtube_dl/extractor/youtube.py +++ b/youtube_dl/extractor/youtube.py @@ -335,7 +335,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): u"info_dict": { u"upload_date": u"20120506", u"title": u"Icona Pop - I Love It (feat. Charli XCX) [OFFICIAL VIDEO]", - u"description": u"md5:b085c9804f5ab69f4adea963a2dceb3c", + u"description": u"md5:3e2666e0a55044490499ea45fe9037b7", u"uploader": u"Icona Pop", u"uploader_id": u"IconaPop" } From 545434670b7b055a7f0ff82b76ee7acbb3d07dd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Thu, 29 Aug 2013 19:16:07 +0200 Subject: [PATCH 116/122] Add an extractor for orf.at (closes #1346) Make find_xpath_attr also accept numbers in the value --- youtube_dl/extractor/__init__.py | 1 + youtube_dl/extractor/orf.py | 65 ++++++++++++++++++++++++++++++++ youtube_dl/utils.py | 2 +- 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 youtube_dl/extractor/orf.py diff --git a/youtube_dl/extractor/__init__.py b/youtube_dl/extractor/__init__.py index 6b5037c8c..90f1a4418 100644 --- a/youtube_dl/extractor/__init__.py +++ b/youtube_dl/extractor/__init__.py @@ -59,6 +59,7 @@ from .myvideo import MyVideoIE from .nba import NBAIE from .nbc import NBCNewsIE from .ooyala import OoyalaIE +from .orf import ORFIE from .pbs import PBSIE from .photobucket import PhotobucketIE from .pornotube import PornotubeIE diff --git a/youtube_dl/extractor/orf.py b/youtube_dl/extractor/orf.py new file mode 100644 index 000000000..8da0a2c8e --- /dev/null +++ b/youtube_dl/extractor/orf.py @@ -0,0 +1,65 @@ +import re +import xml.etree.ElementTree +import json + +from .common import InfoExtractor +from ..utils import ( + compat_urlparse, + ExtractorError, + find_xpath_attr, +) + +class ORFIE(InfoExtractor): + _VALID_URL = r'https?://tvthek.orf.at/(programs/.+?/episodes|topics/.+?)/(?P\d+)' + + _TEST = { + u'url': u'http://tvthek.orf.at/programs/1171769-Wetter-ZIB/episodes/6557323-Wetter', + u'file': u'6566957.flv', + u'info_dict': { + u'title': u'Wetter', + u'description': u'Christa Kummer, Marcus Wadsak und Kollegen präsentieren abwechselnd ihre täglichen Wetterprognosen für Österreich.\r \r Mehr Wetter unter wetter.ORF.at', + }, + u'params': { + # It uses rtmp + u'skip_download': True, + } + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + playlist_id = mobj.group('id') + webpage = self._download_webpage(url, playlist_id) + + flash_xml = self._search_regex('ORF.flashXML = \'(.+?)\'', webpage, u'flash xml') + flash_xml = compat_urlparse.parse_qs('xml='+flash_xml)['xml'][0] + flash_config = xml.etree.ElementTree.fromstring(flash_xml.encode('utf-8')) + playlist_json = self._search_regex(r'playlist\': \'(\[.*?\])\'', webpage, u'playlist').replace(r'\"','"') + playlist = json.loads(playlist_json) + + videos = [] + ns = '{http://tempuri.org/XMLSchema.xsd}' + xpath = '%(ns)sPlaylist/%(ns)sItems/%(ns)sItem' % {'ns': ns} + webpage_description = self._og_search_description(webpage) + for (i, (item, info)) in enumerate(zip(flash_config.findall(xpath), playlist), 1): + # Get best quality url + rtmp_url = None + for q in ['Q6A', 'Q4A', 'Q1A']: + video_url = find_xpath_attr(item, '%sVideoUrl' % ns, 'quality', q) + if video_url is not None: + rtmp_url = video_url.text + break + if rtmp_url is None: + raise ExtractorError(u'Couldn\'t get video url: %s' % info['id']) + description = self._html_search_regex( + r'id="playlist_entry_%s".*?

(.*?)

' % i, webpage, + u'description', default=webpage_description, flags=re.DOTALL) + videos.append({ + '_type': 'video', + 'id': info['id'], + 'title': info['title'], + 'url': rtmp_url, + 'ext': 'flv', + 'description': description, + }) + + return videos diff --git a/youtube_dl/utils.py b/youtube_dl/utils.py index b3d0f64ea..201802cee 100644 --- a/youtube_dl/utils.py +++ b/youtube_dl/utils.py @@ -213,7 +213,7 @@ if sys.version_info >= (2,7): def find_xpath_attr(node, xpath, key, val): """ Find the xpath xpath[@key=val] """ assert re.match(r'^[a-zA-Z]+$', key) - assert re.match(r'^[a-zA-Z@\s]*$', val) + assert re.match(r'^[a-zA-Z0-9@\s]*$', val) expr = xpath + u"[@%s='%s']" % (key, val) return node.find(expr) else: From 8928491074095ec4da84be9c7d5ff4f1c0f98400 Mon Sep 17 00:00:00 2001 From: Jeff Smith Date: Thu, 29 Aug 2013 12:51:38 -0500 Subject: [PATCH 117/122] Fix orf.at extractor by adding file coding mark --- youtube_dl/extractor/orf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/youtube_dl/extractor/orf.py b/youtube_dl/extractor/orf.py index 8da0a2c8e..41ef8e992 100644 --- a/youtube_dl/extractor/orf.py +++ b/youtube_dl/extractor/orf.py @@ -1,3 +1,5 @@ +# coding: utf-8 + import re import xml.etree.ElementTree import json From f1fb2d12b32910c641f27096e585513c5f97f9ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Thu, 29 Aug 2013 21:39:36 +0200 Subject: [PATCH 118/122] [ign] extract videos from articles pages --- youtube_dl/extractor/ign.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/youtube_dl/extractor/ign.py b/youtube_dl/extractor/ign.py index 62abab655..263959716 100644 --- a/youtube_dl/extractor/ign.py +++ b/youtube_dl/extractor/ign.py @@ -13,7 +13,7 @@ class IGNIE(InfoExtractor): Some videos of it.ign.com are also supported """ - _VALID_URL = r'https?://.+?\.ign\.com/(?:videos|show_videos)(/.+)?/(?P.+)' + _VALID_URL = r'https?://.+?\.ign\.com/(?Pvideos|show_videos|articles)(/.+)?/(?P.+)' IE_NAME = u'ign.com' _CONFIG_URL_TEMPLATE = 'http://www.ign.com/videos/configs/id/%s.config' @@ -41,7 +41,11 @@ class IGNIE(InfoExtractor): def _real_extract(self, url): mobj = re.match(self._VALID_URL, url) name_or_id = mobj.group('name_or_id') + page_type = mobj.group('type') webpage = self._download_webpage(url, name_or_id) + if page_type == 'articles': + video_url = self._search_regex(r'var videoUrl = "(.+?)"', webpage, u'video url') + return self.url_result(video_url, ie='IGN') video_id = self._find_video_id(webpage) result = self._get_video_info(video_id) description = self._html_search_regex(self._DESCRIPTION_RE, From ee80d66727d7b194e595fa7e0c19c40cc4adb408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Thu, 29 Aug 2013 21:51:09 +0200 Subject: [PATCH 119/122] [ign] update 1up extractor to work with the updated IGNIE --- youtube_dl/extractor/ign.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtube_dl/extractor/ign.py b/youtube_dl/extractor/ign.py index 263959716..b1c84278a 100644 --- a/youtube_dl/extractor/ign.py +++ b/youtube_dl/extractor/ign.py @@ -72,7 +72,7 @@ class IGNIE(InfoExtractor): class OneUPIE(IGNIE): """Extractor for 1up.com, it uses the ign videos system.""" - _VALID_URL = r'https?://gamevideos.1up.com/video/id/(?P.+)' + _VALID_URL = r'https?://gamevideos.1up.com/(?Pvideo)/id/(?P.+)' IE_NAME = '1up.com' _DESCRIPTION_RE = r'
(.+?)
' From 52e1eea18bae4771137abd888830036c40b6eaa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Thu, 29 Aug 2013 22:33:58 +0200 Subject: [PATCH 120/122] [youtube] update algo for length 86 (fixes #1349) --- devscripts/youtube_genalgo.py | 4 ++-- youtube_dl/extractor/youtube.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/devscripts/youtube_genalgo.py b/devscripts/youtube_genalgo.py index 917e8f79d..b06416e93 100644 --- a/devscripts/youtube_genalgo.py +++ b/devscripts/youtube_genalgo.py @@ -20,9 +20,9 @@ tests = [ # 87 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$^&*()_-+={[]}|:;?/>.<", "uioplkjhgfdsazxcvbnm1t34567890QWE2TYUIOPLKJHGFDSAZXCVeNM!@#$^&*()_-+={[]}|:;?/>.<"), - # 86 - vflh9ybst 2013/08/23 + # 86 - vflg0g8PQ 2013/08/29 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[|};?/>.<", - "yuioplkjhgfdsazxcvbnm1234567890QWERrYUIOPLKqHGFDSAZXCVBNM!@#$%^&*()_-+={[|};?/>.<"), + ">/?;}|[{=+-_)(*&^%$#@!MNBVCXZASDFGHJKLPOIUYTREWq0987654321mnbvcxzasdfghjklpoiuytr"), # 85 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[};?/>.<", ".>/?;}[{=+-_)(*&^%$#@!MNBVCXZASDFGHJKLPOIUYTREWQ0q876543r1mnbvcx9asdfghjklpoiuyt2"), diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py index 4038af256..3a07df027 100644 --- a/youtube_dl/extractor/youtube.py +++ b/youtube_dl/extractor/youtube.py @@ -423,7 +423,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): elif len(s) == 87: return s[6:27] + s[4] + s[28:39] + s[27] + s[40:59] + s[2] + s[60:] elif len(s) == 86: - return s[5:40] + s[3] + s[41:48] + s[0] + s[49:86] + return s[83:36:-1] + s[0] + s[35:2:-1] elif len(s) == 85: return s[83:34:-1] + s[0] + s[33:27:-1] + s[3] + s[26:19:-1] + s[34] + s[18:3:-1] + s[27] elif len(s) == 84: From 23b00bc0e4ae7d85876409fad59d95ce29b00d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Thu, 29 Aug 2013 22:44:29 +0200 Subject: [PATCH 121/122] [youtube] update algo for length 84 Only appears sometimes, nearly identical to length 86. --- devscripts/youtube_genalgo.py | 4 ++-- youtube_dl/extractor/youtube.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/devscripts/youtube_genalgo.py b/devscripts/youtube_genalgo.py index b06416e93..13df535c7 100644 --- a/devscripts/youtube_genalgo.py +++ b/devscripts/youtube_genalgo.py @@ -26,9 +26,9 @@ tests = [ # 85 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[};?/>.<", ".>/?;}[{=+-_)(*&^%$#@!MNBVCXZASDFGHJKLPOIUYTREWQ0q876543r1mnbvcx9asdfghjklpoiuyt2"), - # 84 - vflh9ybst 2013/08/23 (sporadic) + # 84 - vflg0g8PQ 2013/08/29 (sporadic) ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[};?>.<", - "yuioplkjhgfdsazxcvbnm1234567890QWERrYUIOPLKqHGFDSAZXCVBNM!@#$%^&*()_-+={[};?>.<"), + ">?;}[{=+-_)(*&^%$#@!MNBVCXZASDFGHJKLPOIUYTREWq0987654321mnbvcxzasdfghjklpoiuytr"), # 83 ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!#$%^&*()_+={[};?/>.<", ".>/?;}[{=+_)(*&^%<#!MNBVCXZASPFGHJKLwOIUYTREWQ0987654321mnbvcxzasdfghjklpoiuytreq"), diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py index 3a07df027..9e2373bd5 100644 --- a/youtube_dl/extractor/youtube.py +++ b/youtube_dl/extractor/youtube.py @@ -427,7 +427,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): elif len(s) == 85: return s[83:34:-1] + s[0] + s[33:27:-1] + s[3] + s[26:19:-1] + s[34] + s[18:3:-1] + s[27] elif len(s) == 84: - return s[5:40] + s[3] + s[41:48] + s[0] + s[49:84] + return s[81:36:-1] + s[0] + s[35:2:-1] elif len(s) == 83: return s[81:64:-1] + s[82] + s[63:52:-1] + s[45] + s[51:45:-1] + s[1] + s[44:1:-1] + s[0] elif len(s) == 82: From 3243d0f7b669128c91c64816a9ca3502ae4e4094 Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Thu, 29 Aug 2013 23:29:34 +0200 Subject: [PATCH 122/122] release 2013.08.29 --- youtube_dl/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtube_dl/version.py b/youtube_dl/version.py index 2ba75258d..c28320181 100644 --- a/youtube_dl/version.py +++ b/youtube_dl/version.py @@ -1,2 +1,2 @@ -__version__ = '2013.08.28.1' +__version__ = '2013.08.29'