aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorentin Le Moal <florentin.lemoal@gmail.com>2025-04-18 22:12:31 +0200
committerGitHub <noreply@github.com>2025-04-18 22:12:31 +0200
commit839d64325356310e6de6cd9cad28fb546619ca63 (patch)
treeae673d3848f9e93c4b9a43916051b83385903eae
parentf5736bb35bde62348caebf7b188668655e316deb (diff)
[ie/AtresPlayer] Rework extractor (#11424)
Closes #996, Closes #1165 Authored by: meGAmeS1, seproDev Co-authored-by: sepro <sepro@sepr0.com>
-rw-r--r--yt_dlp/extractor/atresplayer.py161
1 files changed, 105 insertions, 56 deletions
diff --git a/yt_dlp/extractor/atresplayer.py b/yt_dlp/extractor/atresplayer.py
index 0fe95bec5..1258a5704 100644
--- a/yt_dlp/extractor/atresplayer.py
+++ b/yt_dlp/extractor/atresplayer.py
@@ -1,64 +1,105 @@
+import urllib.parse
+
from .common import InfoExtractor
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
int_or_none,
+ parse_age_limit,
+ url_or_none,
urlencode_postdata,
)
+from ..utils.traversal import traverse_obj
class AtresPlayerIE(InfoExtractor):
- _VALID_URL = r'https?://(?:www\.)?atresplayer\.com/[^/]+/[^/]+/[^/]+/[^/]+/(?P<display_id>.+?)_(?P<id>[0-9a-f]{24})'
+ _VALID_URL = r'https?://(?:www\.)?atresplayer\.com/(?:[^/?#]+/){4}(?P<display_id>.+?)_(?P<id>[0-9a-f]{24})'
_NETRC_MACHINE = 'atresplayer'
- _TESTS = [
- {
- 'url': 'https://www.atresplayer.com/antena3/series/pequenas-coincidencias/temporada-1/capitulo-7-asuntos-pendientes_5d4aa2c57ed1a88fc715a615/',
- 'info_dict': {
- 'id': '5d4aa2c57ed1a88fc715a615',
- 'ext': 'mp4',
- 'title': 'Capítulo 7: Asuntos pendientes',
- 'description': 'md5:7634cdcb4d50d5381bedf93efb537fbc',
- 'duration': 3413,
- },
- 'skip': 'This video is only available for registered users',
+ _TESTS = [{
+ 'url': 'https://www.atresplayer.com/lasexta/programas/el-objetivo/clips/mbappe-describe-como-entrenador-a-carlo-ancelotti-sabe-cuando-tiene-que-ser-padre-jefe-amigo-entrenador_67f2dfb2fb6ab0e4c7203849/',
+ 'info_dict': {
+ 'ext': 'mp4',
+ 'id': '67f2dfb2fb6ab0e4c7203849',
+ 'display_id': 'md5:c203f8d4e425ed115ba56a1c6e4b3e6c',
+ 'title': 'Mbappé describe como entrenador a Carlo Ancelotti: "Sabe cuándo tiene que ser padre, jefe, amigo, entrenador..."',
+ 'channel': 'laSexta',
+ 'duration': 31,
+ 'thumbnail': 'https://imagenes.atresplayer.com/atp/clipping/cmsimages02/2025/04/06/B02DBE1E-D59B-4683-8404-1A9595D15269/1920x1080.jpg',
+ 'tags': ['Entrevista informativa', 'Actualidad', 'Debate informativo', 'Política', 'Economía', 'Sociedad', 'Cara a cara', 'Análisis', 'Más periodismo'],
+ 'series': 'El Objetivo',
+ 'season': 'Temporada 12',
+ 'timestamp': 1743970079,
+ 'upload_date': '20250406',
},
- {
- 'url': 'https://www.atresplayer.com/lasexta/programas/el-club-de-la-comedia/temporada-4/capitulo-10-especial-solidario-nochebuena_5ad08edf986b2855ed47adc4/',
- 'only_matching': True,
+ }, {
+ 'url': 'https://www.atresplayer.com/antena3/programas/el-hormiguero/clips/revive-la-entrevista-completa-a-miguel-bose-en-el-hormiguero_67f836baa4a5b0e4147ca59a/',
+ 'info_dict': {
+ 'ext': 'mp4',
+ 'id': '67f836baa4a5b0e4147ca59a',
+ 'display_id': 'revive-la-entrevista-completa-a-miguel-bose-en-el-hormiguero',
+ 'title': 'Revive la entrevista completa a Miguel Bosé en El Hormiguero',
+ 'description': 'md5:c6d2b591408d45a7bc2986dfb938eb72',
+ 'channel': 'Antena 3',
+ 'duration': 2556,
+ 'thumbnail': 'https://imagenes.atresplayer.com/atp/clipping/cmsimages02/2025/04/10/9076395F-F1FD-48BE-9F18-540DBA10EBAD/1920x1080.jpg',
+ 'tags': ['Entrevista', 'Variedades', 'Humor', 'Entretenimiento', 'Te sigo', 'Buen rollo', 'Cara a cara'],
+ 'series': 'El Hormiguero ',
+ 'season': 'Temporada 14',
+ 'timestamp': 1744320111,
+ 'upload_date': '20250410',
},
- {
- 'url': 'https://www.atresplayer.com/antena3/series/el-secreto-de-puente-viejo/el-chico-de-los-tres-lunares/capitulo-977-29-12-14_5ad51046986b2886722ccdea/',
- 'only_matching': True,
+ }, {
+ 'url': 'https://www.atresplayer.com/flooxer/series/biara-proyecto-lazarus/temporada-1/capitulo-3-supervivientes_67a6038b64ceca00070f4f69/',
+ 'info_dict': {
+ 'ext': 'mp4',
+ 'id': '67a6038b64ceca00070f4f69',
+ 'display_id': 'capitulo-3-supervivientes',
+ 'title': 'Capítulo 3: Supervivientes',
+ 'description': 'md5:65b231f20302f776c2b0dd24594599a1',
+ 'channel': 'Flooxer',
+ 'duration': 1196,
+ 'thumbnail': 'https://imagenes.atresplayer.com/atp/clipping/cmsimages01/2025/02/14/17CF90D3-FE67-40C5-A941-7825B3E13992/1920x1080.jpg',
+ 'tags': ['Juvenil', 'Terror', 'Piel de gallina', 'Te sigo', 'Un break', 'Del tirón'],
+ 'series': 'BIARA: Proyecto Lázarus',
+ 'season': 'Temporada 1',
+ 'season_number': 1,
+ 'episode': 'Episode 3',
+ 'episode_number': 3,
+ 'timestamp': 1743095191,
+ 'upload_date': '20250327',
},
- ]
+ }, {
+ 'url': 'https://www.atresplayer.com/lasexta/programas/el-club-de-la-comedia/temporada-4/capitulo-10-especial-solidario-nochebuena_5ad08edf986b2855ed47adc4/',
+ 'only_matching': True,
+ }, {
+ 'url': 'https://www.atresplayer.com/antena3/series/el-secreto-de-puente-viejo/el-chico-de-los-tres-lunares/capitulo-977-29-12-14_5ad51046986b2886722ccdea/',
+ 'only_matching': True,
+ }]
_API_BASE = 'https://api.atresplayer.com/'
def _perform_login(self, username, password):
- self._request_webpage(
- self._API_BASE + 'login', None, 'Downloading login page')
-
try:
- target_url = self._download_json(
- 'https://account.atresmedia.com/api/login', None,
- 'Logging in', headers={
- 'Content-Type': 'application/x-www-form-urlencoded',
- }, data=urlencode_postdata({
+ self._download_webpage(
+ 'https://account.atresplayer.com/auth/v1/login', None,
+ 'Logging in', 'Failed to log in', data=urlencode_postdata({
'username': username,
'password': password,
- }))['targetUrl']
+ }))
except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status == 400:
raise ExtractorError('Invalid username and/or password', expected=True)
raise
- self._request_webpage(target_url, None, 'Following Target URL')
-
def _real_extract(self, url):
display_id, video_id = self._match_valid_url(url).groups()
+ metadata_url = self._download_json(
+ self._API_BASE + 'client/v1/url', video_id, 'Downloading API endpoint data',
+ query={'href': urllib.parse.urlparse(url).path})['href']
+ metadata = self._download_json(metadata_url, video_id)
+
try:
- episode = self._download_json(
- self._API_BASE + 'client/v1/player/episode/' + video_id, video_id)
+ video_data = self._download_json(metadata['urlVideo'], video_id, 'Downloading video data')
except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status == 403:
error = self._parse_json(e.cause.response.read(), None)
@@ -67,37 +108,45 @@ class AtresPlayerIE(InfoExtractor):
raise ExtractorError(error['error_description'], expected=True)
raise
- title = episode['titulo']
-
formats = []
subtitles = {}
- for source in episode.get('sources', []):
- src = source.get('src')
- if not src:
- continue
+ for source in traverse_obj(video_data, ('sources', lambda _, v: url_or_none(v['src']))):
+ src_url = source['src']
src_type = source.get('type')
- if src_type == 'application/vnd.apple.mpegurl':
- formats, subtitles = self._extract_m3u8_formats(
- src, video_id, 'mp4', 'm3u8_native',
- m3u8_id='hls', fatal=False)
- elif src_type == 'application/dash+xml':
- formats, subtitles = self._extract_mpd_formats(
- src, video_id, mpd_id='dash', fatal=False)
-
- heartbeat = episode.get('heartbeat') or {}
- omniture = episode.get('omniture') or {}
- get_meta = lambda x: heartbeat.get(x) or omniture.get(x)
+ if src_type in ('application/vnd.apple.mpegurl', 'application/hls+legacy', 'application/hls+hevc'):
+ fmts, subs = self._extract_m3u8_formats_and_subtitles(
+ src_url, video_id, 'mp4', m3u8_id='hls', fatal=False)
+ elif src_type in ('application/dash+xml', 'application/dash+hevc'):
+ fmts, subs = self._extract_mpd_formats_and_subtitles(
+ src_url, video_id, mpd_id='dash', fatal=False)
+ else:
+ continue
+ formats.extend(fmts)
+ self._merge_subtitles(subs, target=subtitles)
return {
'display_id': display_id,
'id': video_id,
- 'title': title,
- 'description': episode.get('descripcion'),
- 'thumbnail': episode.get('imgPoster'),
- 'duration': int_or_none(episode.get('duration')),
'formats': formats,
- 'channel': get_meta('channel'),
- 'season': get_meta('season'),
- 'episode_number': int_or_none(get_meta('episodeNumber')),
'subtitles': subtitles,
+ **traverse_obj(video_data, {
+ 'title': ('titulo', {str}),
+ 'description': ('descripcion', {str}),
+ 'duration': ('duration', {int_or_none}),
+ 'thumbnail': ('imgPoster', {url_or_none}, {lambda v: f'{v}1920x1080.jpg'}),
+ 'age_limit': ('ageRating', {parse_age_limit}),
+ }),
+ **traverse_obj(metadata, {
+ 'title': ('title', {str}),
+ 'description': ('description', {str}),
+ 'duration': ('duration', {int_or_none}),
+ 'tags': ('tags', ..., 'title', {str}),
+ 'age_limit': ('ageRating', {parse_age_limit}),
+ 'series': ('format', 'title', {str}),
+ 'season': ('currentSeason', 'title', {str}),
+ 'season_number': ('currentSeason', 'seasonNumber', {int_or_none}),
+ 'episode_number': ('numberOfEpisode', {int_or_none}),
+ 'timestamp': ('publicationDate', {int_or_none(scale=1000)}),
+ 'channel': ('channel', 'title', {str}),
+ }),
}