mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2026-02-10 07:57:06 +00:00
@@ -2180,7 +2180,10 @@ from .tvc import (
|
||||
TVCIE,
|
||||
TVCArticleIE,
|
||||
)
|
||||
from .tver import TVerIE
|
||||
from .tver import (
|
||||
TVerIE,
|
||||
TVerOlympicIE,
|
||||
)
|
||||
from .tvigle import TvigleIE
|
||||
from .tviplayer import TVIPlayerIE
|
||||
from .tvn24 import TVN24IE
|
||||
|
||||
@@ -22,7 +22,7 @@ class StreaksBaseIE(InfoExtractor):
|
||||
_GEO_BYPASS = False
|
||||
_GEO_COUNTRIES = ['JP']
|
||||
|
||||
def _extract_from_streaks_api(self, project_id, media_id, headers=None, query=None, ssai=False):
|
||||
def _extract_from_streaks_api(self, project_id, media_id, headers=None, query=None, ssai=False, live_from_start=False):
|
||||
try:
|
||||
response = self._download_json(
|
||||
self._API_URL_TEMPLATE.format('playback', project_id, media_id, ''),
|
||||
@@ -83,6 +83,10 @@ class StreaksBaseIE(InfoExtractor):
|
||||
|
||||
fmts, subs = self._extract_m3u8_formats_and_subtitles(
|
||||
src_url, media_id, 'mp4', m3u8_id='hls', fatal=False, live=is_live, query=query)
|
||||
for fmt in fmts:
|
||||
if live_from_start:
|
||||
fmt.setdefault('downloader_options', {}).update({'ffmpeg_args': ['-live_start_index', '0']})
|
||||
fmt['is_from_start'] = True
|
||||
formats.extend(fmts)
|
||||
self._merge_subtitles(subs, target=subtitles)
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from .streaks import StreaksBaseIE
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
GeoRestrictedError,
|
||||
clean_html,
|
||||
int_or_none,
|
||||
join_nonempty,
|
||||
make_archive_id,
|
||||
@@ -11,7 +12,9 @@ from ..utils import (
|
||||
str_or_none,
|
||||
strip_or_none,
|
||||
time_seconds,
|
||||
unified_timestamp,
|
||||
update_url_query,
|
||||
url_or_none,
|
||||
)
|
||||
from ..utils.traversal import require, traverse_obj
|
||||
|
||||
@@ -257,3 +260,113 @@ class TVerIE(StreaksBaseIE):
|
||||
'id': video_id,
|
||||
'_old_archive_ids': [make_archive_id('BrightcoveNew', brightcove_id)] if brightcove_id else None,
|
||||
}
|
||||
|
||||
|
||||
class TVerOlympicIE(StreaksBaseIE):
|
||||
IE_NAME = 'tver:olympic'
|
||||
|
||||
_API_BASE = 'https://olympic-data.tver.jp/api'
|
||||
_VALID_URL = r'https?://(?:www\.)?tver\.jp/olympic/milanocortina2026/(?P<type>live|video)/play/(?P<id>\w+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://tver.jp/olympic/milanocortina2026/video/play/3b1d4462150b42558d9cc8aabb5238d0/',
|
||||
'info_dict': {
|
||||
'id': '3b1d4462150b42558d9cc8aabb5238d0',
|
||||
'ext': 'mp4',
|
||||
'title': '【開会式】ぎゅっと凝縮ハイライト',
|
||||
'display_id': 'ref:3b1d4462150b42558d9cc8aabb5238d0',
|
||||
'duration': 712.045,
|
||||
'live_status': 'not_live',
|
||||
'modified_date': r're:\d{8}',
|
||||
'modified_timestamp': int,
|
||||
'tags': 'count:1',
|
||||
'thumbnail': r're:https://.+\.(?:jpg|png)',
|
||||
'timestamp': 1770420187,
|
||||
'upload_date': '20260206',
|
||||
'uploader_id': 'tver-olympic',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://tver.jp/olympic/milanocortina2026/live/play/glts313itwvj/',
|
||||
'info_dict': {
|
||||
'id': 'glts313itwvj',
|
||||
'ext': 'mp4',
|
||||
'title': '開会式ハイライト',
|
||||
'channel_id': 'ntv',
|
||||
'display_id': 'ref:sp_260207_spc_01_dvr',
|
||||
'duration': 7680,
|
||||
'live_status': 'was_live',
|
||||
'modified_date': r're:\d{8}',
|
||||
'modified_timestamp': int,
|
||||
'thumbnail': r're:https://.+\.(?:jpg|png)',
|
||||
'timestamp': 1770420300,
|
||||
'upload_date': '20260206',
|
||||
'uploader_id': 'tver-olympic-live',
|
||||
},
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_type, video_id = self._match_valid_url(url).group('type', 'id')
|
||||
live_from_start = self.get_param('live_from_start')
|
||||
|
||||
if video_type == 'live':
|
||||
project_id = 'tver-olympic-live'
|
||||
api_key = 'a35ebb1ca7d443758dc7fcc5d99b1f72'
|
||||
olympic_data = traverse_obj(self._download_json(
|
||||
f'{self._API_BASE}/live/{video_id}', video_id), ('contents', 'live', {dict}))
|
||||
media_id = traverse_obj(olympic_data, ('video_id', {str}))
|
||||
|
||||
now = time_seconds()
|
||||
start_timestamp_str = traverse_obj(olympic_data, ('onair_start_date', {str}))
|
||||
start_timestamp = unified_timestamp(start_timestamp_str, tz_offset=9)
|
||||
if not start_timestamp:
|
||||
raise ExtractorError('Unable to extract on-air start time')
|
||||
end_timestamp = traverse_obj(olympic_data, (
|
||||
'onair_end_date', {unified_timestamp(tz_offset=9)}, {require('on-air end time')}))
|
||||
|
||||
if now < start_timestamp:
|
||||
self.raise_no_formats(
|
||||
f'This program is scheduled to start at {start_timestamp_str} JST', expected=True)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'live_status': 'is_upcoming',
|
||||
'release_timestamp': start_timestamp,
|
||||
}
|
||||
elif start_timestamp <= now < end_timestamp:
|
||||
live_status = 'is_live'
|
||||
if live_from_start:
|
||||
media_id += '_dvr'
|
||||
elif end_timestamp <= now:
|
||||
dvr_end_timestamp = traverse_obj(olympic_data, (
|
||||
'dvr_end_date', {unified_timestamp(tz_offset=9)}))
|
||||
if dvr_end_timestamp and now < dvr_end_timestamp:
|
||||
live_status = 'was_live'
|
||||
media_id += '_dvr'
|
||||
else:
|
||||
raise ExtractorError(
|
||||
'This program is no longer available', expected=True)
|
||||
else:
|
||||
project_id = 'tver-olympic'
|
||||
api_key = '4b55a4db3cce4ad38df6dd8543e3e46a'
|
||||
media_id = video_id
|
||||
live_status = 'not_live'
|
||||
olympic_data = traverse_obj(self._download_json(
|
||||
f'{self._API_BASE}/video/{video_id}', video_id), ('contents', 'video', {dict}))
|
||||
|
||||
return {
|
||||
**self._extract_from_streaks_api(project_id, f'ref:{media_id}', {
|
||||
'Origin': 'https://tver.jp',
|
||||
'Referer': 'https://tver.jp/',
|
||||
'X-Streaks-Api-Key': api_key,
|
||||
}, live_from_start=live_from_start),
|
||||
**traverse_obj(olympic_data, {
|
||||
'title': ('title', {clean_html}, filter),
|
||||
'alt_title': ('sub_title', {clean_html}, filter),
|
||||
'channel': ('channel', {clean_html}, filter),
|
||||
'channel_id': ('channel_id', {clean_html}, filter),
|
||||
'description': (('description', 'description_l', 'description_s'), {clean_html}, filter, any),
|
||||
'timestamp': ('onair_start_date', {unified_timestamp(tz_offset=9)}),
|
||||
'thumbnail': (('picture_l_url', 'picture_m_url', 'picture_s_url'), {url_or_none}, any),
|
||||
}),
|
||||
'id': video_id,
|
||||
'live_status': live_status,
|
||||
}
|
||||
|
||||
@@ -511,7 +511,7 @@ def create_parser():
|
||||
general.add_option(
|
||||
'--live-from-start',
|
||||
action='store_true', dest='live_from_start',
|
||||
help='Download livestreams from the start. Currently experimental and only supported for YouTube and Twitch')
|
||||
help='Download livestreams from the start. Currently experimental and only supported for YouTube, Twitch, and TVer')
|
||||
general.add_option(
|
||||
'--no-live-from-start',
|
||||
action='store_false', dest='live_from_start',
|
||||
|
||||
Reference in New Issue
Block a user