mirror of
https://github.com/ytdl-org/youtube-dl.git
synced 2025-07-23 20:18:25 +00:00
[FranceTV] Back-port and update extractors
* FranceTVIE: updated * FranceTVSiteIE: updated * FranceTVInfoIE: updated, add la1ere, use franceinfo.fr * FranceTVInfoSportIE: legacy redirect extractor * FranceTVJeunesseIE: obsolete, not `_WORKING` * GenerationWhatIE: obsolete, not `_WORKING` * CultureboxIE: legacy redirect extractor
This commit is contained in:
parent
a084c80f7b
commit
cbbe117d9d
|
@ -1,70 +1,132 @@
|
|||
# coding: utf-8
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
|
||||
from .dailymotion import DailymotionIE
|
||||
from .youtube import YoutubeIE
|
||||
from ..compat import (
|
||||
compat_str,
|
||||
compat_urlparse,
|
||||
compat_str as str,
|
||||
compat_urllib_parse,
|
||||
)
|
||||
from ..utils import (
|
||||
clean_html,
|
||||
determine_ext,
|
||||
extract_attributes,
|
||||
ExtractorError,
|
||||
filter_dict,
|
||||
# format_field,
|
||||
HEADRequest,
|
||||
int_or_none,
|
||||
join_nonempty,
|
||||
merge_dicts,
|
||||
parse_duration,
|
||||
try_get,
|
||||
parse_iso8601,
|
||||
smuggle_url,
|
||||
T,
|
||||
traverse_obj,
|
||||
unsmuggle_url,
|
||||
url_or_none,
|
||||
urljoin,
|
||||
)
|
||||
from .dailymotion import DailymotionIE
|
||||
|
||||
try:
|
||||
callable(format_field)
|
||||
except NameError:
|
||||
from ..utils import IDENTITY, NO_DEFAULT, variadic
|
||||
|
||||
def format_field(obj, field=None, template='%s', ignore=NO_DEFAULT, default='', func=IDENTITY):
|
||||
val = traverse_obj(obj, *variadic(field))
|
||||
if not (val if ignore is NO_DEFAULT else val in variadic(ignore)):
|
||||
return default
|
||||
return template % (func(val),)
|
||||
|
||||
|
||||
class FranceTVBaseInfoExtractor(InfoExtractor):
|
||||
def _make_url_result(self, video_or_full_id, catalog=None):
|
||||
full_id = 'francetv:%s' % video_or_full_id
|
||||
if '@' not in video_or_full_id and catalog:
|
||||
full_id += '@%s' % catalog
|
||||
return self.url_result(
|
||||
full_id, ie=FranceTVIE.ie_key(),
|
||||
video_id=video_or_full_id.split('@')[0])
|
||||
class FranceTVBaseIE(InfoExtractor):
|
||||
@classmethod
|
||||
def _make_url_result(cls, video_or_full_id, url=None):
|
||||
video_id = video_or_full_id.partition('@')[0] # for compat with old @catalog IDs
|
||||
full_id = 'francetv:%s' % (video_id,)
|
||||
if url:
|
||||
full_id = smuggle_url(full_id, {
|
||||
'hostname': compat_urllib_parse.urlsplit(url).hostname,
|
||||
})
|
||||
return cls.url_result(full_id, ie=FranceTVIE.ie_key())
|
||||
|
||||
|
||||
class FranceTVIE(InfoExtractor):
|
||||
_VALID_URL = r'''(?x)
|
||||
(?:
|
||||
https?://
|
||||
sivideo\.webservices\.francetelevisions\.fr/tools/getInfosOeuvre/v2/\?
|
||||
.*?\bidDiffusion=[^&]+|
|
||||
(?:
|
||||
https?://videos\.francetv\.fr/video/|
|
||||
francetv:
|
||||
)
|
||||
(?P<id>[^@]+)(?:@(?P<catalog>.+))?
|
||||
)
|
||||
'''
|
||||
IE_NAME = 'francetv'
|
||||
_VALID_URL = r'francetv:(?P<id>[^@#]+)'
|
||||
_GEO_COUNTRIES = ['FR']
|
||||
_GEO_BYPASS = False
|
||||
|
||||
_TESTS = [{
|
||||
# without catalog
|
||||
'url': 'https://sivideo.webservices.francetelevisions.fr/tools/getInfosOeuvre/v2/?idDiffusion=162311093&callback=_jsonp_loader_callback_request_0',
|
||||
'md5': 'c2248a8de38c4e65ea8fae7b5df2d84f',
|
||||
# tokenized url is in dinfo['video']['token']
|
||||
'url': 'francetv:ec217ecc-0733-48cf-ac06-af1347b849d1',
|
||||
'info_dict': {
|
||||
'id': '162311093',
|
||||
'id': 'ec217ecc-0733-48cf-ac06-af1347b849d1',
|
||||
'ext': 'mp4',
|
||||
'title': '13h15, le dimanche... - Les mystères de Jésus',
|
||||
'description': 'md5:75efe8d4c0a8205e5904498ffe1e1a42',
|
||||
'timestamp': 1502623500,
|
||||
'upload_date': '20170813',
|
||||
'duration': 2580,
|
||||
'thumbnail': r're:^https?://.*\.jpg$',
|
||||
},
|
||||
'skip': 'Cette vidéo n\'est malheureusement plus disponible.',
|
||||
}, {
|
||||
# with catalog
|
||||
'url': 'https://sivideo.webservices.francetelevisions.fr/tools/getInfosOeuvre/v2/?idDiffusion=NI_1004933&catalogue=Zouzous&callback=_jsonp_loader_callback_request_4',
|
||||
'only_matching': True,
|
||||
# tokenized url is in dinfo['video']['token']['akamai']
|
||||
'url': 'francetv:c5bda21d-2c6f-4470-8849-3d8327adb2ba',
|
||||
'info_dict': {
|
||||
'id': 'c5bda21d-2c6f-4470-8849-3d8327adb2ba',
|
||||
'ext': 'mp4',
|
||||
'title': '13h15, le dimanche... - Les mystères de Jésus',
|
||||
'description': r're:C\'est dans une église .{145} des décors restaurés que le temps avait effacé\.$',
|
||||
'timestamp': 1514118300,
|
||||
'upload_date': '20171224',
|
||||
'duration': 2880,
|
||||
'thumbnail': r're:^https?://.*\.jpg$',
|
||||
},
|
||||
'skip': 'Cette vidéo n\'est malheureusement plus disponible.',
|
||||
}, {
|
||||
'url': 'http://videos.francetv.fr/video/NI_657393@Regions',
|
||||
'only_matching': True,
|
||||
'url': 'francetv:be492d9f-44c6-4ad0-8356-fb35d7c30e62',
|
||||
'info_dict': {
|
||||
'id': 'be492d9f-44c6-4ad0-8356-fb35d7c30e62',
|
||||
'ext': 'mp4',
|
||||
'title': '13h15, le dimanche - Diplomatie : La France face aux empires (épisode 3)',
|
||||
'description': r're:Le 29 avril dernier, Donald Trump .{485} doit fixer un nouveau cap, et des valeurs communes\.$',
|
||||
'timestamp': 1746965738,
|
||||
'upload_date': '20250511',
|
||||
'duration': 2948,
|
||||
'thumbnail': r're:https?:/(?:/[\w.-]+)+\.jpg$',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': 'needs ffmpeg',
|
||||
'format': 'best/bestvideo',
|
||||
},
|
||||
'expected_warnings': ['hlsnative has detected features it does not support'],
|
||||
}, {
|
||||
'url': 'francetv:538f8a8a-2c44-11f0-84d6-19616fa871f9',
|
||||
'info_dict': {
|
||||
'id': '538f8a8a-2c44-11f0-84d6-19616fa871f9',
|
||||
'ext': 'mp4',
|
||||
'title': 'Angélique Kidjo chante "Imagine" de John Lennon',
|
||||
'timestamp': 1746731994,
|
||||
'upload_date': '20250508',
|
||||
'duration': 226,
|
||||
'thumbnail': r're:https?:/(?:/[\w.-]+)+\.jpg$',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': 'needs ffmpeg',
|
||||
'format': 'best/bestvideo',
|
||||
},
|
||||
'expected_warnings': [
|
||||
'hlsnative has detected features it does not support',
|
||||
'Failed to download (?:MPD manifest|m3u8 information)',
|
||||
],
|
||||
'skip': 'geo-restricted to FR',
|
||||
}, {
|
||||
'url': 'francetv:162311093',
|
||||
'only_matching': True,
|
||||
|
@ -86,102 +148,65 @@ class FranceTVIE(InfoExtractor):
|
|||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _extract_video(self, video_id, catalogue=None):
|
||||
# Videos are identified by idDiffusion so catalogue part is optional.
|
||||
# However when provided, some extra formats may be returned so we pass
|
||||
# it if available.
|
||||
info = self._download_json(
|
||||
'https://sivideo.webservices.francetelevisions.fr/tools/getInfosOeuvre/v2/',
|
||||
video_id, 'Downloading video JSON', query={
|
||||
'idDiffusion': video_id,
|
||||
'catalogue': catalogue or '',
|
||||
})
|
||||
def _extract_formats_and_subtitles(self, video_json, video_id):
|
||||
formats, subtitles, video_url = [], {}, None
|
||||
|
||||
if info.get('status') == 'NOK':
|
||||
raise ExtractorError(
|
||||
'%s returned error: %s' % (self.IE_NAME, info['message']),
|
||||
expected=True)
|
||||
allowed_countries = info['videos'][0].get('geoblocage')
|
||||
if allowed_countries:
|
||||
georestricted = True
|
||||
geo_info = self._download_json(
|
||||
'http://geo.francetv.fr/ws/edgescape.json', video_id,
|
||||
'Downloading geo restriction info')
|
||||
country = geo_info['reponse']['geo_info']['country_code']
|
||||
if country not in allowed_countries:
|
||||
raise ExtractorError(
|
||||
'The video is not available from your location',
|
||||
expected=True)
|
||||
else:
|
||||
georestricted = False
|
||||
|
||||
def sign(manifest_url, manifest_id):
|
||||
for host in ('hdfauthftv-a.akamaihd.net', 'hdfauth.francetv.fr'):
|
||||
signed_url = url_or_none(self._download_webpage(
|
||||
'https://%s/esi/TA' % host, video_id,
|
||||
'Downloading signed %s manifest URL' % manifest_id,
|
||||
fatal=False, query={
|
||||
'url': manifest_url,
|
||||
}))
|
||||
if signed_url:
|
||||
return signed_url
|
||||
return manifest_url
|
||||
|
||||
is_live = None
|
||||
|
||||
videos = []
|
||||
|
||||
for video in (info.get('videos') or []):
|
||||
if video.get('statut') != 'ONLINE':
|
||||
continue
|
||||
if not video.get('url'):
|
||||
continue
|
||||
videos.append(video)
|
||||
|
||||
if not videos:
|
||||
for device_type in ['desktop', 'mobile']:
|
||||
fallback_info = self._download_json(
|
||||
'https://player.webservices.francetelevisions.fr/v1/videos/%s' % video_id,
|
||||
video_id, 'Downloading fallback %s video JSON' % device_type, query={
|
||||
'device_type': device_type,
|
||||
'browser': 'chrome',
|
||||
}, fatal=False)
|
||||
|
||||
if fallback_info and fallback_info.get('video'):
|
||||
videos.append(fallback_info['video'])
|
||||
|
||||
formats = []
|
||||
for video in videos:
|
||||
video_url = video.get('url')
|
||||
if not video_url:
|
||||
continue
|
||||
if is_live is None:
|
||||
is_live = (try_get(
|
||||
video, lambda x: x['plages_ouverture'][0]['direct'], bool) is True
|
||||
or video.get('is_live') is True
|
||||
or '/live.francetv.fr/' in video_url)
|
||||
def maybe_tokenize_video_url(video):
|
||||
video_url = video['url']
|
||||
format_id = video.get('format')
|
||||
token_url = traverse_obj(video, (
|
||||
'token', (None, 'akamai'), T(url_or_none)), get_all=False)
|
||||
if token_url:
|
||||
tokenized_url = self._download_json(
|
||||
token_url, video_id, 'Downloading signed {0} manifest URL'.format(format_id),
|
||||
fatal=False, query={
|
||||
'format': 'json',
|
||||
'url': video_url,
|
||||
})
|
||||
video_url = traverse_obj(tokenized_url, (
|
||||
'url', T(url_or_none))) or video_url
|
||||
return video_url, format_id
|
||||
|
||||
for video in traverse_obj(video_json, (
|
||||
lambda _, v: v.get('url'), {
|
||||
'url': ('url', T(url_or_none)),
|
||||
'format': 'format',
|
||||
'token': 'token',
|
||||
}, T(lambda x: x if x['url'] else None))):
|
||||
video_url, format_id = maybe_tokenize_video_url(video)
|
||||
|
||||
ext = determine_ext(video_url)
|
||||
if ext == 'f4m':
|
||||
if georestricted:
|
||||
# See https://github.com/ytdl-org/youtube-dl/issues/3963
|
||||
# m3u8 urls work fine
|
||||
continue
|
||||
formats.extend(self._extract_f4m_formats(
|
||||
sign(video_url, format_id) + '&hdcore=3.7.0&plugin=aasp-3.7.0.39.44',
|
||||
video_id, f4m_id=format_id, fatal=False))
|
||||
video_url, video_id, f4m_id=format_id or ext, fatal=False))
|
||||
elif ext == 'm3u8':
|
||||
formats.extend(self._extract_m3u8_formats(
|
||||
sign(video_url, format_id), video_id, 'mp4',
|
||||
entry_protocol='m3u8_native', m3u8_id=format_id,
|
||||
fatal=False))
|
||||
format_id = format_id or 'hls'
|
||||
# fmts, subs = self._extract_m3u8_formats_and_subtitles(
|
||||
fmts, subs = self._extract_m3u8_formats(
|
||||
video_url, video_id, 'mp4', m3u8_id=format_id,
|
||||
entry_protocol='m3u8_native',
|
||||
fatal=False), {}
|
||||
for f in traverse_obj(fmts, lambda _, v: (
|
||||
v['vcodec'] == 'none' and v.get('tbr') is None)):
|
||||
tbr = traverse_obj(re.match(
|
||||
r'{0}-[Aa]udio-\w+-(\d+)'.format(format_id),
|
||||
f['format_id']), (1, T(int_or_none)))
|
||||
if tbr is not None:
|
||||
f.update({
|
||||
'tbr': tbr,
|
||||
'acodec': 'mp4a',
|
||||
})
|
||||
formats.extend(fmts)
|
||||
self._merge_subtitles(subs, target=subtitles)
|
||||
elif ext == 'mpd':
|
||||
formats.extend(self._extract_mpd_formats(
|
||||
sign(video_url, format_id), video_id, mpd_id=format_id, fatal=False))
|
||||
fmts, subs = self._extract_mpd_formats_and_subtitles(
|
||||
video_url, video_id, mpd_id=format_id or 'dash', fatal=False)
|
||||
formats.extend(fmts)
|
||||
self._merge_subtitles(subs, target=subtitles)
|
||||
elif video_url.startswith('rtmp'):
|
||||
formats.append({
|
||||
'url': video_url,
|
||||
'format_id': 'rtmp-%s' % format_id,
|
||||
'format_id': join_nonempty('rtmp', format_id),
|
||||
'ext': 'flv',
|
||||
})
|
||||
else:
|
||||
|
@ -191,66 +216,223 @@ class FranceTVIE(InfoExtractor):
|
|||
'format_id': format_id,
|
||||
})
|
||||
|
||||
self._sort_formats(formats)
|
||||
# XXX: what is video['captions']?
|
||||
|
||||
title = info['titre']
|
||||
subtitle = info.get('sous_titre')
|
||||
if subtitle:
|
||||
title += ' - %s' % subtitle
|
||||
title = title.strip()
|
||||
if not formats and video_url:
|
||||
urlh = self._request_webpage(
|
||||
HEADRequest(video_url), video_id, 'Checking for geo-restriction',
|
||||
fatal=False, expected_status=403)
|
||||
if urlh and urlh.headers.get('x-errortype') == 'geo':
|
||||
self.raise_geo_restricted(countries=self._GEO_COUNTRIES) # , metadata_available=True)
|
||||
|
||||
subtitles = {}
|
||||
subtitles_list = [{
|
||||
'url': subformat['url'],
|
||||
'ext': subformat.get('format'),
|
||||
} for subformat in info.get('subtitles', []) if subformat.get('url')]
|
||||
if subtitles_list:
|
||||
subtitles['fr'] = subtitles_list
|
||||
self._sort_formats(formats, field_preference=('res', 'tbr', 'proto'))
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': self._live_title(title) if is_live else title,
|
||||
'description': clean_html(info.get('synopsis')),
|
||||
'thumbnail': urljoin('https://sivideo.webservices.francetelevisions.fr', info.get('image')),
|
||||
'duration': int_or_none(info.get('real_duration')) or parse_duration(info.get('duree')),
|
||||
'timestamp': int_or_none(try_get(info, lambda x: x['diffusion']['timestamp'])),
|
||||
'is_live': is_live,
|
||||
for f in formats:
|
||||
if f.get('acodec') != 'none' and f.get('language') in ('qtz', 'qad'):
|
||||
f['language_preference'] = -10
|
||||
f['format_note'] = 'audio description{0}'.format(format_field(f, 'format_note', ', %s'))
|
||||
|
||||
return formats, subtitles
|
||||
|
||||
def _extract_video(self, video_id, hostname=None):
|
||||
videos = []
|
||||
info = {'id': video_id}
|
||||
drm_formats = False
|
||||
|
||||
# desktop+chrome returns dash; mobile+safari returns hls
|
||||
for device_type, browser in [('desktop', 'chrome'), ('mobile', 'safari')]:
|
||||
dinfo = self._download_json(
|
||||
'https://k7.ftven.fr/videos/{0}'.format(video_id), video_id,
|
||||
'Downloading {0} {1} video JSON'.format(device_type, browser), query=filter_dict({
|
||||
'device_type': device_type,
|
||||
'browser': browser,
|
||||
'domain': hostname,
|
||||
}), fatal=False, expected_status=422) # 422 json gives detailed error code/message
|
||||
|
||||
if not dinfo:
|
||||
continue
|
||||
|
||||
video = traverse_obj(dinfo, ('video', T(dict)))
|
||||
code = traverse_obj(dinfo, ('code', T(int_or_none)))
|
||||
if video:
|
||||
videos.append(video)
|
||||
info = merge_dicts(info, traverse_obj(video, {
|
||||
'duration': 'duration',
|
||||
'is_live': 'is_live',
|
||||
}))
|
||||
elif code:
|
||||
if code == 2009:
|
||||
self.raise_geo_restricted(countries=self._GEO_COUNTRIES)
|
||||
elif code in (2015, 2017):
|
||||
# 2015: L'accès à cette vidéo est impossible. (DRM-only)
|
||||
# 2017: Cette vidéo n'est pas disponible depuis le site web mobile (b/c DRM)
|
||||
drm_formats = True
|
||||
continue
|
||||
self.report_warning('{0} said: "({1}) {2}"'.format(
|
||||
self.IE_NAME, code, clean_html(dinfo.get('message'))))
|
||||
continue
|
||||
|
||||
# avoid duplicating title text
|
||||
titles = ('title', 'additional_title')
|
||||
title_parts = [
|
||||
re.sub(r'(?:_|[^\w])+', ' ', t).strip()
|
||||
for t in traverse_obj(dinfo, ('meta', titles))]
|
||||
if len(title_parts) == 2 and title_parts[0] == title_parts[1]:
|
||||
titles = titles[:1]
|
||||
|
||||
info = merge_dicts(info, traverse_obj(dinfo, ('meta', {
|
||||
'title': T(lambda d: join_nonempty(
|
||||
*((d.get(t) or '').strip() or None
|
||||
for t in titles), delim=' - ')),
|
||||
'description': ('description', T(lambda s: s.strip() or None)),
|
||||
'thumbnail': ('image_url', T(url_or_none)),
|
||||
'timestamp': ('broadcasted_at', T(parse_iso8601)),
|
||||
'duration': ('playtime', T(parse_duration)),
|
||||
'alt_title': 'additional_title',
|
||||
})), traverse_obj(dinfo, (
|
||||
# meta['pre_title'] contains season and episode number for series in format "S<ID> E<ID>"
|
||||
'meta', 'pre_title', T(lambda x: re.search(
|
||||
r'S(\d+)\s*E(\d+)', x)), {
|
||||
'season_number': (1, T(int_or_none)),
|
||||
'episode_number': (2, T(int_or_none)),
|
||||
})))
|
||||
|
||||
if not videos and drm_formats:
|
||||
self.report_drm(video_id)
|
||||
|
||||
formats, subtitles = self._extract_formats_and_subtitles(videos, video_id)
|
||||
|
||||
if info.get('is_live'):
|
||||
info['title'] = self._live_title(info['title'])
|
||||
|
||||
return merge_dicts(info, {
|
||||
'formats': formats,
|
||||
'subtitles': subtitles,
|
||||
}
|
||||
# '_format_sort_fields': ('res', 'tbr', 'proto'), # prioritize m3u8 over dash
|
||||
}, {
|
||||
'episode': info.get('alt_title'),
|
||||
'series': info['title'],
|
||||
} if info.get('episode_number') else {})
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
catalog = mobj.group('catalog')
|
||||
url, smuggled_data = unsmuggle_url(url, {})
|
||||
video_id = self._match_id(url)
|
||||
hostname = smuggled_data.get('hostname') or 'www.france.tv'
|
||||
|
||||
if not video_id:
|
||||
qs = compat_urlparse.parse_qs(compat_urlparse.urlparse(url).query)
|
||||
video_id = qs.get('idDiffusion', [None])[0]
|
||||
catalog = qs.get('catalogue', [None])[0]
|
||||
if not video_id:
|
||||
raise ExtractorError('Invalid URL', expected=True)
|
||||
|
||||
return self._extract_video(video_id, catalog)
|
||||
return self._extract_video(video_id, hostname=hostname)
|
||||
|
||||
|
||||
class FranceTVSiteIE(FranceTVBaseInfoExtractor):
|
||||
class FranceTVSiteIE(FranceTVBaseIE):
|
||||
IE_NAME = 'francetv:site'
|
||||
_VALID_URL = r'https?://(?:(?:www\.)?france\.tv|mobile\.france\.tv)/(?:[^/]+/)*(?P<id>[^/]+)\.html'
|
||||
|
||||
_TESTS = [{
|
||||
'url': 'https://www.france.tv/france-2/13h15-le-dimanche/140921-les-mysteres-de-jesus.html',
|
||||
'info_dict': {
|
||||
'id': 'ec217ecc-0733-48cf-ac06-af1347b849d1',
|
||||
'id': 'ec217ecc-0733-48cf-ac06-af1347b849d1', # old: c5bda21d-2c6f-4470-8849-3d8327adb2ba'
|
||||
'ext': 'mp4',
|
||||
'title': '13h15, le dimanche... - Les mystères de Jésus',
|
||||
'description': 'md5:75efe8d4c0a8205e5904498ffe1e1a42',
|
||||
'timestamp': 1502623500,
|
||||
'duration': 2580,
|
||||
'thumbnail': r're:ttps?://.*\.jpg$',
|
||||
'upload_date': '20170813',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
'skip': 'La vidéo n\'est pas disponible',
|
||||
}, {
|
||||
'url': 'https://www.france.tv/france-2/diplomatie/7129460-la-france-face-aux-empires.html',
|
||||
'add_ie': [FranceTVIE.ie_key()],
|
||||
'info_dict': {
|
||||
'id': 'be492d9f-44c6-4ad0-8356-fb35d7c30e62',
|
||||
'ext': 'mp4',
|
||||
'title': '13h15, le dimanche - Diplomatie : La France face aux empires (épisode 3)',
|
||||
'description': r're:Le 29 avril dernier, Donald Trump .{485} doit fixer un nouveau cap, et des valeurs communes\.$',
|
||||
'timestamp': 1746965738,
|
||||
'upload_date': '20250511',
|
||||
'duration': 2948,
|
||||
'thumbnail': r're:https?:/(?:/[\w.-]+)+\.jpg$',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': 'needs ffmpeg',
|
||||
'format': 'best/bestvideo',
|
||||
},
|
||||
'expected_warnings': ['hlsnative has detected features it does not support'],
|
||||
}, {
|
||||
# geo-restricted
|
||||
'url': 'https://www.france.tv/enfants/six-huit-ans/foot2rue/saison-1/3066387-duel-au-vieux-port.html',
|
||||
'info_dict': {
|
||||
'id': 'a9050959-eedd-4b4a-9b0d-de6eeaa73e44',
|
||||
'ext': 'mp4',
|
||||
'title': 'Foot2Rue - Duel au vieux port',
|
||||
'episode': 'Duel au vieux port',
|
||||
'series': 'Foot2Rue',
|
||||
'episode_number': 1,
|
||||
'season_number': 1,
|
||||
'timestamp': 1642761360,
|
||||
'upload_date': '20220121',
|
||||
'season': 'Season 1',
|
||||
'thumbnail': r're:^https?://.*\.jpg$',
|
||||
'duration': 1441,
|
||||
},
|
||||
'skip': 'La vidéo n\'est pas disponible',
|
||||
}, {
|
||||
# geo-restricted
|
||||
'url': 'https://www.france.tv/spectacles-et-culture/musique-pop-rock-electro/7170893-angelique-kidjo-chante-imagine-de-john-lennon.html',
|
||||
'add_ie': [FranceTVIE.ie_key()],
|
||||
'info_dict': {
|
||||
'id': '538f8a8a-2c44-11f0-84d6-19616fa871f9',
|
||||
'ext': 'mp4',
|
||||
'title': 'Angélique Kidjo chante "Imagine" de John Lennon',
|
||||
'timestamp': 1746731994,
|
||||
'upload_date': '20250508',
|
||||
'duration': 226,
|
||||
'thumbnail': r're:https?:/(?:/[\w.-]+)+\.jpg$',
|
||||
},
|
||||
'expected_warnings': [
|
||||
'hlsnative has detected features it does not support',
|
||||
'Failed to download (?:MPD manifest|m3u8 information)',
|
||||
],
|
||||
'skip': 'Geo-restricted to FR',
|
||||
}, {
|
||||
# geo-restricted
|
||||
'url': 'https://la1ere.franceinfo.fr/martinique/programme-video/diffusion/4774522-origine-kongo.html',
|
||||
'add_ie': [FranceTVIE.ie_key()],
|
||||
'info_dict': {
|
||||
'id': '97790f3a-b23a-4004-bd9c-b89fde6f95bf',
|
||||
'ext': 'mp4',
|
||||
'title': 'Origine : Kongo',
|
||||
'timestamp': 1679680110,
|
||||
'upload_date': '20230424',
|
||||
'duration': 3146,
|
||||
'thumbnail': r're:https?:/(?:/[\w.-]+)+\.jpg$',
|
||||
},
|
||||
'expected_warnings': [
|
||||
'hlsnative has detected features it does not support',
|
||||
'Failed to download (?:MPD manifest|m3u8 information)',
|
||||
],
|
||||
'skip': 'Geo-restricted to FR',
|
||||
}, {
|
||||
# geo-restricted livestream (workflow == 'token-akamai')
|
||||
'url': 'https://www.france.tv/france-4/direct.html',
|
||||
'info_dict': {
|
||||
'id': '9a6a7670-dde9-4264-adbc-55b89558594b',
|
||||
'ext': 'mp4',
|
||||
'title': r're:France 4 en direct .+',
|
||||
'live_status': 'is_live',
|
||||
},
|
||||
'skip': 'geo-restricted livestream',
|
||||
}, {
|
||||
# livestream (workflow == 'dai')
|
||||
'url': 'https://www.france.tv/france-2/direct.html',
|
||||
'add_ie': [FranceTVIE.ie_key()],
|
||||
'info_dict': {
|
||||
'id': '006194ea-117d-4bcf-94a9-153d999c59ae',
|
||||
'ext': 'mp4',
|
||||
'title': r're:France 2 en direct \d{4}-[01]\d-[0-3]\d [0-2]\d:[0-5]\d$',
|
||||
# 'live_status': 'is_live',
|
||||
'is_live': True,
|
||||
},
|
||||
'params': {'skip_download': 'livestream', 'format': 'best/bestvideo'},
|
||||
}, {
|
||||
# france3
|
||||
'url': 'https://www.france.tv/france-3/des-chiffres-et-des-lettres/139063-emission-du-mardi-9-mai-2017.html',
|
||||
|
@ -267,10 +449,6 @@ class FranceTVSiteIE(FranceTVBaseInfoExtractor):
|
|||
# franceo
|
||||
'url': 'https://www.france.tv/france-o/archipels/132249-mon-ancetre-l-esclave.html',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
# france2 live
|
||||
'url': 'https://www.france.tv/france-2/direct.html',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.france.tv/documentaires/histoire/136517-argentine-les-500-bebes-voles-de-la-dictature.html',
|
||||
'only_matching': True,
|
||||
|
@ -289,86 +467,158 @@ class FranceTVSiteIE(FranceTVBaseInfoExtractor):
|
|||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _search_nextjs_data_new(self, html, video_id, paths, **kwargs):
|
||||
|
||||
def find_json(s):
|
||||
return self._search_json(
|
||||
r'\w+\s*:\s*', s, 'next js data', video_id, contains_pattern=r'\[([\s\S]+)\]', default=None)
|
||||
|
||||
def yield_nextjs_data(html):
|
||||
for m in re.finditer(r'<script\b[^>]*>\s*self\.__next_f\.push\(\s*(\[.+?\])\s*\);?\s*</script>', html):
|
||||
for from_ in traverse_obj(m, (
|
||||
1, T(json.loads),
|
||||
Ellipsis, T(find_json), Ellipsis)):
|
||||
yield from_
|
||||
|
||||
return traverse_obj(yield_nextjs_data(html), (Ellipsis,) + paths, **kwargs)
|
||||
|
||||
def _real_extract(self, url):
|
||||
display_id = self._match_id(url)
|
||||
|
||||
webpage = self._download_webpage(url, display_id)
|
||||
|
||||
catalogue = None
|
||||
video_id = self._search_regex(
|
||||
r'(?:data-main-video\s*=|videoId["\']?\s*[:=])\s*(["\'])(?P<id>(?:(?!\1).)+)\1',
|
||||
webpage, 'video id', default=None, group='id')
|
||||
E = Ellipsis # abbreviation
|
||||
nextjs_data = self._search_nextjs_data_new(
|
||||
webpage, display_id, (
|
||||
'children', E, E,
|
||||
'children', E, E, 'children'))
|
||||
|
||||
if traverse_obj(nextjs_data, (E, E, 'children', E, 'isLive', T(bool)),
|
||||
get_all=False):
|
||||
# For livestreams we need the id of the stream instead of the currently airing episode id
|
||||
video_id = traverse_obj(nextjs_data, (
|
||||
E, E, (None,
|
||||
('children', E, 'children', E, 'children', E, 'children',
|
||||
E, E, 'children', E, E, 'children', E, E, 'children')),
|
||||
(E, (E, E)), 'options', 'id', T(str)), get_all=False)
|
||||
else:
|
||||
video_id = traverse_obj(nextjs_data, (
|
||||
E, E, E, 'children',
|
||||
lambda _, v: v['video']['url'] == compat_urllib_parse.urlparse(url).path,
|
||||
'video', ('playerReplayId', 'siId'), T(str)), get_all=False)
|
||||
|
||||
if not video_id:
|
||||
video_id, catalogue = self._html_search_regex(
|
||||
r'(?:href=|player\.setVideo\(\s*)"http://videos?\.francetv\.fr/video/([^@]+@[^"]+)"',
|
||||
webpage, 'video ID').split('@')
|
||||
raise ExtractorError('Unable to extract video ID')
|
||||
|
||||
return self._make_url_result(video_id, catalogue)
|
||||
return self._make_url_result(video_id, url)
|
||||
|
||||
|
||||
class FranceTVEmbedIE(FranceTVBaseInfoExtractor):
|
||||
_VALID_URL = r'https?://embed\.francetv\.fr/*\?.*?\bue=(?P<id>[^&]+)'
|
||||
|
||||
class FranceTVInfoIE(FranceTVBaseIE):
|
||||
# new domain w/o `tv` as of 2025-05-14
|
||||
IE_NAME = 'franceinfo.fr'
|
||||
_VALID_URL = r'https?://(?:www|mobile|france3-regions|la1ere)\.franceinfo\.fr/(?:[^/]+/)*(?P<id>[^/?#&.]+)'
|
||||
_TESTS = [{
|
||||
'url': 'http://embed.francetv.fr/?ue=7fd581a2ccf59d2fc5719c5c13cf6961',
|
||||
'info_dict': {
|
||||
'id': 'NI_983319',
|
||||
'ext': 'mp4',
|
||||
'title': 'Le Pen Reims',
|
||||
'upload_date': '20170505',
|
||||
'timestamp': 1493981780,
|
||||
'duration': 16,
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
'url': 'https://www.franceinfo.fr/replay-jt/france-3/soir-3/jt-grand-soir-3-jeudi-22-aout-2019_3561461.html',
|
||||
'add_ie': [FranceTVIE.ie_key()],
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
|
||||
video = self._download_json(
|
||||
'http://api-embed.webservices.francetelevisions.fr/key/%s' % video_id,
|
||||
video_id)
|
||||
|
||||
return self._make_url_result(video['video_id'], video.get('catalog'))
|
||||
|
||||
|
||||
class FranceTVInfoIE(FranceTVBaseInfoExtractor):
|
||||
IE_NAME = 'francetvinfo.fr'
|
||||
_VALID_URL = r'https?://(?:www|mobile|france3-regions)\.francetvinfo\.fr/(?:[^/]+/)*(?P<id>[^/?#&.]+)'
|
||||
|
||||
_TESTS = [{
|
||||
'url': 'http://www.francetvinfo.fr/replay-jt/france-3/soir-3/jt-grand-soir-3-lundi-26-aout-2013_393427.html',
|
||||
'info_dict': {
|
||||
'id': '84981923',
|
||||
'id': 'd12458ee-5062-48fe-bfdd-a30d6a01b793',
|
||||
'ext': 'mp4',
|
||||
'title': 'Soir 3',
|
||||
'upload_date': '20130826',
|
||||
'timestamp': 1377548400,
|
||||
'title': 'Soir 3 - Émission du jeudi 22 août 2019',
|
||||
'description': 'Une heure d\'informations proposée par la rédaction nationale de la chaîne, avec des reportages, des débats, des invités et des chroniques.',
|
||||
'timestamp': 1566510730,
|
||||
'upload_date': '20190822',
|
||||
'thumbnail': r're:^https?://.*\.jpe?g$',
|
||||
'duration': 1637,
|
||||
'subtitles': {
|
||||
'fr': 'mincount:2',
|
||||
# TODO: 'fr': 'mincount:2',
|
||||
},
|
||||
},
|
||||
'params': {
|
||||
'format': 'best/bestvideo',
|
||||
'skip_download': True,
|
||||
},
|
||||
}, {
|
||||
'note': 'Only an image exists in initial webpage instead of the video',
|
||||
'url': 'https://www.franceinfo.fr/sante/maladie/coronavirus/covid-19-en-inde-une-situation-catastrophique-a-new-dehli_4381095.html',
|
||||
'add_ie': [FranceTVIE.ie_key()],
|
||||
'info_dict': {
|
||||
'id': '7d204c9e-a2d3-11eb-9e4c-000d3a23d482',
|
||||
'ext': 'mp4',
|
||||
'title': 'Covid-19 : une situation catastrophique à New Dehli - Édition du mercredi 21 avril 2021',
|
||||
'timestamp': 1619028518,
|
||||
'upload_date': '20210421',
|
||||
'thumbnail': r're:^https?://.*\.jpe?g$',
|
||||
'duration': 76,
|
||||
},
|
||||
'params': {
|
||||
'format': 'best/bestvideo',
|
||||
'skip_download': True,
|
||||
},
|
||||
}, {
|
||||
'url': 'http://www.francetvinfo.fr/elections/europeennes/direct-europeennes-regardez-le-debat-entre-les-candidats-a-la-presidence-de-la-commission_600639.html',
|
||||
'url': 'https://la1ere.franceinfo.fr/martinique/programme-video/diffusion/4774522-origine-kongo.html',
|
||||
'add_ie': [FranceTVIE.ie_key()],
|
||||
'info_dict': {
|
||||
'id': '97790f3a-b23a-4004-bd9c-b89fde6f95bf',
|
||||
'ext': 'mp4',
|
||||
'title': 'Origine : Kongo',
|
||||
'timestamp': 1679680110,
|
||||
'upload_date': '20230424',
|
||||
'duration': 3146,
|
||||
'thumbnail': r're:https?:/(?:/[\w.-]+)+\.jpg$',
|
||||
},
|
||||
'params': {
|
||||
'format': 'best/bestvideo',
|
||||
'skip_download': True,
|
||||
},
|
||||
'skip': 'Geo-restricted to FR',
|
||||
}, {
|
||||
'url': 'https://la1ere.franceinfo.fr/guadeloupe/programme-video/la1ere_guadeloupe_le-13h-en-guadeloupe/diffusion/5643549-emission-du-lundi-29-janvier-2024.html',
|
||||
'add_ie': [FranceTVIE.ie_key()],
|
||||
'info_dict': {
|
||||
'id': r're:[\w-]{35}',
|
||||
'ext': 'mp4',
|
||||
'title': 'Le 13H en Guadeloupe - Émission du lundi 29 janvier 2025',
|
||||
'description': 'Le 13h en Guadeloupe une édition avec la vocation de l\'hyper-proximité. Nos journalistes sur le terrain "duplex-interactivité-nouveaux systèmes d\'informations" une nouvelle écriture de l\'info',
|
||||
'timestamp': int,
|
||||
'upload_date': '20240129',
|
||||
'thumbnail': r're:^https?://.*\.png$',
|
||||
'duration': 1606,
|
||||
},
|
||||
'params': {
|
||||
'format': 'best/bestvideo',
|
||||
'skip_download': True,
|
||||
},
|
||||
'skip': 'expired?',
|
||||
}, {
|
||||
'url': 'https://la1ere.franceinfo.fr/guadeloupe/programme-video/la1ere_guadeloupe_le-13h-en-guadeloupe/diffusion/7151885-emission-du-lundi-12-mai-2025.html',
|
||||
'add_ie': [FranceTVIE.ie_key()],
|
||||
'info_dict': {
|
||||
'id': '7044dc7a-45d1-4dd0-b2a9-21ee31ec4e8c',
|
||||
'ext': 'mp4',
|
||||
'title': 'Le 13H en Guadeloupe - Émission du lundi 12 mai 2025',
|
||||
'description': 'Le 13h en Guadeloupe une édition avec la vocation de l\'hyper-proximité. Nos journalistes sur le terrain "duplex-interactivité-nouveaux systèmes d\'informations" une nouvelle écriture de l\'info',
|
||||
'timestamp': 1747069200,
|
||||
'upload_date': '20250512',
|
||||
'thumbnail': r're:^https?://.*\.png$',
|
||||
'duration': 1537,
|
||||
},
|
||||
'params': {
|
||||
'format': 'best/bestvideo',
|
||||
'skip_download': 'm3u8',
|
||||
},
|
||||
}, {
|
||||
'url': 'http://www.franceinfo.fr/elections/europeennes/direct-europeennes-regardez-le-debat-entre-les-candidats-a-la-presidence-de-la-commission_600639.html',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://www.francetvinfo.fr/economie/entreprises/les-entreprises-familiales-le-secret-de-la-reussite_933271.html',
|
||||
'url': 'http://www.franceinfo.fr/economie/entreprises/les-entreprises-familiales-le-secret-de-la-reussite_933271.html',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://france3-regions.francetvinfo.fr/bretagne/cotes-d-armor/thalassa-echappee-breizh-ce-venredi-dans-les-cotes-d-armor-954961.html',
|
||||
'url': 'http://france3-regions.franceinfo.fr/bretagne/cotes-d-armor/thalassa-echappee-breizh-ce-venredi-dans-les-cotes-d-armor-954961.html',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
# Dailymotion embed
|
||||
'url': 'http://www.francetvinfo.fr/politique/notre-dame-des-landes/video-sur-france-inter-cecile-duflot-denonce-le-regard-meprisant-de-patrick-cohen_1520091.html',
|
||||
'md5': 'ee7f1828f25a648addc90cb2687b1f12',
|
||||
'url': 'http://www.franceinfo.fr/politique/notre-dame-des-landes/video-sur-france-inter-cecile-duflot-denonce-le-regard-meprisant-de-patrick-cohen_1520091.html',
|
||||
'add_ie': ['Dailymotion'],
|
||||
'md5': '95550b6e6802c4c81d4b5b41fb84c691',
|
||||
'info_dict': {
|
||||
'id': 'x4iiko0',
|
||||
'ext': 'mp4',
|
||||
|
@ -378,14 +628,19 @@ class FranceTVInfoIE(FranceTVBaseInfoExtractor):
|
|||
'upload_date': '20160627',
|
||||
'uploader': 'France Inter',
|
||||
'uploader_id': 'x2q2ez',
|
||||
'view_count': int,
|
||||
'tags': ['Politique', 'France Inter', '27 juin 2016', 'Linvité de 8h20', 'Cécile Duflot', 'Patrick Cohen'],
|
||||
'age_limit': 0,
|
||||
'duration': 640,
|
||||
'like_count': int,
|
||||
'thumbnail': r're:https://[^/?#]+/v/[^/?#]+/x1080',
|
||||
},
|
||||
'add_ie': ['Dailymotion'],
|
||||
}, {
|
||||
'url': 'http://france3-regions.francetvinfo.fr/limousin/emissions/jt-1213-limousin',
|
||||
'url': 'http://france3-regions.franceinfo.fr/limousin/emissions/jt-1213-limousin',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
# "<figure id=" pattern (#28792)
|
||||
'url': 'https://www.francetvinfo.fr/culture/patrimoine/incendie-de-notre-dame-de-paris/notre-dame-de-paris-de-l-incendie-de-la-cathedrale-a-sa-reconstruction_4372291.html',
|
||||
'url': 'https://www.franceinfo.fr/culture/patrimoine/incendie-de-notre-dame-de-paris/notre-dame-de-paris-de-l-incendie-de-la-cathedrale-a-sa-reconstruction_4372291.html',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
|
@ -394,23 +649,64 @@ class FranceTVInfoIE(FranceTVBaseInfoExtractor):
|
|||
|
||||
webpage = self._download_webpage(url, display_id)
|
||||
|
||||
dailymotion_urls = DailymotionIE._extract_urls(webpage)
|
||||
if dailymotion_urls:
|
||||
return self.playlist_result([
|
||||
self.url_result(dailymotion_url, DailymotionIE.ie_key())
|
||||
for dailymotion_url in dailymotion_urls])
|
||||
def get_embed_entries(ie):
|
||||
urls = ie._extract_urls(webpage)
|
||||
if urls:
|
||||
return [self.url_result(embed_url, ie.ie_key())
|
||||
for embed_url in urls]
|
||||
|
||||
video_id = self._search_regex(
|
||||
(r'player\.load[^;]+src:\s*["\']([^"\']+)',
|
||||
r'id-video=([^@]+@[^"]+)',
|
||||
r'<a[^>]+href="(?:https?:)?//videos\.francetv\.fr/video/([^@]+@[^"]+)"',
|
||||
r'(?:data-id|<figure[^<]+\bid)=["\']([\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})'),
|
||||
webpage, 'video id')
|
||||
dailymotion_entries = get_embed_entries(DailymotionIE)
|
||||
if dailymotion_entries:
|
||||
return self.playlist_result(dailymotion_entries, display_id)
|
||||
|
||||
return self._make_url_result(video_id)
|
||||
result = {}
|
||||
player_button = extract_attributes(self._search_regex(
|
||||
r'(<button\s[^>]*(?<!-)\bdata-url\s*=\s*["\'][^>]{2,}>)',
|
||||
webpage, 'fi player button', default=''))
|
||||
if player_button.get('data-url'):
|
||||
result = merge_dicts(traverse_obj(player_button, {
|
||||
'id': 'data-expression-uuid',
|
||||
'timestamp': ('data-start-time', T(int_or_none)),
|
||||
'duration': T(lambda x: int(x['data-end-time']) - int(x['data-start-time'])),
|
||||
'title': 'data-extract-title',
|
||||
'description': 'data-diffusion-title',
|
||||
'series': 'data-emission-title',
|
||||
'url': ('data-url', T(url_or_none)),
|
||||
}), {
|
||||
'thumbnail': self._og_search_thumbnail(webpage, default=None),
|
||||
})
|
||||
if result.get('url'):
|
||||
if not result.get('title'):
|
||||
result['title'] = self._html_search_regex(
|
||||
r'''Retrouvez l'intégralité du\s+(.+)\s*:''',
|
||||
webpage, 'Alt title').replace('"', '')
|
||||
|
||||
if not result.get('id'):
|
||||
result['id'] = (
|
||||
extract_attributes(self._search_regex(
|
||||
r'(<button\s[^>]*(?<!-)\bdata-cy\s*=\s*("|\')francetv-player-wrapper\2[^>]*>)',
|
||||
webpage, 'player button', default='')).get('id')
|
||||
or self._search_regex((
|
||||
r'player\.load[^;]+src:\s*["\']([^"\']+)',
|
||||
r'id-video=([^@]+@[^"]+)',
|
||||
r'<a[^>]+href="(?:https?:)?//videos\.francetv\.fr/video/([^@]+@[^"]+)"',
|
||||
r'''(?x)(?:
|
||||
(?:(?:(?<!-)\bdata-(?:expression-uu)?id|<figure[^>]+\bid)=["'])|
|
||||
(?<!-)\bdata-piano="\{.[\s\S]*?"video_factory_id":")
|
||||
([\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12})
|
||||
'''), webpage, 'video id')
|
||||
)
|
||||
|
||||
if result.get('url'):
|
||||
yt_entries = get_embed_entries(YoutubeIE)
|
||||
return self.playlist_result(
|
||||
[result] + yt_entries, display_id) if yt_entries else result
|
||||
|
||||
return self._make_url_result(result['id'], url)
|
||||
|
||||
|
||||
class FranceTVInfoSportIE(FranceTVBaseInfoExtractor):
|
||||
class FranceTVInfoSportIE(FranceTVBaseIE):
|
||||
# now URLs redirect to franceinfo.fr, with /sports prepended if not present
|
||||
IE_NAME = 'sport.francetvinfo.fr'
|
||||
_VALID_URL = r'https?://sport\.francetvinfo\.fr/(?:[^/]+/)*(?P<id>[^/?#&]+)'
|
||||
_TESTS = [{
|
||||
|
@ -425,17 +721,17 @@ class FranceTVInfoSportIE(FranceTVBaseInfoExtractor):
|
|||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
'add_ie': [FranceTVIE.ie_key()],
|
||||
'add_ie': [FranceTVIE.ie_key(), 'Generic'],
|
||||
'skip': '404 La page que vous souhaitiez voir est inaccessible.',
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
display_id = self._match_id(url)
|
||||
webpage = self._download_webpage(url, display_id)
|
||||
video_id = self._search_regex(r'data-video="([^"]+)"', webpage, 'video_id')
|
||||
return self._make_url_result(video_id, 'Sport-web')
|
||||
return self.url_result(smuggle_url(url, {'to_generic': True}), 'Generic')
|
||||
|
||||
|
||||
class GenerationWhatIE(InfoExtractor):
|
||||
_WORKING = False
|
||||
# obsolete site: redirection to https://www.francetelevisions.fr/et-vous/le-lab
|
||||
IE_NAME = 'france2.fr:generation-what'
|
||||
_VALID_URL = r'https?://generation-what\.francetv\.fr/[^/]+/video/(?P<id>[^/?#&]+)'
|
||||
|
||||
|
@ -470,7 +766,9 @@ class GenerationWhatIE(InfoExtractor):
|
|||
return self.url_result(youtube_id, ie='Youtube', video_id=youtube_id)
|
||||
|
||||
|
||||
class CultureboxIE(FranceTVBaseInfoExtractor):
|
||||
class CultureboxIE(FranceTVBaseIE):
|
||||
# obsolete site: redirects to www.france.tv/spectacles-et-culture/
|
||||
# specific URLs redirect under www.france.tv, eg /france-2/...
|
||||
_VALID_URL = r'https?://(?:m\.)?culturebox\.francetvinfo\.fr/(?:[^/]+/)*(?P<id>[^/?#&]+)'
|
||||
|
||||
_TESTS = [{
|
||||
|
@ -487,26 +785,16 @@ class CultureboxIE(FranceTVBaseInfoExtractor):
|
|||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
'add_ie': [FranceTVIE.ie_key()],
|
||||
'add_ie': [FranceTVIE.ie_key(), 'Generic'],
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
display_id = self._match_id(url)
|
||||
|
||||
webpage = self._download_webpage(url, display_id)
|
||||
|
||||
if ">Ce live n'est plus disponible en replay<" in webpage:
|
||||
raise ExtractorError(
|
||||
'Video %s is not available' % display_id, expected=True)
|
||||
|
||||
video_id, catalogue = self._search_regex(
|
||||
r'["\'>]https?://videos\.francetv\.fr/video/([^@]+@.+?)["\'<]',
|
||||
webpage, 'video id').split('@')
|
||||
|
||||
return self._make_url_result(video_id, catalogue)
|
||||
return self.url_result(smuggle_url(url, {'to_generic': True}), 'Generic')
|
||||
|
||||
|
||||
class FranceTVJeunesseIE(FranceTVBaseInfoExtractor):
|
||||
class FranceTVJeunesseIE(FranceTVBaseIE):
|
||||
# obsolete site: redirection to https://www.france.tv/enfants/
|
||||
_WORKING = False
|
||||
_VALID_URL = r'(?P<url>https?://(?:www\.)?(?:zouzous|ludo)\.fr/heros/(?P<id>[^/?#&]+))'
|
||||
|
||||
_TESTS = [{
|
||||
|
@ -540,7 +828,7 @@ class FranceTVJeunesseIE(FranceTVBaseInfoExtractor):
|
|||
entries = []
|
||||
for item in playlist['items']:
|
||||
identity = item.get('identity')
|
||||
if identity and isinstance(identity, compat_str):
|
||||
if identity and isinstance(identity, str):
|
||||
entries.append(self._make_url_result(identity))
|
||||
|
||||
return self.playlist_result(entries, playlist_id)
|
||||
|
|
Loading…
Reference in a new issue