From ed0ecfe56d5e2f4940b31485b27a44520ef1f4ea Mon Sep 17 00:00:00 2001 From: Greg Rotter Date: Tue, 17 Dec 2024 10:34:04 -0800 Subject: [PATCH 01/10] [ie/createacademy] extract single lesson --- yt_dlp/extractor/_extractors.py | 3 + yt_dlp/extractor/createacademy.py | 105 ++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 yt_dlp/extractor/createacademy.py diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py index 967010826..fa7b5313c 100644 --- a/yt_dlp/extractor/_extractors.py +++ b/yt_dlp/extractor/_extractors.py @@ -434,6 +434,9 @@ from .cracked import CrackedIE from .crackle import CrackleIE from .craftsy import CraftsyIE +from .createacademy import ( + CreateAcademyIE, +) from .crooksandliars import CrooksAndLiarsIE from .crowdbunker import ( CrowdBunkerChannelIE, diff --git a/yt_dlp/extractor/createacademy.py b/yt_dlp/extractor/createacademy.py new file mode 100644 index 000000000..705837da3 --- /dev/null +++ b/yt_dlp/extractor/createacademy.py @@ -0,0 +1,105 @@ +import json +import re + +from .common import InfoExtractor +from ..utils import ( + extract_attributes, +) + + +class CreateAcademyIE(InfoExtractor): + _VALID_URL = r'https://www.createacademy.com/(?:[^/]+/)*lessons/(?P[^/?#]+)' + + _TESTS = [ + { + 'url': 'https://www.createacademy.com/courses/dan-pearson/lessons/meet-dan', + 'info_dict': { + 'id': '265', + 'ext': 'mp4', + 'title': 'Create Academy - s10e01 - Meet Dan', + 'description': 'md5:48c8af37219020571a84d5f406e75d86', + 'thumbnail': 'https://cf-images.eu-west-1.prod.boltdns.net/v1/static/6222962662001/22f75006-c49f-4d95-8673-1b60df4223d2/45d953e0-fa58-4cb6-9217-1c7b3c80c932/1280x720/match/image.jpg', + }, + }, + ] + + def _get_lesson_metadata(self, data, lesson_id): + prefix = 'Create Academy - s' + str(data['props']['course']['id']) + 'e' + + for section in data['props']['course']['curriculum']['sections']: + for lesson in section['lessons']: + if lesson['id'] == lesson_id: + if lesson['number'] < 10: + num = '0' + str(lesson['number']) + else: + num = str(lesson['number']) + + return { + 'section_data': section, + 'title': prefix + num + ' - ' + lesson['title'].strip() + } + + return { + 'section_data': { + 'id': 0, + 'number': 0, + 'title': '', + }, + 'title': prefix + '00 - ' + data['props']['lesson']['title'].strip() + } + + def _get_policy_key(self, data, video_id): + accountId = data['props']['brightcove']['accountId'] + playerId = data['props']['brightcove']['playerId'] + + playerData = self._download_webpage(f'https://players.brightcove.net/{accountId}/{playerId}_default/index.min.js', video_id, 'Retrieving policy key') + obj = re.search(r'{policyKey:"(.*?)"}', playerData) + key = re.search(r'"(.*?)"', obj.group()) + + return key.group().replace('"', '') + + + def _get_manifest_url(self, data, video_id): + hostVideoId = data['props']['lesson']['video']['host_video_id'] + accountId = data['props']['brightcove']['accountId'] + policyKey = self._get_policy_key(data, video_id) + + manifestData = self._download_json(f'https://edge.api.brightcove.com/playback/v1/accounts/{accountId}/videos/{hostVideoId}', video_id, 'Retrieving manifest URL', headers={'accept': f'application/json;pk={policyKey}'}) + + for source in manifestData['sources']: + if 'master.m3u8' in source['src']: + return source['src'] + + def _real_extract(self, url): + video_id = self._match_id(url) + webpage = self._download_webpage(url, video_id) + + # parse + page_elem = self._search_regex(r'(]+>)', webpage, 'div') + attributes = extract_attributes(page_elem) + data = json.loads(attributes['data-page']) + createacademy_id = data['props']['lesson']['id'] + + # get media from manifest + manifestUrl = self._get_manifest_url(data, video_id) + + formats, subtitles = [], {} + fmts, subs = self._extract_m3u8_formats_and_subtitles(manifestUrl, str(createacademy_id), 'mp4') + + formats.extend(fmts) + self._merge_subtitles(subs, target=subtitles) + + lesson_metadata = self._get_lesson_metadata(data, createacademy_id) + + return { + 'id': str(createacademy_id), + 'title': lesson_metadata['title'], + 'display_id': video_id, + 'description': data['props']['lesson']['description'], + 'thumbnail': data['props']['lesson']['thumbnail'], + 'formats': formats, + 'subtitles': subtitles, + 'chapter': lesson_metadata['section_data']['title'].strip(), + 'chapter_number': lesson_metadata['section_data']['number'], + 'chapter_id': str(lesson_metadata['section_data']['id']), + } From 6eb69a3e12eb56891d73e935524b55e7a7da033a Mon Sep 17 00:00:00 2001 From: Greg Rotter Date: Tue, 17 Dec 2024 14:18:48 -0800 Subject: [PATCH 02/10] [ie/createacademy] extract course --- yt_dlp/extractor/_extractors.py | 1 + yt_dlp/extractor/createacademy.py | 52 ++++++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py index fa7b5313c..2fddcc206 100644 --- a/yt_dlp/extractor/_extractors.py +++ b/yt_dlp/extractor/_extractors.py @@ -436,6 +436,7 @@ from .craftsy import CraftsyIE from .createacademy import ( CreateAcademyIE, + CreateAcademyCourseIE, ) from .crooksandliars import CrooksAndLiarsIE from .crowdbunker import ( diff --git a/yt_dlp/extractor/createacademy.py b/yt_dlp/extractor/createacademy.py index 705837da3..af489b4d8 100644 --- a/yt_dlp/extractor/createacademy.py +++ b/yt_dlp/extractor/createacademy.py @@ -18,6 +18,10 @@ class CreateAcademyIE(InfoExtractor): 'ext': 'mp4', 'title': 'Create Academy - s10e01 - Meet Dan', 'description': 'md5:48c8af37219020571a84d5f406e75d86', + 'display_id': 'meet-dan', + 'chapter': 'Introduction', + 'chapter_id': '34', + 'chapter_number': 1, 'thumbnail': 'https://cf-images.eu-west-1.prod.boltdns.net/v1/static/6222962662001/22f75006-c49f-4d95-8673-1b60df4223d2/45d953e0-fa58-4cb6-9217-1c7b3c80c932/1280x720/match/image.jpg', }, }, @@ -70,14 +74,17 @@ def _get_manifest_url(self, data, video_id): if 'master.m3u8' in source['src']: return source['src'] - def _real_extract(self, url): - video_id = self._match_id(url) + def _get_page_data(self, url, video_id): webpage = self._download_webpage(url, video_id) - # parse page_elem = self._search_regex(r'(]+>)', webpage, 'div') attributes = extract_attributes(page_elem) - data = json.loads(attributes['data-page']) + + return json.loads(attributes['data-page']) + + def _real_extract(self, url): + video_id = self._match_id(url) + data = self._get_page_data(url, video_id) createacademy_id = data['props']['lesson']['id'] # get media from manifest @@ -103,3 +110,40 @@ def _real_extract(self, url): 'chapter_number': lesson_metadata['section_data']['number'], 'chapter_id': str(lesson_metadata['section_data']['id']), } + + +class CreateAcademyCourseIE(CreateAcademyIE): + _VALID_URL = r'https://www.createacademy.com/courses/(?P[^/?#]+)' + + _TESTS = [ + { + 'url': 'https://www.createacademy.com/courses/dan-pearson', + 'info_dict': { + 'id': '265', + 'ext': 'mp4', + 'chapter_id': '34', + 'description': 'md5:48c8af37219020571a84d5f406e75d86', + 'chapter_number': 1, + 'thumbnail': 'https://cf-images.eu-west-1.prod.boltdns.net/v1/static/6222962662001/22f75006-c49f-4d95-8673-1b60df4223d2/45d953e0-fa58-4cb6-9217-1c7b3c80c932/1280x720/match/image.jpg', + 'title': 'Create Academy - s10e01 - Meet Dan', + 'display_id': 'dan-pearson', + 'chapter': 'Introduction', + }, + }, + ] + + def _real_extract(self, url): + video_id = self._match_id(url) + data = self._get_page_data(url, video_id) + + # iterate lessons + entries = [] + + for section in data['props']['curriculum']['sections']: + for lesson in section['lessons']: + entries.append(super()._real_extract('https://www.createacademy.com' + lesson['lessonPath'])) + + return { + '_type': 'multi_video', + 'entries': entries, + } From ba1d60dfd8df4b97ed1c27cedacb0c925011966f Mon Sep 17 00:00:00 2001 From: Greg Rotter Date: Tue, 17 Dec 2024 14:48:11 -0800 Subject: [PATCH 03/10] [ie/createacademy] formatting --- yt_dlp/extractor/_extractors.py | 2 +- yt_dlp/extractor/createacademy.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py index 2fddcc206..630435002 100644 --- a/yt_dlp/extractor/_extractors.py +++ b/yt_dlp/extractor/_extractors.py @@ -435,7 +435,7 @@ from .crackle import CrackleIE from .craftsy import CraftsyIE from .createacademy import ( - CreateAcademyIE, + CreateAcademyBaseIE, CreateAcademyCourseIE, ) from .crooksandliars import CrooksAndLiarsIE diff --git a/yt_dlp/extractor/createacademy.py b/yt_dlp/extractor/createacademy.py index af489b4d8..6cafd0136 100644 --- a/yt_dlp/extractor/createacademy.py +++ b/yt_dlp/extractor/createacademy.py @@ -7,7 +7,7 @@ ) -class CreateAcademyIE(InfoExtractor): +class CreateAcademyBaseIE(InfoExtractor): _VALID_URL = r'https://www.createacademy.com/(?:[^/]+/)*lessons/(?P[^/?#]+)' _TESTS = [ @@ -40,7 +40,7 @@ def _get_lesson_metadata(self, data, lesson_id): return { 'section_data': section, - 'title': prefix + num + ' - ' + lesson['title'].strip() + 'title': prefix + num + ' - ' + lesson['title'].strip(), } return { @@ -49,7 +49,7 @@ def _get_lesson_metadata(self, data, lesson_id): 'number': 0, 'title': '', }, - 'title': prefix + '00 - ' + data['props']['lesson']['title'].strip() + 'title': prefix + '00 - ' + data['props']['lesson']['title'].strip(), } def _get_policy_key(self, data, video_id): @@ -62,7 +62,6 @@ def _get_policy_key(self, data, video_id): return key.group().replace('"', '') - def _get_manifest_url(self, data, video_id): hostVideoId = data['props']['lesson']['video']['host_video_id'] accountId = data['props']['brightcove']['accountId'] @@ -112,7 +111,7 @@ def _real_extract(self, url): } -class CreateAcademyCourseIE(CreateAcademyIE): +class CreateAcademyCourseIE(CreateAcademyBaseIE): _VALID_URL = r'https://www.createacademy.com/courses/(?P[^/?#]+)' _TESTS = [ From 6e636bdd4c4b328d5db7207b549e0a7bc0b90dac Mon Sep 17 00:00:00 2001 From: Greg Rotter Date: Tue, 17 Dec 2024 16:25:37 -0800 Subject: [PATCH 04/10] [ie/createacademy] better leading zeros --- yt_dlp/extractor/createacademy.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/yt_dlp/extractor/createacademy.py b/yt_dlp/extractor/createacademy.py index 6cafd0136..abbc215b2 100644 --- a/yt_dlp/extractor/createacademy.py +++ b/yt_dlp/extractor/createacademy.py @@ -28,19 +28,14 @@ class CreateAcademyBaseIE(InfoExtractor): ] def _get_lesson_metadata(self, data, lesson_id): - prefix = 'Create Academy - s' + str(data['props']['course']['id']) + 'e' + prefix = 'Create Academy - s' + str(data['props']['course']['id']).zfill(2) + 'e' for section in data['props']['course']['curriculum']['sections']: for lesson in section['lessons']: if lesson['id'] == lesson_id: - if lesson['number'] < 10: - num = '0' + str(lesson['number']) - else: - num = str(lesson['number']) - return { 'section_data': section, - 'title': prefix + num + ' - ' + lesson['title'].strip(), + 'title': prefix + str(lesson['number']).zfill(2) + ' - ' + lesson['title'].strip(), } return { From a8ab7708efa23e5c7667a1679574083d2c363bcb Mon Sep 17 00:00:00 2001 From: Greg Rotter Date: Wed, 18 Dec 2024 12:07:24 -0800 Subject: [PATCH 05/10] [ie/createacademy] cleanup --- yt_dlp/extractor/createacademy.py | 66 ++++++++++++++++++------------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/yt_dlp/extractor/createacademy.py b/yt_dlp/extractor/createacademy.py index abbc215b2..5859d0b0e 100644 --- a/yt_dlp/extractor/createacademy.py +++ b/yt_dlp/extractor/createacademy.py @@ -4,6 +4,8 @@ from .common import InfoExtractor from ..utils import ( extract_attributes, + get_element_html_by_id, + traverse_obj, ) @@ -28,28 +30,32 @@ class CreateAcademyBaseIE(InfoExtractor): ] def _get_lesson_metadata(self, data, lesson_id): - prefix = 'Create Academy - s' + str(data['props']['course']['id']).zfill(2) + 'e' + course = traverse_obj(data, ('props', 'course')) + prefix = 'Create Academy - s' + str(course.get('id')).zfill(2) + 'e' - for section in data['props']['course']['curriculum']['sections']: - for lesson in section['lessons']: - if lesson['id'] == lesson_id: + sections = traverse_obj(course, ('curriculum', 'sections')) + + for section in sections: + for lesson in section.get('lessons'): + if lesson.get('id') == lesson_id: return { - 'section_data': section, - 'title': prefix + str(lesson['number']).zfill(2) + ' - ' + lesson['title'].strip(), + 'section': section, + 'title': prefix + str(lesson.get('number')).zfill(2) + ' - ' + lesson.get('title').strip(), } return { - 'section_data': { + 'section': { 'id': 0, 'number': 0, 'title': '', }, - 'title': prefix + '00 - ' + data['props']['lesson']['title'].strip(), + 'title': prefix + '00 - ' + traverse_obj(data, ('props', 'lesson', 'title')).strip(), } def _get_policy_key(self, data, video_id): - accountId = data['props']['brightcove']['accountId'] - playerId = data['props']['brightcove']['playerId'] + bc = traverse_obj(data, ('props', 'brightcove')) + accountId = bc.get('accountId') + playerId = bc.get('playerId') playerData = self._download_webpage(f'https://players.brightcove.net/{accountId}/{playerId}_default/index.min.js', video_id, 'Retrieving policy key') obj = re.search(r'{policyKey:"(.*?)"}', playerData) @@ -58,28 +64,30 @@ def _get_policy_key(self, data, video_id): return key.group().replace('"', '') def _get_manifest_url(self, data, video_id): - hostVideoId = data['props']['lesson']['video']['host_video_id'] - accountId = data['props']['brightcove']['accountId'] + host_video_id = traverse_obj(data, ('props', 'lesson', 'video', 'host_video_id')) + accountId = traverse_obj(data, ('props', 'brightcove', 'accountId')) policyKey = self._get_policy_key(data, video_id) - manifestData = self._download_json(f'https://edge.api.brightcove.com/playback/v1/accounts/{accountId}/videos/{hostVideoId}', video_id, 'Retrieving manifest URL', headers={'accept': f'application/json;pk={policyKey}'}) + manifest_data = self._download_json(f'https://edge.api.brightcove.com/playback/v1/accounts/{accountId}/videos/{host_video_id}', video_id, 'Retrieving manifest URL', headers={'accept': f'application/json;pk={policyKey}'}) - for source in manifestData['sources']: - if 'master.m3u8' in source['src']: - return source['src'] + for source in manifest_data.get('sources'): + if 'master.m3u8' in source.get('src'): + return source.get('src') def _get_page_data(self, url, video_id): webpage = self._download_webpage(url, video_id) - page_elem = self._search_regex(r'(]+>)', webpage, 'div') + page_elem = get_element_html_by_id('app', webpage) attributes = extract_attributes(page_elem) - return json.loads(attributes['data-page']) + return json.loads(attributes.get('data-page')) def _real_extract(self, url): video_id = self._match_id(url) data = self._get_page_data(url, video_id) - createacademy_id = data['props']['lesson']['id'] + + lesson = traverse_obj(data, ('props', 'lesson')) + createacademy_id = lesson.get('id') # get media from manifest manifestUrl = self._get_manifest_url(data, video_id) @@ -91,18 +99,19 @@ def _real_extract(self, url): self._merge_subtitles(subs, target=subtitles) lesson_metadata = self._get_lesson_metadata(data, createacademy_id) + section = lesson_metadata.get('section') return { 'id': str(createacademy_id), - 'title': lesson_metadata['title'], + 'title': lesson_metadata.get('title'), 'display_id': video_id, - 'description': data['props']['lesson']['description'], - 'thumbnail': data['props']['lesson']['thumbnail'], + 'description': lesson.get('description'), + 'thumbnail': lesson.get('thumbnail'), 'formats': formats, 'subtitles': subtitles, - 'chapter': lesson_metadata['section_data']['title'].strip(), - 'chapter_number': lesson_metadata['section_data']['number'], - 'chapter_id': str(lesson_metadata['section_data']['id']), + 'chapter': section.get('title').strip(), + 'chapter_number': section.get('number'), + 'chapter_id': str(section.get('id')), } @@ -132,10 +141,11 @@ def _real_extract(self, url): # iterate lessons entries = [] + sections = traverse_obj(data, ('props', 'curriculum', 'sections')) - for section in data['props']['curriculum']['sections']: - for lesson in section['lessons']: - entries.append(super()._real_extract('https://www.createacademy.com' + lesson['lessonPath'])) + for section in sections: + for lesson in section.get('lessons'): + entries.append(super()._real_extract('https://www.createacademy.com' + lesson.get('lessonPath'))) return { '_type': 'multi_video', From 7362372a4167dc2a0feb34d50036be5d61b597b8 Mon Sep 17 00:00:00 2001 From: Greg Rotter Date: Wed, 18 Dec 2024 16:19:19 -0800 Subject: [PATCH 06/10] [ie/createacademy] regex fixes --- yt_dlp/extractor/_extractors.py | 2 +- yt_dlp/extractor/createacademy.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py index 630435002..595daf273 100644 --- a/yt_dlp/extractor/_extractors.py +++ b/yt_dlp/extractor/_extractors.py @@ -435,8 +435,8 @@ from .crackle import CrackleIE from .craftsy import CraftsyIE from .createacademy import ( - CreateAcademyBaseIE, CreateAcademyCourseIE, + CreateAcademyIE, ) from .crooksandliars import CrooksAndLiarsIE from .crowdbunker import ( diff --git a/yt_dlp/extractor/createacademy.py b/yt_dlp/extractor/createacademy.py index 5859d0b0e..a898bf5ee 100644 --- a/yt_dlp/extractor/createacademy.py +++ b/yt_dlp/extractor/createacademy.py @@ -9,7 +9,7 @@ ) -class CreateAcademyBaseIE(InfoExtractor): +class CreateAcademyIE(InfoExtractor): _VALID_URL = r'https://www.createacademy.com/(?:[^/]+/)*lessons/(?P[^/?#]+)' _TESTS = [ @@ -83,7 +83,7 @@ def _get_page_data(self, url, video_id): return json.loads(attributes.get('data-page')) def _real_extract(self, url): - video_id = self._match_id(url) + video_id = url.split('/')[-1] data = self._get_page_data(url, video_id) lesson = traverse_obj(data, ('props', 'lesson')) @@ -115,8 +115,8 @@ def _real_extract(self, url): } -class CreateAcademyCourseIE(CreateAcademyBaseIE): - _VALID_URL = r'https://www.createacademy.com/courses/(?P[^/?#]+)' +class CreateAcademyCourseIE(CreateAcademyIE): + _VALID_URL = r'https://www.createacademy.com/courses/(?!.*\/lessons\/)(?P[^/?#]+)' _TESTS = [ { @@ -129,7 +129,7 @@ class CreateAcademyCourseIE(CreateAcademyBaseIE): 'chapter_number': 1, 'thumbnail': 'https://cf-images.eu-west-1.prod.boltdns.net/v1/static/6222962662001/22f75006-c49f-4d95-8673-1b60df4223d2/45d953e0-fa58-4cb6-9217-1c7b3c80c932/1280x720/match/image.jpg', 'title': 'Create Academy - s10e01 - Meet Dan', - 'display_id': 'dan-pearson', + 'display_id': 'meet-dan', 'chapter': 'Introduction', }, }, From 39cf716ff163c19aba53176870ac7f311a008027 Mon Sep 17 00:00:00 2001 From: Greg Rotter Date: Thu, 19 Dec 2024 12:49:09 -0800 Subject: [PATCH 07/10] [ie/createacademy] switch _type to playlist --- yt_dlp/extractor/createacademy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yt_dlp/extractor/createacademy.py b/yt_dlp/extractor/createacademy.py index a898bf5ee..484b61580 100644 --- a/yt_dlp/extractor/createacademy.py +++ b/yt_dlp/extractor/createacademy.py @@ -148,6 +148,6 @@ def _real_extract(self, url): entries.append(super()._real_extract('https://www.createacademy.com' + lesson.get('lessonPath'))) return { - '_type': 'multi_video', + '_type': 'playlist', 'entries': entries, } From f4e5369330df78da53577f6a76455440d0104579 Mon Sep 17 00:00:00 2001 From: Greg Rotter Date: Fri, 20 Dec 2024 14:22:43 -0800 Subject: [PATCH 08/10] [ie/createacademy] adding file_prefix --- yt_dlp/extractor/createacademy.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/yt_dlp/extractor/createacademy.py b/yt_dlp/extractor/createacademy.py index 484b61580..c23bab9dd 100644 --- a/yt_dlp/extractor/createacademy.py +++ b/yt_dlp/extractor/createacademy.py @@ -18,7 +18,8 @@ class CreateAcademyIE(InfoExtractor): 'info_dict': { 'id': '265', 'ext': 'mp4', - 'title': 'Create Academy - s10e01 - Meet Dan', + 'file_prefix': 'Create Academy - s10e01', + 'title': 'Meet Dan', 'description': 'md5:48c8af37219020571a84d5f406e75d86', 'display_id': 'meet-dan', 'chapter': 'Introduction', @@ -40,7 +41,8 @@ def _get_lesson_metadata(self, data, lesson_id): if lesson.get('id') == lesson_id: return { 'section': section, - 'title': prefix + str(lesson.get('number')).zfill(2) + ' - ' + lesson.get('title').strip(), + 'file_prefix': prefix + str(lesson.get('number')).zfill(2), + 'title': lesson.get('title').strip(), } return { @@ -49,7 +51,8 @@ def _get_lesson_metadata(self, data, lesson_id): 'number': 0, 'title': '', }, - 'title': prefix + '00 - ' + traverse_obj(data, ('props', 'lesson', 'title')).strip(), + 'file_prefix': 'Create Academy', + 'title': traverse_obj(data, ('props', 'lesson', 'title')).strip(), } def _get_policy_key(self, data, video_id): @@ -103,6 +106,7 @@ def _real_extract(self, url): return { 'id': str(createacademy_id), + 'file_prefix': lesson_metadata.get('file_prefix'), 'title': lesson_metadata.get('title'), 'display_id': video_id, 'description': lesson.get('description'), @@ -128,7 +132,8 @@ class CreateAcademyCourseIE(CreateAcademyIE): 'description': 'md5:48c8af37219020571a84d5f406e75d86', 'chapter_number': 1, 'thumbnail': 'https://cf-images.eu-west-1.prod.boltdns.net/v1/static/6222962662001/22f75006-c49f-4d95-8673-1b60df4223d2/45d953e0-fa58-4cb6-9217-1c7b3c80c932/1280x720/match/image.jpg', - 'title': 'Create Academy - s10e01 - Meet Dan', + 'file_prefix': 'Create Academy - s10e01', + 'title': 'Meet Dan', 'display_id': 'meet-dan', 'chapter': 'Introduction', }, From 708a60dc83d2d7d852c70bf6ce65cbd6a778ab32 Mon Sep 17 00:00:00 2001 From: Greg Rotter Date: Sat, 21 Dec 2024 14:26:29 -0800 Subject: [PATCH 09/10] [ie/createacademy] add title to playlists --- yt_dlp/extractor/createacademy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/yt_dlp/extractor/createacademy.py b/yt_dlp/extractor/createacademy.py index c23bab9dd..b0d137f16 100644 --- a/yt_dlp/extractor/createacademy.py +++ b/yt_dlp/extractor/createacademy.py @@ -154,5 +154,6 @@ def _real_extract(self, url): return { '_type': 'playlist', + 'title': traverse_obj(data, ('props', 'course', 'name')), 'entries': entries, } From 1386b1fcfe3f1844edddb077d3697172ec046242 Mon Sep 17 00:00:00 2001 From: Greg Rotter Date: Sat, 4 Jan 2025 10:33:08 -0800 Subject: [PATCH 10/10] [ie/createacademy] add course_id to playlists --- yt_dlp/extractor/createacademy.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/yt_dlp/extractor/createacademy.py b/yt_dlp/extractor/createacademy.py index b0d137f16..5042006a7 100644 --- a/yt_dlp/extractor/createacademy.py +++ b/yt_dlp/extractor/createacademy.py @@ -24,6 +24,7 @@ class CreateAcademyIE(InfoExtractor): 'display_id': 'meet-dan', 'chapter': 'Introduction', 'chapter_id': '34', + 'course_id': '10', 'chapter_number': 1, 'thumbnail': 'https://cf-images.eu-west-1.prod.boltdns.net/v1/static/6222962662001/22f75006-c49f-4d95-8673-1b60df4223d2/45d953e0-fa58-4cb6-9217-1c7b3c80c932/1280x720/match/image.jpg', }, @@ -116,6 +117,7 @@ def _real_extract(self, url): 'chapter': section.get('title').strip(), 'chapter_number': section.get('number'), 'chapter_id': str(section.get('id')), + 'course_id': str(traverse_obj(data, ('props', 'course', 'id'))).zfill(2), } @@ -135,6 +137,7 @@ class CreateAcademyCourseIE(CreateAcademyIE): 'file_prefix': 'Create Academy - s10e01', 'title': 'Meet Dan', 'display_id': 'meet-dan', + 'course_id': '10', 'chapter': 'Introduction', }, },