mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2024-06-08 22:38:35 +02:00
Compare commits
19 Commits
6ffe573c3d
...
9fee9e2b74
Author | SHA1 | Date | |
---|---|---|---|
|
9fee9e2b74 | ||
|
12d8ea8246 | ||
|
a5ccc7f0aa | ||
|
7d87c09c5d | ||
|
8a0f2ffd80 | ||
|
e61606eaf2 | ||
|
931a90e7da | ||
|
fddf9a60ee | ||
|
182a18eaa0 | ||
|
e7f90d887d | ||
|
bee20eeb82 | ||
|
c1b5121932 | ||
|
7f44d0fa02 | ||
|
19cc97d1a1 | ||
|
f5fc4de7c9 | ||
|
a997e972a5 | ||
|
0dd98c888d | ||
|
378ecc6b65 | ||
|
fe8d9ec837 |
|
@ -1760,7 +1760,7 @@ # EXTRACTOR ARGUMENTS
|
|||
#### youtube
|
||||
* `lang`: Prefer translated metadata (`title`, `description` etc) of this language code (case-sensitive). By default, the video primary language metadata is preferred, with a fallback to `en` translated. See [youtube.py](https://github.com/yt-dlp/yt-dlp/blob/c26f9b991a0681fd3ea548d535919cec1fbbd430/yt_dlp/extractor/youtube.py#L381-L390) for list of supported content language codes
|
||||
* `skip`: One or more of `hls`, `dash` or `translated_subs` to skip extraction of the m3u8 manifests, dash manifests and [auto-translated subtitles](https://github.com/yt-dlp/yt-dlp/issues/4090#issuecomment-1158102032) respectively
|
||||
* `player_client`: Clients to extract video data from. The main clients are `web`, `android` and `ios` with variants `_music`, `_embedded`, `_embedscreen`, `_creator` (e.g. `web_embedded`); and `mweb`, `mweb_embedscreen`, `mediaconnect` and `tv_embedded` (agegate bypass) with no variants. By default, `ios,android,web` is used, but `tv_embedded` and `creator` variants are added as required for age-gated videos. Similarly, the music variants are added for `music.youtube.com` urls. You can use `all` to use all the clients, and `default` for the default clients.
|
||||
* `player_client`: Clients to extract video data from. The main clients are `web`, `ios` and `android`, with variants `_music`, `_embedded`, `_embedscreen`, `_creator` (e.g. `web_embedded`); and `mweb`, `mweb_embedscreen` and `tv_embedded` (agegate bypass) with no variants. By default, `ios,web` is used, but `tv_embedded` and `creator` variants are added as required for age-gated videos. Similarly, the music variants are added for `music.youtube.com` urls. The `android` clients will always be given lowest priority since their formats are broken. You can use `all` to use all the clients, and `default` for the default clients.
|
||||
* `player_skip`: Skip some network requests that are generally needed for robust extraction. One or more of `configs` (skip client configs), `webpage` (skip initial webpage), `js` (skip js player). While these options can help reduce the number of requests needed or avoid some rate-limiting, they could cause some issues. See [#860](https://github.com/yt-dlp/yt-dlp/pull/860) for more details
|
||||
* `player_params`: YouTube player parameters to use for player requests. Will overwrite any default ones set by yt-dlp.
|
||||
* `comment_sort`: `top` or `new` (default) - choose comment sorting mode (on YouTube's side)
|
||||
|
|
|
@ -754,6 +754,7 @@
|
|||
)
|
||||
from .holodex import HolodexIE
|
||||
from .hotnewhiphop import HotNewHipHopIE
|
||||
from .hotmart import HotmartIE
|
||||
from .hotstar import (
|
||||
HotStarIE,
|
||||
HotStarPrefixIE,
|
||||
|
|
79
yt_dlp/extractor/hotmart.py
Normal file
79
yt_dlp/extractor/hotmart.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
get_element_by_id,
|
||||
traverse_obj,
|
||||
int_or_none,
|
||||
url_or_none,
|
||||
)
|
||||
|
||||
|
||||
class HotmartIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://player\.hotmart\.com/embed/(?P<id>[a-zA-Z0-9]+)'
|
||||
_TESTS = [
|
||||
{
|
||||
'url': (
|
||||
'https://player.hotmart.com/embed/pRQKDWkKLB?signature=S0Pr1OaDwGvKwQ8i6Y9whykEo4uuok2P4AShiYcyarvFkQDT_rBlR5L1qdIbIferFBHfTVJlXcbgUAwMMPiV6sWaA0XIU4OO282MO092DX_Z8KqS1h0Y-452TMjAt3dW2ZYMKWtfA2A2sxM7JmpYZZdMKTrT7nwoPsfbythXfph3dCLzxNQ0gS-rHfD7SYWuKJGN1JmK6iAygJf1thpskoeOJyK04SpDwMoqIOYfsrUktvsJFlV3oWM1tVoeDIQPWSZGXE6WRWDPNmTz6h7IHvc-QKGzoRy3_CvzSEioq2SaDNDdloECrKH37V1eCNvdaIr0dQeHqH_vI0NMBsfCow==&token=aa2d356b-e2f0-45e8-9725-e0efc7b5d29c&autoplay=autoplay'
|
||||
),
|
||||
'md5': '95d7a252bb97954663fcf6c6db4b4555',
|
||||
'info_dict': {
|
||||
'id': 'pRQKDWkKLB',
|
||||
'video_id': 'pRQKDWkKLB',
|
||||
'ext': 'mp4',
|
||||
'title': 'Hotmart video #pRQKDWkKLB',
|
||||
'thumbnail': (
|
||||
r're:https?://.*\.(?:jpg|jpeg|png|gif)\?token=exp=\d+~acl=.*~hmac=[a-f0-9]+$'
|
||||
),
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
video_data_string = get_element_by_id('__NEXT_DATA__', webpage)
|
||||
video_data = self._parse_json(video_data_string, video_id, fatal=False)
|
||||
|
||||
title = self._html_search_meta(
|
||||
['og:title', 'title', 'twitter:title'],
|
||||
webpage, 'title', default='Hotmart video #' + video_id
|
||||
)
|
||||
|
||||
url = traverse_obj(
|
||||
video_data,
|
||||
(
|
||||
'props',
|
||||
'pageProps',
|
||||
'applicationData',
|
||||
'mediaAssets',
|
||||
0,
|
||||
'urlEncrypted',
|
||||
),
|
||||
expected_type=url_or_none,
|
||||
)
|
||||
thumbnail_url = traverse_obj(
|
||||
video_data,
|
||||
('props', 'pageProps', 'applicationData', 'thumbnailUrl'),
|
||||
expected_type=url_or_none,
|
||||
)
|
||||
|
||||
formats, subtitles = self._extract_m3u8_formats_and_subtitles(
|
||||
url, video_id, 'mp4', fatal=False
|
||||
)
|
||||
|
||||
description = self._og_search_description(webpage, default=None)
|
||||
chapter = None
|
||||
chapter_number = None
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'video_id': video_id,
|
||||
'thumbnail': thumbnail_url,
|
||||
'formats': formats,
|
||||
'subtitles': subtitles,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'chapter': chapter,
|
||||
'chapter_number': int_or_none(chapter_number),
|
||||
}
|
|
@ -1,12 +1,14 @@
|
|||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from .wistia import WistiaIE
|
||||
from .hotmart import HotmartIE
|
||||
from ..utils import (
|
||||
clean_html,
|
||||
extract_attributes,
|
||||
ExtractorError,
|
||||
int_or_none,
|
||||
get_element_by_class,
|
||||
get_elements_html_by_class,
|
||||
int_or_none,
|
||||
strip_or_none,
|
||||
urlencode_postdata,
|
||||
urljoin,
|
||||
|
@ -111,15 +113,16 @@ class TeachableIE(TeachableBaseIE):
|
|||
_TESTS = [{
|
||||
'url': 'https://gns3.teachable.com/courses/gns3-certified-associate/lectures/6842364',
|
||||
'info_dict': {
|
||||
'id': 'untlgzk1v7',
|
||||
'ext': 'bin',
|
||||
'id': 'Nq7vkXmXRA',
|
||||
'video_id': 'Nq7vkXmXRA',
|
||||
'ext': 'mp4',
|
||||
'title': 'Overview',
|
||||
'description': 'md5:071463ff08b86c208811130ea1c2464c',
|
||||
'duration': 736.4,
|
||||
'timestamp': 1542315762,
|
||||
'upload_date': '20181115',
|
||||
'chapter': 'Welcome',
|
||||
'chapter_number': 1,
|
||||
'webpage_url': r're:https://player.hotmart.com/embed/Nq7vkXmXRA\?signature=.+&token=.+',
|
||||
'width': 1920,
|
||||
'height': 1080,
|
||||
'thumbnail': r're:https?://.*\.(?:jpg|jpeg|webp)\?token=exp=\d+~acl=.*~hmac=[a-f0-9]+$',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
|
@ -127,6 +130,9 @@ class TeachableIE(TeachableBaseIE):
|
|||
}, {
|
||||
'url': 'http://v1.upskillcourses.com/courses/119763/lectures/1747100',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://v1.upskillcourses.com/courses/119763/lectures/1747100',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://gns3.teachable.com/courses/423415/lectures/6885939',
|
||||
'only_matching': True,
|
||||
|
@ -161,13 +167,35 @@ def _real_extract(self, url):
|
|||
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
wistia_urls = WistiaIE._extract_embed_urls(url, webpage)
|
||||
if not wistia_urls:
|
||||
hotmart_container_elements = get_elements_html_by_class(
|
||||
'hotmart_video_player', webpage
|
||||
)
|
||||
hotmart_urls = []
|
||||
for hotmart_container_element in hotmart_container_elements:
|
||||
hotmart_container_attributes = extract_attributes(hotmart_container_element)
|
||||
attachment_id = hotmart_container_attributes['data-attachment-id']
|
||||
|
||||
hotmart_video_url_data = self._download_json(
|
||||
f'https://{site}/api/v2/hotmart/private_video',
|
||||
video_id,
|
||||
query={'attachment_id': attachment_id},
|
||||
)
|
||||
|
||||
hotmart_url = (
|
||||
'https://player.hotmart.com/embed/'
|
||||
f'{hotmart_video_url_data ["video_id"]}?'
|
||||
f'signature={hotmart_video_url_data ["signature"]}&'
|
||||
'token='
|
||||
f'{hotmart_video_url_data ["teachable_application_key"]}'
|
||||
)
|
||||
|
||||
hotmart_urls.append(hotmart_url)
|
||||
|
||||
if not hotmart_urls:
|
||||
if any(re.search(p, webpage) for p in (
|
||||
r'class=["\']lecture-contents-locked',
|
||||
r'>\s*Lecture contents locked',
|
||||
r'id=["\']lecture-locked',
|
||||
# https://academy.tailoredtutors.co.uk/courses/108779/lectures/1955313
|
||||
r'class=["\'](?:inner-)?lesson-locked',
|
||||
r'>LESSON LOCKED<')):
|
||||
self.raise_login_required('Lecture contents locked')
|
||||
|
@ -196,14 +224,16 @@ def _real_extract(self, url):
|
|||
if chapter_number <= len(sections):
|
||||
chapter = sections[chapter_number - 1]
|
||||
|
||||
entries = [{
|
||||
'_type': 'url_transparent',
|
||||
'url': wistia_url,
|
||||
'ie_key': WistiaIE.ie_key(),
|
||||
'title': title,
|
||||
'chapter': chapter,
|
||||
'chapter_number': chapter_number,
|
||||
} for wistia_url in wistia_urls]
|
||||
entries = []
|
||||
for hotmart_url in hotmart_urls:
|
||||
entries.append({
|
||||
'_type': 'url_transparent',
|
||||
'url': hotmart_url,
|
||||
'ie_key': HotmartIE.ie_key(),
|
||||
'title': title,
|
||||
'chapter': chapter,
|
||||
'chapter_number': chapter_number,
|
||||
})
|
||||
|
||||
return self.playlist_result(entries, video_id, title)
|
||||
|
||||
|
|
|
@ -2353,6 +2353,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||
'format': '17', # 3gp format available on android
|
||||
'extractor_args': {'youtube': {'player_client': ['android']}},
|
||||
},
|
||||
'skip': 'android client broken',
|
||||
},
|
||||
{
|
||||
# Skip download of additional client configs (remix client config in this case)
|
||||
|
@ -2730,7 +2731,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||
'heatmap': 'count:100',
|
||||
},
|
||||
'params': {
|
||||
'extractor_args': {'youtube': {'player_client': ['android'], 'player_skip': ['webpage']}},
|
||||
'extractor_args': {'youtube': {'player_client': ['ios'], 'player_skip': ['webpage']}},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
@ -3662,8 +3663,6 @@ def _extract_player_response(self, client, video_id, master_ytcfg, player_ytcfg,
|
|||
yt_query = {
|
||||
'videoId': video_id,
|
||||
}
|
||||
if _split_innertube_client(client)[0] in ('android', 'android_embedscreen'):
|
||||
yt_query['params'] = 'CgIIAQ=='
|
||||
|
||||
pp_arg = self._configuration_arg('player_params', [None], casesense=True)[0]
|
||||
if pp_arg:
|
||||
|
@ -3679,19 +3678,24 @@ def _extract_player_response(self, client, video_id, master_ytcfg, player_ytcfg,
|
|||
|
||||
def _get_requested_clients(self, url, smuggled_data):
|
||||
requested_clients = []
|
||||
default = ['ios', 'android', 'web']
|
||||
android_clients = []
|
||||
default = ['ios', 'web']
|
||||
allowed_clients = sorted(
|
||||
(client for client in INNERTUBE_CLIENTS.keys() if client[:1] != '_'),
|
||||
key=lambda client: INNERTUBE_CLIENTS[client]['priority'], reverse=True)
|
||||
for client in self._configuration_arg('player_client'):
|
||||
if client in allowed_clients:
|
||||
requested_clients.append(client)
|
||||
elif client == 'default':
|
||||
if client == 'default':
|
||||
requested_clients.extend(default)
|
||||
elif client == 'all':
|
||||
requested_clients.extend(allowed_clients)
|
||||
else:
|
||||
elif client not in allowed_clients:
|
||||
self.report_warning(f'Skipping unsupported client {client}')
|
||||
elif client.startswith('android'):
|
||||
android_clients.append(client)
|
||||
else:
|
||||
requested_clients.append(client)
|
||||
# Force deprioritization of broken Android clients for format de-duplication
|
||||
requested_clients.extend(android_clients)
|
||||
if not requested_clients:
|
||||
requested_clients = default
|
||||
|
||||
|
@ -3910,6 +3914,14 @@ def build_fragments(f):
|
|||
f'{video_id}: Some formats are possibly damaged. They will be deprioritized', only_once=True)
|
||||
|
||||
client_name = fmt.get(STREAMING_DATA_CLIENT_NAME)
|
||||
# Android client formats are broken due to integrity check enforcement
|
||||
# Ref: https://github.com/yt-dlp/yt-dlp/issues/9554
|
||||
is_broken = client_name and client_name.startswith(short_client_name('android'))
|
||||
if is_broken:
|
||||
self.report_warning(
|
||||
f'{video_id}: Android client formats are broken and may yield HTTP Error 403. '
|
||||
'They will be deprioritized', only_once=True)
|
||||
|
||||
name = fmt.get('qualityLabel') or quality.replace('audio_quality_', '') or ''
|
||||
fps = int_or_none(fmt.get('fps')) or 0
|
||||
dct = {
|
||||
|
@ -3922,7 +3934,7 @@ def build_fragments(f):
|
|||
name, fmt.get('isDrc') and 'DRC',
|
||||
try_get(fmt, lambda x: x['projectionType'].replace('RECTANGULAR', '').lower()),
|
||||
try_get(fmt, lambda x: x['spatialAudioType'].replace('SPATIAL_AUDIO_TYPE_', '').lower()),
|
||||
throttled and 'THROTTLED', is_damaged and 'DAMAGED',
|
||||
throttled and 'THROTTLED', is_damaged and 'DAMAGED', is_broken and 'BROKEN',
|
||||
(self.get_param('verbose') or all_formats) and client_name,
|
||||
delim=', '),
|
||||
# Format 22 is likely to be damaged. See https://github.com/yt-dlp/yt-dlp/issues/3372
|
||||
|
@ -3940,8 +3952,8 @@ def build_fragments(f):
|
|||
'language': join_nonempty(audio_track.get('id', '').split('.')[0],
|
||||
'desc' if language_preference < -1 else '') or None,
|
||||
'language_preference': language_preference,
|
||||
# Strictly de-prioritize damaged and 3gp formats
|
||||
'preference': -10 if is_damaged else -2 if itag == '17' else None,
|
||||
# Strictly de-prioritize broken, damaged and 3gp formats
|
||||
'preference': -20 if is_broken else -10 if is_damaged else -2 if itag == '17' else None,
|
||||
}
|
||||
mime_mobj = re.match(
|
||||
r'((?:[^/]+)/(?:[^;]+))(?:;\s*codecs="([^"]+)")?', fmt.get('mimeType') or '')
|
||||
|
|
Loading…
Reference in New Issue
Block a user