From bc069d2bb40bcbc5bdfdf352ca15bf586c7792cb Mon Sep 17 00:00:00 2001 From: hwang-cadent <135065691+hwang-cadent@users.noreply.github.com> Date: Thu, 5 Nov 2026 20:12:14 -0600 Subject: [PATCH] [infomaniak] Add VOD2 embed extractor (#33234) Co-authored-by: dirkf --- youtube_dl/extractor/extractors.py | 1 + youtube_dl/extractor/infomaniak.py | 90 ++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 youtube_dl/extractor/infomaniak.py diff --git a/youtube_dl/extractor/extractors.py b/youtube_dl/extractor/extractors.py index 3da5f8020..8334a43da 100644 --- a/youtube_dl/extractor/extractors.py +++ b/youtube_dl/extractor/extractors.py @@ -527,6 +527,7 @@ from .imgur import ( from .ina import InaIE from .inc import IncIE from .indavideo import IndavideoEmbedIE +from .infomaniak import InfomaniakVod2IE from .infoq import InfoQIE from .instagram import ( InstagramIE, diff --git a/youtube_dl/extractor/infomaniak.py b/youtube_dl/extractor/infomaniak.py new file mode 100644 index 000000000..677050252 --- /dev/null +++ b/youtube_dl/extractor/infomaniak.py @@ -0,0 +1,90 @@ +# coding: utf-8 +from __future__ import unicode_literals + +from .common import InfoExtractor +from ..compat import compat_str as str +from ..utils import ( + ExtractorError, + merge_dicts, + parse_duration, + str_or_none, + T, + traverse_obj, + url_or_none, +) + + +class InfomaniakVod2IE(InfoExtractor): + IE_NAME = 'infomaniak:vod2' + _VALID_URL = r'https?://player\.vod2\.infomaniak\.com/embed/(?P[0-9a-z]+)' + _TEST = { + 'url': 'https://player.vod2.infomaniak.com/embed/1jhvl2uqg6ywp', + 'md5': '08c3a89906b70a614fa0fdb057e8a22e', + 'info_dict': { + 'id': '1jhvl2uqg6xis', + 'display_id': '1jhvl2uqg6ywp', + 'ext': 'mp4', + 'title': 'Conférence à Dyo, octobre 2022', + 'thumbnail': r're:https?://.+\.(?:jpe?g|png)$', + }, + } + + def _real_extract(self, url): + share_id = self._match_id(url) + share = self._download_json( + 'https://api.infomaniak.com/2/vod/res/shares/%s.json' % (share_id,), + share_id) + entries = [] + for media in traverse_obj(share, ( + 'data', 'media', lambda _, v: v.get('source').get)): + media_id = media.get('id') or share_id + source = media['source'] + formats = [] + + for src, src_url in traverse_obj(('hls', 'dash', 'best'), ( + lambda _, v: source.get(v), T(lambda x: (x, source[x])))): + if src == 'hls': + formats.extend(self._extract_m3u8_formats( + src_url, media_id, 'mp4', entry_protocol='m3u8_native', + m3u8_id=src, fatal=False)) + elif src == 'dash': + formats.extend(self._extract_mpd_formats( + src_url, media_id, mpd_id=src, fatal=False)) + elif src == 'best': + formats.append({ + 'url': src_url, + 'format_id': 'http', + 'preference': 10, + }) + + self._sort_formats(formats) + + entries.append(merge_dicts( + traverse_obj(media, { + 'id': ('id', T(str_or_none)), + 'title': ('title', T(str)), + 'duration': ('duration', T(parse_duration)), + 'thumbnail': ('thumbnails', ('poster', 'image'), T(url_or_none), any), + }), { + 'formats': formats, + })) + + n_entries = len(entries) + if n_entries == 1: + return merge_dicts({ + 'id': share_id, + 'display_id': share_id, + }, entries[0], rev=True) + + entries = [e for e in entries if e.get('id')] + n_entries -= len(entries) + if n_entries > 0: + self.report_warning( + 'Share contains multiple media entries, but %d item(s) are missing an id and will be skipped. ' + 'If this looks incorrect, please report the URL.' % n_entries, + share_id, only_once=True) + elif not entries: + # Keep the error message actionable for site support requests + raise ExtractorError('Unable to find any media in share JSON', expected=True) + + return self.playlist_result(entries, playlist_id=share_id)