Repository: yt-dlp/yt-dlp

Issue:
Genius.com support request
### 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 **2022.10.04** ([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

United states 

### Example URLs

https://genius.com/videos/Vince-staples-breaks-down-the-meaning-of-when-sparks-fly
https://genius.com/videos/Breaking-down-drakes-certified-lover-boy-kanye-beef-way-2-sexy-cudi

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

yt-dlp can't extract audio nor video from the site 

### 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', 'https://genius.com/videos/Vince-staples-breaks-down-the-meaning-of-when-sparks-fly'] [debug] Encodings: locale cp1252, fs utf-8, pref cp1252, out utf-8, error utf-8, screen utf-8 [debug] yt-dlp version 2022.10.04 [4e0511f] (win32_exe) [debug] Python 3.8.10 (CPython 64bit) - Windows-10-10.0.19044-SP0 [debug] Checking exe version: ffmpeg -bsfs [debug] Checking exe version: ffprobe -bsfs [debug] exe versions: ffmpeg 2022-08-13-git-c469c3c3b1-full_build-www.gyan.dev (setts), ffprobe 2022-08-13-git-c469c3c3b1-full_build-www.gyan.dev [debug] Optional libraries: Cryptodome-3.15.0, brotli-1.0.9, certifi-2022.09.24, mutagen-1.45.1, sqlite3-2.6.0, websockets-10.3 [debug] Proxy map: {} [debug] Loaded 1690 extractors [debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest Latest version: 2022.10.04, Current version: 2022.10.04 yt-dlp is up to date (2022.10.04) [debug] [generic] Extracting URL: https://genius.com/videos/Vince-staples-breaks-down-the-meaning-of-when-sparks-fly [generic] Vince-staples-breaks-down-the-meaning-of-when-sparks-fly: Downloading webpage WARNING: [generic] Falling back on generic information extractor [generic] Vince-staples-breaks-down-the-meaning-of-when-sparks-fly: Extracting information [debug] Looking for Brightcove embeds [debug] Looking for embeds ERROR: Unsupported URL: https://genius.com/videos/Vince-staples-breaks-down-the-meaning-of-when-sparks-fly Traceback (most recent call last):   File "yt_dlp\YoutubeDL.py", line 1477, in wrapper   File "yt_dlp\YoutubeDL.py", line 1553, in __extract_info   File "yt_dlp\extractor\common.py", line 672, in extract   File "yt_dlp\extractor\generic.py", line 3062, in _real_extract yt_dlp.utils.UnsupportedError: Unsupported URL: https://genius.com/videos/Vince-staples-breaks-down-the-meaning-of-when-sparks-fly
```



Discussion:

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
@@ -629,6 +629,10 @@
 from .gdcvault import GDCVaultIE
 from .gedidigital import GediDigitalIE
 from .generic import GenericIE
+from .genius import (
+    GeniusIE,
+    GeniusLyricsIE,
+)
 from .gettr import (
     GettrIE,
     GettrStreamingIE,
diff --git a/yt_dlp/extractor/genius.py b/yt_dlp/extractor/genius.py
new file mode 100644
--- /dev/null
+++ b/yt_dlp/extractor/genius.py
@@ -0,0 +1,127 @@
+from .common import InfoExtractor
+from ..utils import (
+    ExtractorError,
+    js_to_json,
+    smuggle_url,
+    str_or_none,
+    traverse_obj,
+    unescapeHTML,
+)
+
+
+class GeniusIE(InfoExtractor):
+    _VALID_URL = r'https?://(?:www\.)?genius\.com/videos/(?P<id>[^?/#]+)'
+    _TESTS = [{
+        'url': 'https://genius.com/videos/Vince-staples-breaks-down-the-meaning-of-when-sparks-fly',
+        'md5': '64c2ad98cfafcfda23bfa0ad0c512f4c',
+        'info_dict': {
+            'id': '6313303597112',
+            'ext': 'mp4',
+            'title': 'Vince Staples Breaks Down The Meaning Of “When Sparks Fly”',
+            'description': 'md5:bc15e00342c537c0039d414423ae5752',
+            'tags': 'count:1',
+            'uploader_id': '4863540648001',
+            'duration': 388.416,
+            'upload_date': '20221005',
+            'timestamp': 1664982341,
+            'thumbnail': r're:^https?://.*\.jpg$',
+        },
+    }, {
+        'url': 'https://genius.com/videos/Breaking-down-drakes-certified-lover-boy-kanye-beef-way-2-sexy-cudi',
+        'md5': 'b8ed87a5efd1473bd027c20a969d4060',
+        'info_dict': {
+            'id': '6271792014001',
+            'ext': 'mp4',
+            'title': 'md5:c6355f7fa8a70bc86492a3963919fc15',
+            'description': 'md5:1774638c31548b31b037c09e9b821393',
+            'tags': 'count:3',
+            'uploader_id': '4863540648001',
+            'duration': 2685.099,
+            'upload_date': '20210909',
+            'timestamp': 1631209167,
+            'thumbnail': r're:^https?://.*\.jpg$',
+        },
+    }]
+
+    def _real_extract(self, url):
+        display_id = self._match_id(url)
+        webpage = self._download_webpage(url, display_id)
+
+        metadata = self._search_json(
+            r'<meta content="', webpage, 'metadata', display_id, transform_source=unescapeHTML)
+        video_id = traverse_obj(
+            metadata, ('video', 'provider_id'),
+            ('dfp_kv', lambda _, x: x['name'] == 'brightcove_video_id', 'values', 0), get_all=False)
+        if not video_id:
+            raise ExtractorError('Brightcove video id not found in webpage')
+
+        config = self._search_json(r'var\s*APP_CONFIG\s*=', webpage, 'config', video_id, default={})
+        account_id = config.get('brightcove_account_id', '4863540648001')
+        player_id = traverse_obj(
+            config, 'brightcove_standard_web_player_id', 'brightcove_standard_no_autoplay_web_player_id',
+            'brightcove_modal_web_player_id', 'brightcove_song_story_web_player_id', default='S1ZcmcOC1x')
+
+        return self.url_result(
+            smuggle_url(
+                f'https://players.brightcove.net/{account_id}/{player_id}_default/index.html?videoId={video_id}',
+                {'referrer': url}), 'BrightcoveNew', video_id)
+
+
+class GeniusLyricsIE(InfoExtractor):
+    _VALID_URL = r'https?://(?:www\.)?genius\.com/(?P<id>[^?/#]+)-lyrics[?/#]?'
+    _TESTS = [{
+        'url': 'https://genius.com/Lil-baby-heyy-lyrics',
+        'playlist_mincount': 2,
+        'info_dict': {
+            'id': '8454545',
+            'title': 'Heyy',
+            'description': 'Heyy by Lil Baby',
+        },
+    }, {
+        'url': 'https://genius.com/Outkast-two-dope-boyz-in-a-cadillac-lyrics',
+        'playlist_mincount': 1,
+        'info_dict': {
+            'id': '36239',
+            'title': 'Two Dope Boyz (In a Cadillac)',
+            'description': 'Two Dope Boyz (In a Cadillac) by OutKast',
+        },
+    }, {
+        'url': 'https://genius.com/Playboi-carti-rip-lyrics',
+        'playlist_mincount': 1,
+        'info_dict': {
+            'id': '3710582',
+            'title': 'R.I.P.',
+            'description': 'R.I.P. by Playboi Carti',
+        },
+    }]
+
+    def _real_extract(self, url):
+        display_id = self._match_id(url)
+        webpage = self._download_webpage(url, display_id)
+
+        json_string = self._search_json(
+            r'window\.__PRELOADED_STATE__\s*=\s*JSON\.parse\(', webpage, 'json string',
+            display_id, transform_source=js_to_json, contains_pattern=r'\'{(?s:.+)}\'')
+        song_info = self._parse_json(json_string, display_id)
+        song_id = str_or_none(traverse_obj(song_info, ('songPage', 'song')))
+        if not song_id:
+            raise ExtractorError('Song id not found in webpage')
+
+        title = traverse_obj(
+            song_info, ('songPage', 'trackingData', lambda _, x: x['key'] == 'Title', 'value'),
+            get_all=False, default='untitled')
+        artist = traverse_obj(
+            song_info, ('songPage', 'trackingData', lambda _, x: x['key'] == 'Primary Artist', 'value'),
+            get_all=False, default='unknown artist')
+        media = traverse_obj(
+            song_info, ('entities', 'songs', song_id, 'media'), expected_type=list, default=[])
+
+        entries = []
+        for m in media:
+            if m.get('type') in ('video', 'audio') and m.get('url'):
+                if m.get('provider') == 'spotify':
+                    self.to_screen(f'{song_id}: Skipping Spotify audio embed')
+                else:
+                    entries.append(self.url_result(m['url']))
+
+        return self.playlist_result(entries, song_id, title, f'{title} by {artist}')


Test patch: