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

Code before:
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'Referer': 'https://www.digitalconcerthall.com/en/login',
                    'Authorization': 'Bearer ' + self._ACCESS_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)
                if resp:
                    error = resp.get('extra_info') or resp.get('error_description') or resp.get('error')
                    if error:
                        msg += ': ' + error
            raise ExtractorError('Unable to log in: ' + msg)

    def _real_initialize(self):
        self._login()

    def _real_extract(self, url):
        language, video_id = self._match_valid_url(url).groups()
        if not language:
            language = 'en'
        webpage = self._download_webpage(url, video_id)
        playlist_title = (self._html_search_regex(r'<title>(.+?)</title>', webpage, 'title')
                          or self._og_search_title(webpage))
        thumbnails = []
        thumbnail_url = (self._html_search_regex(r'(https://images.digitalconcerthall.com/cms/thumbnails/.*\.jpg)',
                         webpage, 'thumbnail'))
        thumbnails.append({
            'url': thumbnail_url,
            **parse_resolution(thumbnail_url)
        })

        vid_info = self._download_json(
            f'https://api.digitalconcerthall.com/v2/concert/{video_id}', video_id, headers={
                'Accept': 'application/json',
                'Accept-Language': language
            })
        embedded = vid_info.get('_embedded')
        entries = []
        for embed_type in embedded:
            # embed_type should be either 'work' or 'interview'
            # 'work' will be an array of one or more works
            for item in embedded.get(embed_type):
                stream_href = traverse_obj(item, ('_links', 'streams', 'href'))
                stream_info = self._download_json(
                    self._proto_relative_url(stream_href), video_id,
                    headers={'Accept': 'application/json',
                             'Authorization': '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)


Review comment (suggestion):
```suggestion
        thumbnail_url = (self._html_search_regex(r'(https?://images\.digitalconcerthall\.com/cms/thumbnails/.*\.jpg)',
```

Code after:
            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)

            yield {
                'id': video_id,
                'title': item.get('title'),
                'composer': item.get('name_composer'),
                'url': m3u8_url,
                'formats': formats,
                'duration': item.get('duration_total'),
                'timestamp': traverse_obj(item, ('date', 'published')),
                'description': item.get('short_description') or stream_info.get('short_description'),
                **kwargs,
                'chapters': [{
                    'start_time': chapter.get('time'),
                    'end_time': try_get(chapter, lambda x: x['time'] + x['duration']),
                    'title': chapter.get('text'),
                } for chapter in item['cuepoints']] if item.get('cuepoints') else None,
            }

    def _real_extract(self, url):
        language, video_id = self._match_valid_url(url).group('language', 'id')