mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2026-05-08 00:16:30 +00:00
Compare commits
No commits in common. "22ea0688ed6bcdbe4c51401a84239cda3decfc9c" and "c8ede5f34d6c95c442b936bb01ecbcb724aefdef" have entirely different histories.
22ea0688ed
...
c8ede5f34d
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -131,7 +131,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Needed for changelog
|
fetch-depth: 0 # Needed for changelog
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
- name: Install Requirements
|
- name: Install Requirements
|
||||||
@ -460,7 +460,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python_version }}
|
python-version: ${{ matrix.python_version }}
|
||||||
architecture: ${{ matrix.arch }}
|
architecture: ${{ matrix.arch }}
|
||||||
|
|||||||
2
.github/workflows/core.yml
vendored
2
.github/workflows/core.yml
vendored
@ -53,7 +53,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install test requirements
|
- name: Install test requirements
|
||||||
|
|||||||
4
.github/workflows/download.yml
vendored
4
.github/workflows/download.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.9
|
||||||
- name: Install test requirements
|
- name: Install test requirements
|
||||||
@ -38,7 +38,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install test requirements
|
- name: Install test requirements
|
||||||
|
|||||||
4
.github/workflows/quick-test.yml
vendored
4
.github/workflows/quick-test.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python 3.9
|
- name: Set up Python 3.9
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: '3.9'
|
python-version: '3.9'
|
||||||
- name: Install test requirements
|
- name: Install test requirements
|
||||||
@ -27,7 +27,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: '3.9'
|
python-version: '3.9'
|
||||||
- name: Install dev dependencies
|
- name: Install dev dependencies
|
||||||
|
|||||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@ -79,7 +79,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.10" # Keep this in sync with test-workflows.yml
|
python-version: "3.10" # Keep this in sync with test-workflows.yml
|
||||||
|
|
||||||
@ -173,7 +173,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
|
|
||||||
@ -241,7 +241,7 @@ jobs:
|
|||||||
path: artifact
|
path: artifact
|
||||||
pattern: build-*
|
pattern: build-*
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/signature-tests.yml
vendored
2
.github/workflows/signature-tests.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install test requirements
|
- name: Install test requirements
|
||||||
|
|||||||
2
.github/workflows/test-workflows.yml
vendored
2
.github/workflows/test-workflows.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.10" # Keep this in sync with release.yml's prepare job
|
python-version: "3.10" # Keep this in sync with release.yml's prepare job
|
||||||
- name: Install requirements
|
- name: Install requirements
|
||||||
|
|||||||
@ -1523,6 +1523,10 @@ from .piramidetv import (
|
|||||||
PiramideTVChannelIE,
|
PiramideTVChannelIE,
|
||||||
PiramideTVIE,
|
PiramideTVIE,
|
||||||
)
|
)
|
||||||
|
from .pixivsketch import (
|
||||||
|
PixivSketchIE,
|
||||||
|
PixivSketchUserIE,
|
||||||
|
)
|
||||||
from .planetmarathi import PlanetMarathiIE
|
from .planetmarathi import PlanetMarathiIE
|
||||||
from .platzi import (
|
from .platzi import (
|
||||||
PlatziCourseIE,
|
PlatziCourseIE,
|
||||||
|
|||||||
@ -50,14 +50,7 @@ class NewsPicksIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
fragment = self._search_nextjs_data(webpage, video_id)['props']['pageProps']['fragment']
|
fragment = self._search_nextjs_data(webpage, video_id)['props']['pageProps']['fragment']
|
||||||
movie = fragment['movie']
|
m3u8_url = traverse_obj(fragment, ('movie', 'movieUrl', {url_or_none}, {require('m3u8 URL')}))
|
||||||
|
|
||||||
if traverse_obj(movie, ('viewable', {str})) == 'PARTIAL_FREE' and not traverse_obj(movie, ('canWatch', {bool})):
|
|
||||||
self.report_warning(
|
|
||||||
'Full video is for Premium members. Without cookies, '
|
|
||||||
f'only the preview is downloaded. {self._login_hint()}', video_id)
|
|
||||||
|
|
||||||
m3u8_url = traverse_obj(movie, ('movieUrl', {url_or_none}, {require('m3u8 URL')}))
|
|
||||||
formats, subtitles = self._extract_m3u8_formats_and_subtitles(m3u8_url, video_id, 'mp4')
|
formats, subtitles = self._extract_m3u8_formats_and_subtitles(m3u8_url, video_id, 'mp4')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -66,12 +59,12 @@ class NewsPicksIE(InfoExtractor):
|
|||||||
'series': traverse_obj(fragment, ('series', 'title', {str})),
|
'series': traverse_obj(fragment, ('series', 'title', {str})),
|
||||||
'series_id': series_id,
|
'series_id': series_id,
|
||||||
'subtitles': subtitles,
|
'subtitles': subtitles,
|
||||||
**traverse_obj(movie, {
|
**traverse_obj(fragment, ('movie', {
|
||||||
'title': ('title', {str}),
|
'title': ('title', {str}),
|
||||||
'cast': ('relatedUsers', ..., 'displayName', {str}, filter, all, filter),
|
'cast': ('relatedUsers', ..., 'displayName', {str}, filter, all, filter),
|
||||||
'description': ('explanation', {clean_html}),
|
'description': ('explanation', {clean_html}),
|
||||||
'release_timestamp': ('onAirStartDate', {parse_iso8601}),
|
'release_timestamp': ('onAirStartDate', {parse_iso8601}),
|
||||||
'thumbnail': (('image', 'coverImageUrl'), {url_or_none}, any),
|
'thumbnail': (('image', 'coverImageUrl'), {url_or_none}, any),
|
||||||
'timestamp': ('published', {parse_iso8601}),
|
'timestamp': ('published', {parse_iso8601}),
|
||||||
}),
|
})),
|
||||||
}
|
}
|
||||||
|
|||||||
119
yt_dlp/extractor/pixivsketch.py
Normal file
119
yt_dlp/extractor/pixivsketch.py
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
from .common import InfoExtractor
|
||||||
|
from ..networking.exceptions import HTTPError
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
traverse_obj,
|
||||||
|
unified_timestamp,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PixivSketchBaseIE(InfoExtractor):
|
||||||
|
def _call_api(self, video_id, path, referer, note='Downloading JSON metadata'):
|
||||||
|
response = self._download_json(f'https://sketch.pixiv.net/api/{path}', video_id, note=note, headers={
|
||||||
|
'Referer': referer,
|
||||||
|
'X-Requested-With': referer,
|
||||||
|
})
|
||||||
|
errors = traverse_obj(response, ('errors', ..., 'message'))
|
||||||
|
if errors:
|
||||||
|
raise ExtractorError(' '.join(f'{e}.' for e in errors))
|
||||||
|
return response.get('data') or {}
|
||||||
|
|
||||||
|
|
||||||
|
class PixivSketchIE(PixivSketchBaseIE):
|
||||||
|
IE_NAME = 'pixiv:sketch'
|
||||||
|
_VALID_URL = r'https?://sketch\.pixiv\.net/@(?P<uploader_id>[a-zA-Z0-9_-]+)/lives/(?P<id>\d+)/?'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://sketch.pixiv.net/@nuhutya/lives/3654620468641830507',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '7370666691623196569',
|
||||||
|
'title': 'まにあえクリスマス!',
|
||||||
|
'uploader': 'ぬふちゃ',
|
||||||
|
'uploader_id': 'nuhutya',
|
||||||
|
'channel_id': '9844815',
|
||||||
|
'age_limit': 0,
|
||||||
|
'timestamp': 1640351536,
|
||||||
|
},
|
||||||
|
'skip': True,
|
||||||
|
}, {
|
||||||
|
# these two (age_limit > 0) requires you to login on website, but it's actually not required for download
|
||||||
|
'url': 'https://sketch.pixiv.net/@namahyou/lives/4393103321546851377',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '4907995960957946943',
|
||||||
|
'title': 'クリスマスなんて知らん🖕',
|
||||||
|
'uploader': 'すゃもり',
|
||||||
|
'uploader_id': 'suya2mori2',
|
||||||
|
'channel_id': '31169300',
|
||||||
|
'age_limit': 15,
|
||||||
|
'timestamp': 1640347640,
|
||||||
|
},
|
||||||
|
'skip': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://sketch.pixiv.net/@8aki/lives/3553803162487249670',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1593420639479156945',
|
||||||
|
'title': 'おまけ本作業(リョナ有)',
|
||||||
|
'uploader': 'おぶい / Obui',
|
||||||
|
'uploader_id': 'oving',
|
||||||
|
'channel_id': '17606',
|
||||||
|
'age_limit': 18,
|
||||||
|
'timestamp': 1640330263,
|
||||||
|
},
|
||||||
|
'skip': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id, uploader_id = self._match_valid_url(url).group('id', 'uploader_id')
|
||||||
|
data = self._call_api(video_id, f'lives/{video_id}.json', url)
|
||||||
|
|
||||||
|
if not traverse_obj(data, 'is_broadcasting'):
|
||||||
|
raise ExtractorError(f'This live is offline. Use https://sketch.pixiv.net/@{uploader_id} for ongoing live.', expected=True)
|
||||||
|
|
||||||
|
m3u8_url = traverse_obj(data, ('owner', 'hls_movie', 'url'))
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
|
m3u8_url, video_id, ext='mp4',
|
||||||
|
entry_protocol='m3u8_native', m3u8_id='hls')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': data.get('name'),
|
||||||
|
'formats': formats,
|
||||||
|
'uploader': traverse_obj(data, ('user', 'name'), ('owner', 'user', 'name')),
|
||||||
|
'uploader_id': traverse_obj(data, ('user', 'unique_name'), ('owner', 'user', 'unique_name')),
|
||||||
|
'channel_id': str(traverse_obj(data, ('user', 'pixiv_user_id'), ('owner', 'user', 'pixiv_user_id'))),
|
||||||
|
'age_limit': 18 if data.get('is_r18') else 15 if data.get('is_r15') else 0,
|
||||||
|
'timestamp': unified_timestamp(data.get('created_at')),
|
||||||
|
'is_live': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PixivSketchUserIE(PixivSketchBaseIE):
|
||||||
|
IE_NAME = 'pixiv:sketch:user'
|
||||||
|
_VALID_URL = r'https?://sketch\.pixiv\.net/@(?P<id>[a-zA-Z0-9_-]+)/?'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://sketch.pixiv.net/@nuhutya',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://sketch.pixiv.net/@namahyou',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://sketch.pixiv.net/@8aki',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def suitable(cls, url):
|
||||||
|
return super().suitable(url) and not PixivSketchIE.suitable(url)
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
user_id = self._match_id(url)
|
||||||
|
data = self._call_api(user_id, f'lives/users/@{user_id}.json', url)
|
||||||
|
|
||||||
|
if not traverse_obj(data, 'is_broadcasting'):
|
||||||
|
try:
|
||||||
|
self._call_api(user_id, 'users/current.json', url, 'Investigating reason for request failure')
|
||||||
|
except ExtractorError as e:
|
||||||
|
if isinstance(e.cause, HTTPError) and e.cause.status == 401:
|
||||||
|
self.raise_login_required(f'Please log in, or use direct link like https://sketch.pixiv.net/@{user_id}/1234567890', method='cookies')
|
||||||
|
raise ExtractorError('This user is offline', expected=True)
|
||||||
|
|
||||||
|
return self.url_result(f'https://sketch.pixiv.net/@{user_id}/lives/{data["id"]}')
|
||||||
@ -1518,22 +1518,19 @@ class TikTokLiveIE(TikTokBaseIE):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
uploader, room_id = self._match_valid_url(url).group('uploader', 'id')
|
uploader, room_id = self._match_valid_url(url).group('uploader', 'id')
|
||||||
if not room_id:
|
webpage = self._download_webpage(
|
||||||
webpage = self._download_webpage(
|
url, uploader or room_id, headers={'User-Agent': 'Mozilla/5.0'}, fatal=not room_id)
|
||||||
format_field(uploader, None, self._UPLOADER_URL_FORMAT), uploader)
|
|
||||||
room_id = traverse_obj(
|
|
||||||
self._get_universal_data(webpage, uploader),
|
|
||||||
('webapp.user-detail', 'userInfo', 'user', 'roomId', {str}))
|
|
||||||
|
|
||||||
if not uploader or not room_id:
|
if webpage:
|
||||||
webpage = self._download_webpage(url, uploader or room_id, fatal=not room_id)
|
|
||||||
data = self._get_sigi_state(webpage, uploader or room_id)
|
data = self._get_sigi_state(webpage, uploader or room_id)
|
||||||
room_id = room_id or traverse_obj(data, ((
|
room_id = (
|
||||||
('LiveRoom', 'liveRoomUserInfo', 'user'),
|
traverse_obj(data, ((
|
||||||
('UserModule', 'users', ...)), 'roomId', {str}, any))
|
('LiveRoom', 'liveRoomUserInfo', 'user'),
|
||||||
uploader = uploader or traverse_obj(data, ((
|
('UserModule', 'users', ...)), 'roomId', {str}, any))
|
||||||
('LiveRoom', 'liveRoomUserInfo', 'user'),
|
or self._search_regex(r'snssdk\d*://live\?room_id=(\d+)', webpage, 'room ID', default=room_id))
|
||||||
('UserModule', 'users', ...)), 'uniqueId', {str}, any))
|
uploader = uploader or traverse_obj(
|
||||||
|
data, ('LiveRoom', 'liveRoomUserInfo', 'user', 'uniqueId'),
|
||||||
|
('UserModule', 'users', ..., 'uniqueId'), get_all=False, expected_type=str)
|
||||||
|
|
||||||
if not room_id:
|
if not room_id:
|
||||||
raise UserNotLive(video_id=uploader)
|
raise UserNotLive(video_id=uploader)
|
||||||
|
|||||||
@ -151,7 +151,7 @@ class VimeoBaseInfoExtractor(InfoExtractor):
|
|||||||
'Referer': self._LOGIN_URL,
|
'Referer': self._LOGIN_URL,
|
||||||
})
|
})
|
||||||
except ExtractorError as e:
|
except ExtractorError as e:
|
||||||
if isinstance(e.cause, HTTPError) and e.cause.status in (404, 405, 418):
|
if isinstance(e.cause, HTTPError) and e.cause.status in (405, 418):
|
||||||
raise ExtractorError(
|
raise ExtractorError(
|
||||||
'Unable to log in: bad username or password',
|
'Unable to log in: bad username or password',
|
||||||
expected=True)
|
expected=True)
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import base64
|
|
||||||
import itertools
|
import itertools
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@ -13,7 +12,6 @@ from ..utils import (
|
|||||||
int_or_none,
|
int_or_none,
|
||||||
parse_duration,
|
parse_duration,
|
||||||
str_or_none,
|
str_or_none,
|
||||||
try_call,
|
|
||||||
try_get,
|
try_get,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
url_or_none,
|
url_or_none,
|
||||||
@ -231,11 +229,6 @@ class XHamsterIE(InfoExtractor):
|
|||||||
standard_url = standard_format.get(standard_format_key)
|
standard_url = standard_format.get(standard_format_key)
|
||||||
if not standard_url:
|
if not standard_url:
|
||||||
continue
|
continue
|
||||||
decoded = try_call(lambda: base64.b64decode(standard_url))
|
|
||||||
if decoded and decoded[:4] == b'xor_':
|
|
||||||
standard_url = bytes(
|
|
||||||
a ^ b for a, b in
|
|
||||||
zip(decoded[4:], itertools.cycle(b'xh7999'))).decode()
|
|
||||||
standard_url = urljoin(url, standard_url)
|
standard_url = urljoin(url, standard_url)
|
||||||
if not standard_url or standard_url in format_urls:
|
if not standard_url or standard_url in format_urls:
|
||||||
continue
|
continue
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user