mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2024-11-05 15:01:24 +01:00
Compare commits
2 Commits
f0e8bc7c60
...
a40b0070c2
Author | SHA1 | Date | |
---|---|---|---|
|
a40b0070c2 | ||
|
9cd9044790 |
|
@ -588,6 +588,7 @@ from .facebook import (
|
||||||
FacebookPluginsVideoIE,
|
FacebookPluginsVideoIE,
|
||||||
FacebookRedirectURLIE,
|
FacebookRedirectURLIE,
|
||||||
FacebookReelIE,
|
FacebookReelIE,
|
||||||
|
FacebookAdsIE,
|
||||||
)
|
)
|
||||||
from .fancode import (
|
from .fancode import (
|
||||||
FancodeVodIE,
|
FancodeVodIE,
|
||||||
|
|
|
@ -20,6 +20,7 @@ from ..utils import (
|
||||||
get_element_by_id,
|
get_element_by_id,
|
||||||
get_first,
|
get_first,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
join_nonempty,
|
||||||
js_to_json,
|
js_to_json,
|
||||||
merge_dicts,
|
merge_dicts,
|
||||||
parse_count,
|
parse_count,
|
||||||
|
@ -907,3 +908,114 @@ class FacebookReelIE(InfoExtractor):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
return self.url_result(
|
return self.url_result(
|
||||||
f'https://m.facebook.com/watch/?v={video_id}&_rdr', FacebookIE, video_id)
|
f'https://m.facebook.com/watch/?v={video_id}&_rdr', FacebookIE, video_id)
|
||||||
|
|
||||||
|
|
||||||
|
class FacebookAdsIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:[\w-]+\.)?facebook\.com/ads/library/?\?(?:[^#]+&)?id=(?P<id>\d+)'
|
||||||
|
IE_NAME = 'facebook:ads'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.facebook.com/ads/library/?id=899206155126718',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '899206155126718',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'video by Kandao',
|
||||||
|
'uploader': 'Kandao',
|
||||||
|
'uploader_id': '774114102743284',
|
||||||
|
'uploader_url': r're:^https?://.*',
|
||||||
|
'timestamp': 1702548330,
|
||||||
|
'thumbnail': r're:^https?://.*',
|
||||||
|
'upload_date': '20231214',
|
||||||
|
'like_count': int,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.facebook.com/ads/library/?id=893637265423481',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '893637265423481',
|
||||||
|
'title': 'Jusqu\u2019\u00e0 -25% sur une s\u00e9lection de vins p\u00e9tillants italiens ',
|
||||||
|
'uploader': 'Eataly Paris Marais',
|
||||||
|
'uploader_id': '2086668958314152',
|
||||||
|
'uploader_url': r're:^https?://.*',
|
||||||
|
'timestamp': 1703571529,
|
||||||
|
'upload_date': '20231226',
|
||||||
|
'like_count': int,
|
||||||
|
},
|
||||||
|
'playlist_count': 3,
|
||||||
|
}, {
|
||||||
|
'url': 'https://es-la.facebook.com/ads/library/?id=901230958115569',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://m.facebook.com/ads/library/?id=901230958115569',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
_FORMATS_MAP = {
|
||||||
|
'watermarked_video_sd_url': ('sd-wmk', 'SD, watermarked'),
|
||||||
|
'video_sd_url': ('sd', None),
|
||||||
|
'watermarked_video_hd_url': ('hd-wmk', 'HD, watermarked'),
|
||||||
|
'video_hd_url': ('hd', None),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _extract_formats(self, video_dict):
|
||||||
|
formats = []
|
||||||
|
for format_key, format_url in traverse_obj(video_dict, (
|
||||||
|
{dict.items}, lambda _, v: v[0] in self._FORMATS_MAP and url_or_none(v[1])
|
||||||
|
)):
|
||||||
|
formats.append({
|
||||||
|
'format_id': self._FORMATS_MAP[format_key][0],
|
||||||
|
'format_note': self._FORMATS_MAP[format_key][1],
|
||||||
|
'url': format_url,
|
||||||
|
'ext': 'mp4',
|
||||||
|
'quality': qualities(tuple(self._FORMATS_MAP))(format_key),
|
||||||
|
})
|
||||||
|
return formats
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
post_data = [self._parse_json(j, video_id, fatal=False)
|
||||||
|
for j in re.findall(r's\.handle\(({.*})\);requireLazy\(', webpage)]
|
||||||
|
data = traverse_obj(post_data, (
|
||||||
|
..., 'require', ..., ..., ..., 'props', 'deeplinkAdCard', 'snapshot', {dict}), get_all=False)
|
||||||
|
if not data:
|
||||||
|
raise ExtractorError('Unable to extract ad data')
|
||||||
|
|
||||||
|
title = data.get('title')
|
||||||
|
if not title or title == '{{product.name}}':
|
||||||
|
title = join_nonempty('display_format', 'page_name', delim=' by ', from_dict=data)
|
||||||
|
|
||||||
|
info_dict = traverse_obj(data, {
|
||||||
|
'description': ('link_description', {str}, {lambda x: x if x != '{{product.description}}' else None}),
|
||||||
|
'uploader': ('page_name', {str}),
|
||||||
|
'uploader_id': ('page_id', {str_or_none}),
|
||||||
|
'uploader_url': ('page_profile_uri', {url_or_none}),
|
||||||
|
'timestamp': ('creation_time', {int_or_none}),
|
||||||
|
'like_count': ('page_like_count', {int_or_none}),
|
||||||
|
})
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
for idx, entry in enumerate(traverse_obj(
|
||||||
|
data, (('videos', 'cards'), lambda _, v: any([url_or_none(v[f]) for f in self._FORMATS_MAP]))), 1
|
||||||
|
):
|
||||||
|
entries.append({
|
||||||
|
'id': f'{video_id}_{idx}',
|
||||||
|
'title': entry.get('title') or title,
|
||||||
|
'description': entry.get('link_description') or info_dict.get('description'),
|
||||||
|
'thumbnail': url_or_none(entry.get('video_preview_image_url')),
|
||||||
|
'formats': self._extract_formats(entry),
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(entries) == 1:
|
||||||
|
info_dict.update(entries[0])
|
||||||
|
|
||||||
|
elif len(entries) > 1:
|
||||||
|
info_dict.update({
|
||||||
|
'title': entries[0]['title'],
|
||||||
|
'entries': entries,
|
||||||
|
'_type': 'playlist',
|
||||||
|
})
|
||||||
|
|
||||||
|
info_dict['id'] = video_id
|
||||||
|
|
||||||
|
return info_dict
|
||||||
|
|
|
@ -11,6 +11,7 @@ from ..utils import (
|
||||||
join_nonempty,
|
join_nonempty,
|
||||||
parse_codecs,
|
parse_codecs,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
|
url_or_none,
|
||||||
urljoin,
|
urljoin,
|
||||||
)
|
)
|
||||||
from ..utils.traversal import traverse_obj
|
from ..utils.traversal import traverse_obj
|
||||||
|
@ -108,6 +109,64 @@ class FloatplaneIE(InfoExtractor):
|
||||||
'availability': 'subscriber_only',
|
'availability': 'subscriber_only',
|
||||||
},
|
},
|
||||||
'params': {'skip_download': 'm3u8'},
|
'params': {'skip_download': 'm3u8'},
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.floatplane.com/post/65B5PNoBtf',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '65B5PNoBtf',
|
||||||
|
'description': 'I recorded the inbuilt demo mode for your 90\'s enjoyment, thanks for being Floaties!',
|
||||||
|
'display_id': '65B5PNoBtf',
|
||||||
|
'like_count': int,
|
||||||
|
'release_timestamp': 1701249480,
|
||||||
|
'uploader': 'The Trash Network',
|
||||||
|
'availability': 'subscriber_only',
|
||||||
|
'uploader_id': '61bc20c9a131fb692bf2a513',
|
||||||
|
'uploader_url': 'https://www.floatplane.com/channel/TheTrashNetwork/home',
|
||||||
|
'channel_url': 'https://www.floatplane.com/channel/TheTrashNetwork/home/thedrumthing',
|
||||||
|
'comment_count': int,
|
||||||
|
'title': 'The $50 electronic drum kit.',
|
||||||
|
'channel_id': '64424fe73cd58cbcf8d8e131',
|
||||||
|
'thumbnail': 'https://pbs.floatplane.com/blogPost_thumbnails/65B5PNoBtf/725555379422705_1701247052743.jpeg',
|
||||||
|
'dislike_count': int,
|
||||||
|
'channel': 'The Drum Thing',
|
||||||
|
'release_date': '20231129',
|
||||||
|
},
|
||||||
|
'playlist_count': 2,
|
||||||
|
'playlist': [{
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'ISPJjexylS',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'release_date': '20231129',
|
||||||
|
'release_timestamp': 1701249480,
|
||||||
|
'title': 'The $50 electronic drum kit. .mov',
|
||||||
|
'channel_id': '64424fe73cd58cbcf8d8e131',
|
||||||
|
'thumbnail': 'https://pbs.floatplane.com/video_thumbnails/ISPJjexylS/335202812134041_1701249383392.jpeg',
|
||||||
|
'availability': 'subscriber_only',
|
||||||
|
'uploader': 'The Trash Network',
|
||||||
|
'duration': 622,
|
||||||
|
'channel': 'The Drum Thing',
|
||||||
|
'uploader_id': '61bc20c9a131fb692bf2a513',
|
||||||
|
'channel_url': 'https://www.floatplane.com/channel/TheTrashNetwork/home/thedrumthing',
|
||||||
|
'uploader_url': 'https://www.floatplane.com/channel/TheTrashNetwork/home',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'qKfxu6fEpu',
|
||||||
|
'ext': 'aac',
|
||||||
|
'release_date': '20231129',
|
||||||
|
'release_timestamp': 1701249480,
|
||||||
|
'title': 'Roland TD-7 Demo.m4a',
|
||||||
|
'channel_id': '64424fe73cd58cbcf8d8e131',
|
||||||
|
'availability': 'subscriber_only',
|
||||||
|
'uploader': 'The Trash Network',
|
||||||
|
'duration': 114,
|
||||||
|
'channel': 'The Drum Thing',
|
||||||
|
'uploader_id': '61bc20c9a131fb692bf2a513',
|
||||||
|
'channel_url': 'https://www.floatplane.com/channel/TheTrashNetwork/home/thedrumthing',
|
||||||
|
'uploader_url': 'https://www.floatplane.com/channel/TheTrashNetwork/home',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
'skip': 'requires subscription: "The Trash Network"',
|
||||||
|
'params': {'skip_download': 'm3u8'},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_initialize(self):
|
def _real_initialize(self):
|
||||||
|
@ -124,6 +183,22 @@ class FloatplaneIE(InfoExtractor):
|
||||||
if not any(traverse_obj(post_data, ('metadata', ('hasVideo', 'hasAudio')))):
|
if not any(traverse_obj(post_data, ('metadata', ('hasVideo', 'hasAudio')))):
|
||||||
raise ExtractorError('Post does not contain a video or audio track', expected=True)
|
raise ExtractorError('Post does not contain a video or audio track', expected=True)
|
||||||
|
|
||||||
|
uploader_url = format_field(
|
||||||
|
post_data, [('creator', 'urlname')], 'https://www.floatplane.com/channel/%s/home') or None
|
||||||
|
|
||||||
|
common_info = {
|
||||||
|
'uploader_url': uploader_url,
|
||||||
|
'channel_url': urljoin(f'{uploader_url}/', traverse_obj(post_data, ('channel', 'urlname'))),
|
||||||
|
'availability': self._availability(needs_subscription=True),
|
||||||
|
**traverse_obj(post_data, {
|
||||||
|
'uploader': ('creator', 'title', {str}),
|
||||||
|
'uploader_id': ('creator', 'id', {str}),
|
||||||
|
'channel': ('channel', 'title', {str}),
|
||||||
|
'channel_id': ('channel', 'id', {str}),
|
||||||
|
'release_timestamp': ('releaseDate', {parse_iso8601}),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
for media in traverse_obj(post_data, (('videoAttachments', 'audioAttachments'), ...)):
|
for media in traverse_obj(post_data, (('videoAttachments', 'audioAttachments'), ...)):
|
||||||
media_id = media['id']
|
media_id = media['id']
|
||||||
|
@ -150,11 +225,11 @@ class FloatplaneIE(InfoExtractor):
|
||||||
formats = []
|
formats = []
|
||||||
for quality in traverse_obj(stream, ('resource', 'data', 'qualityLevels', ...)):
|
for quality in traverse_obj(stream, ('resource', 'data', 'qualityLevels', ...)):
|
||||||
url = urljoin(stream['cdn'], format_path(traverse_obj(
|
url = urljoin(stream['cdn'], format_path(traverse_obj(
|
||||||
stream, ('resource', 'data', 'qualityLevelParams', quality['name']))))
|
stream, ('resource', 'data', 'qualityLevelParams', quality['name'], {dict}))))
|
||||||
formats.append({
|
formats.append({
|
||||||
**traverse_obj(quality, {
|
**traverse_obj(quality, {
|
||||||
'format_id': 'name',
|
'format_id': ('name', {str}),
|
||||||
'format_note': 'label',
|
'format_note': ('label', {str}),
|
||||||
'width': ('width', {int}),
|
'width': ('width', {int}),
|
||||||
'height': ('height', {int}),
|
'height': ('height', {int}),
|
||||||
}),
|
}),
|
||||||
|
@ -164,38 +239,28 @@ class FloatplaneIE(InfoExtractor):
|
||||||
})
|
})
|
||||||
|
|
||||||
items.append({
|
items.append({
|
||||||
|
**common_info,
|
||||||
'id': media_id,
|
'id': media_id,
|
||||||
**traverse_obj(metadata, {
|
**traverse_obj(metadata, {
|
||||||
'title': 'title',
|
'title': ('title', {str}),
|
||||||
'duration': ('duration', {int_or_none}),
|
'duration': ('duration', {int_or_none}),
|
||||||
'thumbnail': ('thumbnail', 'path'),
|
'thumbnail': ('thumbnail', 'path', {url_or_none}),
|
||||||
}),
|
}),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
})
|
})
|
||||||
|
|
||||||
uploader_url = format_field(
|
|
||||||
post_data, [('creator', 'urlname')], 'https://www.floatplane.com/channel/%s/home') or None
|
|
||||||
channel_url = urljoin(f'{uploader_url}/', traverse_obj(post_data, ('channel', 'urlname')))
|
|
||||||
|
|
||||||
post_info = {
|
post_info = {
|
||||||
|
**common_info,
|
||||||
'id': post_id,
|
'id': post_id,
|
||||||
'display_id': post_id,
|
'display_id': post_id,
|
||||||
**traverse_obj(post_data, {
|
**traverse_obj(post_data, {
|
||||||
'title': 'title',
|
'title': ('title', {str}),
|
||||||
'description': ('text', {clean_html}),
|
'description': ('text', {clean_html}),
|
||||||
'uploader': ('creator', 'title'),
|
|
||||||
'uploader_id': ('creator', 'id'),
|
|
||||||
'channel': ('channel', 'title'),
|
|
||||||
'channel_id': ('channel', 'id'),
|
|
||||||
'like_count': ('likes', {int_or_none}),
|
'like_count': ('likes', {int_or_none}),
|
||||||
'dislike_count': ('dislikes', {int_or_none}),
|
'dislike_count': ('dislikes', {int_or_none}),
|
||||||
'comment_count': ('comments', {int_or_none}),
|
'comment_count': ('comments', {int_or_none}),
|
||||||
'release_timestamp': ('releaseDate', {parse_iso8601}),
|
'thumbnail': ('thumbnail', 'path', {url_or_none}),
|
||||||
'thumbnail': ('thumbnail', 'path'),
|
|
||||||
}),
|
}),
|
||||||
'uploader_url': uploader_url,
|
|
||||||
'channel_url': channel_url,
|
|
||||||
'availability': self._availability(needs_subscription=True),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(items) > 1:
|
if len(items) > 1:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user