mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2024-11-26 17:21:23 +01:00
Compare commits
9 Commits
e49bec4915
...
6076a63213
Author | SHA1 | Date | |
---|---|---|---|
|
6076a63213 | ||
|
b83ca24eb7 | ||
|
240a7d43c8 | ||
|
f13df591d4 | ||
|
3e9b4fdb60 | ||
|
b68122d9aa | ||
|
d9ab668223 | ||
|
4fc802f965 | ||
|
4d24983442 |
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
|
@ -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-*
|
||||||
|
|
17
.github/workflows/release-master.yml
vendored
17
.github/workflows/release-master.yml
vendored
|
@ -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
|
||||||
|
|
17
.github/workflows/release-nightly.yml
vendored
17
.github/workflows/release-nightly.yml
vendored
|
@ -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
|
||||||
|
|
19
.github/workflows/release.yml
vendored
19
.github/workflows/release.yml
vendored
|
@ -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]
|
||||||
|
|
|
@ -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'",
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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')
|
||||||
|
|
Loading…
Reference in New Issue
Block a user