Repository: yt-dlp/yt-dlp
Issue category: Bug Report

Issue:
DacastVOD - ERROR: Strings must be encoded before hashing
### DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE

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

### Checklist

- [X] I'm reporting that yt-dlp is broken on a **supported** site
- [X] I've verified that I have **updated yt-dlp to nightly or master** ([update instructions](https://github.com/yt-dlp/yt-dlp#update-channels))
- [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 all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/yt-dlp/yt-dlp/wiki/FAQ#video-url-contains-an-ampersand--and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
- [X] I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and 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 I'm willing to share it if required

### Region

Poland

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

Playing link https://iframe.dacast.com/vod/f9823fc6-faba-b98f-0d00-4a7b50a58c5b/348c5c84-b6af-4859-bb9d-1d01009c795b in a web browser works fine but yt-dlp throws: 
ERROR: Strings must be encoded before hashing

### Provide verbose output that clearly demonstrates the problem

- [X] Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
- [ ] If using API, add `'verbose': True` to `YoutubeDL` params instead
- [X] Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below

### Complete Verbose Output

```shell
[debug] Command-line config: ['-vU', 'https://iframe.dacast.com/vod/f9823fc6-faba-b98f-0d00-4a7b50a58c5b/348c5c84-b6af-4859-bb9d-1d01009c795b']
[debug] Encodings: locale UTF-8, fs utf-8, pref UTF-8, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version stable@2024.11.18 from yt-dlp/yt-dlp [7ea278792]
[debug] Python 3.12.7 (CPython x86_64 64bit) - Linux-6.11.9-arch1-1-x86_64-with-glibc2.40 (OpenSSL 3.4.0 22 Oct 2024, glibc 2.40)
[debug] exe versions: ffmpeg 7.1 (setts), ffprobe 7.1, rtmpdump 2.4
[debug] Optional libraries: certifi-2024.08.30, requests-2.32.3, sqlite3-3.46.1, urllib3-1.26.20
[debug] Proxy map: {}
[debug] Request Handlers: urllib, requests
[debug] Loaded 1837 extractors
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
Latest version: stable@2024.11.18 from yt-dlp/yt-dlp
yt-dlp is up to date (stable@2024.11.18 from yt-dlp/yt-dlp)
[DacastVOD] Extracting URL: https://iframe.dacast.com/vod/f9823fc6-faba-b98f-0d00-4a7b50a58c5b/348c5c84-b6af-4859-bb9d-1d01009c795b
[DacastVOD] 348c5c84-b6af-4859-bb9d-1d01009c795b: Downloading JSON metadata
[DacastVOD] 348c5c84-b6af-4859-bb9d-1d01009c795b: Downloading access JSON
ERROR: Strings must be encoded before hashing
Traceback (most recent call last):
  File "/usr/lib/python3.12/site-packages/yt_dlp/YoutubeDL.py", line 1624, in wrapper
    return func(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/yt_dlp/YoutubeDL.py", line 1759, in __extract_info
    ie_result = ie.extract(url)
                ^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/yt_dlp/extractor/common.py", line 742, in extract
    ie_result = self._real_extract(url)
                ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/yt_dlp/extractor/dacast.py", line 99, in _real_extract
    signature = hashlib.sha1(
                ^^^^^^^^^^^^^
TypeError: Strings must be encoded before hashing
```



Hints:
Looks like a combination of a coding mistake plus an outdated key (so it would've needed a fix even without the mistake):

```diff
diff --git a/yt_dlp/extractor/dacast.py b/yt_dlp/extractor/dacast.py
index 4e81aa4a7..537352e5f 100644
--- a/yt_dlp/extractor/dacast.py
+++ b/yt_dlp/extractor/dacast.py
@@ -1,3 +1,4 @@
+import functools
 import hashlib
 import re
 import time
@@ -51,6 +52,15 @@ class DacastVODIE(DacastBaseIE):
             'thumbnail': 'https://universe-files.dacast.com/26137208-5858-65c1-5e9a-9d6b6bd2b6c2',
         },
         'params': {'skip_download': 'm3u8'},
+    }, {  # /uspaes/ in hls_url
+        'url': 'https://iframe.dacast.com/vod/f9823fc6-faba-b98f-0d00-4a7b50a58c5b/348c5c84-b6af-4859-bb9d-1d01009c795b',
+        'info_dict': {
+            'id': '348c5c84-b6af-4859-bb9d-1d01009c795b',
+            'ext': 'mp4',
+            'title': 'pl1-edyta-rubas-211124.mp4',
+            'uploader_id': 'f9823fc6-faba-b98f-0d00-4a7b50a58c5b',
+            'thumbnail': 'https://universe-files.dacast.com/4d0bd042-a536-752d-fc34-ad2fa44bbcbb.png',
+        },
     }]
     _WEBPAGE_TESTS = [{
         'url': 'https://www.dacast.com/support/knowledgebase/how-can-i-embed-a-video-on-my-website/',
@@ -74,6 +84,15 @@ class DacastVODIE(DacastBaseIE):
         'params': {'skip_download': 'm3u8'},
     }]
 
+    @functools.cached_property
+    def _usp_signing_secret(self):
+        player_js = self._download_webpage(
+            'https://player.dacast.com/js/player.js', None, 'Downloading player JS')
+        # Rotates every so often, but hardcode a fallback in case of JS change/breakage before rotation
+        return self._search_regex(
+            r'\bUSP_SIGNING_SECRET\s*=\s*(["\'])(?P<secret>(?:(?!\1).)+)', player_js,
+            'usp signing secret', group='secret', fatal=False) or 'odnInCGqhvtyRTtIiddxtuRtawYYICZP'
+
     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'}
@@ -94,10 +113,10 @@ def _real_extract(self, url):
         if 'DRM_EXT' in hls_url:
             self.report_drm(video_id)
         elif '/uspaes/' in hls_url:
-            # From https://player.dacast.com/js/player.js
+            # Ref: https://player.dacast.com/js/player.js
             ts = int(time.time())
             signature = hashlib.sha1(
-                f'{10413792000 - ts}{ts}YfaKtquEEpDeusCKbvYszIEZnWmBcSvw').digest().hex()
+                f'{10413792000 - ts}{ts}{self._usp_signing_secret}'.encode()).digest().hex()
             hls_aes['uri'] = f'https://keys.dacast.com/uspaes/{video_id}.key?s={signature}&ts={ts}'
 
         for retry in self.RetryManager():
```

Patch:
diff --git a/yt_dlp/extractor/dacast.py b/yt_dlp/extractor/dacast.py
index 4e81aa4a7bca..537352e5f78b 100644
--- a/yt_dlp/extractor/dacast.py
+++ b/yt_dlp/extractor/dacast.py
@@ -1,3 +1,4 @@
+import functools
 import hashlib
 import re
 import time
@@ -51,6 +52,15 @@ class DacastVODIE(DacastBaseIE):
             'thumbnail': 'https://universe-files.dacast.com/26137208-5858-65c1-5e9a-9d6b6bd2b6c2',
         },
         'params': {'skip_download': 'm3u8'},
+    }, {  # /uspaes/ in hls_url
+        'url': 'https://iframe.dacast.com/vod/f9823fc6-faba-b98f-0d00-4a7b50a58c5b/348c5c84-b6af-4859-bb9d-1d01009c795b',
+        'info_dict': {
+            'id': '348c5c84-b6af-4859-bb9d-1d01009c795b',
+            'ext': 'mp4',
+            'title': 'pl1-edyta-rubas-211124.mp4',
+            'uploader_id': 'f9823fc6-faba-b98f-0d00-4a7b50a58c5b',
+            'thumbnail': 'https://universe-files.dacast.com/4d0bd042-a536-752d-fc34-ad2fa44bbcbb.png',
+        },
     }]
     _WEBPAGE_TESTS = [{
         'url': 'https://www.dacast.com/support/knowledgebase/how-can-i-embed-a-video-on-my-website/',
@@ -74,6 +84,15 @@ class DacastVODIE(DacastBaseIE):
         'params': {'skip_download': 'm3u8'},
     }]
 
