1
0
mirror of https://github.com/ytdl-org/youtube-dl.git synced 2024-12-23 16:36:48 +00:00

[patreon] fix extraction(closes #14502)(closes #10471)

This commit is contained in:
Remita Amine 2018-10-05 20:11:01 +01:00
parent d96f976b0c
commit c9d891f19a

View File

@ -2,52 +2,63 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import js_to_json from ..utils import (
clean_html,
determine_ext,
int_or_none,
parse_iso8601,
)
class PatreonIE(InfoExtractor): class PatreonIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?patreon\.com/creation\?hid=(?P<id>[^&#]+)' _VALID_URL = r'https?://(?:www\.)?patreon\.com/(?:creation\?hid=|posts/(?:[\w-]+-)?)(?P<id>\d+)'
_TESTS = [ _TESTS = [{
{ 'url': 'http://www.patreon.com/creation?hid=743933',
'url': 'http://www.patreon.com/creation?hid=743933', 'md5': 'e25505eec1053a6e6813b8ed369875cc',
'md5': 'e25505eec1053a6e6813b8ed369875cc', 'info_dict': {
'info_dict': { 'id': '743933',
'id': '743933', 'ext': 'mp3',
'ext': 'mp3', 'title': 'Episode 166: David Smalley of Dogma Debate',
'title': 'Episode 166: David Smalley of Dogma Debate', 'description': 'md5:713b08b772cd6271b9f3906683cfacdf',
'uploader': 'Cognitive Dissonance Podcast', 'uploader': 'Cognitive Dissonance Podcast',
'thumbnail': 're:^https?://.*$', 'thumbnail': 're:^https?://.*$',
}, 'timestamp': 1406473987,
'upload_date': '20140727',
}, },
{ }, {
'url': 'http://www.patreon.com/creation?hid=754133', 'url': 'http://www.patreon.com/creation?hid=754133',
'md5': '3eb09345bf44bf60451b8b0b81759d0a', 'md5': '3eb09345bf44bf60451b8b0b81759d0a',
'info_dict': { 'info_dict': {
'id': '754133', 'id': '754133',
'ext': 'mp3', 'ext': 'mp3',
'title': 'CD 167 Extra', 'title': 'CD 167 Extra',
'uploader': 'Cognitive Dissonance Podcast', 'uploader': 'Cognitive Dissonance Podcast',
'thumbnail': 're:^https?://.*$', 'thumbnail': 're:^https?://.*$',
},
}, },
{ 'skip': 'Patron-only content',
'url': 'https://www.patreon.com/creation?hid=1682498', }, {
'info_dict': { 'url': 'https://www.patreon.com/creation?hid=1682498',
'id': 'SU4fj_aEMVw', 'info_dict': {
'ext': 'mp4', 'id': 'SU4fj_aEMVw',
'title': 'I\'m on Patreon!', 'ext': 'mp4',
'uploader': 'TraciJHines', 'title': 'I\'m on Patreon!',
'thumbnail': 're:^https?://.*$', 'uploader': 'TraciJHines',
'upload_date': '20150211', 'thumbnail': 're:^https?://.*$',
'description': 'md5:c5a706b1f687817a3de09db1eb93acd4', 'upload_date': '20150211',
'uploader_id': 'TraciJHines', 'description': 'md5:c5a706b1f687817a3de09db1eb93acd4',
}, 'uploader_id': 'TraciJHines',
'params': { },
'noplaylist': True, 'params': {
'skip_download': True, 'noplaylist': True,
} 'skip_download': True,
} }
] }, {
'url': 'https://www.patreon.com/posts/episode-166-of-743933',
'only_matching': True,
}, {
'url': 'https://www.patreon.com/posts/743933',
'only_matching': True,
}]
# Currently Patreon exposes download URL via hidden CSS, so login is not # Currently Patreon exposes download URL via hidden CSS, so login is not
# needed. Keeping this commented for when this inevitably changes. # needed. Keeping this commented for when this inevitably changes.
@ -78,38 +89,43 @@ class PatreonIE(InfoExtractor):
def _real_extract(self, url): def _real_extract(self, url):
video_id = self._match_id(url) video_id = self._match_id(url)
webpage = self._download_webpage(url, video_id) post = self._download_json(
title = self._og_search_title(webpage).strip() 'https://www.patreon.com/api/posts/' + video_id, video_id)
attributes = post['data']['attributes']
attach_fn = self._html_search_regex( title = attributes['title'].strip()
r'<div class="attach"><a target="_blank" href="([^"]+)">', image = attributes.get('image') or {}
webpage, 'attachment URL', default=None) info = {
embed = self._html_search_regex(
r'<div[^>]+id="watchCreation"[^>]*>\s*<iframe[^>]+src="([^"]+)"',
webpage, 'embedded URL', default=None)
if attach_fn is not None:
video_url = 'http://www.patreon.com' + attach_fn
thumbnail = self._og_search_thumbnail(webpage)
uploader = self._html_search_regex(
r'<strong>(.*?)</strong> is creating', webpage, 'uploader')
elif embed is not None:
return self.url_result(embed)
else:
playlist = self._parse_json(self._search_regex(
r'(?s)new\s+jPlayerPlaylist\(\s*\{\s*[^}]*},\s*(\[.*?,?\s*\])',
webpage, 'playlist JSON'),
video_id, transform_source=js_to_json)
data = playlist[0]
video_url = self._proto_relative_url(data['mp3'])
thumbnail = self._proto_relative_url(data.get('cover'))
uploader = data.get('artist')
return {
'id': video_id, 'id': video_id,
'url': video_url,
'ext': 'mp3',
'title': title, 'title': title,
'uploader': uploader, 'description': clean_html(attributes.get('content')),
'thumbnail': thumbnail, 'thumbnail': image.get('large_url') or image.get('url'),
'timestamp': parse_iso8601(attributes.get('published_at')),
'like_count': int_or_none(attributes.get('like_count')),
'comment_count': int_or_none(attributes.get('comment_count')),
} }
for i in post.get('included', []):
i_type = i.get('type')
if i_type == 'attachment':
attachment_attributes = i.get('attributes') or {}
attachment_url = attachment_attributes.get('url')
if attachment_url:
info.update({
'url': attachment_url,
'ext': determine_ext(attachment_attributes.get('name'), 'mp3'),
})
elif i_type == 'user':
user_attributes = i.get('attributes')
if user_attributes:
info.update({
'uploader': user_attributes.get('full_name'),
'uploader_url': user_attributes.get('url'),
})
if not info.get('url'):
info.update({
'_type': 'url',
'url': attributes['embed']['url'],
})
return info