diff options
Diffstat (limited to 'hypervideo_dl/extractor/facebook.py')
-rw-r--r-- | hypervideo_dl/extractor/facebook.py | 151 |
1 files changed, 95 insertions, 56 deletions
diff --git a/hypervideo_dl/extractor/facebook.py b/hypervideo_dl/extractor/facebook.py index 04650af..f32700f 100644 --- a/hypervideo_dl/extractor/facebook.py +++ b/hypervideo_dl/extractor/facebook.py @@ -3,14 +3,11 @@ from __future__ import unicode_literals import json import re -import socket from .common import InfoExtractor from ..compat import ( compat_etree_fromstring, - compat_http_client, compat_str, - compat_urllib_error, compat_urllib_parse_unquote, compat_urllib_parse_unquote_plus, ) @@ -23,6 +20,8 @@ from ..utils import ( int_or_none, js_to_json, limit_length, + merge_dicts, + network_exceptions, parse_count, qualities, sanitized_Request, @@ -36,7 +35,7 @@ class FacebookIE(InfoExtractor): _VALID_URL = r'''(?x) (?: https?:// - (?:[\w-]+\.)?(?:facebook\.com|facebookcorewwwi\.onion)/ + (?:[\w-]+\.)?(?:facebook\.com|facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd\.onion)/ (?:[^#]*?\#!/)? (?: (?: @@ -82,7 +81,8 @@ class FacebookIE(InfoExtractor): 'info_dict': { 'id': '274175099429670', 'ext': 'mp4', - 'title': 're:^Asif Nawab Butt posted a video', + 'title': 'Asif Nawab Butt', + 'description': 'Asif Nawab Butt', 'uploader': 'Asif Nawab Butt', 'upload_date': '20140506', 'timestamp': 1399398998, @@ -137,15 +137,17 @@ class FacebookIE(InfoExtractor): 'upload_date': '20160223', 'uploader': 'Barack Obama', }, + 'skip': 'Gif on giphy.com gone', }, { # have 1080P, but only up to 720p in swf params # data.video.story.attachments[].media 'url': 'https://www.facebook.com/cnn/videos/10155529876156509/', - 'md5': '9571fae53d4165bbbadb17a94651dcdc', + 'md5': '3f3798adb2b73423263e59376f1f5eb7', 'info_dict': { 'id': '10155529876156509', 'ext': 'mp4', - 'title': 'She survived the holocaust — and years later, she’s getting her citizenship s...', + 'title': 'Holocaust survivor becomes US citizen', + 'description': 'She survived the holocaust — and years later, she’s getting her citizenship so she can vote for Hillary Clinton http://cnn.it/2eERh5f', 'timestamp': 1477818095, 'upload_date': '20161030', 'uploader': 'CNN', @@ -159,15 +161,18 @@ class FacebookIE(InfoExtractor): 'info_dict': { 'id': '1417995061575415', 'ext': 'mp4', - 'title': 'md5:1db063d6a8c13faa8da727817339c857', - 'timestamp': 1486648217, + 'title': 'Yaroslav Korpan - Довгоочікуване відео', + 'description': 'Довгоочікуване відео', + 'timestamp': 1486648771, 'upload_date': '20170209', 'uploader': 'Yaroslav Korpan', + 'uploader_id': '100000948048708', }, 'params': { 'skip_download': True, }, }, { + # FIXME 'url': 'https://www.facebook.com/LaGuiaDelVaron/posts/1072691702860471', 'info_dict': { 'id': '1072691702860471', @@ -185,12 +190,14 @@ class FacebookIE(InfoExtractor): # data.node.comet_sections.content.story.attachments[].style_type_renderer.attachment.media 'url': 'https://www.facebook.com/groups/1024490957622648/permalink/1396382447100162/', 'info_dict': { - 'id': '1396382447100162', + 'id': '202882990186699', 'ext': 'mp4', - 'title': 'md5:19a428bbde91364e3de815383b54a235', - 'timestamp': 1486035494, + 'title': 'Elisabeth Ahtn - Hello? Yes your uber ride is here\n* Jukin...', + 'description': 'Hello? Yes your uber ride is here\n* Jukin Media Verified *\nFind this video and others like it by visiting...', + 'timestamp': 1486035513, 'upload_date': '20170202', 'uploader': 'Elisabeth Ahtn', + 'uploader_id': '100013949973717', }, 'params': { 'skip_download': True, @@ -219,7 +226,7 @@ class FacebookIE(InfoExtractor): 'only_matching': True, }, { # data.video - 'url': 'https://www.facebookcorewwwi.onion/video.php?v=274175099429670', + 'url': 'https://www.facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion/video.php?v=274175099429670', 'only_matching': True, }, { # no title @@ -231,8 +238,12 @@ class FacebookIE(InfoExtractor): 'info_dict': { 'id': '359649331226507', 'ext': 'mp4', - 'title': '#ESLOne VoD - Birmingham Finals Day#1 Fnatic vs. @Evil Geniuses', + 'title': 'Fnatic vs. EG - Group A - Opening Match - ESL One Birmingham Day 1', + 'description': '#ESLOne VoD - Birmingham Finals Day#1 Fnatic vs. @Evil Geniuses', + 'timestamp': 1527084179, + 'upload_date': '20180523', 'uploader': 'ESL One Dota 2', + 'uploader_id': '234218833769558', }, 'params': { 'skip_download': True, @@ -249,6 +260,7 @@ class FacebookIE(InfoExtractor): 'url': 'https://www.facebook.com/watch/?v=647537299265662', 'only_matching': True, }, { + # FIXME: https://github.com/hypervideo/hypervideo/issues/542 # data.node.comet_sections.content.story.attachments[].style_type_renderer.attachment.all_subattachments.nodes[].media 'url': 'https://www.facebook.com/PankajShahLondon/posts/10157667649866271', 'info_dict': { @@ -279,6 +291,7 @@ class FacebookIE(InfoExtractor): 'upload_date': '20161122', 'timestamp': 1479793574, }, + 'skip': 'No video', }, { # data.video.creation_story.attachments[].media 'url': 'https://www.facebook.com/watch/live/?v=1823658634322275', @@ -348,7 +361,7 @@ class FacebookIE(InfoExtractor): login_results, 'login error', default=None, group='error') if error: raise ExtractorError('Unable to login: %s' % error, expected=True) - self._downloader.report_warning('unable to log in: bad username/password, or exceeded login rate limit (~3/min). Check credentials or wait.') + self.report_warning('unable to log in: bad username/password, or exceeded login rate limit (~3/min). Check credentials or wait.') return fb_dtsg = self._search_regex( @@ -369,9 +382,9 @@ class FacebookIE(InfoExtractor): check_response = self._download_webpage(check_req, None, note='Confirming login') if re.search(r'id="checkpointSubmitButton"', check_response) is not None: - self._downloader.report_warning('Unable to confirm login, you have to login in your browser and authorize the login.') - except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err: - self._downloader.report_warning('unable to log in: %s' % error_to_compat_str(err)) + self.report_warning('Unable to confirm login, you have to login in your browser and authorize the login.') + except network_exceptions as err: + self.report_warning('unable to log in: %s' % error_to_compat_str(err)) return def _real_initialize(self): @@ -381,6 +394,56 @@ class FacebookIE(InfoExtractor): webpage = self._download_webpage( url.replace('://m.facebook.com/', '://www.facebook.com/'), video_id) + def extract_metadata(webpage): + video_title = self._html_search_regex( + r'<h2\s+[^>]*class="uiHeaderTitle"[^>]*>([^<]*)</h2>', webpage, + 'title', default=None) + if not video_title: + video_title = self._html_search_regex( + r'(?s)<span class="fbPhotosPhotoCaption".*?id="fbPhotoPageCaption"><span class="hasCaption">(.*?)</span>', + webpage, 'alternative title', default=None) + if not video_title: + video_title = self._html_search_meta( + ['og:title', 'twitter:title', 'description'], + webpage, 'title', default=None) + if video_title: + video_title = limit_length(video_title, 80) + else: + video_title = 'Facebook video #%s' % video_id + description = self._html_search_meta( + ['description', 'og:description', 'twitter:description'], + webpage, 'description', default=None) + uploader = clean_html(get_element_by_id( + 'fbPhotoPageAuthorName', webpage)) or self._search_regex( + r'ownerName\s*:\s*"([^"]+)"', webpage, 'uploader', + default=None) or self._og_search_title(webpage, fatal=False) + timestamp = int_or_none(self._search_regex( + r'<abbr[^>]+data-utime=["\'](\d+)', webpage, + 'timestamp', default=None)) + thumbnail = self._html_search_meta( + ['og:image', 'twitter:image'], webpage, 'thumbnail', default=None) + # some webpages contain unretrievable thumbnail urls + # like https://lookaside.fbsbx.com/lookaside/crawler/media/?media_id=10155168902769113&get_thumbnail=1 + # in https://www.facebook.com/yaroslav.korpan/videos/1417995061575415/ + if thumbnail and not re.search(r'\.(?:jpg|png)', thumbnail): + thumbnail = None + view_count = parse_count(self._search_regex( + r'\bviewCount\s*:\s*["\']([\d,.]+)', webpage, 'view count', + default=None)) + info_dict = { + 'title': video_title, + 'description': description, + 'uploader': uploader, + 'timestamp': timestamp, + 'thumbnail': thumbnail, + 'view_count': view_count, + } + info_json_ld = self._search_json_ld(webpage, video_id, default={}) + if info_json_ld.get('title'): + info_json_ld['title'] = limit_length( + re.sub(r'\s*\|\s*Facebook$', '', info_json_ld['title']), 80) + return merge_dicts(info_json_ld, info_dict) + video_data = None def extract_video_data(instances): @@ -416,7 +479,7 @@ class FacebookIE(InfoExtractor): for f in formats: f.setdefault('http_headers', {})['User-Agent'] = 'facebookexternalhit/1.1' - self._sort_formats(formats) + self._sort_formats(formats, ('res', 'quality')) def extract_relay_data(_filter): return self._parse_json(self._search_regex( @@ -513,7 +576,15 @@ class FacebookIE(InfoExtractor): if not entries: parse_graphql_video(video) - return self.playlist_result(entries, video_id) + if len(entries) > 1: + return self.playlist_result(entries, video_id) + + video_info = entries[0] + webpage_info = extract_metadata(webpage) + # honor precise duration in video info + if video_info.get('duration'): + webpage_info['duration'] = video_info['duration'] + return merge_dicts(webpage_info, video_info) if not video_data: m_msg = re.search(r'class="[^"]*uiInterstitialContent[^"]*"><div>(.*?)</div>', webpage) @@ -616,60 +687,28 @@ class FacebookIE(InfoExtractor): for src_type in ('src', 'src_no_ratelimit'): src = f[0].get('%s_%s' % (quality, src_type)) if src: - preference = -10 if format_id == 'progressive' else 0 + preference = -10 if format_id == 'progressive' else -1 if quality == 'hd': preference += 5 formats.append({ 'format_id': '%s_%s_%s' % (format_id, quality, src_type), 'url': src, - 'preference': preference, + 'quality': preference, + 'height': 720 if quality == 'hd' else None }) extract_dash_manifest(f[0], formats) subtitles_src = f[0].get('subtitles_src') if subtitles_src: subtitles.setdefault('en', []).append({'url': subtitles_src}) - if not formats: - raise ExtractorError('Cannot find video formats') process_formats(formats) - video_title = self._html_search_regex( - r'<h2\s+[^>]*class="uiHeaderTitle"[^>]*>([^<]*)</h2>', webpage, - 'title', default=None) - if not video_title: - video_title = self._html_search_regex( - r'(?s)<span class="fbPhotosPhotoCaption".*?id="fbPhotoPageCaption"><span class="hasCaption">(.*?)</span>', - webpage, 'alternative title', default=None) - if not video_title: - video_title = self._html_search_meta( - 'description', webpage, 'title', default=None) - if video_title: - video_title = limit_length(video_title, 80) - else: - video_title = 'Facebook video #%s' % video_id - uploader = clean_html(get_element_by_id( - 'fbPhotoPageAuthorName', webpage)) or self._search_regex( - r'ownerName\s*:\s*"([^"]+)"', webpage, 'uploader', - default=None) or self._og_search_title(webpage, fatal=False) - timestamp = int_or_none(self._search_regex( - r'<abbr[^>]+data-utime=["\'](\d+)', webpage, - 'timestamp', default=None)) - thumbnail = self._html_search_meta(['og:image', 'twitter:image'], webpage) - - view_count = parse_count(self._search_regex( - r'\bviewCount\s*:\s*["\']([\d,.]+)', webpage, 'view count', - default=None)) - info_dict = { 'id': video_id, - 'title': video_title, 'formats': formats, - 'uploader': uploader, - 'timestamp': timestamp, - 'thumbnail': thumbnail, - 'view_count': view_count, 'subtitles': subtitles, } + info_dict.update(extract_metadata(webpage)) return info_dict |