Compare commits

...

9 Commits

Author SHA1 Message Date
Conner
6076a63213
Merge 3e9b4fdb60 into b83ca24eb7 2024-11-10 14:42:03 +01:00
sepro
b83ca24eb7
[core] Catch broken Cryptodome installations (#11486)
Authored by: seproDev
2024-11-10 00:53:49 +01:00
bashonly
240a7d43c8
[build] Pin websockets version to >=13.0,<14 (#11488)
websockets 14.0 causes CI test failures (a lot more of them)

Authored by: bashonly
2024-11-09 23:46:47 +00:00
bashonly
f13df591d4
[build] Enable attestations for trusted publishing (#11420)
Reverts 428ffb75aa

Authored by: bashonly
2024-11-09 23:26:02 +00:00
Conner
3e9b4fdb60
Merge branch 'master' into frontendmasters-fix 2024-08-03 11:27:19 -07:00
Conner Luker
b68122d9aa merge upstream 2024-08-03 10:51:29 -07:00
Conner Luker
d9ab668223 fix captions 2024-02-27 12:45:48 -05:00
Conner Luker
4fc802f965 flake8 2024-02-21 23:26:30 -05:00
Conner Luker
4d24983442 use m3u8 extractor 2024-02-21 22:54:27 -05:00
7 changed files with 88 additions and 47 deletions

View File

@ -504,7 +504,8 @@ jobs:
- windows32 - windows32
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/download-artifact@v4 - name: Download artifacts
uses: actions/download-artifact@v4
with: with:
path: artifact path: artifact
pattern: build-bin-* pattern: build-bin-*

View File

@ -28,3 +28,20 @@ jobs:
actions: write # For cleaning up cache actions: write # For cleaning up cache
id-token: write # mandatory for trusted publishing id-token: write # mandatory for trusted publishing
secrets: inherit secrets: inherit
publish_pypi:
needs: [release]
if: vars.MASTER_PYPI_PROJECT != ''
runs-on: ubuntu-latest
permissions:
id-token: write # mandatory for trusted publishing
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: dist
name: build-pypi
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
verbose: true

View File

@ -41,3 +41,20 @@ jobs:
actions: write # For cleaning up cache actions: write # For cleaning up cache
id-token: write # mandatory for trusted publishing id-token: write # mandatory for trusted publishing
secrets: inherit secrets: inherit
publish_pypi:
needs: [release]
if: vars.NIGHTLY_PYPI_PROJECT != ''
runs-on: ubuntu-latest
permissions:
id-token: write # mandatory for trusted publishing
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: dist
name: build-pypi
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
verbose: true

View File

@ -2,10 +2,6 @@ name: Release
on: on:
workflow_call: workflow_call:
inputs: inputs:
prerelease:
required: false
default: true
type: boolean
source: source:
required: false required: false
default: '' default: ''
@ -18,6 +14,10 @@ on:
required: false required: false
default: '' default: ''
type: string type: string
prerelease:
required: false
default: true
type: boolean
workflow_dispatch: workflow_dispatch:
inputs: inputs:
source: source:
@ -278,11 +278,20 @@ jobs:
make clean-cache make clean-cache
python -m build --no-isolation . python -m build --no-isolation .
- name: Upload artifacts
if: github.event_name != 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: build-pypi
path: |
dist/*
compression-level: 0
- name: Publish to PyPI - name: Publish to PyPI
if: github.event_name == 'workflow_dispatch'
uses: pypa/gh-action-pypi-publish@release/v1 uses: pypa/gh-action-pypi-publish@release/v1
with: with:
verbose: true verbose: true
attestations: false # Currently doesn't work w/ reusable workflows (breaks nightly)
publish: publish:
needs: [prepare, build] needs: [prepare, build]

View File

@ -52,7 +52,7 @@ default = [
"pycryptodomex", "pycryptodomex",
"requests>=2.32.2,<3", "requests>=2.32.2,<3",
"urllib3>=1.26.17,<3", "urllib3>=1.26.17,<3",
"websockets>=13.0", "websockets>=13.0,<14",
] ]
curl-cffi = [ curl-cffi = [
"curl-cffi==0.5.10; os_name=='nt' and implementation_name=='cpython'", "curl-cffi==0.5.10; os_name=='nt' and implementation_name=='cpython'",

View File

@ -24,7 +24,7 @@ try:
from Crypto.Cipher import AES, PKCS1_OAEP, Blowfish, PKCS1_v1_5 # noqa: F401 from Crypto.Cipher import AES, PKCS1_OAEP, Blowfish, PKCS1_v1_5 # noqa: F401
from Crypto.Hash import CMAC, SHA1 # noqa: F401 from Crypto.Hash import CMAC, SHA1 # noqa: F401
from Crypto.PublicKey import RSA # noqa: F401 from Crypto.PublicKey import RSA # noqa: F401
except ImportError: except (ImportError, OSError):
__version__ = f'broken {__version__}'.strip() __version__ = f'broken {__version__}'.strip()

View File

@ -16,11 +16,20 @@ class FrontendMastersBaseIE(InfoExtractor):
_NETRC_MACHINE = 'frontendmasters' _NETRC_MACHINE = 'frontendmasters'
_QUALITIES = { def _get_subtitles(self, lesson_data, course_data):
'low': {'width': 480, 'height': 360}, captions_base = "https://captions.frontendmasters.com/assets/courses/"
'mid': {'width': 1280, 'height': 720}, lesson_slug = lesson_data.get('slug')
'high': {'width': 1920, 'height': 1080}, lesson_index = lesson_data.get('index')
} date_published = course_data.get('datePublished')
course_slug = course_data.get('slug')
subtitles_url = f'{captions_base}{date_published}-{course_slug}/{lesson_index}-{lesson_slug}.vtt'
return {
'en': [{
'url': subtitles_url
}]
}
def _perform_login(self, username, password): def _perform_login(self, username, password):
login_page = self._download_webpage( login_page = self._download_webpage(
@ -72,7 +81,7 @@ class FrontendMastersPageBaseIE(FrontendMastersBaseIE):
return chapters return chapters
@staticmethod @staticmethod
def _extract_lesson(chapters, lesson_id, lesson): def _extract_lesson(chapters, lesson_id, lesson, subtitles):
title = lesson.get('title') or lesson_id title = lesson.get('title') or lesson_id
display_id = lesson.get('slug') display_id = lesson.get('slug')
description = lesson.get('description') description = lesson.get('description')
@ -109,6 +118,7 @@ class FrontendMastersPageBaseIE(FrontendMastersBaseIE):
'duration': duration, 'duration': duration,
'chapter': chapter, 'chapter': chapter,
'chapter_number': chapter_number, 'chapter_number': chapter_number,
'subtitles': subtitles
} }
@ -132,43 +142,28 @@ class FrontendMastersIE(FrontendMastersBaseIE):
lesson_id = self._match_id(url) lesson_id = self._match_id(url)
source_url = f'{self._API_BASE}/video/{lesson_id}/source' source_url = f'{self._API_BASE}/video/{lesson_id}/source'
headers = {
formats = [] 'Referer': 'https://frontendmasters.com/',
for ext in ('webm', 'mp4'):
for quality in ('low', 'mid', 'high'):
resolution = self._QUALITIES[quality].copy()
format_id = f'{ext}-{quality}'
format_url = self._download_json(
source_url, lesson_id,
f'Downloading {format_id} source JSON', query={
'f': ext,
'r': resolution['height'],
}, headers={
'Referer': url,
}, fatal=False)['url']
if not format_url:
continue
f = resolution.copy()
f.update({
'url': format_url,
'ext': ext,
'format_id': format_id,
})
formats.append(f)
subtitles = {
'en': [{
'url': f'{self._API_BASE}/transcripts/{lesson_id}.vtt',
}],
} }
cookies = self._get_cookies("https://frontendmasters.com/")
fem_auth_mod = cookies.get('fem_auth_mod')
if fem_auth_mod:
headers['Cookie'] = f'fem_auth_mod={fem_auth_mod.value}'
json_response = self._download_json(
source_url,
'Downloading source JSON', query={
'f': 'm3u8'
}, headers=headers)
m3u8_url = json_response.get('url')
formats = self._extract_m3u8_formats(m3u8_url, lesson_id)
return { return {
'id': lesson_id, 'id': lesson_id,
'title': lesson_id, 'title': lesson_id,
'formats': formats, 'formats': formats,
'subtitles': subtitles,
} }
@ -203,8 +198,9 @@ class FrontendMastersLessonIE(FrontendMastersPageBaseIE):
for video_id, data in course['lessonData'].items() for video_id, data in course['lessonData'].items()
if data.get('slug') == lesson_name) if data.get('slug') == lesson_name)
subtitles = self.extract_subtitles(lesson, course)
chapters = self._extract_chapters(course) chapters = self._extract_chapters(course)
return self._extract_lesson(chapters, lesson_id, lesson) return self._extract_lesson(chapters, lesson_id, lesson, subtitles)
class FrontendMastersCourseIE(FrontendMastersPageBaseIE): class FrontendMastersCourseIE(FrontendMastersPageBaseIE):
@ -239,9 +235,10 @@ class FrontendMastersCourseIE(FrontendMastersPageBaseIE):
for lesson in lessons: for lesson in lessons:
lesson_name = lesson.get('slug') lesson_name = lesson.get('slug')
lesson_id = lesson.get('hash') or lesson.get('statsId') lesson_id = lesson.get('hash') or lesson.get('statsId')
subtitles = self.extract_subtitles(lesson, course)
if not lesson_id or not lesson_name: if not lesson_id or not lesson_name:
continue continue
entries.append(self._extract_lesson(chapters, lesson_id, lesson)) entries.append(self._extract_lesson(chapters, lesson_id, lesson, subtitles))
title = course.get('title') title = course.get('title')
description = course.get('description') description = course.get('description')