diff --git a/yt_dlp/extractor/tv5unis.py b/yt_dlp/extractor/tv5unis.py index fe7fd0325b..2e7fd3b7da 100644 --- a/yt_dlp/extractor/tv5unis.py +++ b/yt_dlp/extractor/tv5unis.py @@ -1,14 +1,18 @@ from .common import InfoExtractor from ..utils import ( + ExtractorError, int_or_none, + join_nonempty, + make_archive_id, parse_age_limit, - smuggle_url, - try_get, + remove_end, ) +from ..utils.traversal import traverse_obj class TV5UnisBaseIE(InfoExtractor): _GEO_COUNTRIES = ['CA'] + _GEO_BYPASS = False def _real_extract(self, url): groups = self._match_valid_url(url).groups() @@ -16,96 +20,136 @@ class TV5UnisBaseIE(InfoExtractor): 'https://api.tv5unis.ca/graphql', groups[0], query={ 'query': '''{ %s(%s) { + title + summary + tags + duration + seasonNumber + episodeNumber collection { title } - episodeNumber rating { name } - seasonNumber - tags - title videoElement { + __typename ... on Video { mediaId + encodings { + hls { + url + } + } + } + ... on RestrictedVideo { + code + reason } } } }''' % (self._GQL_QUERY_NAME, self._gql_args(groups)), # noqa: UP031 })['data'][self._GQL_QUERY_NAME] - media_id = product['videoElement']['mediaId'] + + video = product['videoElement'] + if video is None: + raise ExtractorError('This content is no longer available', expected=True) + + if video.get('__typename') == 'RestrictedVideo': + code = video.get('code') + if code == 1001: + self.raise_geo_restricted(countries=self._GEO_COUNTRIES) + reason = video.get('reason') + raise ExtractorError(join_nonempty( + 'This video is restricted', + code is not None and f', error code {code}', + reason and f': {remove_end(reason, ".")}', + delim='')) + + media_id = video['mediaId'] + formats, subtitles = self._extract_m3u8_formats_and_subtitles( + video['encodings']['hls']['url'], media_id, 'mp4') return { - '_type': 'url_transparent', 'id': media_id, - 'title': product.get('title'), - 'url': smuggle_url('limelight:media:' + media_id, {'geo_countries': self._GEO_COUNTRIES}), - 'age_limit': parse_age_limit(try_get(product, lambda x: x['rating']['name'])), - 'tags': product.get('tags'), - 'series': try_get(product, lambda x: x['collection']['title']), - 'season_number': int_or_none(product.get('seasonNumber')), - 'episode_number': int_or_none(product.get('episodeNumber')), - 'ie_key': 'LimelightMedia', + '_old_archive_ids': [make_archive_id('LimelightMedia', media_id)], + 'formats': formats, + 'subtitles': subtitles, + **traverse_obj(product, { + 'title': ('title', {str}), + 'description': ('summary', {str}), + 'tags': ('tags', ..., {str}), + 'duration': ('duration', {int_or_none}), + 'season_number': ('seasonNumber', {int_or_none}), + 'episode_number': ('episodeNumber', {int_or_none}), + 'series': ('collection', 'title', {str}), + 'age_limit': ('rating', 'name', {parse_age_limit}), + }), } class TV5UnisVideoIE(TV5UnisBaseIE): - _WORKING = False IE_NAME = 'tv5unis:video' - _VALID_URL = r'https?://(?:www\.)?tv5unis\.ca/videos/[^/]+/(?P\d+)' - _TEST = { - 'url': 'https://www.tv5unis.ca/videos/bande-annonces/71843', - 'md5': '3d794164928bda97fb87a17e89923d9b', + _VALID_URL = r'https?://(?:www\.)?tv5unis\.ca/videos/[^/?#]+/(?P\d+)' + _TESTS = [{ + 'url': 'https://www.tv5unis.ca/videos/bande-annonces/144041', + 'md5': '24a247c96119d77fe1bae8b440457dfa', 'info_dict': { - 'id': 'a883684aecb2486cad9bdc7bbe17f861', + 'id': '56862325352147149dce0ae139afced6', + '_old_archive_ids': ['limelightmedia 56862325352147149dce0ae139afced6'], 'ext': 'mp4', - 'title': 'Watatatow', - 'duration': 10.01, + 'title': 'Antigone', + 'description': r"re:En aidant son frère .+ dicté par l'amour et la solidarité.", + 'duration': 61, }, - } + }] _GQL_QUERY_NAME = 'productById' @staticmethod def _gql_args(groups): - return f'id: {groups}' + return f'id: {groups[0]}' class TV5UnisIE(TV5UnisBaseIE): - _WORKING = False IE_NAME = 'tv5unis' - _VALID_URL = r'https?://(?:www\.)?tv5unis\.ca/videos/(?P[^/]+)(?:/saisons/(?P\d+)/episodes/(?P\d+))?/?(?:[?#&]|$)' + _VALID_URL = r'https?://(?:www\.)?tv5unis\.ca/videos/(?P[^/?#]+)(?:/saisons/(?P\d+)/episodes/(?P\d+))?/?(?:[?#&]|$)' _TESTS = [{ - 'url': 'https://www.tv5unis.ca/videos/watatatow/saisons/6/episodes/1', - 'md5': 'a479907d2e531a73e1f8dc48d6388d02', + # geo-restricted to Canada; xff is ineffective + 'url': 'https://www.tv5unis.ca/videos/watatatow/saisons/11/episodes/1', + 'md5': '43beebd47eefb1c5caf9a47a3fc35589', 'info_dict': { - 'id': 'e5ee23a586c44612a56aad61accf16ef', + 'id': '2c06e4af20f0417b86c2536825287690', + '_old_archive_ids': ['limelightmedia 2c06e4af20f0417b86c2536825287690'], 'ext': 'mp4', - 'title': 'Je ne peux pas lui résister', - 'description': "Atys, le nouveau concierge de l'école, a réussi à ébranler la confiance de Mado en affirmant qu'une médaille, ce n'est que du métal. Comme Mado essaie de lui prouver que ses valeurs sont solides, il veut la mettre à l'épreuve...", + 'title': "L'homme éléphant", + 'description': r're:Paul-André et Jean-Yves, .+ quand elle parle du feu au Spot.', 'subtitles': { 'fr': 'count:1', }, - 'duration': 1370, + 'duration': 1440, 'age_limit': 8, - 'tags': 'count:3', + 'tags': 'count:4', 'series': 'Watatatow', - 'season_number': 6, + 'season': 'Season 11', + 'season_number': 11, + 'episode': 'Episode 1', 'episode_number': 1, }, }, { - 'url': 'https://www.tv5unis.ca/videos/le-voyage-de-fanny', - 'md5': '9ca80ebb575c681d10cae1adff3d4774', + # geo-restricted to Canada; xff is ineffective + 'url': 'https://www.tv5unis.ca/videos/boite-a-savon', + 'md5': '7898e868e8c540f03844660e0aab6bbe', 'info_dict': { - 'id': '726188eefe094d8faefb13381d42bc06', + 'id': '4de6d0c6467b4511a0c04b92037a9f15', + '_old_archive_ids': ['limelightmedia 4de6d0c6467b4511a0c04b92037a9f15'], 'ext': 'mp4', - 'title': 'Le voyage de Fanny', - 'description': "Fanny, 12 ans, cachée dans un foyer loin de ses parents, s'occupe de ses deux soeurs. Devant fuir, Fanny prend la tête d'un groupe de huit enfants et s'engage dans un dangereux périple à travers la France occupée pour rejoindre la frontière suisse.", + 'title': 'Boîte à savon', + 'description': r're:Dans le petit village de Broche-à-foin, .+ celle qui fait battre son coeur.', 'subtitles': { 'fr': 'count:1', }, - 'duration': 5587.034, - 'tags': 'count:4', + 'duration': 1200, + 'tags': 'count:5', }, }] _GQL_QUERY_NAME = 'productByRootProductSlug'