Repository: yt-dlp/yt-dlp

Issue:
Dacast.com
### DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE

- [X] I understand that I will be **blocked** if I remove or skip any mandatory\* field

### Checklist

- [X] I'm reporting a new site support request
- [X] I've verified that I'm running yt-dlp version **2023.01.06** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
- [X] I've checked that all provided URLs are playable in a browser with the same IP and same login details
- [X] I've checked that none of provided URLs [violate any copyrights](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-website-primarily-used-for-piracy) or contain any [DRM](https://en.wikipedia.org/wiki/Digital_rights_management) to the best of my knowledge
- [X] I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
- [X] I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
- [X] I've read about [sharing account credentials](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#are-you-willing-to-share-account-details-if-needed) and am willing to share it if required

### Region

USA

### Example URLs

Single Video: https://iframe.dacast.com/vod/acae82153ef4d7a7344ae4eaa86af534/1c6143e3-5a06-371d-8695-19b96ea49090


### Provide a description that is worded well enough to be understood

Vzaar.com now is Dacast.com

Please support Dacast.com

### Provide verbose output that clearly demonstrates the problem

- [X] Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
- [X] Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below

### Complete Verbose Output

```shell
[debug] Command-line config: ['-vU', 'test:dacast']
[debug] Encodings: locale cp1252, fs utf-8, pref cp1252, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version 2023.01.06 [6becd25] (win_exe)
[debug] Python 3.8.10 (CPython AMD64 64bit) - Windows-10-10.0.19045-SP0 (OpenSSL 1.1.1k  25 Mar 2021)
[debug] exe versions: ffmpeg N-109725-g2d202985b7-20230130 (setts), ffprobe N-109725-g2d202985b7-20230130
[debug] Optional libraries: Cryptodome-3.16.0, brotli-1.0.9, certifi-2022.12.07, mutagen-1.46.0, sqlite3-2.6.0, websockets-10.4
[debug] Proxy map: {}
[debug] Loaded 1760 extractors
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
Latest version: 2023.01.06, Current version: 2023.01.06
yt-dlp is up to date (2023.01.06)
[TestURL] Extracting URL: test:dacast
ERROR: [TestURL] None: No extractors matching 'dacast' found
  File "yt_dlp\extractor\common.py", line 680, in extract
  File "yt_dlp\extractor\testurl.py", line 24, in _real_extract
```



Discussion:
Does the site provide any other type of URL for videos besides the iframe URL (which is presumably provided for embedding)? 
> RL (which is presumably provided for embedding)?
@bashonly

I have registered Dacast.com and upload demo video
It's provide only iframe link like this
<img width="1010" alt="Screenshot 2023-02-08 at 13 51 16" src="https://user-images.githubusercontent.com/26823364/217455922-26543839-b6aa-45a5-8c11-aeff9afa456e.png">
`<script id="587b1232-c5db-1739-e317-7503c9818117-vod-b43ef58d-fba8-41d7-8d65-59ca9c40e5b6" width="100%" height="100%" src="https://player.dacast.com/js/player.js?contentId=587b1232-c5db-1739-e317-7503c9818117-vod-b43ef58d-fba8-41d7-8d65-59ca9c40e5b6" class="dacast-video"></script>`

`<div style="position:relative;padding-bottom:56.25%;overflow:hidden;height:0;max-width:100%;"><iframe id="b43ef58d-fba8-41d7-8d65-59ca9c40e5b6" src="https://iframe.dacast.com/vod/587b1232-c5db-1739-e317-7503c9818117/b43ef58d-fba8-41d7-8d65-59ca9c40e5b6" width="100%" height="100%" frameborder="0" scrolling="no" allow="autoplay;encrypted-media" allowfullscreen webkitallowfullscreen mozallowfullscreen oallowfullscreen msallowfullscreen style="position:absolute;top:0;left:0;"></iframe></div>`

share link is iframe link
`https://iframe.dacast.com/vod/587b1232-c5db-1739-e317-7503c9818117/b43ef58d-fba8-41d7-8d65-59ca9c40e5b6`
Expos link: https://dacastexpo.com/?id=be1e4442-f31a-eb28-d156-243e963e7405
Playlist link: https://iframe.dacast.com/playlist/587b1232-c5db-1739-e317-7503c9818117/a43b34f8-cd41-9cb5-f5e9-b858c978e934
So, you can download dacast video, but you need to find the m3u8 link. I was not able to figure out how to get it from the embedded page, but if you open the page in your browser, then open developer tools and look at the network traffic - you'll see the browser making request to the file chunklist_somerandomstring.m3u8 i.e. (not a real link, just an example) https://dacastmmd.mmdlive.lldns.net/dacastmmd/480b7e1924834a99912ba0a51a09056a/chunklist_b2592000.m3u8
I believe if you are the creator of the video, you can also get the link to m3u8, but I don't know how to get it if you are just a viewer.
Then you can use ffmpeg external downloader and it will work fine. 
The m3u8 link can be extracted easily using the inspector in your browser (F12). The problem is that sometimes, on some videos, you may need to use something like Fiddler to get an encryption key. Once you have those two things, hlsdl works since it can take the encryption key as input and decrypt the video as it comes in from the playlist.