+    @functools.cached_property
+    def _usp_signing_secret(self):
+        player_js = self._download_webpage(
+            'https://player.dacast.com/js/player.js', None, 'Downloading player JS')
+        # Rotates every so often, but hardcode a fallback in case of JS change/breakage before rotation
+        return self._search_regex(
+            r'\bUSP_SIGNING_SECRET\s*=\s*(["\'])(?P<secret>(?:(?!\1).)+)', player_js,
+            'usp signing secret', group='secret', fatal=False) or 'odnInCGqhvtyRTtIiddxtuRtawYYICZP'
+
     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'}
@@ -94,10 +113,10 @@ def _real_extract(self, url):
         if 'DRM_EXT' in hls_url:
             self.report_drm(video_id)
         elif '/uspaes/' in hls_url:
-            # From https://player.dacast.com/js/player.js
+            # Ref: https://player.dacast.com/js/player.js
             ts = int(time.time())
             signature = hashlib.sha1(
-                f'{10413792000 - ts}{ts}YfaKtquEEpDeusCKbvYszIEZnWmBcSvw').digest().hex()
+                f'{10413792000 - ts}{ts}{self._usp_signing_secret}'.encode()).digest().hex()
             hls_aes['uri'] = f'https://keys.dacast.com/uspaes/{video_id}.key?s={signature}&ts={ts}'
 
         for retry in self.RetryManager():


Test patch: