From e24c9760351835fafe942ab31f272f7ee9196f2f Mon Sep 17 00:00:00 2001
From: Nick Payne <Nick.Payne@anuvu.com>
Date: Sun, 28 Aug 2022 13:31:25 -0400
Subject: [PATCH 01/11] [bandlabextractor] added new bandlab extractor for
 track, album, and playlist

---
 youtube_dl/extractor/bandlab.py    | 192 +++++++++++++++++++++++++++++
 youtube_dl/extractor/extractors.py |   1 +
 2 files changed, 193 insertions(+)
 create mode 100644 youtube_dl/extractor/bandlab.py

diff --git a/youtube_dl/extractor/bandlab.py b/youtube_dl/extractor/bandlab.py
new file mode 100644
index 000000000..dcf8c15a2
--- /dev/null
+++ b/youtube_dl/extractor/bandlab.py
@@ -0,0 +1,192 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import unicode_literals
+
+import re
+
+from .common import InfoExtractor
+
+
+class BandlabIE(InfoExtractor):
+    _VALID_URL = r'https?://(?:www\.)?bandlab\.com/post/(?P<id>[^/]+)'
+
+    _TEST = {
+        'url': 'https://www.bandlab.com/post/f5f04998635a44ea819cacdba7ae2076_f8d8574c3bdaec11b6562818783151a1',
+        'info_dict': {
+            'id': 'f5f04998635a44ea819cacdba7ae2076_f8d8574c3bdaec11b6562818783151a1',
+            'ext': 'm4a',
+            'title': 'ON MY OWN (unreleased)',
+            'artist': 'Michael MacDonald',
+        },
+    }
+
+    def _real_extract(self, url):
+        track_id = self._match_id(url)
+        config = self._download_json(
+            'http://www.bandlab.com/api/v1.3/posts/%s' % track_id, track_id)
+        track_url = config['track']['sample']['audioUrl']
+
+        return {
+            'id': track_id,
+            'title': config['track']['name'],
+            'url': track_url,
+            'artist': config['creator']['name']
+        }
+
+
+class BandlabAlbumIE(InfoExtractor):
+    _VALID_URL = r'https?://(?:www\.)?bandlab\.com/[^/]+/albums/(?P<id>[^/]+)'
+    _TEST = {
+        'url': 'https://www.bandlab.com/sbsdasani/albums/dc26e307-e51f-ed11-95d7-002248452390',
+        'playlist': [
+            {
+                'info_dict': {
+                    'id': '91feeb36-8e10-4c91-ae57-ffac0a98c6b4',
+                    'title': 'How\'d I Lose You? (Intro)',
+                    'ext': 'm4a',
+                },
+            },
+            {
+                'info_dict': {
+                    'id': 'd87c50a2-70cb-4937-9b97-3ae8646ca3aa',
+                    'title': 'Money $$$',
+                    'ext': 'm4a',
+                },
+            },
+            {
+                'info_dict': {
+                    'id': 'ff2909ff-348f-448d-9d2c-7edbf2f0ec5e',
+                    'title': 'You\'ll Be Mine',
+                    'ext': 'm4a',
+                },
+            },
+            {
+                'info_dict': {
+                    'id': '53786c38-1f3c-4921-9271-793a64af7186',
+                    'title': 'Who You Are',
+                    'ext': 'm4a',
+                },
+            },
+            {
+                'info_dict': {
+                    'id': '0394bb27-113f-4d19-b902-f9c1fe6ba8a8',
+                    'title': 'In Your Eyes',
+                    'ext': 'm4a',
+                },
+            },
+            {
+                'info_dict': {
+                    'id': '2aa44689-c7fa-4d0e-b28a-e0d1dd570372',
+                    'title': 'The Same',
+                    'ext': 'm4a',
+                },
+            },
+            {
+                'info_dict': {
+                    'id': '281fe589-a559-4260-802d-78a6c7a973d8',
+                    'title': 'Fall In Love',
+                    'ext': 'm4a',
+                },
+            },
+            {
+                'info_dict': {
+                    'id': '83a9dc0a-702f-40fd-82f6-ab847f6a7b46',
+                    'title': 'Tried So Hard',
+                    'ext': 'm4a',
+                },
+            },
+            {
+                'info_dict': {
+                    'id': '3f6a4a1c-eb73-449f-bd45-f7958d6f2de1',
+                    'title': 'The End Of Everything',
+                    'ext': 'm4a',
+                },
+            }
+        ],
+        'info_dict': {
+            'id': 'dc26e307-e51f-ed11-95d7-002248452390',
+            'album': 'ENDLESS SUMMER',
+            'artist': 'Michael MacDonald'
+        },
+    }
+
+    def _real_extract(self, url):
+        album_id = self._match_id(url)
+        config = self._download_json(
+            'http://www.bandlab.com/api/v1.3/albums/%s' % album_id, album_id)
+        tracks = config['posts']
+        entries = []
+        for track in tracks:
+            if 'track' in track:
+                url = track['track']['sample']['audioUrl']
+                name = track['track']['name']
+            elif 'revision' in track:
+                url = track['revision']['mixdown']['file']
+                name = track['revision']['song']['name']
+            else:
+                raise Exception("Neither track nor revision found in 'posts' object")
+            id_regex = r'.+/(?P<id>[^/]+).m4a'
+            id = re.compile(id_regex).match(url).group('id')
+            entries.append({
+                'url': url,
+                'id': id,
+                'title': name
+            })
+
+        return {
+            '_type': 'playlist',
+            'id': album_id,
+            'album': config['name'],
+            'artist': config['creator']['name'],
+            'entries': entries
+        }
+
+
+class BandlabPlaylistIE(InfoExtractor):
+    _VALID_URL = r'https?://(?:www\.)?bandlab\.com/[^/]+/collections/(?P<id>[^/]+)'
+    _TEST = {
+        'url': 'https://www.bandlab.com/hexatetrahedronx/collections/606442da-f426-ed11-9441-000d3a3f83b4',
+        'playlist': [
+            {
+                'info_dict': {
+                    'id': '3086d9cd-983a-4d2c-b088-adc3eb8d618d',
+                    'title': u'If You Leave (En-Esp) Si Te Vas #forkable #fork 🎤🎹📀',
+                    'ext': 'm4a'
+                }
+            }
+        ],
+        'info_dict': {
+            'id': '606442da-f426-ed11-9441-000d3a3f83b4',
+            'playlist': 'youtube-mp3-test'
+        }
+    }
+
+    def _real_extract(self, url):
+        playlist_id = self._match_id(url)
+        config = self._download_json(
+            'http://www.bandlab.com/api/v1.3/collections/%s' % playlist_id, playlist_id)
+        tracks = config['posts']
+        entries = []
+        for track in tracks:
+            if 'track' in track:
+                url = track['track']['sample']['audioUrl']
+                name = track['track']['name']
+            elif 'revision' in track:
+                url = track['revision']['mixdown']['file']
+                name = track['revision']['song']['name']
+            else:
+                raise Exception("Neither track nor revision found in 'posts' object")
+            id_regex = r'.+/(?P<id>[^/]+).m4a'
+            id = re.compile(id_regex).match(url).group('id')
+            entries.append({
+                'url': url,
+                'id': id,
+                'title': name
+            })
+
+        return {
+            '_type': 'playlist',
+            'id': playlist_id,
+            'playlist': config['name'],
+            'entries': entries
+        }
diff --git a/youtube_dl/extractor/extractors.py b/youtube_dl/extractor/extractors.py
index 751fc38b6..3f9acd0ec 100644
--- a/youtube_dl/extractor/extractors.py
+++ b/youtube_dl/extractor/extractors.py
@@ -98,6 +98,7 @@ from .azmedien import AZMedienIE
 from .baidu import BaiduVideoIE
 from .bandaichannel import BandaiChannelIE
 from .bandcamp import BandcampIE, BandcampAlbumIE, BandcampWeeklyIE
+from .bandlab import BandlabIE, BandlabAlbumIE, BandlabPlaylistIE
 from .bbc import (
     BBCCoUkIE,
     BBCCoUkArticleIE,

From 0201ee10820ac40dd30b8721322aa077364bada9 Mon Sep 17 00:00:00 2001
From: Nick Payne <Nick.Payne@anuvu.com>
Date: Mon, 29 Aug 2022 12:03:03 -0400
Subject: [PATCH 02/11] remove utf8 song name in test for bandlab playlist
 extractor

---
 youtube_dl/extractor/bandlab.py | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)

diff --git a/youtube_dl/extractor/bandlab.py b/youtube_dl/extractor/bandlab.py
index dcf8c15a2..b14686ccf 100644
--- a/youtube_dl/extractor/bandlab.py
+++ b/youtube_dl/extractor/bandlab.py
@@ -1,7 +1,3 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import unicode_literals
-
 import re
 
 from .common import InfoExtractor
@@ -145,19 +141,19 @@ class BandlabAlbumIE(InfoExtractor):
 class BandlabPlaylistIE(InfoExtractor):
     _VALID_URL = r'https?://(?:www\.)?bandlab\.com/[^/]+/collections/(?P<id>[^/]+)'
     _TEST = {
-        'url': 'https://www.bandlab.com/hexatetrahedronx/collections/606442da-f426-ed11-9441-000d3a3f83b4',
+        'url': 'https://www.bandlab.com/hexatetrahedronx/collections/8fb1041c-e865-eb11-9889-0050f28a2802',
         'playlist': [
             {
                 'info_dict': {
-                    'id': '3086d9cd-983a-4d2c-b088-adc3eb8d618d',
-                    'title': u'If You Leave (En-Esp) Si Te Vas #forkable #fork 🎤🎹📀',
+                    'id': '8f37e4aa-92c4-eb11-a7ad-0050f280467f',
+                    'title': 'psych ward',
                     'ext': 'm4a'
                 }
             }
         ],
         'info_dict': {
-            'id': '606442da-f426-ed11-9441-000d3a3f83b4',
-            'playlist': 'youtube-mp3-test'
+            'id': '8fb1041c-e865-eb11-9889-0050f28a2802',
+            'playlist': 'DOOMTAPE'
         }
     }
 

From 6dd45b8d4378d0403e6ca9b380673b85ae78c572 Mon Sep 17 00:00:00 2001
From: dirkf <fieldhouse@gmx.net>
Date: Mon, 29 Aug 2022 19:53:51 +0100
Subject: [PATCH 03/11] Add Unicode compatibility header

---
 youtube_dl/extractor/bandlab.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/youtube_dl/extractor/bandlab.py b/youtube_dl/extractor/bandlab.py
index b14686ccf..c3b2dd7d9 100644
--- a/youtube_dl/extractor/bandlab.py
+++ b/youtube_dl/extractor/bandlab.py
@@ -1,3 +1,6 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
 import re
 
 from .common import InfoExtractor

From 06400d1d0f7a7dc29c3f1bd8c1aae1747e83b10e Mon Sep 17 00:00:00 2001
From: npayne <nick.payne@anuvu.com>
Date: Sat, 28 Jan 2023 11:17:51 -0500
Subject: [PATCH 04/11] merge bandlab playlist and album extractors

---
 youtube_dl/extractor/bandlab.py | 76 ++++++++++-----------------------
 1 file changed, 22 insertions(+), 54 deletions(-)

diff --git a/youtube_dl/extractor/bandlab.py b/youtube_dl/extractor/bandlab.py
index c3b2dd7d9..b5c23d857 100644
--- a/youtube_dl/extractor/bandlab.py
+++ b/youtube_dl/extractor/bandlab.py
@@ -9,7 +9,7 @@ from .common import InfoExtractor
 class BandlabIE(InfoExtractor):
     _VALID_URL = r'https?://(?:www\.)?bandlab\.com/post/(?P<id>[^/]+)'
 
-    _TEST = {
+    _TESTS = [{
         'url': 'https://www.bandlab.com/post/f5f04998635a44ea819cacdba7ae2076_f8d8574c3bdaec11b6562818783151a1',
         'info_dict': {
             'id': 'f5f04998635a44ea819cacdba7ae2076_f8d8574c3bdaec11b6562818783151a1',
@@ -17,7 +17,7 @@ class BandlabIE(InfoExtractor):
             'title': 'ON MY OWN (unreleased)',
             'artist': 'Michael MacDonald',
         },
-    }
+    }]
 
     def _real_extract(self, url):
         track_id = self._match_id(url)
@@ -33,9 +33,10 @@ class BandlabIE(InfoExtractor):
         }
 
 