Can yt-dlp be extended to automate this entire process? It already is able to extract the video location from e.g. a YouTube or Twitter link, so I assume it's already doing a web request of some sort.
>  The problem is that sometimes, on some videos, you may need to use something like Fiddler to get an encryption key

Are you able to share an example URL?
I mean, I COULD, but the only example I'm aware of is part one of a paid video series, and I kinda don't wanna get sued.

Is there a way I could reach you privately so I can discuss specific details without being accused of piracy 😛 
discord: bashonly#4457
matrix: @bashonly:matrix.org
email: bashonly ( a t ) protonmail ( d o t ) com
Says I can't add you on either Discord or Matrix, Discord says you have friend requests closed.
fixed, you can try again

Resolving patch:
diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py
--- a/yt_dlp/extractor/_extractors.py
+++ b/yt_dlp/extractor/_extractors.py
@@ -422,6 +422,10 @@
     CybraryIE,
     CybraryCourseIE
 )
+from .dacast import (
+    DacastVODIE,
+    DacastPlaylistIE,
+)
 from .daftsex import DaftsexIE
 from .dailymail import DailyMailIE
 from .dailymotion import (
diff --git a/yt_dlp/extractor/dacast.py b/yt_dlp/extractor/dacast.py
new file mode 100644
--- /dev/null
+++ b/yt_dlp/extractor/dacast.py
@@ -0,0 +1,158 @@
+import hashlib
+import re
+import time
+import urllib.error
+
+from .common import InfoExtractor
+from ..utils import (
+    ExtractorError,
+    classproperty,
+    float_or_none,
+    traverse_obj,
+    url_or_none,
+)
+
+
+class DacastBaseIE(InfoExtractor):
+    _URL_TYPE = None
+
+    @classproperty
+    def _VALID_URL(cls):
+        return fr'https?://iframe\.dacast\.com/{cls._URL_TYPE}/(?P<user_id>[\w-]+)/(?P<id>[\w-]+)'
+
+    @classproperty
+    def _EMBED_REGEX(cls):
+        return [rf'<iframe[^>]+\bsrc=["\'](?P<url>{cls._VALID_URL})']
+
+    _API_INFO_URL = 'https://playback.dacast.com/content/info'
+
+    @classmethod
+    def _get_url_from_id(cls, content_id):
+        user_id, media_id = content_id.split(f'-{cls._URL_TYPE}-')
+        return f'https://iframe.dacast.com/{cls._URL_TYPE}/{user_id}/{media_id}'
+
+    @classmethod
+    def _extract_embed_urls(cls, url, webpage):
+        yield from super()._extract_embed_urls(url, webpage)
+        for content_id in re.findall(
+                rf'<script[^>]+\bsrc=["\']https://player\.dacast\.com/js/player\.js\?contentId=([\w-]+-{cls._URL_TYPE}-[\w-]+)["\']', webpage):
+            yield cls._get_url_from_id(content_id)
+
+
+class DacastVODIE(DacastBaseIE):
+    _URL_TYPE = 'vod'
+    _TESTS = [{
+        'url': 'https://iframe.dacast.com/vod/acae82153ef4d7a7344ae4eaa86af534/1c6143e3-5a06-371d-8695-19b96ea49090',
+        'info_dict': {
+            'id': '1c6143e3-5a06-371d-8695-19b96ea49090',
+            'ext': 'mp4',
+            'uploader_id': 'acae82153ef4d7a7344ae4eaa86af534',
+            'title': '2_4||Adnexal mass characterisation: O-RADS US and MRI||N. Bharwani, London/UK',
+            'thumbnail': 'https://universe-files.dacast.com/26137208-5858-65c1-5e9a-9d6b6bd2b6c2',
+        },
+        'params': {'skip_download': 'm3u8'},
+    }]
+    _WEBPAGE_TESTS = [{
+        'url': 'https://www.dacast.com/support/knowledgebase/how-can-i-embed-a-video-on-my-website/',
+        'info_dict': {
+            'id': 'b6674869-f08a-23c5-1d7b-81f5309e1a90',
+            'ext': 'mp4',
+            'title': '4-HowToEmbedVideo.mp4',
+            'uploader_id': '3b67c4a9-3886-4eb1-d0eb-39b23b14bef3',
+            'thumbnail': 'https://universe-files.dacast.com/d26ab48f-a52a-8783-c42e-a90290ba06b6.png',
+        },
+        'params': {'skip_download': 'm3u8'},
+    }, {
+        'url': 'https://gist.githubusercontent.com/bashonly/4ad249ef2910346fbdf3809b220f11ee/raw/87349778d4af1a80b1fcc3beb9c88108de5858f5/dacast_embeds.html',
+        'info_dict': {
+            'id': 'e7df418e-a83b-7a7f-7b5e-1a667981e8fa',
+            'ext': 'mp4',
+            'title': 'Evening Service 2-5-23',
+            'uploader_id': '943bb1ab3c03695ba85330d92d6d226e',
+            'thumbnail': 'https://universe-files.dacast.com/337472b3-e92c-2ea4-7eb7-5700da477f67',
+        },
+        'params': {'skip_download': 'm3u8'},
+    }]
+
+    def _real_extract(self, url):
+        user_id, video_id = self._match_valid_url(url).group('user_id', 'id')
+        query = {'contentId': f'{user_id}-vod-{video_id}', 'provider': 'universe'}
+        info = self._download_json(self._API_INFO_URL, video_id, query=query, fatal=False)
+        access = self._download_json(
+            'https://playback.dacast.com/content/access', video_id,
+            note='Downloading access JSON', query=query, expected_status=403)
+
+        error = access.get('error')
+        if error in ('Broadcaster has been blocked', 'Content is offline'):
+            raise ExtractorError(error, expected=True)
+        elif error:
+            raise ExtractorError(f'Dacast API says "{error}"')
+
+        hls_url = access['hls']
+        hls_aes = {}
+
+        if 'DRM_EXT' in hls_url:
+            self.report_drm(video_id)
+        elif '/uspaes/' in hls_url:
+            # From https://player.dacast.com/js/player.js
+            ts = int(time.time())
+            signature = hashlib.sha1(
+                f'{10413792000 - ts}{ts}YfaKtquEEpDeusCKbvYszIEZnWmBcSvw').digest().hex()
+            hls_aes['uri'] = f'https://keys.dacast.com/uspaes/{video_id}.key?s={signature}&ts={ts}'
+
+        for retry in self.RetryManager():
+            try:
+                formats = self._extract_m3u8_formats(hls_url, video_id, 'mp4', m3u8_id='hls')
+            except ExtractorError as e:
+                # CDN will randomly respond with 403
+                if isinstance(e.cause, urllib.error.HTTPError) and e.cause.code == 403:
+                    retry.error = e
+                    continue
+                raise
+
+        return {
+            'id': video_id,
+            'uploader_id': user_id,
+            'formats': formats,
+            'hls_aes': hls_aes or None,
+            **traverse_obj(info, ('contentInfo', {
+                'title': 'title',
+                'duration': ('duration', {float_or_none}),
+                'thumbnail': ('thumbnailUrl', {url_or_none}),
+            })),
+        }
+
+
+class DacastPlaylistIE(DacastBaseIE):
+    _URL_TYPE = 'playlist'
+    _TESTS = [{
+        'url': 'https://iframe.dacast.com/playlist/943bb1ab3c03695ba85330d92d6d226e/b632eb053cac17a9c9a02bcfc827f2d8',
+        'playlist_mincount': 28,
+        'info_dict': {
+            'id': 'b632eb053cac17a9c9a02bcfc827f2d8',
+            'title': 'Archive Sermons',
+        },
+    }]
+    _WEBPAGE_TESTS = [{
+        'url': 'https://gist.githubusercontent.com/bashonly/7efb606f49f3c6e07ea0327de5a661d1/raw/05a16eac830245ea301fb0a585023bec71e6093c/dacast_playlist_embed.html',
+        'playlist_mincount': 28,
+        'info_dict': {
+            'id': 'b632eb053cac17a9c9a02bcfc827f2d8',
+            'title': 'Archive Sermons',
+        },
+    }]
+
+    def _real_extract(self, url):
+        user_id, playlist_id = self._match_valid_url(url).group('user_id', 'id')
+        info = self._download_json(
+            self._API_INFO_URL, playlist_id, note='Downloading playlist JSON', query={
+                'contentId': f'{user_id}-playlist-{playlist_id}',
+                'provider': 'universe',
+            })['contentInfo']
+
+        def entries(info):
+            for video in traverse_obj(info, ('features', 'playlist', 'contents', lambda _, v: v['id'])):
+                yield self.url_result(
+                    DacastVODIE._get_url_from_id(video['id']), DacastVODIE, video['id'], video.get('title'))
+
+        return self.playlist_result(entries(info), playlist_id, info.get('title'))


Test patch: