Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/digitalconcerthall.py (Python)

Code before:
        username, password = self._get_login_info()
        if username is None:
            if self._LOGIN_REQUIRED:
                raise ExtractorError('No login info available, needed for using %s.' % self.IE_NAME, expected=True)
            return
        # first get JWT token
        data = {
            'affiliate': 'none',
            'grant_type': 'device',
            'device_vendor': 'unknown',
            'device_model': 'unknown',
            'app_id': 'dch.webapp',
            'app_distributor': 'berlinphil',
            'app_version': '1.0.0',
            'client_secret': self._CLIENT_SECRET,
        }
        try:
            token_response = self._download_json(
                self._OAUTH_URL,
                None, 'Obtaining token', data=urlencode_postdata(data), headers={
                    'Content-Type': 'application/x-www-form-urlencoded',
                })
        except ExtractorError as e:
            msg = 'Unable to obtain token'
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
                resp = self._parse_json(e.cause.read().decode(), None, fatal=False)
                if resp:
                    error = resp.get('extra_info') or resp.get('error_description') or resp.get('error')
                    if error:
                        msg += ': ' + error
            raise ExtractorError('Unable to obtain token: ' + msg)
        self.debug_out("token_response: " + json.dumps(token_response))
        self._ACCCESS_TOKEN = token_response.get('access_token')
        # now login
        data = {
            'grant_type': 'password',
            'username': username,
            'password': password,
        }
        try:
            self._download_json(
                self._OAUTH_URL,
                None, 'Logging in', data=urlencode_postdata(data), headers={
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'Referer': self._LOGIN_URL,
                    'Authorization': 'Bearer ' + self._ACCCESS_TOKEN
                })
        except ExtractorError as e:
            msg = 'Unable to login'
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
                resp = self._parse_json(e.cause.read().decode(), None, fatal=False)

Review comment (suggestion):
(not a actual review) is the error response (JSON i guess) guaranteed to be within ASCII range? (in other words, are non-english languages like french or german never returned here?)

Code after:

    def _login(self):
        username, password = self._get_login_info()
        if not username:
            self.raise_login_required()
        token_response = self._download_json(
            self._OAUTH_URL,
            None, 'Obtaining token', errnote='Unable to obtain token', data=urlencode_postdata({
                'affiliate': 'none',
                'grant_type': 'device',
                'device_vendor': 'unknown',
                'app_id': 'dch.webapp',
                'app_version': '1.0.0',
                'client_secret': '2ySLN+2Fwb',
            }), headers={
                'Content-Type': 'application/x-www-form-urlencoded',
            })
        self._ACCESS_TOKEN = token_response['access_token']
        try:
            self._download_json(
                self._OAUTH_URL,
                None, note='Logging in', errnote='Unable to login', data=urlencode_postdata({
                    'grant_type': 'password',
                    'username': username,
                    'password': password,
                }), headers={
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'Referer': 'https://www.digitalconcerthall.com',
                    'Authorization': f'Bearer {self._ACCESS_TOKEN}'
                })
        except ExtractorError:
            self.raise_login_required(msg='Login info incorrect')

    def _real_initialize(self):
        self._login()

    def _entries(self, items, language, **kwargs):
        for item in items:
            video_id = item['id']
            stream_info = self._download_json(
                self._proto_relative_url(item['_links']['streams']['href']), video_id, headers={
                    'Accept': 'application/json',
                    'Authorization': f'Bearer {self._ACCESS_TOKEN}',
                    'Accept-Language': language
                })

            m3u8_url = traverse_obj(
                stream_info, ('channel', lambda x: x.startswith('vod_mixed'), 'stream', 0, 'url'), get_all=False)
            formats = self._extract_m3u8_formats(m3u8_url, video_id, 'mp4', 'm3u8_native', fatal=False)
            self._sort_formats(formats)