Compare commits

...

17 Commits

Author SHA1 Message Date
N/Ame
33e0aeb741
Merge ea7d7d85d3 into b83ca24eb7 2024-11-10 00:54:44 +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
grqx_wsl
ea7d7d85d3 request with impersonate=True 2024-11-01 11:15:54 +13:00
grqx_wsl
8e010ace41 make formats extraction non-fatal when geo-blocked or login required 2024-11-01 00:00:59 +13:00
grqx_wsl
0a05711805 float_or_none: use partial_application 2024-10-31 23:37:35 +13:00
grqx_wsl
6c6d75be16 Merge remote-tracking branch 'upstream/master' into ie/bahamut 2024-10-31 23:34:04 +13:00
grqx_wsl
50b2820684 Merge remote-tracking branch 'upstream/master' into ie/anigamer 2024-10-30 18:27:57 +13:00
grqx_wsl
ce031318fd [ie/anigamer] rename to bahamut 2024-10-30 18:11:38 +13:00
grqx_wsl
f8e15176cb add http headers at the root of the info_dict
simplify structure
2024-10-30 09:19:58 +13:00
grqx_wsl
2ad1cbf12d add http_headers for downloading 2024-10-29 13:14:30 +13:00
grqx_wsl
106f6c931b Merge remote-tracking branch 'upstream/master' into ie/anigamer 2024-10-28 13:28:21 +13:00
grqx_wsl
3b58fd1a20 add http_headers, extract all formats 2024-10-28 12:01:01 +13:00
grqx_wsl
99665e31b9 Merge remote-tracking branch 'upstream/master' into ie/anigamer 2024-10-28 11:28:14 +13:00
grqx_wsl
058ac436ba Use the same device_id in playlists 2024-10-23 18:32:11 +13:00
grqx_wsl
f87558f7a5 [ie/anigamer] add extractor 2024-10-23 10:40:48 +13:00
9 changed files with 167 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

@ -1869,6 +1869,9 @@ The following extractors use this feature:
#### digitalconcerthall #### digitalconcerthall
* `prefer_combined_hls`: Prefer extracting combined/pre-merged video and audio HLS formats. This will exclude 4K/HEVC video and lossless/FLAC audio formats, which are only available as split video/audio HLS formats * `prefer_combined_hls`: Prefer extracting combined/pre-merged video and audio HLS formats. This will exclude 4K/HEVC video and lossless/FLAC audio formats, which are only available as split video/audio HLS formats
#### bahamut
* `device_id`: (optional) Device ID got from `https://ani.gamer.com.tw/ajax/getdeviceid.php` (bound to cookies and `User-Agent` HTTP header). The extractor will automatically fetch one if it is not present. E.g. `"bahamut:device_id=1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab"`
**Note**: These options may be changed/removed in the future without concern for backward compatibility **Note**: These options may be changed/removed in the future without concern for backward compatibility
<!-- MANPAGE: MOVE "INSTALLATION" SECTION HERE --> <!-- MANPAGE: MOVE "INSTALLATION" SECTION HERE -->

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

