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

Code before:
    ExtractorError,
    urlencode_postdata,
)


class DigitalConcertHallIE(InfoExtractor):
    IE_DESC = 'DigitalConcertHall extractor'
    _VALID_URL = r'https?://(?:www\.)?digitalconcerthall\.com/(?P<language>[a-z]+)/concert/(?P<id>[0-9]+)'
    _LOGIN_URL = 'https://www.digitalconcerthall.com/en/login'
    _OAUTH_URL = 'https://api.digitalconcerthall.com/v2/oauth2/token'
    _ACCESS_TOKEN = 'none'
    _CLIENT_SECRET = '2ySLN+2Fwb'
    _NETRC_MACHINE = 'digitalconcerthall'
    # if you don't login, all you will get is trailers
    _LOGIN_REQUIRED = True
    _TEST = {
        'url': 'https://www.digitalconcerthall.com/en/concert/53785',
        'md5': 'TODO: md5 sum of the first 10241 bytes of the video file (use --test)',
        'info_dict': {
            'id': '53785',
            'language': 'en',
            'ext': 'mp4',
            'title': 'Video title goes here',
            'thumbnail': r're:^https?://.*/images/core/Phil.*\.jpg$',
        }
    }

    def debug_out(self, args):
        if not self._downloader.params.get('verbose', False):
            return

        self.to_screen('[debug] %s' % args)

    def _login(self):
        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:

Review comment (suggestion):
```suggestion
    _TESTS = [{
        'url': 'https://www.digitalconcerthall.com/en/concert/53785',
        'md5': 'TODO: md5 sum of the first 10241 bytes of the video file (use --test)',
        'info_dict': {
            'id': '53785',
            'language': 'en',
            'ext': 'mp4',
            'title': 'Video title goes here',
            'thumbnail': r're:^https?://.*/images/core/Phil.*\.jpg$',
        }
    }]
```

Code after:


class DigitalConcertHallIE(InfoExtractor):
    IE_DESC = 'DigitalConcertHall extractor'
    _VALID_URL = r'https?://(?:www\.)?digitalconcerthall\.com/(?P<language>[a-z]+)/concert/(?P<id>[0-9]+)'
    _OAUTH_URL = 'https://api.digitalconcerthall.com/v2/oauth2/token'
    _ACCESS_TOKEN = None
    _NETRC_MACHINE = 'digitalconcerthall'
    _TESTS = [{
        'note': 'Playlist with only one video',
        'url': 'https://www.digitalconcerthall.com/en/concert/53201',
        'info_dict': {
            'id': '53201-1',
            'ext': 'mp4',
            'composer': 'Kurt Weill',
            'title': '[Magic Night]',
            'thumbnail': r're:^https?://images.digitalconcerthall.com/cms/thumbnails.*\.jpg$',
            'upload_date': '20210624',
            'timestamp': 1624548600,
            'duration': 2798,
            'album_artist': 'Members of the Berliner Philharmoniker / Simon Rössler',
        },
        'params': {'skip_download': 'm3u8'},
    }, {
        'note': 'Concert with several works and an interview',
        'url': 'https://www.digitalconcerthall.com/en/concert/53785',
        'info_dict': {
            'id': '53785',
            'album_artist': 'Berliner Philharmoniker / Kirill Petrenko',
            'title': 'Kirill Petrenko conducts Mendelssohn and Shostakovich',
        },
        'params': {'skip_download': 'm3u8'},
        'playlist_count': 3,
    }]

    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',
            })