-class BandlabAlbumIE(InfoExtractor):
-    _VALID_URL = r'https?://(?:www\.)?bandlab\.com/[^/]+/albums/(?P<id>[^/]+)'
-    _TEST = {
+class BandlabAlbumOrPlaylistIE(InfoExtractor):
+    _VALID_URL = r'https?://(?:www\.)?bandlab\.com/[^/]+/(?P<kind>albums|collections)/(?P<id>[^/]+)'
+    _TRACK_URL_RE = re.compile(r'.+/(?P<id>[^/]+)\.m4a')
+    _TESTS = [{
         'url': 'https://www.bandlab.com/sbsdasani/albums/dc26e307-e51f-ed11-95d7-002248452390',
         'playlist': [
             {
@@ -107,43 +108,8 @@ class BandlabAlbumIE(InfoExtractor):
             'album': 'ENDLESS SUMMER',
             'artist': 'Michael MacDonald'
         },
-    }
-
-    def _real_extract(self, url):
-        album_id = self._match_id(url)
-        config = self._download_json(
-            'http://www.bandlab.com/api/v1.3/albums/%s' % album_id, album_id)
-        tracks = config['posts']
-        entries = []
-        for track in tracks:
-            if 'track' in track:
-                url = track['track']['sample']['audioUrl']
-                name = track['track']['name']
-            elif 'revision' in track:
-                url = track['revision']['mixdown']['file']
-                name = track['revision']['song']['name']
-            else:
-                raise Exception("Neither track nor revision found in 'posts' object")
-            id_regex = r'.+/(?P<id>[^/]+).m4a'
-            id = re.compile(id_regex).match(url).group('id')
-            entries.append({
-                'url': url,
-                'id': id,
-                'title': name
-            })
-
-        return {
-            '_type': 'playlist',
-            'id': album_id,
-            'album': config['name'],
-            'artist': config['creator']['name'],
-            'entries': entries
-        }
-
-
-class BandlabPlaylistIE(InfoExtractor):
-    _VALID_URL = r'https?://(?:www\.)?bandlab\.com/[^/]+/collections/(?P<id>[^/]+)'
-    _TEST = {
+    },
+    {
         'url': 'https://www.bandlab.com/hexatetrahedronx/collections/8fb1041c-e865-eb11-9889-0050f28a2802',
         'playlist': [
             {
@@ -156,17 +122,20 @@ class BandlabPlaylistIE(InfoExtractor):
         ],
         'info_dict': {
             'id': '8fb1041c-e865-eb11-9889-0050f28a2802',
-            'playlist': 'DOOMTAPE'
+            'album': 'DOOMTAPE',
+            'artist': '12days'
         }
-    }
+    }]
 
     def _real_extract(self, url):
-        playlist_id = self._match_id(url)
+        resource_id = self._match_id(url)
+        kind_regex = re.compile(self._VALID_URL)
+        kind = kind_regex.match(url).group('kind')
         config = self._download_json(
-            'http://www.bandlab.com/api/v1.3/collections/%s' % playlist_id, playlist_id)
+            'http://www.bandlab.com/api/v1.3/%s/%s' % kind, resource_id)
         tracks = config['posts']
         entries = []
-        for track in tracks:
+        for track in config['posts']:
             if 'track' in track:
                 url = track['track']['sample']['audioUrl']
                 name = track['track']['name']
@@ -175,17 +144,16 @@ class BandlabPlaylistIE(InfoExtractor):
                 name = track['revision']['song']['name']
             else:
                 raise Exception("Neither track nor revision found in 'posts' object")
-            id_regex = r'.+/(?P<id>[^/]+).m4a'
-            id = re.compile(id_regex).match(url).group('id')
             entries.append({
                 'url': url,
-                'id': id,
+                'id': self._TRACK_URL_RE.match(url).group('id'),
                 'title': name
             })
 
-        return {
+        res = {
             '_type': 'playlist',
-            'id': playlist_id,
-            'playlist': config['name'],
-            'entries': entries
+            'id': resource_id,
+            'entries': entries,
+            'album': config['name'],
+            'artist': config['creator']['name']
         }

From c673485d4eb6714057e0b86329591be48c7f00e8 Mon Sep 17 00:00:00 2001
From: npayne <nick.payne@anuvu.com>
Date: Sat, 28 Jan 2023 11:23:41 -0500
Subject: [PATCH 05/11] use try_get in bandlab extractor

---
 youtube_dl/extractor/bandlab.py | 37 ++++++++++++++++++++++-----------
 1 file changed, 25 insertions(+), 12 deletions(-)

diff --git a/youtube_dl/extractor/bandlab.py b/youtube_dl/extractor/bandlab.py
index b5c23d857..340734f4c 100644
--- a/youtube_dl/extractor/bandlab.py
+++ b/youtube_dl/extractor/bandlab.py
@@ -5,6 +5,16 @@ import re
 
 from .common import InfoExtractor
 
+from ..compat import (
+        compat_str,
+)
+
+from ..utils import (
+    strip_or_none,
+    try_get,
+    url_or_none,
+)
+
 
 class BandlabIE(InfoExtractor):
     _VALID_URL = r'https?://(?:www\.)?bandlab\.com/post/(?P<id>[^/]+)'
@@ -133,17 +143,20 @@ class BandlabAlbumOrPlaylistIE(InfoExtractor):
         kind = kind_regex.match(url).group('kind')
         config = self._download_json(
             'http://www.bandlab.com/api/v1.3/%s/%s' % kind, resource_id)
-        tracks = config['posts']
         entries = []
-        for track in config['posts']:
-            if 'track' in track:
-                url = track['track']['sample']['audioUrl']
-                name = track['track']['name']
-            elif 'revision' in track:
-                url = track['revision']['mixdown']['file']
-                name = track['revision']['song']['name']
-            else:
-                raise Exception("Neither track nor revision found in 'posts' object")
+        for track in try_get(config, lambda x: x['posts'], list) or []:
+            url, name = try_get(
+                track,
+                (lambda x: (x['track']['sample']['audioUrl'], x['track']['name']),
+                 lambda x: (x['revision']['mixdown']['file'], x['revision']['song']['name'])),
+                tuple) or (None, '', )
+            url = url_or_none(url)
+            name = strip_or_none(name)
+            if not (url and name):
+                continue
+            id = self._TRACK_ID_RE.match(url).groupdict().get('id')
+            if not id:
+                continue
             entries.append({
                 'url': url,
                 'id': self._TRACK_URL_RE.match(url).group('id'),
@@ -154,6 +167,6 @@ class BandlabAlbumOrPlaylistIE(InfoExtractor):
             '_type': 'playlist',
             'id': resource_id,
             'entries': entries,
-            'album': config['name'],
-            'artist': config['creator']['name']
+            'album': try_get(config, lambda x: x['name'].strip(), compat_str),
+            'artist': try_get(config, lambda x: x['creator']['name'].strip(), compat_str)
         }

From 8dced09ed0fd0961e8842066516e047f68835b66 Mon Sep 17 00:00:00 2001
From: npayne <nick.payne@anuvu.com>
Date: Sat, 28 Jan 2023 11:33:00 -0500
Subject: [PATCH 06/11] fix some errors in bandlab extractor - test now passes

---
 youtube_dl/extractor/bandlab.py    | 12 ++++++------
 youtube_dl/extractor/extractors.py |  2 +-
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/youtube_dl/extractor/bandlab.py b/youtube_dl/extractor/bandlab.py
index 340734f4c..bb06251ba 100644
--- a/youtube_dl/extractor/bandlab.py
+++ b/youtube_dl/extractor/bandlab.py
@@ -45,7 +45,7 @@ class BandlabIE(InfoExtractor):
 
 class BandlabAlbumOrPlaylistIE(InfoExtractor):
     _VALID_URL = r'https?://(?:www\.)?bandlab\.com/[^/]+/(?P<kind>albums|collections)/(?P<id>[^/]+)'
-    _TRACK_URL_RE = re.compile(r'.+/(?P<id>[^/]+)\.m4a')
+    _TRACK_ID_RE = re.compile(r'.+/(?P<id>[^/]+)\.m4a')
     _TESTS = [{
         'url': 'https://www.bandlab.com/sbsdasani/albums/dc26e307-e51f-ed11-95d7-002248452390',
         'playlist': [
@@ -142,7 +142,7 @@ class BandlabAlbumOrPlaylistIE(InfoExtractor):
         kind_regex = re.compile(self._VALID_URL)
         kind = kind_regex.match(url).group('kind')
         config = self._download_json(
-            'http://www.bandlab.com/api/v1.3/%s/%s' % kind, resource_id)
+            'http://www.bandlab.com/api/v1.3/%s/%s' % (kind, resource_id), resource_id)
         entries = []
         for track in try_get(config, lambda x: x['posts'], list) or []:
             url, name = try_get(
@@ -154,16 +154,16 @@ class BandlabAlbumOrPlaylistIE(InfoExtractor):
             name = strip_or_none(name)
             if not (url and name):
                 continue
-            id = self._TRACK_ID_RE.match(url).groupdict().get('id')
-            if not id:
+            track_id = self._TRACK_ID_RE.match(url).groupdict().get('id')
+            if not track_id:
                 continue
             entries.append({
                 'url': url,
-                'id': self._TRACK_URL_RE.match(url).group('id'),
+                'id': track_id,
                 'title': name
             })
 
-        res = {
+        return {
             '_type': 'playlist',
             'id': resource_id,
             'entries': entries,
diff --git a/youtube_dl/extractor/extractors.py b/youtube_dl/extractor/extractors.py
index 3f9acd0ec..a1327fb8c 100644
--- a/youtube_dl/extractor/extractors.py
+++ b/youtube_dl/extractor/extractors.py
@@ -98,7 +98,7 @@ from .azmedien import AZMedienIE
 from .baidu import BaiduVideoIE
 from .bandaichannel import BandaiChannelIE
 from .bandcamp import BandcampIE, BandcampAlbumIE, BandcampWeeklyIE
-from .bandlab import BandlabIE, BandlabAlbumIE, BandlabPlaylistIE
+from .bandlab import BandlabIE, BandlabAlbumOrPlaylistIE
 from .bbc import (
     BBCCoUkIE,
     BBCCoUkArticleIE,

From b5676df6dacd1d407fee5886c92b21bdd5c88ddd Mon Sep 17 00:00:00 2001
From: dirkf <fieldhouse@gmx.net>
Date: Sat, 28 Jan 2023 19:47:16 +0000
Subject: [PATCH 07/11] Linted?

---
 youtube_dl/extractor/bandlab.py | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/youtube_dl/extractor/bandlab.py b/youtube_dl/extractor/bandlab.py
index bb06251ba..ee1f3ff13 100644
--- a/youtube_dl/extractor/bandlab.py
+++ b/youtube_dl/extractor/bandlab.py
@@ -6,7 +6,7 @@ import re
 from .common import InfoExtractor
 
 from ..compat import (
-        compat_str,
+    compat_str,
 )
 
 from ..utils import (
@@ -118,8 +118,7 @@ class BandlabAlbumOrPlaylistIE(InfoExtractor):
             'album': 'ENDLESS SUMMER',
             'artist': 'Michael MacDonald'
         },
-    },
-    {
+    }, {
         'url': 'https://www.bandlab.com/hexatetrahedronx/collections/8fb1041c-e865-eb11-9889-0050f28a2802',
         'playlist': [
             {

From d6b19dca91dd4404becb9ee583e632f13423c5e2 Mon Sep 17 00:00:00 2001
From: npayne <nick.payne@anuvu.com>
Date: Sat, 28 Jan 2023 18:13:12 -0500
Subject: [PATCH 08/11] bandlab nits

---
 youtube_dl/extractor/bandlab.py    | 10 ++++------
 youtube_dl/extractor/extractors.py |  5 ++++-
 2 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/youtube_dl/extractor/bandlab.py b/youtube_dl/extractor/bandlab.py
index bb06251ba..2cfb711e2 100644
--- a/youtube_dl/extractor/bandlab.py
+++ b/youtube_dl/extractor/bandlab.py
@@ -45,7 +45,7 @@ class BandlabIE(InfoExtractor):
 
 class BandlabAlbumOrPlaylistIE(InfoExtractor):
     _VALID_URL = r'https?://(?:www\.)?bandlab\.com/[^/]+/(?P<kind>albums|collections)/(?P<id>[^/]+)'
-    _TRACK_ID_RE = re.compile(r'.+/(?P<id>[^/]+)\.m4a')
+    _TRACK_ID_RE = r'.+/(?P<id>[^/]+)\.m4a'
     _TESTS = [{
         'url': 'https://www.bandlab.com/sbsdasani/albums/dc26e307-e51f-ed11-95d7-002248452390',
         'playlist': [
@@ -138,9 +138,7 @@ class BandlabAlbumOrPlaylistIE(InfoExtractor):
     }]
 
     def _real_extract(self, url):
-        resource_id = self._match_id(url)
-        kind_regex = re.compile(self._VALID_URL)
-        kind = kind_regex.match(url).group('kind')
+        resource_id, kind = re.match(self._VALID_URL, url).group('id', 'kind')
         config = self._download_json(
             'http://www.bandlab.com/api/v1.3/%s/%s' % (kind, resource_id), resource_id)
         entries = []
@@ -154,13 +152,13 @@ class BandlabAlbumOrPlaylistIE(InfoExtractor):
             name = strip_or_none(name)
             if not (url and name):
                 continue
-            track_id = self._TRACK_ID_RE.match(url).groupdict().get('id')
+            track_id = re.match(self._TRACK_ID_RE, url).group('id')
             if not track_id:
                 continue
             entries.append({
                 'url': url,
                 'id': track_id,
-                'title': name
+                'title': name,
             })
 
         return {
diff --git a/youtube_dl/extractor/extractors.py b/youtube_dl/extractor/extractors.py
index a1327fb8c..ea0c3a0b8 100644
--- a/youtube_dl/extractor/extractors.py
+++ b/youtube_dl/extractor/extractors.py
@@ -98,7 +98,10 @@ from .azmedien import AZMedienIE
 from .baidu import BaiduVideoIE
 from .bandaichannel import BandaiChannelIE
 from .bandcamp import BandcampIE, BandcampAlbumIE, BandcampWeeklyIE
-from .bandlab import BandlabIE, BandlabAlbumOrPlaylistIE
+from .bandlab import (
+    BandlabIE, 
+    BandlabAlbumOrPlaylistIE
+)
 from .bbc import (
     BBCCoUkIE,
     BBCCoUkArticleIE,

From 2d7b8c95e32eeff3d7c28979ceb453810a1879cc Mon Sep 17 00:00:00 2001
From: dirkf <fieldhouse@gmx.net>
Date: Tue, 23 Jan 2024 03:56:46 +0000
Subject: [PATCH 09/11] Update with `traverse_obj()`

---
 youtube_dl/extractor/bandlab.py | 58 ++++++++++++++++-----------------
 1 file changed, 29 insertions(+), 29 deletions(-)

diff --git a/youtube_dl/extractor/bandlab.py b/youtube_dl/extractor/bandlab.py
index e93ea588d..5b713e2c4 100644
--- a/youtube_dl/extractor/bandlab.py
+++ b/youtube_dl/extractor/bandlab.py
@@ -1,17 +1,13 @@
 # coding: utf-8
 from __future__ import unicode_literals
 
-import re
-
 from .common import InfoExtractor
 
-from ..compat import (
-    compat_str,
-)
-
 from ..utils import (
-    strip_or_none,
-    try_get,
+    merge_dicts,
+    T,
+    traverse_obj,
+    txt_or_none,
     url_or_none,
 )
 
@@ -33,19 +29,23 @@ class BandlabIE(InfoExtractor):
         track_id = self._match_id(url)
         config = self._download_json(
             'http://www.bandlab.com/api/v1.3/posts/%s' % track_id, track_id)
-        track_url = config['track']['sample']['audioUrl']
+        track_url = traverse_obj(config, ('track', 'sample', 'audioUrl', T(url_or_none)))
+        if not track_url:
+            raise ExtractorError(
+                '[%s] 'No video formats found!' % (self.ie_key(), ),
+                video_id=track_id, expected=True)
+        title = config['track']['name']
 
         return {
             'id': track_id,
-            'title': config['track']['name'],
+            'title': title,
             'url': track_url,
-            'artist': config['creator']['name']
+            'artist': traverse_obj(config, ('creator', 'name', T(txt_or_none))),
         }
 
 
 class BandlabAlbumOrPlaylistIE(InfoExtractor):
     _VALID_URL = r'https?://(?:www\.)?bandlab\.com/[^/]+/(?P<kind>albums|collections)/(?P<id>[^/]+)'
-    _TRACK_ID_RE = r'.+/(?P<id>[^/]+)\.m4a'
     _TESTS = [{
         'url': 'https://www.bandlab.com/sbsdasani/albums/dc26e307-e51f-ed11-95d7-002248452390',
         'playlist': [
@@ -137,21 +137,22 @@ class BandlabAlbumOrPlaylistIE(InfoExtractor):
     }]
 
     def _real_extract(self, url):
-        resource_id, kind = re.match(self._VALID_URL, url).group('id', 'kind')
+        resource_id, kind = self._match_valid_url(url).group('id', 'kind')
         config = self._download_json(
             'http://www.bandlab.com/api/v1.3/%s/%s' % (kind, resource_id), resource_id)
         entries = []
-        for track in try_get(config, lambda x: x['posts'], list) or []:
-            url, name = try_get(
-                track,
-                (lambda x: (x['track']['sample']['audioUrl'], x['track']['name']),
-                 lambda x: (x['revision']['mixdown']['file'], x['revision']['song']['name'])),
-                tuple) or (None, '', )
-            url = url_or_none(url)
-            name = strip_or_none(name)
+        for track in traverse_obj(config, ('posts', Ellipsis)):
+            url, name = (traverse_obj(track, ('track', {
+                'url': ('sample', 'audioUrl', T(url_or_none)
+                'name': ('name', T(txt_or_none)),
+            }), ('revision', {
+                'url': ('mixdown', 'file', T(url_or_none)),
+                'name': ('song', 'name', T(txt_or_none)),
+            })).get(x) for x in ('url', 'name'))
             if not (url and name):
                 continue
-            track_id = re.match(self._TRACK_ID_RE, url).group('id')
+            track_id = self._search_regex(
+                r'/([^/]+)\.m4a$', url, 'track id', default=None)
             if not track_id:
                 continue
             entries.append({
@@ -160,10 +161,9 @@ class BandlabAlbumOrPlaylistIE(InfoExtractor):
                 'title': name,
             })
 
-        return {
-            '_type': 'playlist',
-            'id': resource_id,
-            'entries': entries,
-            'album': try_get(config, lambda x: x['name'].strip(), compat_str),
-            'artist': try_get(config, lambda x: x['creator']['name'].strip(), compat_str)
-        }
+        return merge_dicts(
+            self.playlist_result(entries, playlist_id=resource_id),
+            traverse_obj(config, {
+                'album': ('name', T(txt_or_none)),
+                'artist': ('creator', 'name', T(txt_or_none)),
+            }))

From f7c7ffbd09607b6ac8b1954592dfe36f532b8738 Mon Sep 17 00:00:00 2001
From: dirkf <fieldhouse@gmx.net>
Date: Tue, 23 Jan 2024 04:00:35 +0000
Subject: [PATCH 10/11] Apply suggestions from code review

---
 youtube_dl/extractor/bandlab.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/youtube_dl/extractor/bandlab.py b/youtube_dl/extractor/bandlab.py
index 5b713e2c4..5ec4d808e 100644
--- a/youtube_dl/extractor/bandlab.py
+++ b/youtube_dl/extractor/bandlab.py
@@ -4,6 +4,7 @@ from __future__ import unicode_literals
 from .common import InfoExtractor
 
 from ..utils import (
+    ExtractorError,
     merge_dicts,
     T,
     traverse_obj,
@@ -32,7 +33,7 @@ class BandlabIE(InfoExtractor):
         track_url = traverse_obj(config, ('track', 'sample', 'audioUrl', T(url_or_none)))
         if not track_url:
             raise ExtractorError(
-                '[%s] 'No video formats found!' % (self.ie_key(), ),
+                '[%s] No video formats found!' % (self.ie_key(), ),
                 video_id=track_id, expected=True)
         title = config['track']['name']
 

From 00d8a07eb61fef134a9addb50a584ebb8d3efeb1 Mon Sep 17 00:00:00 2001
From: dirkf <fieldhouse@gmx.net>
Date: Tue, 23 Jan 2024 04:02:26 +0000
Subject: [PATCH 11/11] Update youtube_dl/extractor/bandlab.py

---
 youtube_dl/extractor/bandlab.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/youtube_dl/extractor/bandlab.py b/youtube_dl/extractor/bandlab.py
index 5ec4d808e..5c0062fb8 100644
--- a/youtube_dl/extractor/bandlab.py
+++ b/youtube_dl/extractor/bandlab.py
@@ -144,7 +144,7 @@ class BandlabAlbumOrPlaylistIE(InfoExtractor):
         entries = []
         for track in traverse_obj(config, ('posts', Ellipsis)):
             url, name = (traverse_obj(track, ('track', {
-                'url': ('sample', 'audioUrl', T(url_or_none)
+                'url': ('sample', 'audioUrl', T(url_or_none)),
                 'name': ('name', T(txt_or_none)),
             }), ('revision', {
                 'url': ('mixdown', 'file', T(url_or_none)),