@ -196,6 +196,7 @@ from .awaan import (
) )
from .axs import AxsIE from .axs import AxsIE
from .azmedien import AZMedienIE from .azmedien import AZMedienIE
from .bahamut import BahamutIE
from .baidu import BaiduVideoIE from .baidu import BaiduVideoIE
from .banbye import ( from .banbye import (
BanByeChannelIE, BanByeChannelIE,

111
yt_dlp/extractor/bahamut.py Normal file
View File

@ -0,0 +1,111 @@
from .common import InfoExtractor
from ..utils import (
ExtractorError,
float_or_none,
smuggle_url,
unified_timestamp,
unsmuggle_url,
)
from ..utils.traversal import traverse_obj
class BahamutIE(InfoExtractor):
_VALID_URL = r'https?://ani\.gamer\.com\.tw/animeVideo\.php\?sn=(?P<id>\d+)'
# see anime_player.js
RATING_TO_AGE_LIMIT = {
1: 0,
2: 6,
3: 12,
4: 15,
5: 18,
6: 18, # age-gated, needs login
}
def _real_extract(self, url):
url, unsmuggled_data = unsmuggle_url(url, {})
video_id = self._match_id(url)
device_id = (
self._configuration_arg('device_id', [None], casesense=True)[0]
or unsmuggled_data.get('device_id')
or self._download_json(
'https://ani.gamer.com.tw/ajax/getdeviceid.php', video_id,
'Downloading device ID', 'Failed to download device ID',
impersonate=True, headers=self.geo_verification_headers())['deviceid'])
# TODO: extract metadata from webpage
metadata = {}
if api_result := self._download_json(
'https://api.gamer.com.tw/anime/v1/video.php', video_id,
'Downloading video info', 'Failed to download video info',
impersonate=True, query={'videoSn': video_id}).get('data'):
metadata.update(traverse_obj(api_result, ('anime', {
'description': 'content',
'thumbnail': 'cover',
'tags': 'tags',
'creators': ('director', {lambda x: [x]}),
'title': 'title',
})))
playlist_id = traverse_obj(api_result, ('video', 'animeSn')) or ''
if self._yes_playlist(playlist_id, video_id) and unsmuggled_data.get('extract_playlist') is not False:
return self.playlist_result(
(self.url_result(
# it may be better to use self.cache for storing device_id
smuggle_url(f'https://ani.gamer.com.tw/animeVideo.php?sn={ep["videoSn"]}', {
'extract_playlist': False,
'device_id': device_id,
}), ie=BahamutIE,
video_id=ep['videoSn'], thumbnail=ep.get('cover')) for ep in traverse_obj(
api_result,
# This (the first ellipsis) extracts episodes of all languages,
# maybe just extract episodes of the current language?
('anime', 'episodes', ..., ...))),
playlist_id=playlist_id, **metadata)
# video-specific metadata, extract after returning the playlist result
metadata.update(traverse_obj(api_result, ('video', {
'thumbnail': 'cover',
'title': 'title',
'timestamp': ('upTime', {unified_timestamp}),
'duration': ('duration', {float_or_none(scale=60)}),
'age_limit': ('rating', {lambda x: self.RATING_TO_AGE_LIMIT.get(x)}),
})))
m3u8_info, urlh = self._download_json_handle(
'https://ani.gamer.com.tw/ajax/m3u8.php', video_id,
note='Downloading m3u8 URL', errnote='Failed to download m3u8 URL', query={
'sn': video_id,
'device': device_id,
}, impersonate=True, headers=self.geo_verification_headers(), expected_status=400)
formats_fatal = True
if urlh.status == 400:
# TODO: handle more error codes, search for /case \d+{4}:/g in anime_player.js
error_code = traverse_obj(m3u8_info, ('error', 'code'))
if error_code == 1011:
self.raise_geo_restricted(metadata_available=True)
formats_fatal = False
elif error_code == 1007:
if unsmuggled_data.pop('device_id', None) is not None:
return self.url_result(
smuggle_url(f'https://ani.gamer.com.tw/animeVideo.php?sn={video_id}',
unsmuggled_data), ie=BahamutIE, video_id=video_id)
raise ExtractorError('Invalid device id!')
elif error_code == 1017:
self.raise_login_required(metadata_available=True)
formats_fatal = False
else:
raise ExtractorError(
traverse_obj(m3u8_info, ('error', 'message')) or 'Failed to download m3u8 URL')
return {
**metadata,
'id': video_id,
'formats': self._extract_m3u8_formats(
m3u8_info.get('src'), video_id, ext='mp4', fatal=formats_fatal, headers={
'Origin': 'https://ani.gamer.com.tw',
**self.geo_verification_headers(),
}),
'http_headers': {'Origin': 'https://ani.gamer.com.tw'},
}