Compare commits

...

7 Commits

Author SHA1 Message Date
kclauhk
9a3b3cbcce
Merge bf4aa73fb7 into b83ca24eb7 2024-11-10 09:28:03 +05:30
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
kclauhk
bf4aa73fb7 add ffmpeg argument to avoid keepalive request failure in downloading live video 2024-09-10 15:12:58 +08:00
kclauhk
6a3d3dca93 Update test data 2024-09-07 16:22:12 +08:00
kclauhk
c071dbdc18 [ie/meritplus] Add extractor 2024-08-29 23:24:42 +08:00
8 changed files with 205 additions and 8 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

@ -1120,6 +1120,7 @@ from .megatvcom import (
) )
from .meipai import MeipaiIE from .meipai import MeipaiIE
from .melonvod import MelonVODIE from .melonvod import MelonVODIE
from .meritplus import MeritPlusIE
from .metacritic import MetacriticIE from .metacritic import MetacriticIE
from .mgtv import MGTVIE from .mgtv import MGTVIE
from .microsoftembed import ( from .microsoftembed import (

View File

@ -0,0 +1,152 @@
from .common import InfoExtractor
from ..utils import (
determine_ext,
float_or_none,
int_or_none,
join_nonempty,
traverse_obj,
url_or_none,
)
class MeritPlusIE(InfoExtractor):
_VALID_URL = r'https?://(?:[\w-]+\.)?meritplus\.com/(?:c/)?(?P<type>[a-z]+)/(?P<ep>[^/]+\?episodeId=)?(?P<id>[^/&\?]+)'
_TESTS = [{
'url': 'https://www.meritplus.com/c/s/VQ2aB6Sp?episodeId=uNLp2Rgg&play=1',
'info_dict': {
'id': 'uNLp2Rgg',
'ext': 'mp4',
'title': 'Right to Stand Your Ground | Dr. Phil Primetime',
'description': r're:^Employees who fought back against robbers, unruly customers, and',
'thumbnail': 'https://cdn.jwplayer.com/v2/media/uNLp2Rgg/poster.jpg?width=1920',
'duration': 2519.0,
'tags': 'count:14',
'timestamp': 1724716800,
'upload_date': '20240827',
'series': 'Dr. Phil Primetime',
'season': 'Season 1',
'season_number': 1,
'episode': 'Episode 79',
'episode_number': 79,
},
}, {
'url': 'https://www.meritplus.com/c/m/ebVUK1wS?r=ok5bikOE',
'info_dict': {
'id': 'ebVUK1wS',
'ext': 'mp4',
'title': 'PBR Teams 2024: Gambler Days | Day 1',
'description': r're:^Get ready for Gambler Days with the PBR! Watch elite riders battle',
'thumbnail': 'https://cdn.jwplayer.com/v2/media/ebVUK1wS/poster.jpg?width=1920',
'duration': 10876.0,
'tags': 'count:6',
'timestamp': 1724461200,
'upload_date': '20240824',
},
}, {
'url': 'https://www.meritplus.com/m/AzVJ4sEH/bull-rider-najiah-knight-the-cowgirl-way?r=uzD8QNRj',
'info_dict': {
'id': 'AzVJ4sEH',
'ext': 'mp4',
'title': 'Bull Rider Najiah Knight | The Cowgirl Way',
'description': r're:^Najiah Knight is rewriting the rules and inspiring future Cowgirls',
'thumbnail': 'https://cdn.jwplayer.com/v2/media/AzVJ4sEH/poster.jpg?width=1920',
'duration': 300.0,
'tags': ['The Cowgirl Way', 'seriesId_uzD8QNRj', 'PBR'],
'timestamp': 1720195320,
'upload_date': '20240705',
},
}, {
'url': 'https://www.meritplus.com/c/sns/jryHEWXj',
'info_dict': {
'id': 'jryHEWXj',
'title': 'Morning On Merit Street',
'description': r're:^Award winning journalist Dominique Sachse and co-host Fanchon Stinger',
'thumbnail': 'https://assets.mediabackstage.com/merit_prod/thumbnails/moms_thumbnail_1920x1080-1708708358462.jpg',
},
'playlist_count': 5,
}, {
'url': 'https://www.meritplus.com/c/s/eAzd5bqW',
'info_dict': {
'id': 'eAzd5bqW',
'title': 'Crime Stories with Nancy Grace',
'description': r're:^Nancy Grace explores the inside story of true crimes and cold cases',
'thumbnail': 'https://assets.mediabackstage.com/merit_prod%2Fthumbnails%2Fnancygrace_thumbnail_1920x1080-1721658217049.jpg',
},
'playlist_count': 15,
}]
def _real_extract(self, url):
video_id, c_type, is_episode = self._match_valid_url(url).group('id', 'type', 'ep')
if is_episode or c_type == 'm':
json = self._download_json(f'https://cdn.jwplayer.com/v2/media/{video_id}', video_id)
else:
json = self._download_json(f'https://cdn.jwplayer.com/v2/playlists/{video_id}?format=json&page_limit=500', video_id)
entries = []
for video in json.get('playlist', []):
thumbnails, formats, subtitles = [], [], {}
for image in video.get('images', []):
thumbnails.append({
'url': url_or_none(image.get('src')),
'width': image.get('width'),
})
for caption in video.get('tracks', []):
if caption.get('kind') == 'captions':
subtitles.setdefault(caption.get('label', 'und'), []).append({
'url': caption.get('file'),
'name': caption.get('label'),
})
is_live = bool(video.get('is_live'))
for source in video.get('sources', []):
if media_url := url_or_none(source.get('file')):
if determine_ext(media_url) == 'm3u8':
hls_fmts, hls_subs = self._extract_m3u8_formats_and_subtitles(
media_url, video['mediaid'], fatal=None)
if is_live:
for f in hls_fmts:
f['downloader_options'] = {'ffmpeg_args_out': ['-http_persistent', '0']}
formats.extend(hls_fmts)
self._merge_subtitles(hls_subs, target=subtitles)
else:
formats.append(traverse_obj(source, {
'format_id': ('label', {lambda v: 'audio' if 'Audio' in v else v}),
'url': ('file', {str}),
'height': ('height', {int_or_none}),
'width': ('width', {int_or_none}),
'filesize': ('filesize', {int}),
'fps': ('framerate', {float_or_none}),
'tbr': ('bitrate', {lambda v: int_or_none(v, 1000)}),
'acodec': ('label', {lambda v: 'aac' if 'AAC' in v else None}),
'vcodec': ('type', {lambda v: 'none' if 'audio' in v else None}),
}))
entries.append({**traverse_obj(video, {
'id': ('mediaid', {str}),
'title': ('title', {str}),
'description': ('description', {str}),
'timestamp': ('pubdate', {int}),
'tags': ('tags', {lambda v: v.split(',') if v else None}),
'series': ('programName', {lambda v: v or None}),
'season_number': ('seasonNumber', {int_or_none}),
'episode_number': ('episodeNumber', {int_or_none}),
'cast': ('cast', {lambda v: v.split(',') if v else None}),
'duration': ('duration', {float_or_none}),
'webpage_url': ('mediaid', {lambda v: url + (f'?episodeId={v}' if v not in url else '')}),
'thumbnail': ('image', {lambda v: url_or_none(v) if not thumbnails else None}),
}),
'is_live': is_live,
'thumbnails': thumbnails,
'formats': formats,
'subtitles': subtitles,
})
if len(entries) == 1:
return entries[0]
elif len(entries) > 1:
description = join_nonempty('shortDescription', 'description', delim=' ', from_dict=json)
thumbnail = traverse_obj(json, (('imgHomeRailThumb16x9', 'imgFeaturedTvBanner16x9'),
{url_or_none}), get_all=False)
return self.playlist_result(entries, id=json['seriesId'], title=json['title'],
description=description, thumbnail=thumbnail)
else:
self.raise_no_formats('No video formats found!')