mirror of https://github.com/yt-dlp/yt-dlp
Merge branch 'fb_parsedata_error' of https://github.com/ringus1/yt-dlp into fb_parsedata_error
commit
94f85add47
@ -0,0 +1,77 @@
|
|||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
float_or_none,
|
||||||
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
|
url_or_none,
|
||||||
|
)
|
||||||
|
from ..utils.traversal import traverse_obj
|
||||||
|
|
||||||
|
|
||||||
|
class AmadeusTVIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?amadeus\.tv/library/(?P<id>[\da-f]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.amadeus.tv/library/65091a87ff85af59d9fc54c3',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '5576678021301411311',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Jieon Park - 第五届珠海莫扎特国际青少年音乐周小提琴C组第三轮',
|
||||||
|
'thumbnail': 'http://1253584441.vod2.myqcloud.com/a0046a27vodtransbj1253584441/7db4af535576678021301411311/coverBySnapshot_10_0.jpg',
|
||||||
|
'duration': 1264.8,
|
||||||
|
'upload_date': '20230918',
|
||||||
|
'timestamp': 1695034800,
|
||||||
|
'display_id': '65091a87ff85af59d9fc54c3',
|
||||||
|
'view_count': int,
|
||||||
|
'description': 'md5:a0357b9c215489e2067cbae0b777bb95',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
nuxt_data = self._search_nuxt_data(webpage, display_id, traverse=('fetch', '0'))
|
||||||
|
video_id = traverse_obj(nuxt_data, ('item', 'video', {str}))
|
||||||
|
|
||||||
|
if not video_id:
|
||||||
|
raise ExtractorError('Unable to extract actual video ID')
|
||||||
|
|
||||||
|
video_data = self._download_json(
|
||||||
|
f'http://playvideo.qcloud.com/getplayinfo/v2/1253584441/{video_id}',
|
||||||
|
video_id, headers={'Referer': 'http://www.amadeus.tv/'})
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for video in traverse_obj(video_data, ('videoInfo', ('sourceVideo', ('transcodeList', ...)), {dict})):
|
||||||
|
if not url_or_none(video.get('url')):
|
||||||
|
continue
|
||||||
|
formats.append({
|
||||||
|
**traverse_obj(video, {
|
||||||
|
'url': 'url',
|
||||||
|
'format_id': ('definition', {lambda x: f'http-{x or "0"}'}),
|
||||||
|
'width': ('width', {int_or_none}),
|
||||||
|
'height': ('height', {int_or_none}),
|
||||||
|
'filesize': (('totalSize', 'size'), {int_or_none}),
|
||||||
|
'vcodec': ('videoStreamList', 0, 'codec'),
|
||||||
|
'acodec': ('audioStreamList', 0, 'codec'),
|
||||||
|
'fps': ('videoStreamList', 0, 'fps', {float_or_none}),
|
||||||
|
}, get_all=False),
|
||||||
|
'http_headers': {'Referer': 'http://www.amadeus.tv/'},
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'formats': formats,
|
||||||
|
**traverse_obj(video_data, {
|
||||||
|
'title': ('videoInfo', 'basicInfo', 'name', {str}),
|
||||||
|
'thumbnail': ('coverInfo', 'coverUrl', {url_or_none}),
|
||||||
|
'duration': ('videoInfo', 'sourceVideo', ('floatDuration', 'duration'), {float_or_none}),
|
||||||
|
}, get_all=False),
|
||||||
|
**traverse_obj(nuxt_data, ('item', {
|
||||||
|
'title': (('title', 'title_en', 'title_cn'), {str}),
|
||||||
|
'description': (('description', 'description_en', 'description_cn'), {str}),
|
||||||
|
'timestamp': ('date', {parse_iso8601}),
|
||||||
|
'view_count': ('view', {int_or_none}),
|
||||||
|
}), get_all=False),
|
||||||
|
}
|
@ -0,0 +1,303 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import float_or_none, int_or_none, parse_iso8601, url_or_none
|
||||||
|
from ..utils.traversal import traverse_obj
|
||||||
|
|
||||||
|
|
||||||
|
class Art19IE(InfoExtractor):
|
||||||
|
_UUID_REGEX = r'[\da-f]{8}-?[\da-f]{4}-?[\da-f]{4}-?[\da-f]{4}-?[\da-f]{12}'
|
||||||
|
_VALID_URL = [
|
||||||
|
rf'https?://(?:www\.)?art19\.com/shows/[^/#?]+/episodes/(?P<id>{_UUID_REGEX})',
|
||||||
|
rf'https?://rss\.art19\.com/episodes/(?P<id>{_UUID_REGEX})\.mp3',
|
||||||
|
]
|
||||||
|
_EMBED_REGEX = [rf'<iframe[^>]+\bsrc=[\'"](?P<url>{_VALID_URL[0]})']
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://rss.art19.com/episodes/5ba1413c-48b8-472b-9cc3-cfd952340bdb.mp3',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '5ba1413c-48b8-472b-9cc3-cfd952340bdb',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'Why Did DeSantis Drop Out?',
|
||||||
|
'series': 'The Daily Briefing',
|
||||||
|
'release_timestamp': 1705941275,
|
||||||
|
'description': 'md5:da38961da4a3f7e419471365e3c6b49f',
|
||||||
|
'episode': 'Episode 582',
|
||||||
|
'thumbnail': r're:^https?://content\.production\.cdn\.art19\.com.*\.jpeg$',
|
||||||
|
'series_id': 'ed52a0ab-08b1-4def-8afc-549e4d93296d',
|
||||||
|
'upload_date': '20240122',
|
||||||
|
'timestamp': 1705940815,
|
||||||
|
'episode_number': 582,
|
||||||
|
'modified_date': '20240122',
|
||||||
|
'episode_id': '5ba1413c-48b8-472b-9cc3-cfd952340bdb',
|
||||||
|
'modified_timestamp': 1705941275,
|
||||||
|
'release_date': '20240122',
|
||||||
|
'duration': 527.4,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://art19.com/shows/scamfluencers/episodes/8319b776-4153-4d22-8630-631f204a03dd',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '8319b776-4153-4d22-8630-631f204a03dd',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'Martha Stewart: The Homemaker Hustler Part 2',
|
||||||
|
'modified_date': '20240116',
|
||||||
|
'upload_date': '20240105',
|
||||||
|
'modified_timestamp': 1705435802,
|
||||||
|
'episode_id': '8319b776-4153-4d22-8630-631f204a03dd',
|
||||||
|
'series_id': 'd3c9b8ca-26b3-42f4-9bd8-21d1a9031e75',
|
||||||
|
'thumbnail': r're:^https?://content\.production\.cdn\.art19\.com.*\.jpeg$',
|
||||||
|
'description': 'md5:4aa7cfd1358dc57e729835bc208d7893',
|
||||||
|
'release_timestamp': 1705305660,
|
||||||
|
'release_date': '20240115',
|
||||||
|
'timestamp': 1704481536,
|
||||||
|
'episode_number': 88,
|
||||||
|
'series': 'Scamfluencers',
|
||||||
|
'duration': 2588.37501,
|
||||||
|
'episode': 'Episode 88',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
_WEBPAGE_TESTS = [{
|
||||||
|
'url': 'https://www.nu.nl/formule-1/6291456/verstappen-wordt-een-synoniem-voor-formule-1.html',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '7d42626a-7301-47db-bb8a-3b6f054d77d7',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': "'Verstappen wordt een synoniem voor Formule 1'",
|
||||||
|
'season': 'Seizoen 6',
|
||||||
|
'description': 'md5:39a7159a31c4cda312b2e893bdd5c071',
|
||||||
|
'episode_id': '7d42626a-7301-47db-bb8a-3b6f054d77d7',
|
||||||
|
'duration': 3061.82111,
|
||||||
|
'series_id': '93f4e113-2a60-4609-a564-755058fa40d8',
|
||||||
|
'release_date': '20231126',
|
||||||
|
'modified_timestamp': 1701156004,
|
||||||
|
'thumbnail': r're:^https?://content\.production\.cdn\.art19\.com.*\.jpeg$',
|
||||||
|
'season_number': 6,
|
||||||
|
'episode_number': 52,
|
||||||
|
'modified_date': '20231128',
|
||||||
|
'upload_date': '20231126',
|
||||||
|
'timestamp': 1701025981,
|
||||||
|
'season_id': '36097c1e-7455-490d-a2fe-e2f10b4d5f26',
|
||||||
|
'series': 'De Boordradio',
|
||||||
|
'release_timestamp': 1701026308,
|
||||||
|
'episode': 'Episode 52',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.wishtv.com/podcast-episode/larry-bucshon-announces-retirement-from-congress/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '8da368bd-08d1-46d0-afaa-c134a4af7dc0',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'Larry Bucshon announces retirement from congress',
|
||||||
|
'upload_date': '20240115',
|
||||||
|
'episode_number': 148,
|
||||||
|
'episode': 'Episode 148',
|
||||||
|
'thumbnail': r're:^https?://content\.production\.cdn\.art19\.com.*\.jpeg$',
|
||||||
|
'release_date': '20240115',
|
||||||
|
'timestamp': 1705328205,
|
||||||
|
'release_timestamp': 1705329275,
|
||||||
|
'series': 'All INdiana Politics',
|
||||||
|
'modified_date': '20240117',
|
||||||
|
'modified_timestamp': 1705458901,
|
||||||
|
'series_id': 'c4af6c27-b10f-4ff2-9f84-0f407df86ff1',
|
||||||
|
'episode_id': '8da368bd-08d1-46d0-afaa-c134a4af7dc0',
|
||||||
|
'description': 'md5:53b5239e4d14973a87125c217c255b2a',
|
||||||
|
'duration': 1256.18848,
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _extract_embed_urls(cls, url, webpage):
|
||||||
|
yield from super()._extract_embed_urls(url, webpage)
|
||||||
|
for episode_id in re.findall(
|
||||||
|
rf'<div[^>]+\bclass=[\'"][^\'"]*art19-web-player[^\'"]*[\'"][^>]+\bdata-episode-id=[\'"]({cls._UUID_REGEX})[\'"]', webpage):
|
||||||
|
yield f'https://rss.art19.com/episodes/{episode_id}.mp3'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
episode_id = self._match_id(url)
|
||||||
|
|
||||||
|
player_metadata = self._download_json(
|
||||||
|
f'https://art19.com/episodes/{episode_id}', episode_id,
|
||||||
|
note='Downloading player metadata', fatal=False,
|
||||||
|
headers={'Accept': 'application/vnd.art19.v0+json'})
|
||||||
|
rss_metadata = self._download_json(
|
||||||
|
f'https://rss.art19.com/episodes/{episode_id}.json', episode_id, fatal=False,
|
||||||
|
note='Downloading RSS metadata')
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'format_id': 'direct',
|
||||||
|
'url': f'https://rss.art19.com/episodes/{episode_id}.mp3',
|
||||||
|
'vcodec': 'none',
|
||||||
|
'acodec': 'mp3',
|
||||||
|
}]
|
||||||
|
for fmt_id, fmt_data in traverse_obj(rss_metadata, ('content', 'media', {dict.items}, ...)):
|
||||||
|
if fmt_id == 'waveform_bin':
|
||||||
|
continue
|
||||||
|
fmt_url = traverse_obj(fmt_data, ('url', {url_or_none}))
|
||||||
|
if not fmt_url:
|
||||||
|
continue
|
||||||
|
formats.append({
|
||||||
|
'format_id': fmt_id,
|
||||||
|
'url': fmt_url,
|
||||||
|
'vcodec': 'none',
|
||||||
|
'acodec': fmt_id,
|
||||||
|
'quality': -2 if fmt_id == 'ogg' else -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': episode_id,
|
||||||
|
'formats': formats,
|
||||||
|
**traverse_obj(player_metadata, ('episode', {
|
||||||
|
'title': ('title', {str}),
|
||||||
|
'description': ('description_plain', {str}),
|
||||||
|
'episode_id': ('id', {str}),
|
||||||
|
'episode_number': ('episode_number', {int_or_none}),
|
||||||
|
'season_id': ('season_id', {str}),
|
||||||
|
'series_id': ('series_id', {str}),
|
||||||
|
'timestamp': ('created_at', {parse_iso8601}),
|
||||||
|
'release_timestamp': ('released_at', {parse_iso8601}),
|
||||||
|
'modified_timestamp': ('updated_at', {parse_iso8601})
|
||||||
|
})),
|
||||||
|
**traverse_obj(rss_metadata, ('content', {
|
||||||
|
'title': ('episode_title', {str}),
|
||||||
|
'description': ('episode_description_plain', {str}),
|
||||||
|
'episode_id': ('episode_id', {str}),
|
||||||
|
'episode_number': ('episode_number', {int_or_none}),
|
||||||
|
'season': ('season_title', {str}),
|
||||||
|
'season_id': ('season_id', {str}),
|
||||||
|
'season_number': ('season_number', {int_or_none}),
|
||||||
|
'series': ('series_title', {str}),
|
||||||
|
'series_id': ('series_id', {str}),
|
||||||
|
'thumbnail': ('cover_image', {url_or_none}),
|
||||||
|
'duration': ('duration', {float_or_none}),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Art19ShowIE(InfoExtractor):
|
||||||
|
_VALID_URL_BASE = r'https?://(?:www\.)?art19\.com/shows/(?P<id>[\w-]+)(?:/embed)?/?'
|
||||||
|
_VALID_URL = [
|
||||||
|
rf'{_VALID_URL_BASE}(?:$|[#?])',
|
||||||
|
r'https?://rss\.art19\.com/(?P<id>[\w-]+)/?(?:$|[#?])',
|
||||||
|
]
|
||||||
|
_EMBED_REGEX = [rf'<iframe[^>]+\bsrc=[\'"](?P<url>{_VALID_URL_BASE}[^\'"])']
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.art19.com/shows/5898c087-a14f-48dc-b6fc-a2280a1ff6e0/',
|
||||||
|
'info_dict': {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'id': '5898c087-a14f-48dc-b6fc-a2280a1ff6e0',
|
||||||
|
'display_id': 'echt-gebeurd',
|
||||||
|
'title': 'Echt Gebeurd',
|
||||||
|
'description': 'md5:5fd11dc80b76e51ffd34b6067fd5e560',
|
||||||
|
'timestamp': 1492642167,
|
||||||
|
'upload_date': '20170419',
|
||||||
|
'modified_timestamp': int,
|
||||||
|
'modified_date': str,
|
||||||
|
'tags': 'count:7',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 425,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.art19.com/shows/echt-gebeurd',
|
||||||
|
'info_dict': {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'id': '5898c087-a14f-48dc-b6fc-a2280a1ff6e0',
|
||||||
|
'display_id': 'echt-gebeurd',
|
||||||
|
'title': 'Echt Gebeurd',
|
||||||
|
'description': 'md5:5fd11dc80b76e51ffd34b6067fd5e560',
|
||||||
|
'timestamp': 1492642167,
|
||||||
|
'upload_date': '20170419',
|
||||||
|
'modified_timestamp': int,
|
||||||
|
'modified_date': str,
|
||||||
|
'tags': 'count:7',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 425,
|
||||||
|
}, {
|
||||||
|
'url': 'https://rss.art19.com/scamfluencers',
|
||||||
|
'info_dict': {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'id': 'd3c9b8ca-26b3-42f4-9bd8-21d1a9031e75',
|
||||||
|
'display_id': 'scamfluencers',
|
||||||
|
'title': 'Scamfluencers',
|
||||||
|
'description': 'md5:7d239d670c0ced6dadbf71c4caf764b7',
|
||||||
|
'timestamp': 1647368573,
|
||||||
|
'upload_date': '20220315',
|
||||||
|
'modified_timestamp': int,
|
||||||
|
'modified_date': str,
|
||||||
|
'tags': [],
|
||||||
|
},
|
||||||
|
'playlist_mincount': 90,
|
||||||
|
}, {
|
||||||
|
'url': 'https://art19.com/shows/enthuellt/embed',
|
||||||
|
'info_dict': {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'id': 'e2cacf57-bb8a-4263-aa81-719bcdd4f80c',
|
||||||
|
'display_id': 'enthuellt',
|
||||||
|
'title': 'Enthüllt',
|
||||||
|
'description': 'md5:17752246643414a2fd51744fc9a1c08e',
|
||||||
|
'timestamp': 1601645860,
|
||||||
|
'upload_date': '20201002',
|
||||||
|
'modified_timestamp': int,
|
||||||
|
'modified_date': str,
|
||||||
|
'tags': 'count:10',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 10,
|
||||||
|
}]
|
||||||
|
_WEBPAGE_TESTS = [{
|
||||||
|
'url': 'https://deconstructingyourself.com/deconstructing-yourself-podcast',
|
||||||
|
'info_dict': {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'id': 'cfbb9b01-c295-4adb-8726-adde7c03cf21',
|
||||||
|
'display_id': 'deconstructing-yourself',
|
||||||
|
'title': 'Deconstructing Yourself',
|
||||||
|
'description': 'md5:dab5082b28b248a35476abf64768854d',
|
||||||
|
'timestamp': 1570581181,
|
||||||
|
'upload_date': '20191009',
|
||||||
|
'modified_timestamp': int,
|
||||||
|
'modified_date': str,
|
||||||
|
'tags': 'count:5',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 80,
|
||||||
|
}, {
|
||||||
|
'url': 'https://chicagoreader.com/columns-opinion/podcasts/ben-joravsky-show-podcast-episodes/',
|
||||||
|
'info_dict': {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'id': '9dfa2c37-ab87-4c13-8388-4897914313ec',
|
||||||
|
'display_id': 'the-ben-joravsky-show',
|
||||||
|
'title': 'The Ben Joravsky Show',
|
||||||
|
'description': 'md5:c0f3ec0ee0dbea764390e521adc8780a',
|
||||||
|
'timestamp': 1550875095,
|
||||||
|
'upload_date': '20190222',
|
||||||
|
'modified_timestamp': int,
|
||||||
|
'modified_date': str,
|
||||||
|
'tags': ['Chicago Politics', 'chicago', 'Ben Joravsky'],
|
||||||
|
},
|
||||||
|
'playlist_mincount': 1900,
|
||||||
|
}]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _extract_embed_urls(cls, url, webpage):
|
||||||
|
yield from super()._extract_embed_urls(url, webpage)
|
||||||
|
for series_id in re.findall(
|
||||||
|
r'<div[^>]+\bclass=[\'"][^\'"]*art19-web-player[^\'"]*[\'"][^>]+\bdata-series-id=[\'"]([\w-]+)[\'"]', webpage):
|
||||||
|
yield f'https://art19.com/shows/{series_id}'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
series_id = self._match_id(url)
|
||||||
|
series_metadata = self._download_json(
|
||||||
|
f'https://art19.com/series/{series_id}', series_id, note='Downloading series metadata',
|
||||||
|
headers={'Accept': 'application/vnd.art19.v0+json'})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'entries': [
|
||||||
|
self.url_result(f'https://rss.art19.com/episodes/{episode_id}.mp3', Art19IE)
|
||||||
|
for episode_id in traverse_obj(series_metadata, ('series', 'episode_ids', ..., {str}))
|
||||||
|
],
|
||||||
|
**traverse_obj(series_metadata, ('series', {
|
||||||
|
'id': ('id', {str}),
|
||||||
|
'display_id': ('slug', {str}),
|
||||||
|
'title': ('title', {str}),
|
||||||
|
'description': ('description_plain', {str}),
|
||||||
|
'timestamp': ('created_at', {parse_iso8601}),
|
||||||
|
'modified_timestamp': ('updated_at', {parse_iso8601}),
|
||||||
|
})),
|
||||||
|
'tags': traverse_obj(series_metadata, ('tags', ..., 'name', {str})),
|
||||||
|
}
|
@ -0,0 +1,168 @@
|
|||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
clean_html,
|
||||||
|
merge_dicts,
|
||||||
|
parse_iso8601,
|
||||||
|
url_or_none,
|
||||||
|
)
|
||||||
|
from ..utils.traversal import traverse_obj
|
||||||
|
|
||||||
|
|
||||||
|
class AsobiChannelBaseIE(InfoExtractor):
|
||||||
|
_MICROCMS_HEADER = {'X-MICROCMS-API-KEY': 'qRaKehul9AHU8KtL0dnq1OCLKnFec6yrbcz3'}
|
||||||
|
|
||||||
|
def _extract_info(self, metadata):
|
||||||
|
return traverse_obj(metadata, {
|
||||||
|
'id': ('id', {str}),
|
||||||
|
'title': ('title', {str}),
|
||||||
|
'description': ('body', {clean_html}),
|
||||||
|
'thumbnail': ('contents', 'video_thumb', 'url', {url_or_none}),
|
||||||
|
'timestamp': ('publishedAt', {parse_iso8601}),
|
||||||
|
'modified_timestamp': ('updatedAt', {parse_iso8601}),
|
||||||
|
'channel': ('channel', 'name', {str}),
|
||||||
|
'channel_id': ('channel', 'id', {str}),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class AsobiChannelIE(AsobiChannelBaseIE):
|
||||||
|
IE_NAME = 'asobichannel'
|
||||||
|
IE_DESC = 'ASOBI CHANNEL'
|
||||||
|
|
||||||
|
_VALID_URL = r'https?://asobichannel\.asobistore\.jp/watch/(?P<id>[\w-]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://asobichannel.asobistore.jp/watch/1ypp48qd32p',
|
||||||
|
'md5': '39df74e872afe032c4eb27b89144fc92',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1ypp48qd32p',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'アイドルマスター ミリオンライブ! 765プロch 原っぱ通信 #1',
|
||||||
|
'description': 'md5:b930bd2199c9b2fd75951ce4aaa7efd2',
|
||||||
|
'thumbnail': 'https://images.microcms-assets.io/assets/d2420de4b9194e11beb164f99edb1f95/a8e6f84119f54eb9ab4ce16729239905/%E3%82%B5%E3%83%A0%E3%83%8D%20(1).png',
|
||||||
|
'timestamp': 1697098247,
|
||||||
|
'upload_date': '20231012',
|
||||||
|
'modified_timestamp': 1698381162,
|
||||||
|
'modified_date': '20231027',
|
||||||
|
'channel': 'アイドルマスター',
|
||||||
|
'channel_id': 'idolmaster',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://asobichannel.asobistore.jp/watch/redigiwnjzqj',
|
||||||
|
'md5': '229fa8fb5c591c75ce8c37a497f113f6',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'redigiwnjzqj',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '【おまけ放送】アイドルマスター ミリオンライブ! 765プロch 原っぱ通信 #1',
|
||||||
|
'description': 'md5:7d9cd35fb54425a6967822bd564ea2d9',
|
||||||
|
'thumbnail': 'https://images.microcms-assets.io/assets/d2420de4b9194e11beb164f99edb1f95/20e5c1d6184242eebc2512a5dec59bf0/P1_%E5%8E%9F%E3%81%A3%E3%81%B1%E3%82%B5%E3%83%A0%E3%83%8D.png',
|
||||||
|
'modified_timestamp': 1697797125,
|
||||||
|
'modified_date': '20231020',
|
||||||
|
'timestamp': 1697261769,
|
||||||
|
'upload_date': '20231014',
|
||||||
|
'channel': 'アイドルマスター',
|
||||||
|
'channel_id': 'idolmaster',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
_survapi_header = None
|
||||||
|
|
||||||
|
def _real_initialize(self):
|
||||||
|
token = self._download_json(
|
||||||
|
'https://asobichannel-api.asobistore.jp/api/v1/vspf/token', None,
|
||||||
|
note='Retrieving API token')
|
||||||
|
self._survapi_header = {'Authorization': f'Bearer {token}'}
|
||||||
|
|
||||||
|
def _process_vod(self, video_id, metadata):
|
||||||
|
content_id = metadata['contents']['video_id']
|
||||||
|
|
||||||
|
vod_data = self._download_json(
|
||||||
|
f'https://survapi.channel.or.jp/proxy/v1/contents/{content_id}/get_by_cuid', video_id,
|
||||||
|
headers=self._survapi_header, note='Downloading vod data')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'formats': self._extract_m3u8_formats(vod_data['ex_content']['streaming_url'], video_id),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _process_live(self, video_id, metadata):
|
||||||
|
content_id = metadata['contents']['video_id']
|
||||||
|
event_data = self._download_json(
|
||||||
|
f'https://survapi.channel.or.jp/ex/events/{content_id}?embed=channel', video_id,
|
||||||
|
headers=self._survapi_header, note='Downloading event data')
|
||||||
|
|
||||||
|
player_type = traverse_obj(event_data, ('data', 'Player_type', {str}))
|
||||||
|
if player_type == 'poster':
|
||||||
|
self.raise_no_formats('Live event has not yet started', expected=True)
|
||||||
|
live_status = 'is_upcoming'
|
||||||
|
formats = []
|
||||||
|
elif player_type == 'player':
|
||||||
|
live_status = 'is_live'
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
|
event_data['data']['Channel']['Custom_live_url'], video_id, live=True)
|
||||||
|
else:
|
||||||
|
raise ExtractorError('Unsupported player type {player_type!r}')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'release_timestamp': traverse_obj(metadata, ('period', 'start', {parse_iso8601})),
|
||||||
|
'live_status': live_status,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
metadata = self._download_json(
|
||||||
|
f'https://channel.microcms.io/api/v1/media/{video_id}', video_id,
|
||||||
|
headers=self._MICROCMS_HEADER)
|
||||||
|
|
||||||
|
info = self._extract_info(metadata)
|
||||||
|
|
||||||
|
video_type = traverse_obj(metadata, ('contents', 'video_type', 0, {str}))
|
||||||
|
if video_type == 'VOD':
|
||||||
|
return merge_dicts(info, self._process_vod(video_id, metadata))
|
||||||
|
if video_type == 'LIVE':
|
||||||
|
return merge_dicts(info, self._process_live(video_id, metadata))
|
||||||
|
|
||||||
|
raise ExtractorError(f'Unexpected video type {video_type!r}')
|
||||||
|
|
||||||
|
|
||||||
|
class AsobiChannelTagURLIE(AsobiChannelBaseIE):
|
||||||
|
IE_NAME = 'asobichannel:tag'
|
||||||
|
IE_DESC = 'ASOBI CHANNEL'
|
||||||
|
|
||||||
|
_VALID_URL = r'https?://asobichannel\.asobistore\.jp/tag/(?P<id>[a-z0-9-_]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://asobichannel.asobistore.jp/tag/bjhh-nbcja',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'bjhh-nbcja',
|
||||||
|
'title': 'アイドルマスター ミリオンライブ! 765プロch 原っぱ通信',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 16,
|
||||||
|
}, {
|
||||||
|
'url': 'https://asobichannel.asobistore.jp/tag/hvm5qw3c6od',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'hvm5qw3c6od',
|
||||||
|
'title': 'アイマスMOIW2023ラジオ',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 13,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
tag_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, tag_id)
|
||||||
|
title = traverse_obj(self._search_nextjs_data(
|
||||||
|
webpage, tag_id, fatal=False), ('props', 'pageProps', 'data', 'name', {str}))
|
||||||
|
|
||||||
|
media = self._download_json(
|
||||||
|
f'https://channel.microcms.io/api/v1/media?limit=999&filters=(tag[contains]{tag_id})',
|
||||||
|
tag_id, headers=self._MICROCMS_HEADER)
|
||||||
|
|
||||||
|
def entries():
|
||||||
|
for metadata in traverse_obj(media, ('contents', lambda _, v: v['id'])):
|
||||||
|
yield {
|
||||||
|
'_type': 'url',
|
||||||
|
'url': f'https://asobichannel.asobistore.jp/watch/{metadata["id"]}',
|
||||||
|
'ie_key': AsobiChannelIE.ie_key(),
|
||||||
|
**self._extract_info(metadata),
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.playlist_result(entries(), tag_id, title)
|
@ -0,0 +1,139 @@
|
|||||||
|
import functools
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
float_or_none,
|
||||||
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
|
url_or_none,
|
||||||
|
)
|
||||||
|
from ..utils.traversal import traverse_obj
|
||||||
|
|
||||||
|
|
||||||
|
class CHZZKLiveIE(InfoExtractor):
|
||||||
|
IE_NAME = 'chzzk:live'
|
||||||
|
_VALID_URL = r'https?://chzzk\.naver\.com/live/(?P<id>[\da-f]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://chzzk.naver.com/live/c68b8ef525fb3d2fa146344d84991753',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'c68b8ef525fb3d2fa146344d84991753',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': str,
|
||||||
|
'channel': '진짜도현',
|
||||||
|
'channel_id': 'c68b8ef525fb3d2fa146344d84991753',
|
||||||
|
'channel_is_verified': False,
|
||||||
|
'thumbnail': r're:^https?://.*\.jpg$',
|
||||||
|
'timestamp': 1705510344,
|
||||||
|
'upload_date': '20240117',
|
||||||
|
'live_status': 'is_live',
|
||||||
|
'view_count': int,
|
||||||
|
'concurrent_view_count': int,
|
||||||
|
},
|
||||||
|
'skip': 'The channel is not currently live',
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
channel_id = self._match_id(url)
|
||||||
|
live_detail = self._download_json(
|
||||||
|
f'https://api.chzzk.naver.com/service/v2/channels/{channel_id}/live-detail', channel_id,
|
||||||
|
note='Downloading channel info', errnote='Unable to download channel info')['content']
|
||||||
|
|
||||||
|
if live_detail.get('status') == 'CLOSE':
|
||||||
|
raise ExtractorError('The channel is not currently live', expected=True)
|
||||||
|
|
||||||
|
live_playback = self._parse_json(live_detail['livePlaybackJson'], channel_id)
|
||||||
|
|
||||||
|
thumbnails = []
|
||||||
|
thumbnail_template = traverse_obj(
|
||||||
|
live_playback, ('thumbnail', 'snapshotThumbnailTemplate', {url_or_none}))
|
||||||
|
if thumbnail_template and '{type}' in thumbnail_template:
|
||||||
|
for width in traverse_obj(live_playback, ('thumbnail', 'types', ..., {str})):
|
||||||
|
thumbnails.append({
|
||||||
|
'id': width,
|
||||||
|
'url': thumbnail_template.replace('{type}', width),
|
||||||
|
'width': int_or_none(width),
|
||||||
|
})
|
||||||
|
|
||||||
|
formats, subtitles = [], {}
|
||||||
|
for media in traverse_obj(live_playback, ('media', lambda _, v: url_or_none(v['path']))):
|
||||||
|
is_low_latency = media.get('mediaId') == 'LLHLS'
|
||||||
|
fmts, subs = self._extract_m3u8_formats_and_subtitles(
|
||||||
|
media['path'], channel_id, 'mp4', fatal=False, live=True,
|
||||||
|
m3u8_id='hls-ll' if is_low_latency else 'hls')
|
||||||
|
for f in fmts:
|
||||||
|
if is_low_latency:
|
||||||
|
f['source_preference'] = -2
|
||||||
|
if '-afragalow.stream-audio.stream' in f['format_id']:
|
||||||
|
f['quality'] = -2
|
||||||
|
formats.extend(fmts)
|
||||||
|
self._merge_subtitles(subs, target=subtitles)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': channel_id,
|
||||||
|
'is_live': True,
|
||||||
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
'thumbnails': thumbnails,
|
||||||
|
**traverse_obj(live_detail, {
|
||||||
|
'title': ('liveTitle', {str}),
|
||||||
|
'timestamp': ('openDate', {functools.partial(parse_iso8601, delimiter=' ')}),
|
||||||
|
'concurrent_view_count': ('concurrentUserCount', {int_or_none}),
|
||||||
|
'view_count': ('accumulateCount', {int_or_none}),
|
||||||
|
'channel': ('channel', 'channelName', {str}),
|
||||||
|
'channel_id': ('channel', 'channelId', {str}),
|
||||||
|
'channel_is_verified': ('channel', 'verifiedMark', {bool}),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CHZZKVideoIE(InfoExtractor):
|
||||||
|
IE_NAME = 'chzzk:video'
|
||||||
|
_VALID_URL = r'https?://chzzk\.naver\.com/video/(?P<id>\d+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://chzzk.naver.com/video/1754',
|
||||||
|
'md5': 'b0c0c1bb888d913b93d702b1512c7f06',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1754',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '치지직 테스트 방송',
|
||||||
|
'channel': '침착맨',
|
||||||
|
'channel_id': 'bb382c2c0cc9fa7c86ab3b037fb5799c',
|
||||||
|
'channel_is_verified': False,
|
||||||
|
'thumbnail': r're:^https?://.*\.jpg$',
|
||||||
|
'duration': 15577,
|
||||||
|
'timestamp': 1702970505.417,
|
||||||
|
'upload_date': '20231219',
|
||||||
|
'view_count': int,
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
video_meta = self._download_json(
|
||||||
|
f'https://api.chzzk.naver.com/service/v2/videos/{video_id}', video_id,
|
||||||
|
note='Downloading video info', errnote='Unable to download video info')['content']
|
||||||
|
formats, subtitles = self._extract_mpd_formats_and_subtitles(
|
||||||
|
f'https://apis.naver.com/neonplayer/vodplay/v1/playback/{video_meta["videoId"]}', video_id,
|
||||||
|
query={
|
||||||
|
'key': video_meta['inKey'],
|
||||||
|
'env': 'real',
|
||||||
|
'lc': 'en_US',
|
||||||
|
'cpl': 'en_US',
|
||||||
|
}, note='Downloading video playback', errnote='Unable to download video playback')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
**traverse_obj(video_meta, {
|
||||||
|
'title': ('videoTitle', {str}),
|
||||||
|
'thumbnail': ('thumbnailImageUrl', {url_or_none}),
|
||||||
|
'timestamp': ('publishDateAt', {functools.partial(float_or_none, scale=1000)}),
|
||||||
|
'view_count': ('readCount', {int_or_none}),
|
||||||
|
'duration': ('duration', {int_or_none}),
|
||||||
|
'channel': ('channel', 'channelName', {str}),
|
||||||
|
'channel_id': ('channel', 'channelId', {str}),
|
||||||
|
'channel_is_verified': ('channel', 'verifiedMark', {bool}),
|
||||||
|
}),
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
|
url_or_none,
|
||||||
|
urlencode_postdata,
|
||||||
|
)
|
||||||
|
from ..utils.traversal import traverse_obj
|
||||||
|
|
||||||
|
|
||||||
|
class CloudyCDNIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'(?:https?:)?//embed\.cloudycdn\.services/(?P<site_id>[^/?#]+)/media/(?P<id>[\w-]+)'
|
||||||
|
_EMBED_REGEX = [rf'<iframe[^>]+\bsrc=[\'"](?P<url>{_VALID_URL})']
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://embed.cloudycdn.services/ltv/media/46k_d23-6000-105?',
|
||||||
|
'md5': '64f72a360ca530d5ed89c77646c9eee5',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '46k_d23-6000-105',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'timestamp': 1700589151,
|
||||||
|
'duration': 1442,
|
||||||
|
'upload_date': '20231121',
|
||||||
|
'title': 'D23-6000-105_cetstud',
|
||||||
|
'thumbnail': 'https://store.cloudycdn.services/tmsp00060/assets/media/660858/placeholder1700589200.jpg',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'https://embed.cloudycdn.services/izm/media/26e_lv-8-5-1',
|
||||||
|
'md5': '798828a479151e2444d8dcfbec76e482',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '26e_lv-8-5-1',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'LV-8-5-1',
|
||||||
|
'timestamp': 1669767167,
|
||||||
|
'thumbnail': 'https://store.cloudycdn.services/tmsp00120/assets/media/488306/placeholder1679423604.jpg',
|
||||||
|
'duration': 1205,
|
||||||
|
'upload_date': '20221130',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
_WEBPAGE_TESTS = [{
|
||||||
|
'url': 'https://www.tavaklase.lv/video/es-esmu-mina-um-2/',
|
||||||
|
'md5': '63074e8e6c84ac2a01f2fb8bf03b8f43',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'cqd_lib-2',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'upload_date': '20230223',
|
||||||
|
'duration': 629,
|
||||||
|
'thumbnail': 'https://store.cloudycdn.services/tmsp00120/assets/media/518407/placeholder1678748124.jpg',
|
||||||
|
'timestamp': 1677181513,
|
||||||
|
'title': 'LIB-2',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
site_id, video_id = self._match_valid_url(url).group('site_id', 'id')
|
||||||
|
|
||||||
|
data = self._download_json(
|
||||||
|
f'https://player.cloudycdn.services/player/{site_id}/media/{video_id}/',
|
||||||
|
video_id, data=urlencode_postdata({
|
||||||
|
'version': '6.4.0',
|
||||||
|
'referer': url,
|
||||||
|
}))
|
||||||
|
|
||||||
|
formats, subtitles = [], {}
|
||||||
|
for m3u8_url in traverse_obj(data, ('source', 'sources', ..., 'src', {url_or_none})):
|
||||||
|
fmts, subs = self._extract_m3u8_formats_and_subtitles(m3u8_url, video_id, fatal=False)
|
||||||
|
formats.extend(fmts)
|
||||||
|
self._merge_subtitles(subs, target=subtitles)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
**traverse_obj(data, {
|
||||||
|
'title': ('name', {str}),
|
||||||
|
'duration': ('duration', {int_or_none}),
|
||||||
|
'timestamp': ('upload_date', {parse_iso8601}),
|
||||||
|
'thumbnail': ('source', 'poster', {url_or_none}),
|
||||||
|
}),
|
||||||
|
}
|
@ -0,0 +1,199 @@
|
|||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
clean_html,
|
||||||
|
int_or_none,
|
||||||
|
str_or_none,
|
||||||
|
url_or_none,
|
||||||
|
)
|
||||||
|
from ..utils.traversal import traverse_obj
|
||||||
|
|
||||||
|
|
||||||
|
class ERRJupiterIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://jupiter(?:pluss)?\.err\.ee/(?P<id>\d+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'note': 'Jupiter: Movie: siin-me-oleme',
|
||||||
|
'url': 'https://jupiter.err.ee/1211107/siin-me-oleme',
|
||||||
|
'md5': '9b45d1682a98853acaa1e1b0c791f425',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1211107',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Siin me oleme!',
|
||||||
|
'alt_title': '',
|
||||||
|
'description': 'md5:1825b795f5f7584241aeb59e5bbb4f70',
|
||||||
|
'release_date': '20231226',
|
||||||
|
'upload_date': '20201217',
|
||||||
|
'modified_date': '20201217',
|
||||||
|
'release_timestamp': 1703577600,
|
||||||
|
'timestamp': 1608210000,
|
||||||
|
'modified_timestamp': 1608220800,
|
||||||
|
'release_year': 1978,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'note': 'Jupiter: Series: Impulss',
|
||||||
|
'url': 'https://jupiter.err.ee/1609145945/impulss',
|
||||||
|
'md5': 'a378486df07ed1ba74e46cc861886243',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1609145945',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Impulss',
|
||||||
|
'alt_title': 'Loteriipilet hooldekodusse',
|
||||||
|
'description': 'md5:fa8a2ed0cdccb130211513443ee4d571',
|
||||||
|
'release_date': '20231107',
|
||||||
|
'upload_date': '20231026',
|
||||||
|
'modified_date': '20231118',
|
||||||
|
'release_timestamp': 1699380000,
|
||||||
|
'timestamp': 1698327601,
|
||||||
|
'modified_timestamp': 1700311802,
|
||||||
|
'series': 'Impulss',
|
||||||
|
'season': 'Season 1',
|
||||||
|
'season_number': 1,
|
||||||
|
'episode': 'Loteriipilet hooldekodusse',
|
||||||
|
'episode_number': 6,
|
||||||
|
'series_id': '1609108187',
|
||||||
|
'release_year': 2023,
|
||||||
|
'episode_id': '1609145945',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'note': 'Jupiter: Radio Show: mnemoturniir episode',
|
||||||
|
'url': 'https://jupiter.err.ee/1037919/mnemoturniir',
|
||||||
|
'md5': 'f1eb95fe66f9620ff84e81bbac37076a',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1037919',
|
||||||
|
'ext': 'm4a',
|
||||||
|
'title': 'Mnemoturniir',
|
||||||
|
'alt_title': '',
|
||||||
|
'description': 'md5:626db52394e7583c26ab74d6a34d9982',
|
||||||
|
'release_date': '20240121',
|
||||||
|
'upload_date': '20240108',
|
||||||
|
'modified_date': '20240121',
|
||||||
|
'release_timestamp': 1705827900,
|
||||||
|
'timestamp': 1704675602,
|
||||||
|
'modified_timestamp': 1705827601,
|
||||||
|
'series': 'Mnemoturniir',
|
||||||
|
'season': 'Season 0',
|
||||||
|
'season_number': 0,
|
||||||
|
'episode': 'Episode 0',
|
||||||
|
'episode_number': 0,
|
||||||
|
'series_id': '1037919',
|
||||||
|
'release_year': 2024,
|
||||||
|
'episode_id': '1609215101',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'note': 'Jupiter+: Clip: bolee-zelenyj-tallinn',
|
||||||
|
'url': 'https://jupiterpluss.err.ee/1609180445/bolee-zelenyj-tallinn',
|
||||||
|
'md5': '1b812270c4daf6ce51c06bfeaf33ed95',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1609180445',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Более зеленый Таллинн',
|
||||||
|
'alt_title': '',
|
||||||
|
'description': 'md5:fd34d9bf939c28c4a725b19a7f0d6320',
|
||||||
|
'release_date': '20231224',
|
||||||
|
'upload_date': '20231130',
|
||||||
|
'modified_date': '20231207',
|
||||||
|
'release_timestamp': 1703423400,
|
||||||
|
'timestamp': 1701338400,
|
||||||
|
'modified_timestamp': 1701967200,
|
||||||
|
'release_year': 2023,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'note': 'Jupiter+: Series: The Sniffer',
|
||||||
|
'url': 'https://jupiterpluss.err.ee/1608311387/njuhach',
|
||||||
|
'md5': '2abdeb7131ce551bce49e8d0cea08536',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1608311387',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Нюхач',
|
||||||
|
'alt_title': '',
|
||||||
|
'description': 'md5:8c5c7d8f32ec6e54cd498c9e59ca83bc',
|
||||||
|
'release_date': '20230601',
|
||||||
|
'upload_date': '20210818',
|
||||||
|
'modified_date': '20210903',
|
||||||
|
'release_timestamp': 1685633400,
|
||||||
|
'timestamp': 1629318000,
|
||||||
|
'modified_timestamp': 1630686000,
|
||||||
|
'release_year': 2013,
|
||||||
|
'episode': 'Episode 1',
|
||||||
|
'episode_id': '1608311390',
|
||||||
|
'episode_number': 1,
|
||||||
|
'season': 'Season 1',
|
||||||
|
'season_number': 1,
|
||||||
|
'series': 'Нюхач',
|
||||||
|
'series_id': '1608311387',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'note': 'Jupiter+: Podcast: lesnye-istorii-aisty',
|
||||||
|
'url': 'https://jupiterpluss.err.ee/1608990335/lesnye-istorii-aisty',
|
||||||
|
'md5': '8b46d7e4510b254a14b7a52211b5bf96',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1608990335',
|
||||||
|
'ext': 'm4a',
|
||||||
|
'title': 'Лесные истории | Аисты',
|
||||||
|
'alt_title': '',
|
||||||
|
'description': 'md5:065e721623e271e7a63e6540d409ca6b',
|
||||||
|
'release_date': '20230609',
|
||||||
|
'upload_date': '20230527',
|
||||||
|
'modified_date': '20230608',
|
||||||
|
'release_timestamp': 1686308700,
|
||||||
|
'timestamp': 1685145600,
|
||||||
|
'modified_timestamp': 1686252600,
|
||||||
|
'release_year': 2023,
|
||||||
|
'episode': 'Episode 0',
|
||||||
|
'episode_id': '1608990335',
|
||||||
|
'episode_number': 0,
|
||||||
|
'season': 'Season 0',
|
||||||
|
'season_number': 0,
|
||||||
|
'series': 'Лесные истории | Аисты',
|
||||||
|
'series_id': '1037497',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
data = self._download_json(
|
||||||
|
'https://services.err.ee/api/v2/vodContent/getContentPageData', video_id,
|
||||||
|
query={'contentId': video_id})['data']['mainContent']
|
||||||
|
|
||||||
|
media_data = traverse_obj(data, ('medias', ..., {dict}), get_all=False)
|
||||||
|
if traverse_obj(media_data, ('restrictions', 'drm', {bool})):
|
||||||
|
self.report_drm(video_id)
|
||||||
|
|
||||||
|
formats, subtitles = [], {}
|
||||||
|
for format_url in set(traverse_obj(media_data, ('src', ('hls', 'hls2', 'hlsNew'), {url_or_none}))):
|
||||||
|
fmts, subs = self._extract_m3u8_formats_and_subtitles(
|
||||||
|
format_url, video_id, 'mp4', m3u8_id='hls', fatal=False)
|
||||||
|
formats.extend(fmts)
|
||||||
|
self._merge_subtitles(subs, target=subtitles)
|
||||||
|
for format_url in set(traverse_obj(media_data, ('src', ('dash', 'dashNew'), {url_or_none}))):
|
||||||
|
fmts, subs = self._extract_mpd_formats_and_subtitles(
|
||||||
|
format_url, video_id, mpd_id='dash', fatal=False)
|
||||||
|
formats.extend(fmts)
|
||||||
|
self._merge_subtitles(subs, target=subtitles)
|
||||||
|
if format_url := traverse_obj(media_data, ('src', 'file', {url_or_none})):
|
||||||
|
formats.append({
|
||||||
|
'url': format_url,
|
||||||
|
'format_id': 'http',
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
**traverse_obj(data, {
|
||||||
|
'title': ('heading', {str}),
|
||||||
|
'alt_title': ('subHeading', {str}),
|
||||||
|
'description': (('lead', 'body'), {clean_html}, {lambda x: x or None}),
|
||||||
|
'timestamp': ('created', {int_or_none}),
|
||||||
|
'modified_timestamp': ('updated', {int_or_none}),
|
||||||
|
'release_timestamp': (('scheduleStart', 'publicStart'), {int_or_none}),
|
||||||
|
'release_year': ('year', {int_or_none}),
|
||||||
|
}, get_all=False),
|
||||||
|
**(traverse_obj(data, {
|
||||||
|
'series': ('heading', {str}),
|
||||||
|
'series_id': ('rootContentId', {str_or_none}),
|
||||||
|
'episode': ('subHeading', {str}),
|
||||||
|
'season_number': ('season', {int_or_none}),
|
||||||
|
'episode_number': ('episode', {int_or_none}),
|
||||||
|
'episode_id': ('id', {str_or_none}),
|
||||||
|
}) if data.get('type') == 'episode' else {}),
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
import functools
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
float_or_none,
|
||||||
|
int_or_none,
|
||||||
|
url_or_none,
|
||||||
|
urlencode_postdata,
|
||||||
|
)
|
||||||
|
from ..utils.traversal import traverse_obj
|
||||||
|
|
||||||
|
|
||||||
|
class IlPostIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?ilpost\.it/episodes/(?P<id>[^/?#]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.ilpost.it/episodes/1-avis-akvasas-ka/',
|
||||||
|
'md5': '43649f002d85e1c2f319bb478d479c40',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2972047',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'display_id': '1-avis-akvasas-ka',
|
||||||
|
'title': '1. Avis akvasas ka',
|
||||||
|
'url': 'https://www.ilpost.it/wp-content/uploads/2023/12/28/1703781217-l-invasione-pt1-v6.mp3',
|
||||||
|
'timestamp': 1703835014,
|
||||||
|
'upload_date': '20231229',
|
||||||
|
'duration': 2495.0,
|
||||||
|
'availability': 'public',
|
||||||
|
'series_id': '235598',
|
||||||
|
'description': '',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
endpoint_metadata = self._search_json(
|
||||||
|
r'var\s+ilpostpodcast\s*=', webpage, 'metadata', display_id)
|
||||||
|
episode_id = endpoint_metadata['post_id']
|
||||||
|
podcast_id = endpoint_metadata['podcast_id']
|
||||||
|
podcast_metadata = self._download_json(
|
||||||
|
endpoint_metadata['ajax_url'], display_id, data=urlencode_postdata({
|
||||||
|
'action': 'checkpodcast',
|
||||||
|
'cookie': endpoint_metadata['cookie'],
|
||||||
|
'post_id': episode_id,
|
||||||
|
'podcast_id': podcast_id,
|
||||||
|
}))
|
||||||
|
|
||||||
|
episode = traverse_obj(podcast_metadata, (
|
||||||
|
'data', 'postcastList', lambda _, v: str(v['id']) == episode_id, {dict}), get_all=False)
|
||||||
|
if not episode:
|
||||||
|
raise ExtractorError('Episode could not be extracted')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': episode_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'series_id': podcast_id,
|
||||||
|
'vcodec': 'none',
|
||||||
|
**traverse_obj(episode, {
|
||||||
|
'title': ('title', {str}),
|
||||||
|
'description': ('description', {str}),
|
||||||
|
'url': ('podcast_raw_url', {url_or_none}),
|
||||||
|
'thumbnail': ('image', {url_or_none}),
|
||||||
|
'timestamp': ('timestamp', {int_or_none}),
|
||||||
|
'duration': ('milliseconds', {functools.partial(float_or_none, scale=1000)}),
|
||||||
|
'availability': ('free', {lambda v: 'public' if v else 'subscriber_only'}),
|
||||||
|
}),
|
||||||
|
}
|
@ -0,0 +1,282 @@
|
|||||||
|
import re
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
determine_ext,
|
||||||
|
int_or_none,
|
||||||
|
js_to_json,
|
||||||
|
parse_iso8601,
|
||||||
|
parse_qs,
|
||||||
|
str_or_none,
|
||||||
|
url_or_none,
|
||||||
|
urljoin,
|
||||||
|
)
|
||||||
|
from ..utils.traversal import traverse_obj
|
||||||
|
|
||||||
|
|
||||||
|
class LSMLREmbedIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'''(?x)
|
||||||
|
https?://(?:
|
||||||
|
(?:latvijasradio|lr1|lr2|klasika|lr4|naba|radioteatris)\.lsm|
|
||||||
|
pieci
|
||||||
|
)\.lv/[^/?#]+/(?:
|
||||||
|
pleijeris|embed
|
||||||
|
)/?\?(?:[^#]+&)?(?:show|id)=(?P<id>\d+)'''
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://latvijasradio.lsm.lv/lv/embed/?theme=black&size=16x9&showCaptions=0&id=183522',
|
||||||
|
'md5': '719b33875cd1429846eeeaeec6df2830',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'a342781',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'duration': 1823,
|
||||||
|
'title': '#138 Nepilnīgā kompensējamo zāļu sistēma pat mēnešiem dzenā pacientus pa aptiekām',
|
||||||
|
'thumbnail': 'https://pic.latvijasradio.lv/public/assets/media/9/d/gallery_fd4675ac.jpg',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'https://radioteatris.lsm.lv/lv/embed/?id=&show=1270&theme=white&size=16x9',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1270',
|
||||||
|
},
|
||||||
|
'playlist_count': 3,
|
||||||
|
'playlist': [{
|
||||||
|
'md5': '2e61b6eceff00d14d57fdbbe6ab24cac',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'a297397',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'Eriks Emanuels Šmits "Pilāta evaņģēlijs". 1. daļa',
|
||||||
|
'thumbnail': 'https://radioteatris.lsm.lv/public/assets/shows/62f131ae81e3c.jpg',
|
||||||
|
'duration': 3300,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
}, {
|
||||||
|
'url': 'https://radioteatris.lsm.lv/lv/embed/?id=&show=1269&theme=white&size=16x9',
|
||||||
|
'md5': '24810d4a961da2295d9860afdcaf4f5a',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'a230690',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'Jens Ahlboms "Spārni". Radioizrāde ar Mārtiņa Freimaņa mūziku',
|
||||||
|
'thumbnail': 'https://radioteatris.lsm.lv/public/assets/shows/62f13023a457c.jpg',
|
||||||
|
'duration': 1788,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'https://lr1.lsm.lv/lv/embed/?id=166557&show=0&theme=white&size=16x9',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '166557',
|
||||||
|
},
|
||||||
|
'playlist_count': 2,
|
||||||
|
'playlist': [{
|
||||||
|
'md5': '6a8b0927572f443f09c6e50a3ad65f2d',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'a303104',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'thumbnail': 'https://pic.latvijasradio.lv/public/assets/media/c/5/gallery_a83ad2c2.jpg',
|
||||||
|
'title': 'Krustpunktā Lielā intervija: Valsts prezidents Egils Levits',
|
||||||
|
'duration': 3222,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'md5': '5d5e191e718b7644e5118b7b4e093a6d',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'v303104',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'thumbnail': 'https://pic.latvijasradio.lv/public/assets/media/c/5/gallery_a83ad2c2.jpg',
|
||||||
|
'title': 'Krustpunktā Lielā intervija: Valsts prezidents Egils Levits - Video Version',
|
||||||
|
'duration': 3222,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
}, {
|
||||||
|
'url': 'https://lr1.lsm.lv/lv/embed/?id=183522&show=0&theme=white&size=16x9',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://lr2.lsm.lv/lv/embed/?id=182126&show=0&theme=white&size=16x9',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://klasika.lsm.lv/lv/embed/?id=110806&show=0&theme=white&size=16x9',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://lr4.lsm.lv/lv/embed/?id=184282&show=0&theme=white&size=16x9',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://pieci.lv/lv/embed/?id=168896&show=0&theme=white&size=16x9',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://naba.lsm.lv/lv/embed/?id=182901&show=0&theme=white&size=16x9',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://radioteatris.lsm.lv/lv/embed/?id=176439&show=0&theme=white&size=16x9',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://lr1.lsm.lv/lv/pleijeris/?embed=0&id=48205&time=00%3A00&idx=0',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
query = parse_qs(url)
|
||||||
|
video_id = traverse_obj(query, (
|
||||||
|
('show', 'id'), 0, {int_or_none}, {lambda x: x or None}, {str_or_none}), get_all=False)
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
player_data, media_data = self._search_regex(
|
||||||
|
r'LR\.audio\.Player\s*\([^{]*(?P<player>\{.*?\}),(?P<media>\{.*\})\);',
|
||||||
|
webpage, 'player json', group=('player', 'media'))
|
||||||
|
|
||||||
|
player_json = self._parse_json(
|
||||||
|
player_data, video_id, transform_source=js_to_json, fatal=False) or {}
|
||||||
|
media_json = self._parse_json(media_data, video_id, transform_source=js_to_json)
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
for item in traverse_obj(media_json, (('audio', 'video'), lambda _, v: v['id'])):
|
||||||
|
formats = []
|
||||||
|
for source_url in traverse_obj(item, ('sources', ..., 'file', {url_or_none})):
|
||||||
|
if determine_ext(source_url) == 'm3u8':
|
||||||
|
formats.extend(self._extract_m3u8_formats(source_url, video_id, fatal=False))
|
||||||
|
else:
|
||||||
|
formats.append({'url': source_url})
|
||||||
|
|
||||||
|
id_ = item['id']
|
||||||
|
title = item.get('title')
|
||||||
|
if id_.startswith('v') and not title:
|
||||||
|
title = traverse_obj(
|
||||||
|
media_json, ('audio', lambda _, v: v['id'][1:] == id_[1:], 'title',
|
||||||
|
{lambda x: x and f'{x} - Video Version'}), get_all=False)
|
||||||
|
|
||||||
|
entries.append({
|
||||||
|
'formats': formats,
|
||||||
|
'thumbnail': urljoin(url, player_json.get('poster')),
|
||||||
|
'id': id_,
|
||||||
|
'title': title,
|
||||||
|
'duration': traverse_obj(item, ('duration', {int_or_none})),
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(entries) == 1:
|
||||||
|
return entries[0]
|
||||||
|
|
||||||
|
return self.playlist_result(entries, video_id)
|
||||||
|
|
||||||
|
|
||||||
|
class LSMLTVEmbedIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://ltv\.lsm\.lv/embed\?(?:[^#]+&)?c=(?P<id>[^#&]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://ltv.lsm.lv/embed?c=eyJpdiI6IjQzbHVUeHAyaDJiamFjcjdSUUFKdnc9PSIsInZhbHVlIjoiMHl3SnJNRmd2TmFIdnZwOGtGUUpzODFzUEZ4SVVsN2xoRjliSW9vckUyMWZIWG8vbWVzaFFkY0lhNmRjbjRpaCIsIm1hYyI6ImMzNjdhMzFhNTFhZmY1ZmE0NWI5YmFjZGI1YmJiNGEyNjgzNDM4MjUzMWEwM2FmMDMyZDMwYWM1MDFjZmM5MGIiLCJ0YWciOiIifQ==',
|
||||||
|
'md5': '64f72a360ca530d5ed89c77646c9eee5',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '46k_d23-6000-105',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'timestamp': 1700589151,
|
||||||
|
'duration': 1442,
|
||||||
|
'upload_date': '20231121',
|
||||||
|
'title': 'D23-6000-105_cetstud',
|
||||||
|
'thumbnail': 'https://store.cloudycdn.services/tmsp00060/assets/media/660858/placeholder1700589200.jpg',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'https://ltv.lsm.lv/embed?enablesdkjs=1&c=eyJpdiI6IncwVzZmUFk2MU12enVWK1I3SUcwQ1E9PSIsInZhbHVlIjoid3FhV29vamc3T2sxL1RaRmJ5Rm1GTXozU0o2dVczdUtLK0cwZEZJMDQ2a3ZIRG5DK2pneGlnbktBQy9uazVleHN6VXhxdWIweWNvcHRDSnlISlNYOHlVZ1lpcTUrcWZSTUZPQW14TVdkMW9aOUtRWVNDcFF4eWpHNGcrT0VZbUNFQStKQk91cGpndW9FVjJIa0lpbkh3PT0iLCJtYWMiOiIyZGI1NDJlMWRlM2QyMGNhOGEwYTM2MmNlN2JlOGRhY2QyYjdkMmEzN2RlOTEzYTVkNzI1ODlhZDlhZjU4MjQ2IiwidGFnIjoiIn0=',
|
||||||
|
'md5': 'a1711e190fe680fdb68fd8413b378e87',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'wUnFArIPDSY',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'uploader': 'LTV_16plus',
|
||||||
|
'release_date': '20220514',
|
||||||
|
'channel_url': 'https://www.youtube.com/channel/UCNMrnafwXD2XKeeQOyfkFCw',
|
||||||
|
'view_count': int,
|
||||||
|
'availability': 'public',
|
||||||
|
'thumbnail': 'https://i.ytimg.com/vi/wUnFArIPDSY/maxresdefault.jpg',
|
||||||
|
'release_timestamp': 1652544074,
|
||||||
|
'title': 'EIROVĪZIJA SALĀTOS',
|
||||||
|
'live_status': 'was_live',
|
||||||
|
'uploader_id': '@LTV16plus',
|
||||||
|
'comment_count': int,
|
||||||
|
'channel_id': 'UCNMrnafwXD2XKeeQOyfkFCw',
|
||||||
|
'channel_follower_count': int,
|
||||||
|
'categories': ['Entertainment'],
|
||||||
|
'duration': 5269,
|
||||||
|
'upload_date': '20220514',
|
||||||
|
'age_limit': 0,
|
||||||
|
'channel': 'LTV_16plus',
|
||||||
|
'playable_in_embed': True,
|
||||||
|
'tags': [],
|
||||||
|
'uploader_url': 'https://www.youtube.com/@LTV16plus',
|
||||||
|
'like_count': int,
|
||||||
|
'description': 'md5:7ff0c42ba971e3c13e4b8a2ff03b70b5',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = urllib.parse.unquote(self._match_id(url))
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
data = self._search_json(
|
||||||
|
r'window\.ltvEmbedPayload\s*=', webpage, 'embed json', video_id)
|
||||||
|
embed_type = traverse_obj(data, ('source', 'name', {str}))
|
||||||
|
|
||||||
|
if embed_type == 'telia':
|
||||||
|
ie_key = 'CloudyCDN'
|
||||||
|
embed_url = traverse_obj(data, ('source', 'embed_url', {url_or_none}))
|
||||||
|
elif embed_type == 'youtube':
|
||||||
|
ie_key = 'Youtube'
|
||||||
|
embed_url = traverse_obj(data, ('source', 'id', {str}))
|
||||||
|
else:
|
||||||
|
raise ExtractorError(f'Unsupported embed type {embed_type!r}')
|
||||||
|
|
||||||
|
return self.url_result(
|
||||||
|
embed_url, ie_key, video_id, **traverse_obj(data, {
|
||||||
|
'title': ('parentInfo', 'title'),
|
||||||
|
'duration': ('parentInfo', 'duration', {int_or_none}),
|
||||||
|
'thumbnail': ('source', 'poster', {url_or_none}),
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
class LSMReplayIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://replay\.lsm\.lv/[^/?#]+/(?:ieraksts|statja)/[^/?#]+/(?P<id>\d+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://replay.lsm.lv/lv/ieraksts/ltv/311130/4-studija-zolitudes-tragedija-un-incupes-stacija',
|
||||||
|
'md5': '64f72a360ca530d5ed89c77646c9eee5',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '46k_d23-6000-105',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'timestamp': 1700586300,
|
||||||
|
'description': 'md5:0f1b14798cc39e1ae578bd0eb268f759',
|
||||||
|
'duration': 1442,
|
||||||
|
'upload_date': '20231121',
|
||||||
|
'title': '4. studija. Zolitūdes traģēdija un Inčupes stacija',
|
||||||
|
'thumbnail': 'https://ltv.lsm.lv/storage/media/8/7/large/5/1f9604e1.jpg',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'https://replay.lsm.lv/lv/ieraksts/lr/183522/138-nepilniga-kompensejamo-zalu-sistema-pat-menesiem-dzena-pacientus-pa-aptiekam',
|
||||||
|
'md5': '719b33875cd1429846eeeaeec6df2830',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'a342781',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'duration': 1823,
|
||||||
|
'title': '#138 Nepilnīgā kompensējamo zāļu sistēma pat mēnešiem dzenā pacientus pa aptiekām',
|
||||||
|
'thumbnail': 'https://pic.latvijasradio.lv/public/assets/media/9/d/large_fd4675ac.jpg',
|
||||||
|
'upload_date': '20231102',
|
||||||
|
'timestamp': 1698921060,
|
||||||
|
'description': 'md5:7bac3b2dd41e44325032943251c357b1',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'https://replay.lsm.lv/ru/statja/ltv/311130/4-studija-zolitudes-tragedija-un-incupes-stacija',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _fix_nuxt_data(self, webpage):
|
||||||
|
return re.sub(r'Object\.create\(null(?:,(\{.+\}))?\)', lambda m: m.group(1) or 'null', webpage)
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
data = self._search_nuxt_data(
|
||||||
|
self._fix_nuxt_data(webpage), video_id, context_name='__REPLAY__')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'id': video_id,
|
||||||
|
**traverse_obj(data, {
|
||||||
|
'url': ('playback', 'service', 'url', {url_or_none}),
|
||||||
|
'title': ('mediaItem', 'title'),
|
||||||
|
'description': ('mediaItem', ('lead', 'body')),
|
||||||
|
'duration': ('mediaItem', 'duration', {int_or_none}),
|
||||||
|
'timestamp': ('mediaItem', 'aired_at', {parse_iso8601}),
|
||||||
|
'thumbnail': ('mediaItem', 'largeThumbnail', {url_or_none}),
|
||||||
|
}, get_all=False),
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import ExtractorError, int_or_none, join_nonempty, url_or_none
|
||||||
|
from ..utils.traversal import traverse_obj
|
||||||
|
|
||||||
|
|
||||||
|
class MagentaMusikIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?magentamusik\.de/(?P<id>[^/?#]+)'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.magentamusik.de/marty-friedman-woa-2023-9208205928595409235',
|
||||||
|
'md5': 'd82dd4748f55fc91957094546aaf8584',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '9208205928595409235',
|
||||||
|
'display_id': 'marty-friedman-woa-2023-9208205928595409235',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Marty Friedman: W:O:A 2023',
|
||||||
|
'alt_title': 'Konzert vom: 05.08.2023 13:00',
|
||||||
|
'duration': 2760,
|
||||||
|
'categories': ['Musikkonzert'],
|
||||||
|
'release_year': 2023,
|
||||||
|
'location': 'Deutschland',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
player_config = self._search_json(
|
||||||
|
r'data-js-element="o-video-player__config">', webpage, 'player config', display_id, fatal=False)
|
||||||
|
if not player_config:
|
||||||
|
raise ExtractorError('No video found', expected=True)
|
||||||
|
|
||||||
|
asset_id = player_config['assetId']
|
||||||
|
asset_details = self._download_json(
|
||||||
|
f'https://wcps.t-online.de/cvss/magentamusic/vodclient/v2/assetdetails/58938/{asset_id}',
|
||||||
|
display_id, note='Downloading asset details')
|
||||||
|
|
||||||
|
video_id = traverse_obj(
|
||||||
|
asset_details, ('content', 'partnerInformation', ..., 'reference', {str}), get_all=False)
|
||||||
|
if not video_id:
|
||||||
|
raise ExtractorError('Unable to extract video id')
|
||||||
|
|
||||||
|
vod_data = self._download_json(
|
||||||
|
f'https://wcps.t-online.de/cvss/magentamusic/vodclient/v2/player/58935/{video_id}/Main%20Movie', video_id)
|
||||||
|
smil_url = traverse_obj(
|
||||||
|
vod_data, ('content', 'feature', 'representations', ...,
|
||||||
|
'contentPackages', ..., 'media', 'href', {url_or_none}), get_all=False)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'formats': self._extract_smil_formats(smil_url, video_id),
|
||||||
|
**traverse_obj(vod_data, ('content', 'feature', 'metadata', {
|
||||||
|
'title': 'title',
|
||||||
|
'alt_title': 'originalTitle',
|
||||||
|
'description': 'longDescription',
|
||||||
|
'duration': ('runtimeInSeconds', {int_or_none}),
|
||||||
|
'location': ('countriesOfProduction', {list}, {lambda x: join_nonempty(*x, delim=', ')}),
|
||||||
|
'release_year': ('yearOfProduction', {int_or_none}),
|
||||||
|
'categories': ('mainGenre', {str}, {lambda x: x and [x]}),
|
||||||
|
})),
|
||||||
|
}
|
@ -1,58 +0,0 @@
|
|||||||
from .common import InfoExtractor
|
|
||||||
|
|
||||||
|
|
||||||
class MagentaMusik360IE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?magenta-musik-360\.de/([a-z0-9-]+-(?P<id>[0-9]+)|festivals/.+)'
|
|
||||||
_TESTS = [{
|
|
||||||
'url': 'https://www.magenta-musik-360.de/within-temptation-wacken-2019-1-9208205928595185932',
|
|
||||||
'md5': '65b6f060b40d90276ec6fb9b992c1216',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '9208205928595185932',
|
|
||||||
'ext': 'm3u8',
|
|
||||||
'title': 'WITHIN TEMPTATION',
|
|
||||||
'description': 'Robert Westerholt und Sharon Janny den Adel gründeten die Symphonic Metal-Band. Privat sind die Niederländer ein Paar und haben zwei Kinder. Die Single Ice Queen brachte ihnen Platin und Gold und verhalf 2002 zum internationalen Durchbruch. Charakteristisch für die Band war Anfangs der hohe Gesang von Frontfrau Sharon. Stilistisch fing die Band im Gothic Metal an. Mit neuem Sound, schnellen Gitarrenriffs und Gitarrensoli, avancierte Within Temptation zur erfolgreichen Rockband. Auch dieses Jahr wird die Band ihre Fangemeinde wieder mitreißen.',
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
'url': 'https://www.magenta-musik-360.de/festivals/wacken-world-wide-2020-body-count-feat-ice-t',
|
|
||||||
'md5': '81010d27d7cab3f7da0b0f681b983b7e',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '9208205928595231363',
|
|
||||||
'ext': 'm3u8',
|
|
||||||
'title': 'Body Count feat. Ice-T',
|
|
||||||
'description': 'Body Count feat. Ice-T konnten bereits im vergangenen Jahr auf dem „Holy Ground“ in Wacken überzeugen. 2020 gehen die Crossover-Metaller aus einem Club in Los Angeles auf Sendung und bringen mit ihrer Mischung aus Metal und Hip-Hop Abwechslung und ordentlich Alarm zum WWW. Bereits seit 1990 stehen die beiden Gründer Ice-T (Gesang) und Ernie C (Gitarre) auf der Bühne. Sieben Studioalben hat die Gruppe bis jetzt veröffentlicht, darunter das Debüt „Body Count“ (1992) mit dem kontroversen Track „Cop Killer“.',
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
# _match_id casts to string, but since "None" is not a valid video_id for magenta
|
|
||||||
# there is no risk for confusion
|
|
||||||
if video_id == "None":
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
video_id = self._html_search_regex(r'data-asset-id="([^"]+)"', webpage, 'video_id')
|
|
||||||
json = self._download_json("https://wcps.t-online.de/cvss/magentamusic/vodplayer/v3/player/58935/%s/Main%%20Movie" % video_id, video_id)
|
|
||||||
xml_url = json['content']['feature']['representations'][0]['contentPackages'][0]['media']['href']
|
|
||||||
metadata = json['content']['feature'].get('metadata')
|
|
||||||
title = None
|
|
||||||
description = None
|
|
||||||
duration = None
|
|
||||||
thumbnails = []
|
|
||||||
if metadata:
|
|
||||||
title = metadata.get('title')
|
|
||||||
description = metadata.get('fullDescription')
|
|
||||||
duration = metadata.get('runtimeInSeconds')
|
|
||||||
for img_key in ('teaserImageWide', 'smallCoverImage'):
|
|
||||||
if img_key in metadata:
|
|
||||||
thumbnails.append({'url': metadata[img_key].get('href')})
|
|
||||||
|
|
||||||
xml = self._download_xml(xml_url, video_id)
|
|
||||||
final_url = xml[0][0][0].attrib['src']
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'title': title,
|
|
||||||
'description': description,
|
|
||||||
'url': final_url,
|
|
||||||
'duration': duration,
|
|
||||||
'thumbnails': thumbnails
|
|
||||||
}
|
|
@ -0,0 +1,171 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..networking import HEADRequest
|
||||||
|
from ..utils import (
|
||||||
|
get_element_by_class,
|
||||||
|
int_or_none,
|
||||||
|
try_call,
|
||||||
|
url_or_none,
|
||||||
|
urlhandle_detect_ext,
|
||||||
|
)
|
||||||
|
from ..utils.traversal import traverse_obj
|
||||||
|
|
||||||
|
|
||||||
|
class Mx3BaseIE(InfoExtractor):
|
||||||
|
_VALID_URL_TMPL = r'https?://(?:www\.)?%s/t/(?P<id>\w+)'
|
||||||
|
_FORMATS = [{
|
||||||
|
'url': 'player_asset',
|
||||||
|
'format_id': 'default',
|
||||||
|
'quality': 0,
|
||||||
|
}, {
|
||||||
|
'url': 'player_asset?quality=hd',
|
||||||
|
'format_id': 'hd',
|
||||||
|
'quality': 1,
|
||||||
|
}, {
|
||||||
|
'url': 'download',
|
||||||
|
'format_id': 'download',
|
||||||
|
'quality': 2,
|
||||||
|
}, {
|
||||||
|
'url': 'player_asset?quality=source',
|
||||||
|
'format_id': 'source',
|
||||||
|
'quality': 2,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _extract_formats(self, track_id):
|
||||||
|
formats = []
|
||||||
|
for fmt in self._FORMATS:
|
||||||
|
format_url = f'https://{self._DOMAIN}/tracks/{track_id}/{fmt["url"]}'
|
||||||
|
urlh = self._request_webpage(
|
||||||
|
HEADRequest(format_url), track_id, fatal=False, expected_status=404,
|
||||||
|
note=f'Checking for format {fmt["format_id"]}')
|
||||||
|
if urlh and urlh.status == 200:
|
||||||
|
formats.append({
|
||||||
|
**fmt,
|
||||||
|
'url': format_url,
|
||||||
|
'ext': urlhandle_detect_ext(urlh),
|
||||||
|
'filesize': int_or_none(urlh.headers.get('Content-Length')),
|
||||||
|
})
|
||||||
|
return formats
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
track_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, track_id)
|
||||||
|
more_info = get_element_by_class('single-more-info', webpage)
|
||||||
|
data = self._download_json(f'https://{self._DOMAIN}/t/{track_id}.json', track_id, fatal=False)
|
||||||
|
|
||||||
|
def get_info_field(name):
|
||||||
|
return self._html_search_regex(
|
||||||
|
rf'<dt[^>]*>\s*{name}\s*</dt>\s*<dd[^>]*>(.*?)</dd>',
|
||||||
|
more_info, name, default=None, flags=re.DOTALL)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': track_id,
|
||||||
|
'formats': self._extract_formats(track_id),
|
||||||
|
'genre': self._html_search_regex(
|
||||||
|
r'<div\b[^>]+class="single-band-genre"[^>]*>([^<]+)</div>', webpage, 'genre', default=None),
|
||||||
|
'release_year': int_or_none(get_info_field('Year of creation')),
|
||||||
|
'description': get_info_field('Description'),
|
||||||
|
'tags': try_call(lambda: get_info_field('Tag').split(', '), list),
|
||||||
|
**traverse_obj(data, {
|
||||||
|
'title': ('title', {str}),
|
||||||
|
'artist': (('performer_name', 'artist'), {str}),
|
||||||
|
'album_artist': ('artist', {str}),
|
||||||
|
'composer': ('composer_name', {str}),
|
||||||
|
'thumbnail': (('picture_url_xlarge', 'picture_url'), {url_or_none}),
|
||||||
|
}, get_all=False),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Mx3IE(Mx3BaseIE):
|
||||||
|
_DOMAIN = 'mx3.ch'
|
||||||
|
_VALID_URL = Mx3BaseIE._VALID_URL_TMPL % re.escape(_DOMAIN)
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://mx3.ch/t/1Cru',
|
||||||
|
'md5': '7ba09e9826b4447d4e1ce9d69e0e295f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1Cru',
|
||||||
|
'ext': 'wav',
|
||||||
|
'artist': 'Godina',
|
||||||
|
'album_artist': 'Tortue Tortue',
|
||||||
|
'composer': 'Olivier Godinat',
|
||||||
|
'genre': 'Rock',
|
||||||
|
'thumbnail': 'https://mx3.ch/pictures/mx3/file/0101/4643/square_xlarge/1-s-envoler-1.jpg?1630272813',
|
||||||
|
'title': "S'envoler",
|
||||||
|
'release_year': 2021,
|
||||||
|
'tags': [],
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'https://mx3.ch/t/1LIY',
|
||||||
|
'md5': '48293cb908342547827f963a5a2e9118',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1LIY',
|
||||||
|
'ext': 'mov',
|
||||||
|
'artist': 'Tania Kimfumu',
|
||||||
|
'album_artist': 'The Broots',
|
||||||
|
'composer': 'Emmanuel Diserens',
|
||||||
|
'genre': 'Electro',
|
||||||
|
'thumbnail': 'https://mx3.ch/pictures/mx3/file/0110/0003/video_xlarge/frame_0000.png?1686963670',
|
||||||
|
'title': 'The Broots-Larytta remix "Begging For Help"',
|
||||||
|
'release_year': 2023,
|
||||||
|
'tags': ['the broots', 'cassata records', 'larytta'],
|
||||||
|
'description': '"Begging for Help" Larytta Remix Official Video\nRealized By Kali Donkilie in 2023',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'https://mx3.ch/t/1C6E',
|
||||||
|
'md5': '1afcd578493ddb8e5008e94bb6d97e25',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1C6E',
|
||||||
|
'ext': 'wav',
|
||||||
|
'artist': 'Alien Bubblegum',
|
||||||
|
'album_artist': 'Alien Bubblegum',
|
||||||
|
'composer': 'Alien Bubblegum',
|
||||||
|
'genre': 'Punk',
|
||||||
|
'thumbnail': 'https://mx3.ch/pictures/mx3/file/0101/1551/square_xlarge/pandora-s-box-cover-with-title.png?1627054733',
|
||||||
|
'title': 'Wide Awake',
|
||||||
|
'release_year': 2021,
|
||||||
|
'tags': ['alien bubblegum', 'bubblegum', 'alien', 'pop punk', 'poppunk'],
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
class Mx3NeoIE(Mx3BaseIE):
|
||||||
|
_DOMAIN = 'neo.mx3.ch'
|
||||||
|
_VALID_URL = Mx3BaseIE._VALID_URL_TMPL % re.escape(_DOMAIN)
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://neo.mx3.ch/t/1hpd',
|
||||||
|
'md5': '6d9986bbae5cac3296ec8813bf965eb2',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1hpd',
|
||||||
|
'ext': 'wav',
|
||||||
|
'artist': 'Baptiste Lopez',
|
||||||
|
'album_artist': 'Kammerorchester Basel',
|
||||||
|
'composer': 'Jannik Giger',
|
||||||
|
'genre': 'Composition, Orchestra',
|
||||||
|
'title': 'Troisième œil. Für Kammerorchester (2023)',
|
||||||
|
'thumbnail': 'https://neo.mx3.ch/pictures/neo/file/0000/0241/square_xlarge/kammerorchester-basel-group-photo-2_c_-lukasz-rajchert.jpg?1560341252',
|
||||||
|
'release_year': 2023,
|
||||||
|
'tags': [],
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
class Mx3VolksmusikIE(Mx3BaseIE):
|
||||||
|
_DOMAIN = 'volksmusik.mx3.ch'
|
||||||
|
_VALID_URL = Mx3BaseIE._VALID_URL_TMPL % re.escape(_DOMAIN)
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://volksmusik.mx3.ch/t/Zx',
|
||||||
|
'md5': 'dd967a7b0c1ef898f3e072cf9c2eae3c',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'Zx',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'artist': 'Ländlerkapelle GrischArt',
|
||||||
|
'album_artist': 'Ländlerkapelle GrischArt',
|
||||||
|
'composer': 'Urs Glauser',
|
||||||
|
'genre': 'Instrumental, Graubünden',
|
||||||
|
'title': 'Chämilouf',
|
||||||
|
'thumbnail': 'https://volksmusik.mx3.ch/pictures/vxm/file/0000/3815/square_xlarge/grischart1.jpg?1450530120',
|
||||||
|
'release_year': 2012,
|
||||||
|
'tags': [],
|
||||||
|
}
|
||||||
|
}]
|
@ -0,0 +1,72 @@
|
|||||||
|
from .common import InfoExtractor
|
||||||
|
from .brightcove import BrightcoveNewIE
|
||||||
|
from ..utils import ExtractorError
|
||||||
|
from ..utils.traversal import traverse_obj
|
||||||
|
|
||||||
|
|
||||||
|
class NineNewsIE(InfoExtractor):
|
||||||
|
IE_NAME = '9News'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?9news\.com\.au/(?:[\w-]+/){2,3}(?P<id>[\w-]+)/?(?:$|[?#])'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.9news.com.au/videos/national/fair-trading-pulls-dozens-of-toys-from-shelves/clqgc7dvj000y0jnvfism0w5m',
|
||||||
|
'md5': 'd1a65b2e9d126e5feb9bc5cb96e62c80',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6343717246112',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Fair Trading pulls dozens of toys from shelves',
|
||||||
|
'description': 'Fair Trading Australia have been forced to pull dozens of toys from shelves over hazard fears.',
|
||||||
|
'thumbnail': 'md5:bdbe44294e2323b762d97acf8843f66c',
|
||||||
|
'duration': 93.44,
|
||||||
|
'timestamp': 1703231748,
|
||||||
|
'upload_date': '20231222',
|
||||||
|
'uploader_id': '664969388001',
|
||||||
|
'tags': ['networkclip', 'aunews_aunationalninenews', 'christmas presents', 'toys', 'fair trading', 'au_news'],
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.9news.com.au/world/tape-reveals-donald-trump-pressured-michigan-officials-not-to-certify-2020-vote-a-new-report-says/0b8b880e-7d3c-41b9-b2bd-55bc7e492259',
|
||||||
|
'md5': 'a885c44d20898c3e70e9a53e8188cea1',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6343587450112',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Trump found ineligible to run for president by state court',
|
||||||
|
'description': 'md5:40e6e7db7a4ac6be0e960569a5af6066',
|
||||||
|
'thumbnail': 'md5:3e132c48c186039fd06c10787de9bff2',
|
||||||
|
'duration': 104.64,
|
||||||
|
'timestamp': 1703058034,
|
||||||
|
'upload_date': '20231220',
|
||||||
|
'uploader_id': '664969388001',
|
||||||
|
'tags': ['networkclip', 'aunews_aunationalninenews', 'ineligible', 'presidential candidate', 'donald trump', 'au_news'],
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.9news.com.au/national/outrage-as-parents-banned-from-giving-gifts-to-kindergarten-teachers/e19b49d4-a1a4-4533-9089-6e10e2d9386a',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6343716797112',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Outrage as parents banned from giving gifts to kindergarten teachers',
|
||||||
|
'description': 'md5:7a8b0ed2f9e08875fd9a3e86e462bc46',
|
||||||
|
'thumbnail': 'md5:5ee4d66717bdd0dee9fc9a705ef041b8',
|
||||||
|
'duration': 91.307,
|
||||||
|
'timestamp': 1703229584,
|
||||||
|
'upload_date': '20231222',
|
||||||
|
'uploader_id': '664969388001',
|
||||||
|
'tags': ['networkclip', 'aunews_aunationalninenews', 'presents', 'teachers', 'kindergarten', 'au_news'],
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
article_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, article_id)
|
||||||
|
initial_state = self._search_json(
|
||||||
|
r'var\s+__INITIAL_STATE__\s*=', webpage, 'initial state', article_id)
|
||||||
|
video_id = traverse_obj(
|
||||||
|
initial_state, ('videoIndex', 'currentVideo', 'brightcoveId', {str}),
|
||||||
|
('article', ..., 'media', lambda _, v: v['type'] == 'video', 'urn', {str}), get_all=False)
|
||||||
|
account = traverse_obj(initial_state, (
|
||||||
|
'videoIndex', 'config', (None, 'video'), 'account', {str}), get_all=False)
|
||||||
|
|
||||||
|
if not video_id or not account:
|
||||||
|
raise ExtractorError('Unable to get the required video data')
|
||||||
|
|
||||||
|
return self.url_result(
|
||||||
|
f'https://players.brightcove.net/{account}/default_default/index.html?videoId={video_id}',
|
||||||
|
BrightcoveNewIE, video_id)
|
@ -0,0 +1,135 @@
|
|||||||
|
import functools
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..networking import HEADRequest
|
||||||
|
from ..utils import (
|
||||||
|
float_or_none,
|
||||||
|
int_or_none,
|
||||||
|
join_nonempty,
|
||||||
|
parse_qs,
|
||||||
|
update_url_query,
|
||||||
|
)
|
||||||
|
from ..utils.traversal import traverse_obj
|
||||||
|
|
||||||
|
|
||||||
|
class RedCDNLivxIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://[^.]+\.(?:dcs\.redcdn|atmcdn)\.pl/(?:live(?:dash|hls|ss)|nvr)/o2/(?P<tenant>[^/?#]+)/(?P<id>[^?#]+)\.livx'
|
||||||
|
IE_NAME = 'redcdnlivx'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://r.dcs.redcdn.pl/livedash/o2/senat/ENC02/channel.livx?indexMode=true&startTime=638272860000&stopTime=638292544000',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'ENC02-638272860000-638292544000',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'ENC02',
|
||||||
|
'duration': 19683.982,
|
||||||
|
'live_status': 'was_live',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://r.dcs.redcdn.pl/livedash/o2/sejm/ENC18/live.livx?indexMode=true&startTime=722333096000&stopTime=722335562000',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'ENC18-722333096000-722335562000',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'ENC18',
|
||||||
|
'duration': 2463.995,
|
||||||
|
'live_status': 'was_live',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://r.dcs.redcdn.pl/livehls/o2/sportevolution/live/triathlon2018/warsaw.livx/playlist.m3u8?startTime=550305000000&stopTime=550327620000',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'triathlon2018-warsaw-550305000000-550327620000',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'triathlon2018/warsaw',
|
||||||
|
'duration': 22619.98,
|
||||||
|
'live_status': 'was_live',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://n-25-12.dcs.redcdn.pl/nvr/o2/sejm/Migacz-ENC01/1.livx?startTime=722347200000&stopTime=722367345000',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://redir.atmcdn.pl/nvr/o2/sejm/ENC08/1.livx?startTime=503831270000&stopTime=503840040000',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
"""
|
||||||
|
Known methods (first in url path):
|
||||||
|
- `livedash` - DASH MPD
|
||||||
|
- `livehls` - HTTP Live Streaming
|
||||||
|
- `livess` - IIS Smooth Streaming
|
||||||
|
- `nvr` - CCTV mode, directly returns a file, typically flv, avc1, aac
|
||||||
|
- `sc` - shoutcast/icecast (audio streams, like radio)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
tenant, path = self._match_valid_url(url).group('tenant', 'id')
|
||||||
|
qs = parse_qs(url)
|
||||||
|
start_time = traverse_obj(qs, ('startTime', 0, {int_or_none}))
|
||||||
|
stop_time = traverse_obj(qs, ('stopTime', 0, {int_or_none}))
|
||||||
|
|
||||||
|
def livx_mode(mode):
|
||||||
|
suffix = ''
|
||||||
|
if mode == 'livess':
|
||||||
|
suffix = '/manifest'
|
||||||
|
elif mode == 'livehls':
|
||||||
|
suffix = '/playlist.m3u8'
|
||||||
|
file_qs = {}
|
||||||
|
if start_time:
|
||||||
|
file_qs['startTime'] = start_time
|
||||||
|
if stop_time:
|
||||||
|
file_qs['stopTime'] = stop_time
|
||||||
|
if mode == 'nvr':
|
||||||
|
file_qs['nolimit'] = 1
|
||||||
|
elif mode != 'sc':
|
||||||
|
file_qs['indexMode'] = 'true'
|
||||||
|
return update_url_query(f'https://r.dcs.redcdn.pl/{mode}/o2/{tenant}/{path}.livx{suffix}', file_qs)
|
||||||
|
|
||||||
|
# no id or title for a transmission. making ones up.
|
||||||
|
title = path \
|
||||||
|
.replace('/live', '').replace('live/', '') \
|
||||||
|
.replace('/channel', '').replace('channel/', '') \
|
||||||
|
.strip('/')
|
||||||
|
video_id = join_nonempty(title.replace('/', '-'), start_time, stop_time)
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
# downloading the manifest separately here instead of _extract_ism_formats to also get some stream metadata
|
||||||
|
ism_res = self._download_xml_handle(
|
||||||
|
livx_mode('livess'), video_id,
|
||||||
|
note='Downloading ISM manifest',
|
||||||
|
errnote='Failed to download ISM manifest',
|
||||||
|
fatal=False)
|
||||||
|
ism_doc = None
|
||||||
|
if ism_res is not False:
|
||||||
|
ism_doc, ism_urlh = ism_res
|
||||||
|
formats, _ = self._parse_ism_formats_and_subtitles(ism_doc, ism_urlh.url, 'ss')
|
||||||
|
|
||||||
|
nvr_urlh = self._request_webpage(
|
||||||
|
HEADRequest(livx_mode('nvr')), video_id, 'Follow flv file redirect', fatal=False,
|
||||||
|
expected_status=lambda _: True)
|
||||||
|
if nvr_urlh and nvr_urlh.status == 200:
|
||||||
|
formats.append({
|
||||||
|
'url': nvr_urlh.url,
|
||||||
|
'ext': 'flv',
|
||||||
|
'format_id': 'direct-0',
|
||||||
|
'preference': -1, # might be slow
|
||||||
|
})
|
||||||
|
formats.extend(self._extract_mpd_formats(livx_mode('livedash'), video_id, mpd_id='dash', fatal=False))
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
livx_mode('livehls'), video_id, m3u8_id='hls', ext='mp4', fatal=False))
|
||||||
|
|
||||||
|
time_scale = traverse_obj(ism_doc, ('@TimeScale', {int_or_none})) or 10000000
|
||||||
|
duration = traverse_obj(
|
||||||
|
ism_doc, ('@Duration', {functools.partial(float_or_none, scale=time_scale)})) or None
|
||||||
|
|
||||||
|
live_status = None
|
||||||
|
if traverse_obj(ism_doc, '@IsLive') == 'TRUE':
|
||||||
|
live_status = 'is_live'
|
||||||
|
elif duration:
|
||||||
|
live_status = 'was_live'
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'formats': formats,
|
||||||
|
'duration': duration,
|
||||||
|
'live_status': live_status,
|
||||||
|
}
|
@ -0,0 +1,218 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from .redge import RedCDNLivxIE
|
||||||
|
from ..utils import (
|
||||||
|
clean_html,
|
||||||
|
join_nonempty,
|
||||||
|
js_to_json,
|
||||||
|
strip_or_none,
|
||||||
|
update_url_query,
|
||||||
|
)
|
||||||
|
from ..utils.traversal import traverse_obj
|
||||||
|
|
||||||
|
|
||||||
|
def is_dst(date):
|
||||||
|
last_march = datetime.datetime(date.year, 3, 31)
|
||||||
|
last_october = datetime.datetime(date.year, 10, 31)
|
||||||
|
last_sunday_march = last_march - datetime.timedelta(days=last_march.isoweekday() % 7)
|
||||||
|
last_sunday_october = last_october - datetime.timedelta(days=last_october.isoweekday() % 7)
|
||||||
|
return last_sunday_march.replace(hour=2) <= date <= last_sunday_october.replace(hour=3)
|
||||||
|
|
||||||
|
|
||||||
|
def rfc3339_to_atende(date):
|
||||||
|
date = datetime.datetime.fromisoformat(date)
|
||||||
|
date = date + datetime.timedelta(hours=1 if is_dst(date) else 0)
|
||||||
|
return int((date.timestamp() - 978307200) * 1000)
|
||||||
|
|
||||||
|
|
||||||
|
class SejmIE(InfoExtractor):
|
||||||
|
_VALID_URL = (
|
||||||
|
r'https?://(?:www\.)?sejm\.gov\.pl/[Ss]ejm(?P<term>\d+)\.nsf/transmisje(?:_arch)?\.xsp(?:\?[^#]*)?#(?P<id>[\dA-F]+)',
|
||||||
|
r'https?://(?:www\.)?sejm\.gov\.pl/[Ss]ejm(?P<term>\d+)\.nsf/transmisje(?:_arch)?\.xsp\?(?:[^#]+&)?unid=(?P<id>[\dA-F]+)',
|
||||||
|
r'https?://sejm-embed\.redcdn\.pl/[Ss]ejm(?P<term>\d+)\.nsf/VideoFrame\.xsp/(?P<id>[\dA-F]+)',
|
||||||
|
)
|
||||||
|
IE_NAME = 'sejm'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
# multiple cameras, polish SL iterpreter
|
||||||
|
'url': 'https://www.sejm.gov.pl/Sejm10.nsf/transmisje_arch.xsp#6181EF1AD9CEEBB5C1258A6D006452B5',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6181EF1AD9CEEBB5C1258A6D006452B5',
|
||||||
|
'title': '1. posiedzenie Sejmu X kadencji',
|
||||||
|
'duration': 20145,
|
||||||
|
'live_status': 'was_live',
|
||||||
|
'location': 'Sala Posiedzeń',
|
||||||
|
},
|
||||||
|
'playlist': [{
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'ENC01-722340000000-722360145000',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'duration': 20145,
|
||||||
|
'title': '1. posiedzenie Sejmu X kadencji - ENC01',
|
||||||
|
'live_status': 'was_live',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'ENC30-722340000000-722360145000',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'duration': 20145,
|
||||||
|
'title': '1. posiedzenie Sejmu X kadencji - ENC30',
|
||||||
|
'live_status': 'was_live',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'ENC31-722340000000-722360145000',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'duration': 20145,
|
||||||
|
'title': '1. posiedzenie Sejmu X kadencji - ENC31',
|
||||||
|
'live_status': 'was_live',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'ENC32-722340000000-722360145000',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'duration': 20145,
|
||||||
|
'title': '1. posiedzenie Sejmu X kadencji - ENC32',
|
||||||
|
'live_status': 'was_live',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
# sign lang interpreter
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'Migacz-ENC01-1-722340000000-722360145000',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'duration': 20145,
|
||||||
|
'title': '1. posiedzenie Sejmu X kadencji - Migacz-ENC01',
|
||||||
|
'live_status': 'was_live',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.sejm.gov.pl/Sejm8.nsf/transmisje.xsp?unid=9377A9D65518E9A5C125808E002E9FF2',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '9377A9D65518E9A5C125808E002E9FF2',
|
||||||
|
'title': 'Debata "Lepsza Polska: obywatelska"',
|
||||||
|
'description': 'KP .Nowoczesna',
|
||||||
|
'duration': 8770,
|
||||||
|
'live_status': 'was_live',
|
||||||
|
'location': 'sala kolumnowa im. Kazimierza Pużaka (bud. C-D)',
|
||||||
|
},
|
||||||
|
'playlist': [{
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'ENC08-1-503831270000-503840040000',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'duration': 8770,
|
||||||
|
'title': 'Debata "Lepsza Polska: obywatelska" - ENC08',
|
||||||
|
'live_status': 'was_live',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
}, {
|
||||||
|
# 7th term is very special, since it does not use redcdn livx
|
||||||
|
'url': 'https://www.sejm.gov.pl/sejm7.nsf/transmisje_arch.xsp?rok=2015&month=11#A6E6D475ECCC6FE5C1257EF90034817F',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'A6E6D475ECCC6FE5C1257EF90034817F',
|
||||||
|
'title': 'Konferencja prasowa - Stanowisko SLD ws. składu nowego rządu',
|
||||||
|
'description': 'SLD - Biuro Prasowe Klubu',
|
||||||
|
'duration': 514,
|
||||||
|
'location': 'sala 101/bud. C',
|
||||||
|
'live_status': 'was_live',
|
||||||
|
},
|
||||||
|
'playlist': [{
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'A6E6D475ECCC6FE5C1257EF90034817F',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Konferencja prasowa - Stanowisko SLD ws. składu nowego rządu',
|
||||||
|
'duration': 514,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
}, {
|
||||||
|
'url': 'https://sejm-embed.redcdn.pl/Sejm10.nsf/VideoFrame.xsp/FED58EABB97FBD53C1258A7400386492',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
term, video_id = self._match_valid_url(url).group('term', 'id')
|
||||||
|
frame = self._download_webpage(
|
||||||
|
f'https://sejm-embed.redcdn.pl/Sejm{term}.nsf/VideoFrame.xsp/{video_id}',
|
||||||
|
video_id)
|
||||||
|
# despite it says "transmisje_arch", it works for live streams too!
|
||||||
|
data = self._download_json(
|
||||||
|
f'https://www.sejm.gov.pl/Sejm{term}.nsf/transmisje_arch.xsp/json/{video_id}',
|
||||||
|
video_id)
|
||||||
|
params = data['params']
|
||||||
|
|
||||||
|
title = strip_or_none(data.get('title'))
|
||||||
|
|
||||||
|
if data.get('status') == 'VIDEO_ENDED':
|
||||||
|
live_status = 'was_live'
|
||||||
|
elif data.get('status') == 'VIDEO_PLAYING':
|
||||||
|
live_status = 'is_live'
|
||||||
|
else:
|
||||||
|
live_status = None
|
||||||
|
self.report_warning(f'unknown status: {data.get("status")}')
|
||||||
|
|
||||||
|
start_time = rfc3339_to_atende(params['start'])
|
||||||
|
# current streams have a stop time of *expected* end of session, but actual times
|
||||||
|
# can change during the transmission. setting a stop_time would artificially
|
||||||
|
# end the stream at that time, while the session actually keeps going.
|
||||||
|
if live_status == 'was_live':
|
||||||
|
stop_time = rfc3339_to_atende(params['stop'])
|
||||||
|
duration = (stop_time - start_time) // 1000
|
||||||
|
else:
|
||||||
|
stop_time, duration = None, None
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
|
||||||
|
def add_entry(file, legacy_file=False):
|
||||||
|
if not file:
|
||||||
|
return
|
||||||
|
file = self._proto_relative_url(file)
|
||||||
|
if not legacy_file:
|
||||||
|
file = update_url_query(file, {'startTime': start_time})
|
||||||
|
if stop_time is not None:
|
||||||
|
file = update_url_query(file, {'stopTime': stop_time})
|
||||||
|
stream_id = self._search_regex(r'/o2/sejm/([^/]+)/[^./]+\.livx', file, 'stream id')
|
||||||
|
common_info = {
|
||||||
|
'url': file,
|
||||||
|
'duration': duration,
|
||||||
|
}
|
||||||
|
if legacy_file:
|
||||||
|
entries.append({
|
||||||
|
**common_info,
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
entries.append({
|
||||||
|
**common_info,
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'ie_key': RedCDNLivxIE.ie_key(),
|
||||||
|
'id': stream_id,
|
||||||
|
'title': join_nonempty(title, stream_id, delim=' - '),
|
||||||
|
})
|
||||||
|
|
||||||
|
cameras = self._search_json(
|
||||||
|
r'var\s+cameras\s*=', frame, 'camera list', video_id,
|
||||||
|
contains_pattern=r'\[(?s:.+)\]', transform_source=js_to_json,
|
||||||
|
fatal=False) or []
|
||||||
|
for camera_file in traverse_obj(cameras, (..., 'file', {dict})):
|
||||||
|
if camera_file.get('flv'):
|
||||||
|
add_entry(camera_file['flv'])
|
||||||
|
elif camera_file.get('mp4'):
|
||||||
|
# this is only a thing in 7th term. no streams before, and starting 8th it's redcdn livx
|
||||||
|
add_entry(camera_file['mp4'], legacy_file=True)
|
||||||
|
else:
|
||||||
|
self.report_warning('Unknown camera stream type found')
|
||||||
|
|
||||||
|
if params.get('mig'):
|
||||||
|
add_entry(self._search_regex(r"var sliUrl\s*=\s*'([^']+)'", frame, 'sign language interpreter url', fatal=False))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'entries': entries,
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': clean_html(data.get('desc')) or None,
|
||||||
|
'duration': duration,
|
||||||
|
'live_status': live_status,
|
||||||
|
'location': strip_or_none(data.get('location')),
|
||||||
|
}
|
Loading…
Reference in New Issue