mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2024-11-30 12:01:28 +00:00
[cleanup] Misc (#11347)
Closes #11361 Authored by: avagordon01, bashonly, grqz, Grub4K, seproDev Co-authored-by: Ava Gordon <avagordon01@gmail.com> Co-authored-by: bashonly <bashonly@protonmail.com> Co-authored-by: N/Ame <173015200+grqz@users.noreply.github.com> Co-authored-by: Simon Sawicki <contact@grub4k.xyz>
This commit is contained in:
parent
b03267bf06
commit
197d0b03b6
|
@ -479,7 +479,8 @@ ## Video Selection:
|
||||||
--no-download-archive Do not use archive file (default)
|
--no-download-archive Do not use archive file (default)
|
||||||
--max-downloads NUMBER Abort after downloading NUMBER files
|
--max-downloads NUMBER Abort after downloading NUMBER files
|
||||||
--break-on-existing Stop the download process when encountering
|
--break-on-existing Stop the download process when encountering
|
||||||
a file that is in the archive
|
a file that is in the archive supplied with
|
||||||
|
the --download-archive option
|
||||||
--no-break-on-existing Do not stop the download process when
|
--no-break-on-existing Do not stop the download process when
|
||||||
encountering a file that is in the archive
|
encountering a file that is in the archive
|
||||||
(default)
|
(default)
|
||||||
|
|
|
@ -490,7 +490,7 @@ def test_subs_list_to_dict(self):
|
||||||
{'url': 'https://example.com/subs/en', 'name': 'en'},
|
{'url': 'https://example.com/subs/en', 'name': 'en'},
|
||||||
], [..., {
|
], [..., {
|
||||||
'id': 'name',
|
'id': 'name',
|
||||||
'ext': ['url', {lambda x: determine_ext(x, default_ext=None)}],
|
'ext': ['url', {determine_ext(default_ext=None)}],
|
||||||
'url': 'url',
|
'url': 'url',
|
||||||
}, all, {subs_list_to_dict(ext='ext')}]) == {
|
}, all, {subs_list_to_dict(ext='ext')}]) == {
|
||||||
'de': [{'url': 'https://example.com/subs/de.ass', 'ext': 'ass'}],
|
'de': [{'url': 'https://example.com/subs/de.ass', 'ext': 'ass'}],
|
||||||
|
|
|
@ -2156,7 +2156,7 @@ def test_partial_application(self):
|
||||||
assert callable(int_or_none(scale=10)), 'missing positional parameter should apply partially'
|
assert callable(int_or_none(scale=10)), 'missing positional parameter should apply partially'
|
||||||
assert int_or_none(10, scale=0.1) == 100, 'positionally passed argument should call function'
|
assert int_or_none(10, scale=0.1) == 100, 'positionally passed argument should call function'
|
||||||
assert int_or_none(v=10) == 10, 'keyword passed positional should call function'
|
assert int_or_none(v=10) == 10, 'keyword passed positional should call function'
|
||||||
assert int_or_none(scale=0.1)(10) == 100, 'call after partial applicatino should call the function'
|
assert int_or_none(scale=0.1)(10) == 100, 'call after partial application should call the function'
|
||||||
|
|
||||||
assert callable(join_nonempty(delim=', ')), 'varargs positional should apply partially'
|
assert callable(join_nonempty(delim=', ')), 'varargs positional should apply partially'
|
||||||
assert callable(join_nonempty()), 'varargs positional should apply partially'
|
assert callable(join_nonempty()), 'varargs positional should apply partially'
|
||||||
|
|
|
@ -154,7 +154,7 @@ def _real_extract(self, url):
|
||||||
'title': ('title', {str}),
|
'title': ('title', {str}),
|
||||||
'uploader': ('writer_nick', {str}),
|
'uploader': ('writer_nick', {str}),
|
||||||
'uploader_id': ('bj_id', {str}),
|
'uploader_id': ('bj_id', {str}),
|
||||||
'duration': ('total_file_duration', {functools.partial(int_or_none, scale=1000)}),
|
'duration': ('total_file_duration', {int_or_none(scale=1000)}),
|
||||||
'thumbnail': ('thumb', {url_or_none}),
|
'thumbnail': ('thumb', {url_or_none}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ def _real_extract(self, url):
|
||||||
'title': f'{common_info.get("title") or "Untitled"} (part {file_num})',
|
'title': f'{common_info.get("title") or "Untitled"} (part {file_num})',
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
**traverse_obj(file_element, {
|
**traverse_obj(file_element, {
|
||||||
'duration': ('duration', {functools.partial(int_or_none, scale=1000)}),
|
'duration': ('duration', {int_or_none(scale=1000)}),
|
||||||
'timestamp': ('file_start', {unified_timestamp}),
|
'timestamp': ('file_start', {unified_timestamp}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
@ -234,7 +234,7 @@ def _entries(data):
|
||||||
'catch_list', lambda _, v: v['files'][0]['file'], {
|
'catch_list', lambda _, v: v['files'][0]['file'], {
|
||||||
'id': ('files', 0, 'file_info_key', {str}),
|
'id': ('files', 0, 'file_info_key', {str}),
|
||||||
'url': ('files', 0, 'file', {url_or_none}),
|
'url': ('files', 0, 'file', {url_or_none}),
|
||||||
'duration': ('files', 0, 'duration', {functools.partial(int_or_none, scale=1000)}),
|
'duration': ('files', 0, 'duration', {int_or_none(scale=1000)}),
|
||||||
'title': ('title', {str}),
|
'title': ('title', {str}),
|
||||||
'uploader': ('writer_nick', {str}),
|
'uploader': ('writer_nick', {str}),
|
||||||
'uploader_id': ('writer_id', {str}),
|
'uploader_id': ('writer_id', {str}),
|
||||||
|
|
|
@ -71,7 +71,7 @@ def media_url_or_none(path):
|
||||||
'thumbnails': (('clipImageThumb', 'clipImageSource'), {'url': {media_url_or_none}}),
|
'thumbnails': (('clipImageThumb', 'clipImageSource'), {'url': {media_url_or_none}}),
|
||||||
'duration': ('clipLength', {int_or_none}),
|
'duration': ('clipLength', {int_or_none}),
|
||||||
'filesize': ('clipSizeBytes', {int_or_none}),
|
'filesize': ('clipSizeBytes', {int_or_none}),
|
||||||
'timestamp': ('createdDate', {functools.partial(int_or_none, scale=1000)}),
|
'timestamp': ('createdDate', {int_or_none(scale=1000)}),
|
||||||
'uploader': ('username', {str}),
|
'uploader': ('username', {str}),
|
||||||
'uploader_id': ('user', '_id', {str}),
|
'uploader_id': ('user', '_id', {str}),
|
||||||
'view_count': ('views', {int_or_none}),
|
'view_count': ('views', {int_or_none}),
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import functools
|
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
@ -10,7 +9,6 @@
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
extract_attributes,
|
extract_attributes,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
get_element_html_by_id,
|
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_filesize,
|
parse_filesize,
|
||||||
str_or_none,
|
str_or_none,
|
||||||
|
@ -21,7 +19,7 @@
|
||||||
url_or_none,
|
url_or_none,
|
||||||
urljoin,
|
urljoin,
|
||||||
)
|
)
|
||||||
from ..utils.traversal import traverse_obj
|
from ..utils.traversal import find_element, traverse_obj
|
||||||
|
|
||||||
|
|
||||||
class BandcampIE(InfoExtractor):
|
class BandcampIE(InfoExtractor):
|
||||||
|
@ -45,6 +43,8 @@ class BandcampIE(InfoExtractor):
|
||||||
'uploader_url': 'https://youtube-dl.bandcamp.com',
|
'uploader_url': 'https://youtube-dl.bandcamp.com',
|
||||||
'uploader_id': 'youtube-dl',
|
'uploader_id': 'youtube-dl',
|
||||||
'thumbnail': 'https://f4.bcbits.com/img/a3216802731_5.jpg',
|
'thumbnail': 'https://f4.bcbits.com/img/a3216802731_5.jpg',
|
||||||
|
'artists': ['youtube-dl "\'/\\ä↭'],
|
||||||
|
'album_artists': ['youtube-dl "\'/\\ä↭'],
|
||||||
},
|
},
|
||||||
'skip': 'There is a limit of 200 free downloads / month for the test song',
|
'skip': 'There is a limit of 200 free downloads / month for the test song',
|
||||||
}, {
|
}, {
|
||||||
|
@ -271,6 +271,18 @@ class BandcampAlbumIE(BandcampIE): # XXX: Do not subclass from concrete IE
|
||||||
'timestamp': 1311756226,
|
'timestamp': 1311756226,
|
||||||
'upload_date': '20110727',
|
'upload_date': '20110727',
|
||||||
'uploader': 'Blazo',
|
'uploader': 'Blazo',
|
||||||
|
'thumbnail': 'https://f4.bcbits.com/img/a1721150828_5.jpg',
|
||||||
|
'album_artists': ['Blazo'],
|
||||||
|
'uploader_url': 'https://blazo.bandcamp.com',
|
||||||
|
'release_date': '20110727',
|
||||||
|
'release_timestamp': 1311724800.0,
|
||||||
|
'track': 'Intro',
|
||||||
|
'uploader_id': 'blazo',
|
||||||
|
'track_number': 1,
|
||||||
|
'album': 'Jazz Format Mixtape vol.1',
|
||||||
|
'artists': ['Blazo'],
|
||||||
|
'duration': 19.335,
|
||||||
|
'track_id': '1353101989',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -282,6 +294,18 @@ class BandcampAlbumIE(BandcampIE): # XXX: Do not subclass from concrete IE
|
||||||
'timestamp': 1311757238,
|
'timestamp': 1311757238,
|
||||||
'upload_date': '20110727',
|
'upload_date': '20110727',
|
||||||
'uploader': 'Blazo',
|
'uploader': 'Blazo',
|
||||||
|
'track': 'Kero One - Keep It Alive (Blazo remix)',
|
||||||
|
'release_date': '20110727',
|
||||||
|
'track_id': '38097443',
|
||||||
|
'track_number': 2,
|
||||||
|
'duration': 181.467,
|
||||||
|
'uploader_url': 'https://blazo.bandcamp.com',
|
||||||
|
'album': 'Jazz Format Mixtape vol.1',
|
||||||
|
'uploader_id': 'blazo',
|
||||||
|
'album_artists': ['Blazo'],
|
||||||
|
'artists': ['Blazo'],
|
||||||
|
'thumbnail': 'https://f4.bcbits.com/img/a1721150828_5.jpg',
|
||||||
|
'release_timestamp': 1311724800.0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -289,6 +313,7 @@ class BandcampAlbumIE(BandcampIE): # XXX: Do not subclass from concrete IE
|
||||||
'title': 'Jazz Format Mixtape vol.1',
|
'title': 'Jazz Format Mixtape vol.1',
|
||||||
'id': 'jazz-format-mixtape-vol-1',
|
'id': 'jazz-format-mixtape-vol-1',
|
||||||
'uploader_id': 'blazo',
|
'uploader_id': 'blazo',
|
||||||
|
'description': 'md5:38052a93217f3ffdc033cd5dbbce2989',
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'playlistend': 2,
|
'playlistend': 2,
|
||||||
|
@ -363,10 +388,10 @@ class BandcampWeeklyIE(BandcampIE): # XXX: Do not subclass from concrete IE
|
||||||
_VALID_URL = r'https?://(?:www\.)?bandcamp\.com/?\?(?:.*?&)?show=(?P<id>\d+)'
|
_VALID_URL = r'https?://(?:www\.)?bandcamp\.com/?\?(?:.*?&)?show=(?P<id>\d+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://bandcamp.com/?show=224',
|
'url': 'https://bandcamp.com/?show=224',
|
||||||
'md5': 'b00df799c733cf7e0c567ed187dea0fd',
|
'md5': '61acc9a002bed93986b91168aa3ab433',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '224',
|
'id': '224',
|
||||||
'ext': 'opus',
|
'ext': 'mp3',
|
||||||
'title': 'BC Weekly April 4th 2017 - Magic Moments',
|
'title': 'BC Weekly April 4th 2017 - Magic Moments',
|
||||||
'description': 'md5:5d48150916e8e02d030623a48512c874',
|
'description': 'md5:5d48150916e8e02d030623a48512c874',
|
||||||
'duration': 5829.77,
|
'duration': 5829.77,
|
||||||
|
@ -376,7 +401,7 @@ class BandcampWeeklyIE(BandcampIE): # XXX: Do not subclass from concrete IE
|
||||||
'episode_id': '224',
|
'episode_id': '224',
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'format': 'opus-lo',
|
'format': 'mp3-128',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://bandcamp.com/?blah/blah@&show=228',
|
'url': 'https://bandcamp.com/?blah/blah@&show=228',
|
||||||
|
@ -484,7 +509,7 @@ def _yield_items(self, webpage):
|
||||||
or re.findall(r'<div[^>]+trackTitle["\'][^"\']+["\']([^"\']+)', webpage))
|
or re.findall(r'<div[^>]+trackTitle["\'][^"\']+["\']([^"\']+)', webpage))
|
||||||
|
|
||||||
yield from traverse_obj(webpage, (
|
yield from traverse_obj(webpage, (
|
||||||
{functools.partial(get_element_html_by_id, 'music-grid')}, {extract_attributes},
|
{find_element(id='music-grid', html=True)}, {extract_attributes},
|
||||||
'data-client-items', {json.loads}, ..., 'page_url', {str}))
|
'data-client-items', {json.loads}, ..., 'page_url', {str}))
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@ -493,4 +518,4 @@ def _real_extract(self, url):
|
||||||
|
|
||||||
return self.playlist_from_matches(
|
return self.playlist_from_matches(
|
||||||
self._yield_items(webpage), uploader, f'Discography of {uploader}',
|
self._yield_items(webpage), uploader, f'Discography of {uploader}',
|
||||||
getter=functools.partial(urljoin, url))
|
getter=urljoin(url))
|
||||||
|
|
|
@ -1284,9 +1284,9 @@ def parse_model(model):
|
||||||
**traverse_obj(model, {
|
**traverse_obj(model, {
|
||||||
'title': ('title', {str}),
|
'title': ('title', {str}),
|
||||||
'thumbnail': ('imageUrl', {lambda u: urljoin(url, u.replace('$recipe', 'raw'))}),
|
'thumbnail': ('imageUrl', {lambda u: urljoin(url, u.replace('$recipe', 'raw'))}),
|
||||||
'description': ('synopses', ('long', 'medium', 'short'), {str}, {lambda x: x or None}, any),
|
'description': ('synopses', ('long', 'medium', 'short'), {str}, filter, any),
|
||||||
'duration': ('versions', 0, 'duration', {int}),
|
'duration': ('versions', 0, 'duration', {int}),
|
||||||
'timestamp': ('versions', 0, 'availableFrom', {functools.partial(int_or_none, scale=1000)}),
|
'timestamp': ('versions', 0, 'availableFrom', {int_or_none(scale=1000)}),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1386,7 +1386,7 @@ def parse_media(media):
|
||||||
formats = traverse_obj(media_data, ('playlist', lambda _, v: url_or_none(v['url']), {
|
formats = traverse_obj(media_data, ('playlist', lambda _, v: url_or_none(v['url']), {
|
||||||
'url': ('url', {url_or_none}),
|
'url': ('url', {url_or_none}),
|
||||||
'ext': ('format', {str}),
|
'ext': ('format', {str}),
|
||||||
'tbr': ('bitrate', {functools.partial(int_or_none, scale=1000)}),
|
'tbr': ('bitrate', {int_or_none(scale=1000)}),
|
||||||
}))
|
}))
|
||||||
if formats:
|
if formats:
|
||||||
entry = {
|
entry = {
|
||||||
|
@ -1398,7 +1398,7 @@ def parse_media(media):
|
||||||
'title': ('title', {str}),
|
'title': ('title', {str}),
|
||||||
'thumbnail': ('imageUrl', {lambda u: urljoin(url, u.replace('$recipe', 'raw'))}),
|
'thumbnail': ('imageUrl', {lambda u: urljoin(url, u.replace('$recipe', 'raw'))}),
|
||||||
'description': ('synopses', ('long', 'medium', 'short'), {str}, any),
|
'description': ('synopses', ('long', 'medium', 'short'), {str}, any),
|
||||||
'timestamp': ('firstPublished', {functools.partial(int_or_none, scale=1000)}),
|
'timestamp': ('firstPublished', {int_or_none(scale=1000)}),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
done = True
|
done = True
|
||||||
|
@ -1428,7 +1428,7 @@ def extract_all(pattern):
|
||||||
if not entry.get('timestamp'):
|
if not entry.get('timestamp'):
|
||||||
entry['timestamp'] = traverse_obj(next_data, (
|
entry['timestamp'] = traverse_obj(next_data, (
|
||||||
..., 'contents', is_type('timestamp'), 'model',
|
..., 'contents', is_type('timestamp'), 'model',
|
||||||
'timestamp', {functools.partial(int_or_none, scale=1000)}, any))
|
'timestamp', {int_or_none(scale=1000)}, any))
|
||||||
entries.append(entry)
|
entries.append(entry)
|
||||||
return self.playlist_result(
|
return self.playlist_result(
|
||||||
entries, playlist_id, playlist_title, playlist_description)
|
entries, playlist_id, playlist_title, playlist_description)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import functools
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
@ -50,7 +49,7 @@ def _extract_base_info(data):
|
||||||
**traverse_obj(data, {
|
**traverse_obj(data, {
|
||||||
'title': 'title',
|
'title': 'title',
|
||||||
'description': 'description',
|
'description': 'description',
|
||||||
'duration': ('duration', {functools.partial(int_or_none, scale=1000)}),
|
'duration': ('duration', {int_or_none(scale=1000)}),
|
||||||
'timestamp': ('schedulingStart', {parse_iso8601}),
|
'timestamp': ('schedulingStart', {parse_iso8601}),
|
||||||
'season_number': 'seasonNumber',
|
'season_number': 'seasonNumber',
|
||||||
'episode_number': 'episodeNumber',
|
'episode_number': 'episodeNumber',
|
||||||
|
|
|
@ -109,7 +109,7 @@ def extract_formats(self, play_info):
|
||||||
|
|
||||||
fragments = traverse_obj(play_info, ('durl', lambda _, v: url_or_none(v['url']), {
|
fragments = traverse_obj(play_info, ('durl', lambda _, v: url_or_none(v['url']), {
|
||||||
'url': ('url', {url_or_none}),
|
'url': ('url', {url_or_none}),
|
||||||
'duration': ('length', {functools.partial(float_or_none, scale=1000)}),
|
'duration': ('length', {float_or_none(scale=1000)}),
|
||||||
'filesize': ('size', {int_or_none}),
|
'filesize': ('size', {int_or_none}),
|
||||||
}))
|
}))
|
||||||
if fragments:
|
if fragments:
|
||||||
|
@ -124,7 +124,7 @@ def extract_formats(self, play_info):
|
||||||
'quality': ('quality', {int_or_none}),
|
'quality': ('quality', {int_or_none}),
|
||||||
'format_id': ('quality', {str_or_none}),
|
'format_id': ('quality', {str_or_none}),
|
||||||
'format_note': ('quality', {lambda x: format_names.get(x)}),
|
'format_note': ('quality', {lambda x: format_names.get(x)}),
|
||||||
'duration': ('timelength', {functools.partial(float_or_none, scale=1000)}),
|
'duration': ('timelength', {float_or_none(scale=1000)}),
|
||||||
}),
|
}),
|
||||||
**parse_resolution(format_names.get(play_info.get('quality'))),
|
**parse_resolution(format_names.get(play_info.get('quality'))),
|
||||||
})
|
})
|
||||||
|
@ -1585,7 +1585,7 @@ def _real_extract(self, url):
|
||||||
'title': ('title', {str}),
|
'title': ('title', {str}),
|
||||||
'uploader': ('upper', 'name', {str}),
|
'uploader': ('upper', 'name', {str}),
|
||||||
'uploader_id': ('upper', 'mid', {str_or_none}),
|
'uploader_id': ('upper', 'mid', {str_or_none}),
|
||||||
'timestamp': ('ctime', {int_or_none}, {lambda x: x or None}),
|
'timestamp': ('ctime', {int_or_none}, filter),
|
||||||
'thumbnail': ('cover', {url_or_none}),
|
'thumbnail': ('cover', {url_or_none}),
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
|
|
|
@ -382,7 +382,7 @@ def _extract_videos(self, root, video_id, embed_path='embed', record_path='recor
|
||||||
'age_limit': (
|
'age_limit': (
|
||||||
'labels', ..., 'val', {lambda x: 18 if x in ('sexual', 'porn', 'graphic-media') else None}, any),
|
'labels', ..., 'val', {lambda x: 18 if x in ('sexual', 'porn', 'graphic-media') else None}, any),
|
||||||
'description': (*record_path, 'text', {str}, filter),
|
'description': (*record_path, 'text', {str}, filter),
|
||||||
'title': (*record_path, 'text', {lambda x: x.replace('\n', '')}, {truncate_string(left=50)}),
|
'title': (*record_path, 'text', {lambda x: x.replace('\n', ' ')}, {truncate_string(left=50)}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
return entries
|
return entries
|
||||||
|
|
|
@ -1,35 +1,20 @@
|
||||||
import functools
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
clean_html,
|
clean_html,
|
||||||
extract_attributes,
|
extract_attributes,
|
||||||
get_element_text_and_html_by_tag,
|
|
||||||
get_elements_by_class,
|
|
||||||
join_nonempty,
|
join_nonempty,
|
||||||
js_to_json,
|
js_to_json,
|
||||||
mimetype2ext,
|
mimetype2ext,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
url_or_none,
|
url_or_none,
|
||||||
urljoin,
|
urljoin,
|
||||||
variadic,
|
|
||||||
)
|
)
|
||||||
from ..utils.traversal import traverse_obj
|
from ..utils.traversal import (
|
||||||
|
find_element,
|
||||||
|
traverse_obj,
|
||||||
def html_get_element(tag=None, cls=None):
|
)
|
||||||
assert tag or cls, 'One of tag or class is required'
|
|
||||||
|
|
||||||
if cls:
|
|
||||||
func = functools.partial(get_elements_by_class, cls, tag=tag)
|
|
||||||
else:
|
|
||||||
func = functools.partial(get_element_text_and_html_by_tag, tag)
|
|
||||||
|
|
||||||
def html_get_element_wrapper(html):
|
|
||||||
return variadic(func(html))[0]
|
|
||||||
|
|
||||||
return html_get_element_wrapper
|
|
||||||
|
|
||||||
|
|
||||||
class BpbIE(InfoExtractor):
|
class BpbIE(InfoExtractor):
|
||||||
|
@ -41,12 +26,12 @@ class BpbIE(InfoExtractor):
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '297',
|
'id': '297',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'creator': 'Kooperative Berlin',
|
'creators': ['Kooperative Berlin'],
|
||||||
'description': 'md5:f4f75885ba009d3e2b156247a8941ce6',
|
'description': r're:Joachim Gauck, .*\n\nKamera: .*',
|
||||||
'release_date': '20160115',
|
'release_date': '20150716',
|
||||||
'series': 'Interview auf dem Geschichtsforum 1989 | 2009',
|
'series': 'Interview auf dem Geschichtsforum 1989 | 2009',
|
||||||
'tags': ['Friedliche Revolution', 'Erinnerungskultur', 'Vergangenheitspolitik', 'DDR 1949 - 1990', 'Freiheitsrecht', 'BStU', 'Deutschland'],
|
'tags': [],
|
||||||
'thumbnail': 'https://www.bpb.de/cache/images/7/297_teaser_16x9_1240.jpg?8839D',
|
'thumbnail': r're:https?://www\.bpb\.de/cache/images/7/297_teaser_16x9_1240\.jpg.*',
|
||||||
'title': 'Joachim Gauck zu 1989 und die Erinnerung an die DDR',
|
'title': 'Joachim Gauck zu 1989 und die Erinnerung an die DDR',
|
||||||
'uploader': 'Bundeszentrale für politische Bildung',
|
'uploader': 'Bundeszentrale für politische Bildung',
|
||||||
},
|
},
|
||||||
|
@ -55,11 +40,12 @@ class BpbIE(InfoExtractor):
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '522184',
|
'id': '522184',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'creator': 'Institute for Strategic Dialogue Germany gGmbH (ISD)',
|
'creators': ['Institute for Strategic Dialogue Germany gGmbH (ISD)'],
|
||||||
'description': 'md5:f83c795ff8f825a69456a9e51fc15903',
|
'description': 'md5:f83c795ff8f825a69456a9e51fc15903',
|
||||||
'release_date': '20230621',
|
'release_date': '20230621',
|
||||||
'tags': ['Desinformation', 'Ukraine', 'Russland', 'Geflüchtete'],
|
'series': 'Narrative über den Krieg Russlands gegen die Ukraine (NUK)',
|
||||||
'thumbnail': 'https://www.bpb.de/cache/images/4/522184_teaser_16x9_1240.png?EABFB',
|
'tags': [],
|
||||||
|
'thumbnail': r're:https://www\.bpb\.de/cache/images/4/522184_teaser_16x9_1240\.png.*',
|
||||||
'title': 'md5:9b01ccdbf58dbf9e5c9f6e771a803b1c',
|
'title': 'md5:9b01ccdbf58dbf9e5c9f6e771a803b1c',
|
||||||
'uploader': 'Bundeszentrale für politische Bildung',
|
'uploader': 'Bundeszentrale für politische Bildung',
|
||||||
},
|
},
|
||||||
|
@ -68,11 +54,12 @@ class BpbIE(InfoExtractor):
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '518789',
|
'id': '518789',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'creator': 'Institute for Strategic Dialogue Germany gGmbH (ISD)',
|
'creators': ['Institute for Strategic Dialogue Germany gGmbH (ISD)'],
|
||||||
'description': 'md5:85228aed433e84ff0ff9bc582abd4ea8',
|
'description': 'md5:85228aed433e84ff0ff9bc582abd4ea8',
|
||||||
'release_date': '20230302',
|
'release_date': '20230302',
|
||||||
'tags': ['Desinformation', 'Ukraine', 'Russland', 'Geflüchtete'],
|
'series': 'Narrative über den Krieg Russlands gegen die Ukraine (NUK)',
|
||||||
'thumbnail': 'https://www.bpb.de/cache/images/9/518789_teaser_16x9_1240.jpeg?56D0D',
|
'tags': [],
|
||||||
|
'thumbnail': r're:https://www\.bpb\.de/cache/images/9/518789_teaser_16x9_1240\.jpeg.*',
|
||||||
'title': 'md5:3e956f264bb501f6383f10495a401da4',
|
'title': 'md5:3e956f264bb501f6383f10495a401da4',
|
||||||
'uploader': 'Bundeszentrale für politische Bildung',
|
'uploader': 'Bundeszentrale für politische Bildung',
|
||||||
},
|
},
|
||||||
|
@ -84,12 +71,12 @@ class BpbIE(InfoExtractor):
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '315813',
|
'id': '315813',
|
||||||
'ext': 'mp3',
|
'ext': 'mp3',
|
||||||
'creator': 'Axel Schröder',
|
'creators': ['Axel Schröder'],
|
||||||
'description': 'md5:eda9d1af34e5912efef5baf54fba4427',
|
'description': 'md5:eda9d1af34e5912efef5baf54fba4427',
|
||||||
'release_date': '20200921',
|
'release_date': '20200921',
|
||||||
'series': 'Auf Endlagersuche. Der deutsche Weg zu einem sicheren Atommülllager',
|
'series': 'Auf Endlagersuche. Der deutsche Weg zu einem sicheren Atommülllager',
|
||||||
'tags': ['Atomenergie', 'Endlager', 'hoch-radioaktiver Abfall', 'Endlagersuche', 'Atommüll', 'Atomendlager', 'Gorleben', 'Deutschland'],
|
'tags': ['Atomenergie', 'Endlager', 'hoch-radioaktiver Abfall', 'Endlagersuche', 'Atommüll', 'Atomendlager', 'Gorleben', 'Deutschland'],
|
||||||
'thumbnail': 'https://www.bpb.de/cache/images/3/315813_teaser_16x9_1240.png?92A94',
|
'thumbnail': r're:https://www\.bpb\.de/cache/images/3/315813_teaser_16x9_1240\.png.*',
|
||||||
'title': 'Folge 1: Eine Einführung',
|
'title': 'Folge 1: Eine Einführung',
|
||||||
'uploader': 'Bundeszentrale für politische Bildung',
|
'uploader': 'Bundeszentrale für politische Bildung',
|
||||||
},
|
},
|
||||||
|
@ -98,12 +85,12 @@ class BpbIE(InfoExtractor):
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '517806',
|
'id': '517806',
|
||||||
'ext': 'mp3',
|
'ext': 'mp3',
|
||||||
'creator': 'Bundeszentrale für politische Bildung',
|
'creators': ['Bundeszentrale für politische Bildung'],
|
||||||
'description': 'md5:594689600e919912aade0b2871cc3fed',
|
'description': 'md5:594689600e919912aade0b2871cc3fed',
|
||||||
'release_date': '20230127',
|
'release_date': '20230127',
|
||||||
'series': 'Vorträge des Fachtags "Modernisierer. Grenzgänger. Anstifter. Sechs Jahrzehnte \'Neue Rechte\'"',
|
'series': 'Vorträge des Fachtags "Modernisierer. Grenzgänger. Anstifter. Sechs Jahrzehnte \'Neue Rechte\'"',
|
||||||
'tags': ['Rechtsextremismus', 'Konservatismus', 'Konservativismus', 'neue Rechte', 'Rechtspopulismus', 'Schnellroda', 'Deutschland'],
|
'tags': ['Rechtsextremismus', 'Konservatismus', 'Konservativismus', 'neue Rechte', 'Rechtspopulismus', 'Schnellroda', 'Deutschland'],
|
||||||
'thumbnail': 'https://www.bpb.de/cache/images/6/517806_teaser_16x9_1240.png?7A7A0',
|
'thumbnail': r're:https://www\.bpb\.de/cache/images/6/517806_teaser_16x9_1240\.png.*',
|
||||||
'title': 'Die Weltanschauung der "Neuen Rechten"',
|
'title': 'Die Weltanschauung der "Neuen Rechten"',
|
||||||
'uploader': 'Bundeszentrale für politische Bildung',
|
'uploader': 'Bundeszentrale für politische Bildung',
|
||||||
},
|
},
|
||||||
|
@ -147,7 +134,7 @@ def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
title_result = traverse_obj(webpage, ({html_get_element(cls='opening-header__title')}, {self._TITLE_RE.match}))
|
title_result = traverse_obj(webpage, ({find_element(cls='opening-header__title')}, {self._TITLE_RE.match}))
|
||||||
json_lds = list(self._yield_json_ld(webpage, video_id, fatal=False))
|
json_lds = list(self._yield_json_ld(webpage, video_id, fatal=False))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -156,15 +143,15 @@ def _real_extract(self, url):
|
||||||
# This metadata could be interpreted otherwise, but it fits "series" the most
|
# This metadata could be interpreted otherwise, but it fits "series" the most
|
||||||
'series': traverse_obj(title_result, ('series', {str.strip})) or None,
|
'series': traverse_obj(title_result, ('series', {str.strip})) or None,
|
||||||
'description': join_nonempty(*traverse_obj(webpage, [(
|
'description': join_nonempty(*traverse_obj(webpage, [(
|
||||||
{html_get_element(cls='opening-intro')},
|
{find_element(cls='opening-intro')},
|
||||||
[{html_get_element(tag='bpb-accordion-item')}, {html_get_element(cls='text-content')}],
|
[{find_element(tag='bpb-accordion-item')}, {find_element(cls='text-content')}],
|
||||||
), {clean_html}]), delim='\n\n') or None,
|
), {clean_html}]), delim='\n\n') or None,
|
||||||
'creator': self._html_search_meta('author', webpage),
|
'creators': traverse_obj(self._html_search_meta('author', webpage), all),
|
||||||
'uploader': self._html_search_meta('publisher', webpage),
|
'uploader': self._html_search_meta('publisher', webpage),
|
||||||
'release_date': unified_strdate(self._html_search_meta('date', webpage)),
|
'release_date': unified_strdate(self._html_search_meta('date', webpage)),
|
||||||
'tags': traverse_obj(json_lds, (..., 'keywords', {lambda x: x.split(',')}, ...)),
|
'tags': traverse_obj(json_lds, (..., 'keywords', {lambda x: x.split(',')}, ...)),
|
||||||
**traverse_obj(self._parse_vue_attributes('bpb-player', webpage, video_id), {
|
**traverse_obj(self._parse_vue_attributes('bpb-player', webpage, video_id), {
|
||||||
'formats': (':sources', ..., {self._process_source}),
|
'formats': (':sources', ..., {self._process_source}),
|
||||||
'thumbnail': ('poster', {lambda x: urljoin(url, x)}),
|
'thumbnail': ('poster', {urljoin(url)}),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,10 +145,9 @@ def _real_extract(self, url):
|
||||||
tp_metadata = self._download_json(
|
tp_metadata = self._download_json(
|
||||||
update_url_query(tp_url, {'format': 'preview'}), video_id, fatal=False)
|
update_url_query(tp_url, {'format': 'preview'}), video_id, fatal=False)
|
||||||
|
|
||||||
seconds_or_none = lambda x: float_or_none(x, 1000)
|
|
||||||
chapters = traverse_obj(tp_metadata, ('chapters', ..., {
|
chapters = traverse_obj(tp_metadata, ('chapters', ..., {
|
||||||
'start_time': ('startTime', {seconds_or_none}),
|
'start_time': ('startTime', {float_or_none(scale=1000)}),
|
||||||
'end_time': ('endTime', {seconds_or_none}),
|
'end_time': ('endTime', {float_or_none(scale=1000)}),
|
||||||
}))
|
}))
|
||||||
# prune pointless single chapters that span the entire duration from short videos
|
# prune pointless single chapters that span the entire duration from short videos
|
||||||
if len(chapters) == 1 and not traverse_obj(chapters, (0, 'end_time')):
|
if len(chapters) == 1 and not traverse_obj(chapters, (0, 'end_time')):
|
||||||
|
@ -168,8 +167,8 @@ def _real_extract(self, url):
|
||||||
**merge_dicts(traverse_obj(tp_metadata, {
|
**merge_dicts(traverse_obj(tp_metadata, {
|
||||||
'title': 'title',
|
'title': 'title',
|
||||||
'description': 'description',
|
'description': 'description',
|
||||||
'duration': ('duration', {seconds_or_none}),
|
'duration': ('duration', {float_or_none(scale=1000)}),
|
||||||
'timestamp': ('pubDate', {seconds_or_none}),
|
'timestamp': ('pubDate', {float_or_none(scale=1000)}),
|
||||||
'season_number': (('pl1$seasonNumber', 'nbcu$seasonNumber'), {int_or_none}),
|
'season_number': (('pl1$seasonNumber', 'nbcu$seasonNumber'), {int_or_none}),
|
||||||
'episode_number': (('pl1$episodeNumber', 'nbcu$episodeNumber'), {int_or_none}),
|
'episode_number': (('pl1$episodeNumber', 'nbcu$episodeNumber'), {int_or_none}),
|
||||||
'series': (('pl1$show', 'nbcu$show'), (None, ...), {str}),
|
'series': (('pl1$show', 'nbcu$show'), (None, ...), {str}),
|
||||||
|
|
|
@ -8,11 +8,13 @@
|
||||||
bug_reports_message,
|
bug_reports_message,
|
||||||
clean_html,
|
clean_html,
|
||||||
format_field,
|
format_field,
|
||||||
get_element_text_and_html_by_tag,
|
|
||||||
int_or_none,
|
int_or_none,
|
||||||
url_or_none,
|
url_or_none,
|
||||||
)
|
)
|
||||||
from ..utils.traversal import traverse_obj
|
from ..utils.traversal import (
|
||||||
|
find_element,
|
||||||
|
traverse_obj,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BundestagIE(InfoExtractor):
|
class BundestagIE(InfoExtractor):
|
||||||
|
@ -115,9 +117,8 @@ def _real_extract(self, url):
|
||||||
note='Downloading metadata overlay', fatal=False,
|
note='Downloading metadata overlay', fatal=False,
|
||||||
), {
|
), {
|
||||||
'title': (
|
'title': (
|
||||||
{functools.partial(get_element_text_and_html_by_tag, 'h3')}, 0,
|
{find_element(tag='h3')}, {functools.partial(re.sub, r'<span[^>]*>[^<]+</span>', '')}, {clean_html}),
|
||||||
{functools.partial(re.sub, r'<span[^>]*>[^<]+</span>', '')}, {clean_html}),
|
'description': ({find_element(tag='p')}, {clean_html}),
|
||||||
'description': ({functools.partial(get_element_text_and_html_by_tag, 'p')}, 0, {clean_html}),
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -53,7 +53,7 @@ def _real_extract(self, url):
|
||||||
'like_count': ('like_count', {int_or_none}),
|
'like_count': ('like_count', {int_or_none}),
|
||||||
'view_count': ('view_count', {int_or_none}),
|
'view_count': ('view_count', {int_or_none}),
|
||||||
'comment_count': ('comment_count', {int_or_none}),
|
'comment_count': ('comment_count', {int_or_none}),
|
||||||
'tags': ('tags', ..., {str}, {lambda x: x or None}),
|
'tags': ('tags', ..., {str}, filter),
|
||||||
'uploader': ('user', 'name', {str}),
|
'uploader': ('user', 'name', {str}),
|
||||||
'uploader_id': (((None, 'user'), 'username'), {str}, any),
|
'uploader_id': (((None, 'user'), 'username'), {str}, any),
|
||||||
'is_live': ('is_live', {bool}),
|
'is_live': ('is_live', {bool}),
|
||||||
|
@ -62,7 +62,7 @@ def _real_extract(self, url):
|
||||||
'title': ('broadcast_title', {str}),
|
'title': ('broadcast_title', {str}),
|
||||||
'duration': ('content_duration', {int_or_none}),
|
'duration': ('content_duration', {int_or_none}),
|
||||||
'timestamp': ('broadcast_start_time', {parse_iso8601}),
|
'timestamp': ('broadcast_start_time', {parse_iso8601}),
|
||||||
'thumbnail': ('preview_image_path', {lambda x: urljoin(url, x)}),
|
'thumbnail': ('preview_image_path', {urljoin(url)}),
|
||||||
}),
|
}),
|
||||||
'age_limit': {
|
'age_limit': {
|
||||||
# assume Apple Store ratings: https://en.wikipedia.org/wiki/Mobile_software_content_rating_system
|
# assume Apple Store ratings: https://en.wikipedia.org/wiki/Mobile_software_content_rating_system
|
||||||
|
|
|
@ -453,8 +453,8 @@ def _real_extract(self, url):
|
||||||
|
|
||||||
chapters = traverse_obj(data, (
|
chapters = traverse_obj(data, (
|
||||||
'media', 'chapters', lambda _, v: float(v['startTime']) is not None, {
|
'media', 'chapters', lambda _, v: float(v['startTime']) is not None, {
|
||||||
'start_time': ('startTime', {functools.partial(float_or_none, scale=1000)}),
|
'start_time': ('startTime', {float_or_none(scale=1000)}),
|
||||||
'end_time': ('endTime', {functools.partial(float_or_none, scale=1000)}),
|
'end_time': ('endTime', {float_or_none(scale=1000)}),
|
||||||
'title': ('name', {str}),
|
'title': ('name', {str}),
|
||||||
}))
|
}))
|
||||||
# Filter out pointless single chapters with start_time==0 and no end_time
|
# Filter out pointless single chapters with start_time==0 and no end_time
|
||||||
|
@ -465,8 +465,8 @@ def _real_extract(self, url):
|
||||||
**traverse_obj(data, {
|
**traverse_obj(data, {
|
||||||
'title': ('title', {str}),
|
'title': ('title', {str}),
|
||||||
'description': ('description', {str.strip}),
|
'description': ('description', {str.strip}),
|
||||||
'thumbnail': ('image', 'url', {url_or_none}, {functools.partial(update_url, query=None)}),
|
'thumbnail': ('image', 'url', {url_or_none}, {update_url(query=None)}),
|
||||||
'timestamp': ('publishedAt', {functools.partial(float_or_none, scale=1000)}),
|
'timestamp': ('publishedAt', {float_or_none(scale=1000)}),
|
||||||
'media_type': ('media', 'clipType', {str}),
|
'media_type': ('media', 'clipType', {str}),
|
||||||
'series': ('showName', {str}),
|
'series': ('showName', {str}),
|
||||||
'season_number': ('media', 'season', {int_or_none}),
|
'season_number': ('media', 'season', {int_or_none}),
|
||||||
|
|
|
@ -96,7 +96,7 @@ def get_subtitles(subs_url):
|
||||||
**traverse_obj(item, {
|
**traverse_obj(item, {
|
||||||
'title': (None, ('fulltitle', 'title')),
|
'title': (None, ('fulltitle', 'title')),
|
||||||
'description': 'dek',
|
'description': 'dek',
|
||||||
'timestamp': ('timestamp', {lambda x: float_or_none(x, 1000)}),
|
'timestamp': ('timestamp', {float_or_none(scale=1000)}),
|
||||||
'duration': ('duration', {float_or_none}),
|
'duration': ('duration', {float_or_none}),
|
||||||
'subtitles': ('captions', {get_subtitles}),
|
'subtitles': ('captions', {get_subtitles}),
|
||||||
'thumbnail': ('images', ('hd', 'sd'), {url_or_none}),
|
'thumbnail': ('images', ('hd', 'sd'), {url_or_none}),
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import functools
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
UserNotLive,
|
UserNotLive,
|
||||||
|
@ -77,7 +75,7 @@ def _real_extract(self, url):
|
||||||
'thumbnails': thumbnails,
|
'thumbnails': thumbnails,
|
||||||
**traverse_obj(live_detail, {
|
**traverse_obj(live_detail, {
|
||||||
'title': ('liveTitle', {str}),
|
'title': ('liveTitle', {str}),
|
||||||
'timestamp': ('openDate', {functools.partial(parse_iso8601, delimiter=' ')}),
|
'timestamp': ('openDate', {parse_iso8601(delimiter=' ')}),
|
||||||
'concurrent_view_count': ('concurrentUserCount', {int_or_none}),
|
'concurrent_view_count': ('concurrentUserCount', {int_or_none}),
|
||||||
'view_count': ('accumulateCount', {int_or_none}),
|
'view_count': ('accumulateCount', {int_or_none}),
|
||||||
'channel': ('channel', 'channelName', {str}),
|
'channel': ('channel', 'channelName', {str}),
|
||||||
|
@ -176,7 +174,7 @@ def _real_extract(self, url):
|
||||||
**traverse_obj(video_meta, {
|
**traverse_obj(video_meta, {
|
||||||
'title': ('videoTitle', {str}),
|
'title': ('videoTitle', {str}),
|
||||||
'thumbnail': ('thumbnailImageUrl', {url_or_none}),
|
'thumbnail': ('thumbnailImageUrl', {url_or_none}),
|
||||||
'timestamp': ('publishDateAt', {functools.partial(float_or_none, scale=1000)}),
|
'timestamp': ('publishDateAt', {float_or_none(scale=1000)}),
|
||||||
'view_count': ('readCount', {int_or_none}),
|
'view_count': ('readCount', {int_or_none}),
|
||||||
'duration': ('duration', {int_or_none}),
|
'duration': ('duration', {int_or_none}),
|
||||||
'channel': ('channel', 'channelName', {str}),
|
'channel': ('channel', 'channelName', {str}),
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
filter_dict,
|
filter_dict,
|
||||||
|
float_or_none,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_age_limit,
|
parse_age_limit,
|
||||||
smuggle_url,
|
smuggle_url,
|
||||||
|
@ -85,7 +86,7 @@ def _real_extract(self, url):
|
||||||
'title': 'title',
|
'title': 'title',
|
||||||
'id': ('details', 'item_id'),
|
'id': ('details', 'item_id'),
|
||||||
'description': ('details', 'description'),
|
'description': ('details', 'description'),
|
||||||
'duration': ('duration', {lambda x: x / 1000}),
|
'duration': ('duration', {float_or_none(scale=1000)}),
|
||||||
'cast': ('details', 'cast', {lambda x: x.split(', ')}),
|
'cast': ('details', 'cast', {lambda x: x.split(', ')}),
|
||||||
'modified_timestamp': ('details', 'updated_by', 0, 'update_time', 'time', {int_or_none}),
|
'modified_timestamp': ('details', 'updated_by', 0, 'update_time', 'time', {int_or_none}),
|
||||||
'season_number': ('details', 'season', {int_or_none}),
|
'season_number': ('details', 'season', {int_or_none}),
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import functools
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
@ -199,7 +198,7 @@ def _real_extract(self, url):
|
||||||
'timestamp': ('data-publish-date', {parse_iso8601}),
|
'timestamp': ('data-publish-date', {parse_iso8601}),
|
||||||
'thumbnail': (
|
'thumbnail': (
|
||||||
'data-poster-image-override', {json.loads}, 'big', 'uri', {url_or_none},
|
'data-poster-image-override', {json.loads}, 'big', 'uri', {url_or_none},
|
||||||
{functools.partial(update_url, query='c=original')}),
|
{update_url(query='c=original')}),
|
||||||
'display_id': 'data-video-slug',
|
'display_id': 'data-video-slug',
|
||||||
}),
|
}),
|
||||||
**traverse_obj(video_data, {
|
**traverse_obj(video_data, {
|
||||||
|
|
|
@ -1578,7 +1578,9 @@ def _yield_json_ld(self, html, video_id, *, fatal=True, default=NO_DEFAULT):
|
||||||
if default is not NO_DEFAULT:
|
if default is not NO_DEFAULT:
|
||||||
fatal = False
|
fatal = False
|
||||||
for mobj in re.finditer(JSON_LD_RE, html):
|
for mobj in re.finditer(JSON_LD_RE, html):
|
||||||
json_ld_item = self._parse_json(mobj.group('json_ld'), video_id, fatal=fatal)
|
json_ld_item = self._parse_json(
|
||||||
|
mobj.group('json_ld'), video_id, fatal=fatal,
|
||||||
|
errnote=False if default is not NO_DEFAULT else None)
|
||||||
for json_ld in variadic(json_ld_item):
|
for json_ld in variadic(json_ld_item):
|
||||||
if isinstance(json_ld, dict):
|
if isinstance(json_ld, dict):
|
||||||
yield json_ld
|
yield json_ld
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
strip_or_none,
|
strip_or_none,
|
||||||
try_get,
|
try_get,
|
||||||
|
urljoin,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -112,8 +113,7 @@ def _extract_series(self, url, webpage):
|
||||||
m_paths = re.finditer(
|
m_paths = re.finditer(
|
||||||
r'(?s)<p class="cne-thumb-title">.*?<a href="(/watch/.+?)["\?]', webpage)
|
r'(?s)<p class="cne-thumb-title">.*?<a href="(/watch/.+?)["\?]', webpage)
|
||||||
paths = orderedSet(m.group(1) for m in m_paths)
|
paths = orderedSet(m.group(1) for m in m_paths)
|
||||||
build_url = lambda path: urllib.parse.urljoin(base_url, path)
|
entries = [self.url_result(urljoin(base_url, path), 'CondeNast') for path in paths]
|
||||||
entries = [self.url_result(build_url(path), 'CondeNast') for path in paths]
|
|
||||||
return self.playlist_result(entries, playlist_title=title)
|
return self.playlist_result(entries, playlist_title=title)
|
||||||
|
|
||||||
def _extract_video_params(self, webpage, display_id):
|
def _extract_video_params(self, webpage, display_id):
|
||||||
|
|
|
@ -456,7 +456,7 @@ def _transform_episode_response(data):
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
**traverse_obj(metadata, {
|
**traverse_obj(metadata, {
|
||||||
'duration': ('duration_ms', {lambda x: float_or_none(x, 1000)}),
|
'duration': ('duration_ms', {float_or_none(scale=1000)}),
|
||||||
'timestamp': ('upload_date', {parse_iso8601}),
|
'timestamp': ('upload_date', {parse_iso8601}),
|
||||||
'series': ('series_title', {str}),
|
'series': ('series_title', {str}),
|
||||||
'series_id': ('series_id', {str}),
|
'series_id': ('series_id', {str}),
|
||||||
|
@ -484,7 +484,7 @@ def _transform_movie_response(data):
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
**traverse_obj(metadata, {
|
**traverse_obj(metadata, {
|
||||||
'duration': ('duration_ms', {lambda x: float_or_none(x, 1000)}),
|
'duration': ('duration_ms', {float_or_none(scale=1000)}),
|
||||||
'age_limit': ('maturity_ratings', -1, {parse_age_limit}),
|
'age_limit': ('maturity_ratings', -1, {parse_age_limit}),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ def _extract_episode_info(self, metadata, episode_slug, series_slug):
|
||||||
'id': ('content_id', {str}),
|
'id': ('content_id', {str}),
|
||||||
'title': ('display_title', {str}),
|
'title': ('display_title', {str}),
|
||||||
'episode': ('title', {str}),
|
'episode': ('title', {str}),
|
||||||
'series': ('show_name', {str}, {lambda x: x or None}),
|
'series': ('show_name', {str}, filter),
|
||||||
'series_id': ('catalog_id', {str}),
|
'series_id': ('catalog_id', {str}),
|
||||||
'duration': ('duration', {int_or_none}),
|
'duration': ('duration', {int_or_none}),
|
||||||
'release_timestamp': ('release_date_uts', {int_or_none}),
|
'release_timestamp': ('release_date_uts', {int_or_none}),
|
||||||
|
|
|
@ -207,7 +207,7 @@ def _real_extract(self, url):
|
||||||
**traverse_obj(data, {
|
**traverse_obj(data, {
|
||||||
'title': ('heading', {str}),
|
'title': ('heading', {str}),
|
||||||
'alt_title': ('subHeading', {str}),
|
'alt_title': ('subHeading', {str}),
|
||||||
'description': (('lead', 'body'), {clean_html}, {lambda x: x or None}),
|
'description': (('lead', 'body'), {clean_html}, filter),
|
||||||
'timestamp': ('created', {int_or_none}),
|
'timestamp': ('created', {int_or_none}),
|
||||||
'modified_timestamp': ('updated', {int_or_none}),
|
'modified_timestamp': ('updated', {int_or_none}),
|
||||||
'release_timestamp': (('scheduleStart', 'publicStart'), {int_or_none}),
|
'release_timestamp': (('scheduleStart', 'publicStart'), {int_or_none}),
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import functools
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
@ -63,7 +62,7 @@ def _real_extract(self, url):
|
||||||
'url': ('podcast_raw_url', {url_or_none}),
|
'url': ('podcast_raw_url', {url_or_none}),
|
||||||
'thumbnail': ('image', {url_or_none}),
|
'thumbnail': ('image', {url_or_none}),
|
||||||
'timestamp': ('timestamp', {int_or_none}),
|
'timestamp': ('timestamp', {int_or_none}),
|
||||||
'duration': ('milliseconds', {functools.partial(float_or_none, scale=1000)}),
|
'duration': ('milliseconds', {float_or_none(scale=1000)}),
|
||||||
'availability': ('free', {lambda v: 'public' if v else 'subscriber_only'}),
|
'availability': ('free', {lambda v: 'public' if v else 'subscriber_only'}),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -326,11 +326,11 @@ def _real_extract(self, url):
|
||||||
# fallback metadata
|
# fallback metadata
|
||||||
'title': ('name', {str}),
|
'title': ('name', {str}),
|
||||||
'description': ('fullSynopsis', {str}),
|
'description': ('fullSynopsis', {str}),
|
||||||
'series': ('show', 'name', {str}, {lambda x: x or None}),
|
'series': ('show', 'name', {str}, filter),
|
||||||
'season': ('tournamentName', {str}, {lambda x: x if x != 'Season 0' else None}),
|
'season': ('tournamentName', {str}, {lambda x: x if x != 'Season 0' else None}),
|
||||||
'season_number': ('episode', 'season', {int_or_none}, {lambda x: x or None}),
|
'season_number': ('episode', 'season', {int_or_none}, filter),
|
||||||
'episode': ('fullTitle', {str}),
|
'episode': ('fullTitle', {str}),
|
||||||
'episode_number': ('episode', 'episodeNo', {int_or_none}, {lambda x: x or None}),
|
'episode_number': ('episode', 'episodeNo', {int_or_none}, filter),
|
||||||
'age_limit': ('ageNemonic', {parse_age_limit}),
|
'age_limit': ('ageNemonic', {parse_age_limit}),
|
||||||
'duration': ('totalDuration', {float_or_none}),
|
'duration': ('totalDuration', {float_or_none}),
|
||||||
'thumbnail': ('images', {url_or_none}),
|
'thumbnail': ('images', {url_or_none}),
|
||||||
|
@ -338,10 +338,10 @@ def _real_extract(self, url):
|
||||||
**traverse_obj(metadata, ('result', 0, {
|
**traverse_obj(metadata, ('result', 0, {
|
||||||
'title': ('fullTitle', {str}),
|
'title': ('fullTitle', {str}),
|
||||||
'description': ('fullSynopsis', {str}),
|
'description': ('fullSynopsis', {str}),
|
||||||
'series': ('showName', {str}, {lambda x: x or None}),
|
'series': ('showName', {str}, filter),
|
||||||
'season': ('seasonName', {str}, {lambda x: x or None}),
|
'season': ('seasonName', {str}, filter),
|
||||||
'season_number': ('season', {int_or_none}),
|
'season_number': ('season', {int_or_none}),
|
||||||
'season_id': ('seasonId', {str}, {lambda x: x or None}),
|
'season_id': ('seasonId', {str}, filter),
|
||||||
'episode': ('fullTitle', {str}),
|
'episode': ('fullTitle', {str}),
|
||||||
'episode_number': ('episode', {int_or_none}),
|
'episode_number': ('episode', {int_or_none}),
|
||||||
'timestamp': ('uploadTime', {int_or_none}),
|
'timestamp': ('uploadTime', {int_or_none}),
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import functools
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..networking import HEADRequest
|
from ..networking import HEADRequest
|
||||||
|
@ -137,7 +136,7 @@ def _real_extract(self, url):
|
||||||
'uploader': ('livestream', 'channel', 'user', 'username', {str}),
|
'uploader': ('livestream', 'channel', 'user', 'username', {str}),
|
||||||
'uploader_id': ('livestream', 'channel', 'user_id', {int}, {str_or_none}),
|
'uploader_id': ('livestream', 'channel', 'user_id', {int}, {str_or_none}),
|
||||||
'timestamp': ('created_at', {parse_iso8601}),
|
'timestamp': ('created_at', {parse_iso8601}),
|
||||||
'duration': ('livestream', 'duration', {functools.partial(float_or_none, scale=1000)}),
|
'duration': ('livestream', 'duration', {float_or_none(scale=1000)}),
|
||||||
'thumbnail': ('livestream', 'thumbnail', {url_or_none}),
|
'thumbnail': ('livestream', 'thumbnail', {url_or_none}),
|
||||||
'categories': ('livestream', 'categories', ..., 'name', {str}),
|
'categories': ('livestream', 'categories', ..., 'name', {str}),
|
||||||
'view_count': ('views', {int_or_none}),
|
'view_count': ('views', {int_or_none}),
|
||||||
|
|
|
@ -119,7 +119,7 @@ def _extract_formats(self, media_info, video_id):
|
||||||
'width': ('frameWidth', {int_or_none}),
|
'width': ('frameWidth', {int_or_none}),
|
||||||
'height': ('frameHeight', {int_or_none}),
|
'height': ('frameHeight', {int_or_none}),
|
||||||
# NB: filesize is 0 if unknown, bitrate is -1 if unknown
|
# NB: filesize is 0 if unknown, bitrate is -1 if unknown
|
||||||
'filesize': ('fileSize', {int_or_none}, {lambda x: x or None}),
|
'filesize': ('fileSize', {int_or_none}, filter),
|
||||||
'abr': ('bitrateAudio', {int_or_none}, {lambda x: None if x == -1 else x}),
|
'abr': ('bitrateAudio', {int_or_none}, {lambda x: None if x == -1 else x}),
|
||||||
'vbr': ('bitrateVideo', {int_or_none}, {lambda x: None if x == -1 else x}),
|
'vbr': ('bitrateVideo', {int_or_none}, {lambda x: None if x == -1 else x}),
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -32,7 +32,7 @@ def _parse_episode(self, episode):
|
||||||
VimeoIE, url_transparent=True,
|
VimeoIE, url_transparent=True,
|
||||||
**traverse_obj(episode, {
|
**traverse_obj(episode, {
|
||||||
'id': ('id', {int}, {str_or_none}),
|
'id': ('id', {int}, {str_or_none}),
|
||||||
'webpage_url': ('path', {lambda x: urljoin('https://laracasts.com', x)}),
|
'webpage_url': ('path', {urljoin('https://laracasts.com')}),
|
||||||
'title': ('title', {clean_html}),
|
'title': ('title', {clean_html}),
|
||||||
'season_number': ('chapter', {int_or_none}),
|
'season_number': ('chapter', {int_or_none}),
|
||||||
'episode_number': ('position', {int_or_none}),
|
'episode_number': ('position', {int_or_none}),
|
||||||
|
@ -104,7 +104,7 @@ def _real_extract(self, url):
|
||||||
'description': ('body', {clean_html}),
|
'description': ('body', {clean_html}),
|
||||||
'thumbnail': (('large_thumbnail', 'thumbnail'), {url_or_none}, any),
|
'thumbnail': (('large_thumbnail', 'thumbnail'), {url_or_none}, any),
|
||||||
'duration': ('runTime', {parse_duration}),
|
'duration': ('runTime', {parse_duration}),
|
||||||
'categories': ('taxonomy', 'name', {str}, {lambda x: x and [x]}),
|
'categories': ('taxonomy', 'name', {str}, all, filter),
|
||||||
'tags': ('topics', ..., 'name', {str}),
|
'tags': ('topics', ..., 'name', {str}),
|
||||||
'modified_date': ('lastUpdated', {unified_strdate}),
|
'modified_date': ('lastUpdated', {unified_strdate}),
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -66,7 +66,7 @@ def _parse_stream(self, stream, url):
|
||||||
'license': ('value', 'license', {str}),
|
'license': ('value', 'license', {str}),
|
||||||
'timestamp': ('timestamp', {int_or_none}),
|
'timestamp': ('timestamp', {int_or_none}),
|
||||||
'release_timestamp': ('value', 'release_time', {int_or_none}),
|
'release_timestamp': ('value', 'release_time', {int_or_none}),
|
||||||
'tags': ('value', 'tags', ..., {lambda x: x or None}),
|
'tags': ('value', 'tags', ..., filter),
|
||||||
'duration': ('value', stream_type, 'duration', {int_or_none}),
|
'duration': ('value', stream_type, 'duration', {int_or_none}),
|
||||||
'channel': ('signing_channel', 'value', 'title', {str}),
|
'channel': ('signing_channel', 'value', 'title', {str}),
|
||||||
'channel_id': ('signing_channel', 'claim_id', {str}),
|
'channel_id': ('signing_channel', 'claim_id', {str}),
|
||||||
|
|
|
@ -6,13 +6,11 @@
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
clean_html,
|
clean_html,
|
||||||
extract_attributes,
|
extract_attributes,
|
||||||
get_element_by_class,
|
|
||||||
get_element_html_by_id,
|
|
||||||
join_nonempty,
|
join_nonempty,
|
||||||
parse_duration,
|
parse_duration,
|
||||||
unified_timestamp,
|
unified_timestamp,
|
||||||
)
|
)
|
||||||
from ..utils.traversal import traverse_obj
|
from ..utils.traversal import find_element, traverse_obj
|
||||||
|
|
||||||
|
|
||||||
class LearningOnScreenIE(InfoExtractor):
|
class LearningOnScreenIE(InfoExtractor):
|
||||||
|
@ -32,28 +30,24 @@ class LearningOnScreenIE(InfoExtractor):
|
||||||
|
|
||||||
def _real_initialize(self):
|
def _real_initialize(self):
|
||||||
if not self._get_cookies('https://learningonscreen.ac.uk/').get('PHPSESSID-BOB-LIVE'):
|
if not self._get_cookies('https://learningonscreen.ac.uk/').get('PHPSESSID-BOB-LIVE'):
|
||||||
self.raise_login_required(
|
self.raise_login_required(method='session_cookies')
|
||||||
'Use --cookies for authentication. See '
|
|
||||||
' https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp '
|
|
||||||
'for how to manually pass cookies', method=None)
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
details = traverse_obj(webpage, (
|
details = traverse_obj(webpage, (
|
||||||
{functools.partial(get_element_html_by_id, 'programme-details')}, {
|
{find_element(id='programme-details', html=True)}, {
|
||||||
'title': ({functools.partial(re.search, r'<h2>([^<]+)</h2>')}, 1, {clean_html}),
|
'title': ({find_element(tag='h2')}, {clean_html}),
|
||||||
'timestamp': (
|
'timestamp': (
|
||||||
{functools.partial(get_element_by_class, 'broadcast-date')},
|
{find_element(cls='broadcast-date')},
|
||||||
{functools.partial(re.match, r'([^<]+)')}, 1, {unified_timestamp}),
|
{functools.partial(re.match, r'([^<]+)')}, 1, {unified_timestamp}),
|
||||||
'duration': (
|
'duration': (
|
||||||
{functools.partial(get_element_by_class, 'prog-running-time')},
|
{find_element(cls='prog-running-time')}, {clean_html}, {parse_duration}),
|
||||||
{clean_html}, {parse_duration}),
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
title = details.pop('title', None) or traverse_obj(webpage, (
|
title = details.pop('title', None) or traverse_obj(webpage, (
|
||||||
{functools.partial(get_element_html_by_id, 'add-to-existing-playlist')},
|
{find_element(id='add-to-existing-playlist', html=True)},
|
||||||
{extract_attributes}, 'data-record-title', {clean_html}))
|
{extract_attributes}, 'data-record-title', {clean_html}))
|
||||||
|
|
||||||
entries = self._parse_html5_media_entries(
|
entries = self._parse_html5_media_entries(
|
||||||
|
|
|
@ -6,12 +6,10 @@
|
||||||
extract_attributes,
|
extract_attributes,
|
||||||
get_element_by_class,
|
get_element_by_class,
|
||||||
get_element_html_by_id,
|
get_element_html_by_id,
|
||||||
get_element_text_and_html_by_tag,
|
|
||||||
parse_duration,
|
parse_duration,
|
||||||
strip_or_none,
|
strip_or_none,
|
||||||
traverse_obj,
|
|
||||||
try_call,
|
|
||||||
)
|
)
|
||||||
|
from ..utils.traversal import find_element, traverse_obj
|
||||||
|
|
||||||
|
|
||||||
class ListenNotesIE(InfoExtractor):
|
class ListenNotesIE(InfoExtractor):
|
||||||
|
@ -22,14 +20,14 @@ class ListenNotesIE(InfoExtractor):
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'KrDgvNb_u1n',
|
'id': 'KrDgvNb_u1n',
|
||||||
'ext': 'mp3',
|
'ext': 'mp3',
|
||||||
'title': 'md5:32236591a921adf17bbdbf0441b6c0e9',
|
'title': r're:Tim O’Reilly on noticing things other people .{113}',
|
||||||
'description': 'md5:c581ed197eeddcee55a67cdb547c8cbd',
|
'description': r're:(?s)‘’We shape reality by what we notice and .{27459}',
|
||||||
'duration': 2148.0,
|
'duration': 2215.0,
|
||||||
'channel': 'Thriving on Overload',
|
'channel': 'Amplifying Cognition',
|
||||||
'channel_id': 'ed84wITivxF',
|
'channel_id': 'ed84wITivxF',
|
||||||
'episode_id': 'e1312583fa7b4e24acfbb5131050be00',
|
'episode_id': 'e1312583fa7b4e24acfbb5131050be00',
|
||||||
'thumbnail': 'https://production.listennotes.com/podcasts/thriving-on-overload-ross-dawson-1wb_KospA3P-ed84wITivxF.300x300.jpg',
|
'thumbnail': 'https://cdn-images-3.listennotes.com/podcasts/amplifying-cognition-ross-dawson-Iemft4Gdr0k-ed84wITivxF.300x300.jpg',
|
||||||
'channel_url': 'https://www.listennotes.com/podcasts/thriving-on-overload-ross-dawson-ed84wITivxF/',
|
'channel_url': 'https://www.listennotes.com/podcasts/amplifying-cognition-ross-dawson-ed84wITivxF/',
|
||||||
'cast': ['Tim O’Reilly', 'Cookie Monster', 'Lao Tzu', 'Wallace Steven', 'Eric Raymond', 'Christine Peterson', 'John Maynard Keyne', 'Ross Dawson'],
|
'cast': ['Tim O’Reilly', 'Cookie Monster', 'Lao Tzu', 'Wallace Steven', 'Eric Raymond', 'Christine Peterson', 'John Maynard Keyne', 'Ross Dawson'],
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
|
@ -39,13 +37,13 @@ class ListenNotesIE(InfoExtractor):
|
||||||
'id': 'lwEA3154JzG',
|
'id': 'lwEA3154JzG',
|
||||||
'ext': 'mp3',
|
'ext': 'mp3',
|
||||||
'title': 'Episode 177: WireGuard with Jason Donenfeld',
|
'title': 'Episode 177: WireGuard with Jason Donenfeld',
|
||||||
'description': 'md5:24744f36456a3e95f83c1193a3458594',
|
'description': r're:(?s)Jason Donenfeld lead developer joins us this hour to discuss WireGuard, .{3169}',
|
||||||
'duration': 3861.0,
|
'duration': 3861.0,
|
||||||
'channel': 'Ask Noah Show',
|
'channel': 'Ask Noah Show',
|
||||||
'channel_id': '4DQTzdS5-j7',
|
'channel_id': '4DQTzdS5-j7',
|
||||||
'episode_id': '8c8954b95e0b4859ad1eecec8bf6d3a4',
|
'episode_id': '8c8954b95e0b4859ad1eecec8bf6d3a4',
|
||||||
'channel_url': 'https://www.listennotes.com/podcasts/ask-noah-show-noah-j-chelliah-4DQTzdS5-j7/',
|
'channel_url': 'https://www.listennotes.com/podcasts/ask-noah-show-noah-j-chelliah-4DQTzdS5-j7/',
|
||||||
'thumbnail': 'https://production.listennotes.com/podcasts/ask-noah-show-noah-j-chelliah-cfbRUw9Gs3F-4DQTzdS5-j7.300x300.jpg',
|
'thumbnail': 'https://cdn-images-3.listennotes.com/podcasts/ask-noah-show-noah-j-chelliah-gD7vG150cxf-4DQTzdS5-j7.300x300.jpg',
|
||||||
'cast': ['noah showlink', 'noah show', 'noah dashboard', 'jason donenfeld'],
|
'cast': ['noah showlink', 'noah show', 'noah dashboard', 'jason donenfeld'],
|
||||||
},
|
},
|
||||||
}]
|
}]
|
||||||
|
@ -70,7 +68,7 @@ def _real_extract(self, url):
|
||||||
'id': audio_id,
|
'id': audio_id,
|
||||||
'url': data['audio'],
|
'url': data['audio'],
|
||||||
'title': (data.get('data-title')
|
'title': (data.get('data-title')
|
||||||
or try_call(lambda: get_element_text_and_html_by_tag('h1', webpage)[0])
|
or traverse_obj(webpage, ({find_element(tag='h1')}, {clean_html}))
|
||||||
or self._html_search_meta(('og:title', 'title', 'twitter:title'), webpage, 'title')),
|
or self._html_search_meta(('og:title', 'title', 'twitter:title'), webpage, 'title')),
|
||||||
'description': (self._clean_description(get_element_by_class('ln-text-p', webpage))
|
'description': (self._clean_description(get_element_by_class('ln-text-p', webpage))
|
||||||
or strip_or_none(description)),
|
or strip_or_none(description)),
|
||||||
|
|
|
@ -114,7 +114,7 @@ class LSMLREmbedIE(InfoExtractor):
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
query = parse_qs(url)
|
query = parse_qs(url)
|
||||||
video_id = traverse_obj(query, (
|
video_id = traverse_obj(query, (
|
||||||
('show', 'id'), 0, {int_or_none}, {lambda x: x or None}, {str_or_none}), get_all=False)
|
('show', 'id'), 0, {int_or_none}, filter, {str_or_none}), get_all=False)
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
player_data, media_data = self._search_regex(
|
player_data, media_data = self._search_regex(
|
||||||
|
|
|
@ -57,6 +57,6 @@ def _real_extract(self, url):
|
||||||
'duration': ('runtimeInSeconds', {int_or_none}),
|
'duration': ('runtimeInSeconds', {int_or_none}),
|
||||||
'location': ('countriesOfProduction', {list}, {lambda x: join_nonempty(*x, delim=', ')}),
|
'location': ('countriesOfProduction', {list}, {lambda x: join_nonempty(*x, delim=', ')}),
|
||||||
'release_year': ('yearOfProduction', {int_or_none}),
|
'release_year': ('yearOfProduction', {int_or_none}),
|
||||||
'categories': ('mainGenre', {str}, {lambda x: x and [x]}),
|
'categories': ('mainGenre', {str}, all, filter),
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ class MediaStreamBaseIE(InfoExtractor):
|
||||||
_BASE_URL_RE = r'https?://mdstrm\.com/(?:embed|live-stream)'
|
_BASE_URL_RE = r'https?://mdstrm\.com/(?:embed|live-stream)'
|
||||||
|
|
||||||
def _extract_mediastream_urls(self, webpage):
|
def _extract_mediastream_urls(self, webpage):
|
||||||
yield from traverse_obj(list(self._yield_json_ld(webpage, None, fatal=False)), (
|
yield from traverse_obj(list(self._yield_json_ld(webpage, None, default={})), (
|
||||||
lambda _, v: v['@type'] == 'VideoObject', ('embedUrl', 'contentUrl'),
|
lambda _, v: v['@type'] == 'VideoObject', ('embedUrl', 'contentUrl'),
|
||||||
{lambda x: x if re.match(rf'{self._BASE_URL_RE}/\w+', x) else None}))
|
{lambda x: x if re.match(rf'{self._BASE_URL_RE}/\w+', x) else None}))
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ def _get_comments(self, video_id):
|
||||||
note='Downloading comments', errnote='Failed to download comments'), (..., {
|
note='Downloading comments', errnote='Failed to download comments'), (..., {
|
||||||
'author': ('name', {str}),
|
'author': ('name', {str}),
|
||||||
'author_id': ('user_id', {str_or_none}),
|
'author_id': ('user_id', {str_or_none}),
|
||||||
'id': ('message_id', {str}, {lambda x: x or None}),
|
'id': ('message_id', {str}, filter),
|
||||||
'text': ('body', {str}),
|
'text': ('body', {str}),
|
||||||
'timestamp': ('created', {int}),
|
'timestamp': ('created', {int}),
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -4,15 +4,11 @@
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
clean_html,
|
clean_html,
|
||||||
extract_attributes,
|
extract_attributes,
|
||||||
get_element_by_class,
|
|
||||||
get_element_html_by_class,
|
|
||||||
get_element_text_and_html_by_tag,
|
|
||||||
int_or_none,
|
int_or_none,
|
||||||
strip_or_none,
|
strip_or_none,
|
||||||
traverse_obj,
|
|
||||||
try_call,
|
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
)
|
)
|
||||||
|
from ..utils.traversal import find_element, traverse_obj
|
||||||
|
|
||||||
|
|
||||||
class MonstercatIE(InfoExtractor):
|
class MonstercatIE(InfoExtractor):
|
||||||
|
@ -26,19 +22,21 @@ class MonstercatIE(InfoExtractor):
|
||||||
'thumbnail': 'https://www.monstercat.com/release/742779548009/cover',
|
'thumbnail': 'https://www.monstercat.com/release/742779548009/cover',
|
||||||
'release_date': '20230711',
|
'release_date': '20230711',
|
||||||
'album': 'The Secret Language of Trees',
|
'album': 'The Secret Language of Trees',
|
||||||
'album_artist': 'BT',
|
'album_artists': ['BT'],
|
||||||
},
|
},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _extract_tracks(self, table, album_meta):
|
def _extract_tracks(self, table, album_meta):
|
||||||
for td in re.findall(r'<tr[^<]*>((?:(?!</tr>)[\w\W])+)', table): # regex by chatgpt due to lack of get_elements_by_tag
|
for td in re.findall(r'<tr[^<]*>((?:(?!</tr>)[\w\W])+)', table): # regex by chatgpt due to lack of get_elements_by_tag
|
||||||
title = clean_html(try_call(
|
title = traverse_obj(td, (
|
||||||
lambda: get_element_by_class('d-inline-flex flex-column', td).partition(' <span')[0]))
|
{find_element(cls='d-inline-flex flex-column')},
|
||||||
ids = extract_attributes(try_call(lambda: get_element_html_by_class('btn-play cursor-pointer mr-small', td)) or '')
|
{lambda x: x.partition(' <span')}, 0, {clean_html}))
|
||||||
|
ids = traverse_obj(td, (
|
||||||
|
{find_element(cls='btn-play cursor-pointer mr-small', html=True)}, {extract_attributes})) or {}
|
||||||
track_id = ids.get('data-track-id')
|
track_id = ids.get('data-track-id')
|
||||||
release_id = ids.get('data-release-id')
|
release_id = ids.get('data-release-id')
|
||||||
|
|
||||||
track_number = int_or_none(try_call(lambda: get_element_by_class('py-xsmall', td)))
|
track_number = traverse_obj(td, ({find_element(cls='py-xsmall')}, {int_or_none}))
|
||||||
if not track_id or not release_id:
|
if not track_id or not release_id:
|
||||||
self.report_warning(f'Skipping track {track_number}, ID(s) not found')
|
self.report_warning(f'Skipping track {track_number}, ID(s) not found')
|
||||||
self.write_debug(f'release_id={release_id!r} track_id={track_id!r}')
|
self.write_debug(f'release_id={release_id!r} track_id={track_id!r}')
|
||||||
|
@ -48,7 +46,7 @@ def _extract_tracks(self, table, album_meta):
|
||||||
'title': title,
|
'title': title,
|
||||||
'track': title,
|
'track': title,
|
||||||
'track_number': track_number,
|
'track_number': track_number,
|
||||||
'artist': clean_html(try_call(lambda: get_element_by_class('d-block fs-xxsmall', td))),
|
'artists': traverse_obj(td, ({find_element(cls='d-block fs-xxsmall')}, {clean_html}, all)),
|
||||||
'url': f'https://www.monstercat.com/api/release/{release_id}/track-stream/{track_id}',
|
'url': f'https://www.monstercat.com/api/release/{release_id}/track-stream/{track_id}',
|
||||||
'id': track_id,
|
'id': track_id,
|
||||||
'ext': 'mp3',
|
'ext': 'mp3',
|
||||||
|
@ -57,20 +55,19 @@ def _extract_tracks(self, table, album_meta):
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
url_id = self._match_id(url)
|
url_id = self._match_id(url)
|
||||||
html = self._download_webpage(url, url_id)
|
html = self._download_webpage(url, url_id)
|
||||||
# wrap all `get_elements` in `try_call`, HTMLParser has problems with site's html
|
# NB: HTMLParser may choke on this html; use {find_element} or try_call(lambda: get_element...)
|
||||||
tracklist_table = try_call(lambda: get_element_by_class('table table-small', html)) or ''
|
tracklist_table = traverse_obj(html, {find_element(cls='table table-small')}) or ''
|
||||||
|
title = traverse_obj(html, ({find_element(tag='h1')}, {clean_html}))
|
||||||
title = try_call(lambda: get_element_text_and_html_by_tag('h1', html)[0])
|
|
||||||
date = traverse_obj(html, ({lambda html: get_element_by_class('font-italic mb-medium d-tablet-none d-phone-block',
|
|
||||||
html).partition('Released ')}, 2, {strip_or_none}, {unified_strdate}))
|
|
||||||
|
|
||||||
album_meta = {
|
album_meta = {
|
||||||
'title': title,
|
'title': title,
|
||||||
'album': title,
|
'album': title,
|
||||||
'thumbnail': f'https://www.monstercat.com/release/{url_id}/cover',
|
'thumbnail': f'https://www.monstercat.com/release/{url_id}/cover',
|
||||||
'album_artist': try_call(
|
'album_artists': traverse_obj(html, (
|
||||||
lambda: get_element_by_class('h-normal text-uppercase mb-desktop-medium mb-smallish', html)),
|
{find_element(cls='h-normal text-uppercase mb-desktop-medium mb-smallish')}, {clean_html}, all)),
|
||||||
'release_date': date,
|
'release_date': traverse_obj(html, (
|
||||||
|
{find_element(cls='font-italic mb-medium d-tablet-none d-phone-block')},
|
||||||
|
{lambda x: x.partition('Released ')}, 2, {strip_or_none}, {unified_strdate})),
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.playlist_result(
|
return self.playlist_result(
|
||||||
|
|
|
@ -86,7 +86,7 @@ def _extract_formats(self, content_id, slug):
|
||||||
|
|
||||||
def _extract_video_metadata(self, episode):
|
def _extract_video_metadata(self, episode):
|
||||||
channel_url = traverse_obj(
|
channel_url = traverse_obj(
|
||||||
episode, (('channel_slug', 'class_slug'), {lambda x: urljoin('https://nebula.tv/', x)}), get_all=False)
|
episode, (('channel_slug', 'class_slug'), {urljoin('https://nebula.tv/')}), get_all=False)
|
||||||
return {
|
return {
|
||||||
'id': episode['id'].partition(':')[2],
|
'id': episode['id'].partition(':')[2],
|
||||||
**traverse_obj(episode, {
|
**traverse_obj(episode, {
|
||||||
|
|
|
@ -6,12 +6,10 @@
|
||||||
determine_ext,
|
determine_ext,
|
||||||
extract_attributes,
|
extract_attributes,
|
||||||
get_element_by_class,
|
get_element_by_class,
|
||||||
get_element_text_and_html_by_tag,
|
|
||||||
parse_duration,
|
parse_duration,
|
||||||
traverse_obj,
|
|
||||||
try_call,
|
|
||||||
url_or_none,
|
url_or_none,
|
||||||
)
|
)
|
||||||
|
from ..utils.traversal import find_element, traverse_obj
|
||||||
|
|
||||||
|
|
||||||
class NekoHackerIE(InfoExtractor):
|
class NekoHackerIE(InfoExtractor):
|
||||||
|
@ -35,7 +33,7 @@ class NekoHackerIE(InfoExtractor):
|
||||||
'acodec': 'mp3',
|
'acodec': 'mp3',
|
||||||
'release_date': '20221101',
|
'release_date': '20221101',
|
||||||
'album': 'Nekoverse',
|
'album': 'Nekoverse',
|
||||||
'artist': 'Neko Hacker',
|
'artists': ['Neko Hacker'],
|
||||||
'track': 'Spaceship',
|
'track': 'Spaceship',
|
||||||
'track_number': 1,
|
'track_number': 1,
|
||||||
'duration': 195.0,
|
'duration': 195.0,
|
||||||
|
@ -53,7 +51,7 @@ class NekoHackerIE(InfoExtractor):
|
||||||
'acodec': 'mp3',
|
'acodec': 'mp3',
|
||||||
'release_date': '20221101',
|
'release_date': '20221101',
|
||||||
'album': 'Nekoverse',
|
'album': 'Nekoverse',
|
||||||
'artist': 'Neko Hacker',
|
'artists': ['Neko Hacker'],
|
||||||
'track': 'City Runner',
|
'track': 'City Runner',
|
||||||
'track_number': 2,
|
'track_number': 2,
|
||||||
'duration': 148.0,
|
'duration': 148.0,
|
||||||
|
@ -71,7 +69,7 @@ class NekoHackerIE(InfoExtractor):
|
||||||
'acodec': 'mp3',
|
'acodec': 'mp3',
|
||||||
'release_date': '20221101',
|
'release_date': '20221101',
|
||||||
'album': 'Nekoverse',
|
'album': 'Nekoverse',
|
||||||
'artist': 'Neko Hacker',
|
'artists': ['Neko Hacker'],
|
||||||
'track': 'Nature Talk',
|
'track': 'Nature Talk',
|
||||||
'track_number': 3,
|
'track_number': 3,
|
||||||
'duration': 174.0,
|
'duration': 174.0,
|
||||||
|
@ -89,7 +87,7 @@ class NekoHackerIE(InfoExtractor):
|
||||||
'acodec': 'mp3',
|
'acodec': 'mp3',
|
||||||
'release_date': '20221101',
|
'release_date': '20221101',
|
||||||
'album': 'Nekoverse',
|
'album': 'Nekoverse',
|
||||||
'artist': 'Neko Hacker',
|
'artists': ['Neko Hacker'],
|
||||||
'track': 'Crystal World',
|
'track': 'Crystal World',
|
||||||
'track_number': 4,
|
'track_number': 4,
|
||||||
'duration': 199.0,
|
'duration': 199.0,
|
||||||
|
@ -115,7 +113,7 @@ class NekoHackerIE(InfoExtractor):
|
||||||
'acodec': 'mp3',
|
'acodec': 'mp3',
|
||||||
'release_date': '20210115',
|
'release_date': '20210115',
|
||||||
'album': '進め!むじなカンパニー',
|
'album': '進め!むじなカンパニー',
|
||||||
'artist': 'Neko Hacker',
|
'artists': ['Neko Hacker'],
|
||||||
'track': 'md5:1a5fcbc96ca3c3265b1c6f9f79f30fd0',
|
'track': 'md5:1a5fcbc96ca3c3265b1c6f9f79f30fd0',
|
||||||
'track_number': 1,
|
'track_number': 1,
|
||||||
},
|
},
|
||||||
|
@ -132,7 +130,7 @@ class NekoHackerIE(InfoExtractor):
|
||||||
'acodec': 'mp3',
|
'acodec': 'mp3',
|
||||||
'release_date': '20210115',
|
'release_date': '20210115',
|
||||||
'album': '進め!むじなカンパニー',
|
'album': '進め!むじなカンパニー',
|
||||||
'artist': 'Neko Hacker',
|
'artists': ['Neko Hacker'],
|
||||||
'track': 'むじな de なじむ feat. 六科なじむ (CV: 日高里菜 )',
|
'track': 'むじな de なじむ feat. 六科なじむ (CV: 日高里菜 )',
|
||||||
'track_number': 2,
|
'track_number': 2,
|
||||||
},
|
},
|
||||||
|
@ -149,7 +147,7 @@ class NekoHackerIE(InfoExtractor):
|
||||||
'acodec': 'mp3',
|
'acodec': 'mp3',
|
||||||
'release_date': '20210115',
|
'release_date': '20210115',
|
||||||
'album': '進め!むじなカンパニー',
|
'album': '進め!むじなカンパニー',
|
||||||
'artist': 'Neko Hacker',
|
'artists': ['Neko Hacker'],
|
||||||
'track': '進め!むじなカンパニー (instrumental)',
|
'track': '進め!むじなカンパニー (instrumental)',
|
||||||
'track_number': 3,
|
'track_number': 3,
|
||||||
},
|
},
|
||||||
|
@ -166,7 +164,7 @@ class NekoHackerIE(InfoExtractor):
|
||||||
'acodec': 'mp3',
|
'acodec': 'mp3',
|
||||||
'release_date': '20210115',
|
'release_date': '20210115',
|
||||||
'album': '進め!むじなカンパニー',
|
'album': '進め!むじなカンパニー',
|
||||||
'artist': 'Neko Hacker',
|
'artists': ['Neko Hacker'],
|
||||||
'track': 'むじな de なじむ (instrumental)',
|
'track': 'むじな de なじむ (instrumental)',
|
||||||
'track_number': 4,
|
'track_number': 4,
|
||||||
},
|
},
|
||||||
|
@ -181,14 +179,17 @@ def _real_extract(self, url):
|
||||||
playlist = get_element_by_class('playlist', webpage)
|
playlist = get_element_by_class('playlist', webpage)
|
||||||
|
|
||||||
if not playlist:
|
if not playlist:
|
||||||
iframe = try_call(lambda: get_element_text_and_html_by_tag('iframe', webpage)[1]) or ''
|
iframe_src = traverse_obj(webpage, (
|
||||||
iframe_src = url_or_none(extract_attributes(iframe).get('src'))
|
{find_element(tag='iframe', html=True)}, {extract_attributes}, 'src', {url_or_none}))
|
||||||
if not iframe_src:
|
if not iframe_src:
|
||||||
raise ExtractorError('No playlist or embed found in webpage')
|
raise ExtractorError('No playlist or embed found in webpage')
|
||||||
elif re.match(r'https?://(?:\w+\.)?spotify\.com/', iframe_src):
|
elif re.match(r'https?://(?:\w+\.)?spotify\.com/', iframe_src):
|
||||||
raise ExtractorError('Spotify embeds are not supported', expected=True)
|
raise ExtractorError('Spotify embeds are not supported', expected=True)
|
||||||
return self.url_result(url, 'Generic')
|
return self.url_result(url, 'Generic')
|
||||||
|
|
||||||
|
player_params = self._search_json(
|
||||||
|
r'var srp_player_params_[\da-f]+\s*=', webpage, 'player params', playlist_id, default={})
|
||||||
|
|
||||||
entries = []
|
entries = []
|
||||||
for track_number, track in enumerate(re.findall(r'(<li[^>]+data-audiopath[^>]+>)', playlist), 1):
|
for track_number, track in enumerate(re.findall(r'(<li[^>]+data-audiopath[^>]+>)', playlist), 1):
|
||||||
entry = traverse_obj(extract_attributes(track), {
|
entry = traverse_obj(extract_attributes(track), {
|
||||||
|
@ -200,12 +201,12 @@ def _real_extract(self, url):
|
||||||
'album': 'data-albumtitle',
|
'album': 'data-albumtitle',
|
||||||
'duration': ('data-tracktime', {parse_duration}),
|
'duration': ('data-tracktime', {parse_duration}),
|
||||||
'release_date': ('data-releasedate', {lambda x: re.match(r'\d{8}', x.replace('.', ''))}, 0),
|
'release_date': ('data-releasedate', {lambda x: re.match(r'\d{8}', x.replace('.', ''))}, 0),
|
||||||
'thumbnail': ('data-albumart', {url_or_none}),
|
|
||||||
})
|
})
|
||||||
entries.append({
|
entries.append({
|
||||||
**entry,
|
**entry,
|
||||||
|
'thumbnail': url_or_none(player_params.get('artwork')),
|
||||||
'track_number': track_number,
|
'track_number': track_number,
|
||||||
'artist': 'Neko Hacker',
|
'artists': ['Neko Hacker'],
|
||||||
'vcodec': 'none',
|
'vcodec': 'none',
|
||||||
'acodec': 'mp3' if entry['ext'] == 'mp3' else None,
|
'acodec': 'mp3' if entry['ext'] == 'mp3' else None,
|
||||||
})
|
})
|
||||||
|
|
|
@ -36,10 +36,6 @@ class NetEaseMusicBaseIE(InfoExtractor):
|
||||||
_API_BASE = 'http://music.163.com/api/'
|
_API_BASE = 'http://music.163.com/api/'
|
||||||
_GEO_BYPASS = False
|
_GEO_BYPASS = False
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _kilo_or_none(value):
|
|
||||||
return int_or_none(value, scale=1000)
|
|
||||||
|
|
||||||
def _create_eapi_cipher(self, api_path, query_body, cookies):
|
def _create_eapi_cipher(self, api_path, query_body, cookies):
|
||||||
request_text = json.dumps({**query_body, 'header': cookies}, separators=(',', ':'))
|
request_text = json.dumps({**query_body, 'header': cookies}, separators=(',', ':'))
|
||||||
|
|
||||||
|
@ -101,7 +97,7 @@ def _extract_formats(self, info):
|
||||||
'vcodec': 'none',
|
'vcodec': 'none',
|
||||||
**traverse_obj(song, {
|
**traverse_obj(song, {
|
||||||
'ext': ('type', {str}),
|
'ext': ('type', {str}),
|
||||||
'abr': ('br', {self._kilo_or_none}),
|
'abr': ('br', {int_or_none(scale=1000)}),
|
||||||
'filesize': ('size', {int_or_none}),
|
'filesize': ('size', {int_or_none}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
@ -282,9 +278,9 @@ def _real_extract(self, url):
|
||||||
**lyric_data,
|
**lyric_data,
|
||||||
**traverse_obj(info, {
|
**traverse_obj(info, {
|
||||||
'title': ('name', {str}),
|
'title': ('name', {str}),
|
||||||
'timestamp': ('album', 'publishTime', {self._kilo_or_none}),
|
'timestamp': ('album', 'publishTime', {int_or_none(scale=1000)}),
|
||||||
'thumbnail': ('album', 'picUrl', {url_or_none}),
|
'thumbnail': ('album', 'picUrl', {url_or_none}),
|
||||||
'duration': ('duration', {self._kilo_or_none}),
|
'duration': ('duration', {int_or_none(scale=1000)}),
|
||||||
'album': ('album', 'name', {str}),
|
'album': ('album', 'name', {str}),
|
||||||
'average_rating': ('score', {int_or_none}),
|
'average_rating': ('score', {int_or_none}),
|
||||||
}),
|
}),
|
||||||
|
@ -440,7 +436,7 @@ def _real_extract(self, url):
|
||||||
'tags': ('tags', ..., {str}),
|
'tags': ('tags', ..., {str}),
|
||||||
'uploader': ('creator', 'nickname', {str}),
|
'uploader': ('creator', 'nickname', {str}),
|
||||||
'uploader_id': ('creator', 'userId', {str_or_none}),
|
'uploader_id': ('creator', 'userId', {str_or_none}),
|
||||||
'timestamp': ('updateTime', {self._kilo_or_none}),
|
'timestamp': ('updateTime', {int_or_none(scale=1000)}),
|
||||||
}))
|
}))
|
||||||
if traverse_obj(info, ('playlist', 'specialType')) == 10:
|
if traverse_obj(info, ('playlist', 'specialType')) == 10:
|
||||||
metainfo['title'] = f'{metainfo.get("title")} {strftime_or_none(metainfo.get("timestamp"), "%Y-%m-%d")}'
|
metainfo['title'] = f'{metainfo.get("title")} {strftime_or_none(metainfo.get("timestamp"), "%Y-%m-%d")}'
|
||||||
|
@ -517,10 +513,10 @@ def _real_extract(self, url):
|
||||||
'creators': traverse_obj(info, ('artists', ..., 'name')) or [info.get('artistName')],
|
'creators': traverse_obj(info, ('artists', ..., 'name')) or [info.get('artistName')],
|
||||||
**traverse_obj(info, {
|
**traverse_obj(info, {
|
||||||
'title': ('name', {str}),
|
'title': ('name', {str}),
|
||||||
'description': (('desc', 'briefDesc'), {str}, {lambda x: x or None}),
|
'description': (('desc', 'briefDesc'), {str}, filter),
|
||||||
'upload_date': ('publishTime', {unified_strdate}),
|
'upload_date': ('publishTime', {unified_strdate}),
|
||||||
'thumbnail': ('cover', {url_or_none}),
|
'thumbnail': ('cover', {url_or_none}),
|
||||||
'duration': ('duration', {self._kilo_or_none}),
|
'duration': ('duration', {int_or_none(scale=1000)}),
|
||||||
'view_count': ('playCount', {int_or_none}),
|
'view_count': ('playCount', {int_or_none}),
|
||||||
'like_count': ('likeCount', {int_or_none}),
|
'like_count': ('likeCount', {int_or_none}),
|
||||||
'comment_count': ('commentCount', {int_or_none}),
|
'comment_count': ('commentCount', {int_or_none}),
|
||||||
|
@ -588,7 +584,7 @@ def _real_extract(self, url):
|
||||||
'description': ('description', {str}),
|
'description': ('description', {str}),
|
||||||
'creator': ('dj', 'brand', {str}),
|
'creator': ('dj', 'brand', {str}),
|
||||||
'thumbnail': ('coverUrl', {url_or_none}),
|
'thumbnail': ('coverUrl', {url_or_none}),
|
||||||
'timestamp': ('createTime', {self._kilo_or_none}),
|
'timestamp': ('createTime', {int_or_none(scale=1000)}),
|
||||||
})
|
})
|
||||||
|
|
||||||
if not self._yes_playlist(
|
if not self._yes_playlist(
|
||||||
|
@ -598,7 +594,7 @@ def _real_extract(self, url):
|
||||||
return {
|
return {
|
||||||
'id': str(info['mainSong']['id']),
|
'id': str(info['mainSong']['id']),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'duration': traverse_obj(info, ('mainSong', 'duration', {self._kilo_or_none})),
|
'duration': traverse_obj(info, ('mainSong', 'duration', {int_or_none(scale=1000)})),
|
||||||
**metainfo,
|
**metainfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -371,11 +371,11 @@ def _extract_format_for_quality(self, video_id, audio_quality, video_quality, dm
|
||||||
'acodec': 'aac',
|
'acodec': 'aac',
|
||||||
'vcodec': 'h264',
|
'vcodec': 'h264',
|
||||||
**traverse_obj(audio_quality, ('metadata', {
|
**traverse_obj(audio_quality, ('metadata', {
|
||||||
'abr': ('bitrate', {functools.partial(float_or_none, scale=1000)}),
|
'abr': ('bitrate', {float_or_none(scale=1000)}),
|
||||||
'asr': ('samplingRate', {int_or_none}),
|
'asr': ('samplingRate', {int_or_none}),
|
||||||
})),
|
})),
|
||||||
**traverse_obj(video_quality, ('metadata', {
|
**traverse_obj(video_quality, ('metadata', {
|
||||||
'vbr': ('bitrate', {functools.partial(float_or_none, scale=1000)}),
|
'vbr': ('bitrate', {float_or_none(scale=1000)}),
|
||||||
'height': ('resolution', 'height', {int_or_none}),
|
'height': ('resolution', 'height', {int_or_none}),
|
||||||
'width': ('resolution', 'width', {int_or_none}),
|
'width': ('resolution', 'width', {int_or_none}),
|
||||||
})),
|
})),
|
||||||
|
@ -428,7 +428,7 @@ def _yield_dms_formats(self, api_data, video_id):
|
||||||
**audio_fmt,
|
**audio_fmt,
|
||||||
**traverse_obj(audios, (lambda _, v: audio_fmt['format_id'].startswith(v['id']), {
|
**traverse_obj(audios, (lambda _, v: audio_fmt['format_id'].startswith(v['id']), {
|
||||||
'format_id': ('id', {str}),
|
'format_id': ('id', {str}),
|
||||||
'abr': ('bitRate', {functools.partial(float_or_none, scale=1000)}),
|
'abr': ('bitRate', {float_or_none(scale=1000)}),
|
||||||
'asr': ('samplingRate', {int_or_none}),
|
'asr': ('samplingRate', {int_or_none}),
|
||||||
}), get_all=False),
|
}), get_all=False),
|
||||||
'acodec': 'aac',
|
'acodec': 'aac',
|
||||||
|
|
|
@ -10,10 +10,10 @@
|
||||||
get_element_html_by_class,
|
get_element_html_by_class,
|
||||||
get_elements_by_class,
|
get_elements_by_class,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
try_call,
|
|
||||||
unified_timestamp,
|
unified_timestamp,
|
||||||
urlencode_postdata,
|
urlencode_postdata,
|
||||||
)
|
)
|
||||||
|
from ..utils.traversal import find_element, find_elements, traverse_obj
|
||||||
|
|
||||||
|
|
||||||
class NubilesPornIE(InfoExtractor):
|
class NubilesPornIE(InfoExtractor):
|
||||||
|
@ -70,9 +70,8 @@ def _real_extract(self, url):
|
||||||
url, get_element_by_class('watch-page-video-wrapper', page), video_id)[0]
|
url, get_element_by_class('watch-page-video-wrapper', page), video_id)[0]
|
||||||
|
|
||||||
channel_id, channel_name = self._search_regex(
|
channel_id, channel_name = self._search_regex(
|
||||||
r'/video/website/(?P<id>\d+).+>(?P<name>\w+).com', get_element_html_by_class('site-link', page),
|
r'/video/website/(?P<id>\d+).+>(?P<name>\w+).com', get_element_html_by_class('site-link', page) or '',
|
||||||
'channel', fatal=False, group=('id', 'name')) or (None, None)
|
'channel', fatal=False, group=('id', 'name')) or (None, None)
|
||||||
channel_name = re.sub(r'([^A-Z]+)([A-Z]+)', r'\1 \2', channel_name)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
@ -82,14 +81,14 @@ def _real_extract(self, url):
|
||||||
'thumbnail': media_entries.get('thumbnail'),
|
'thumbnail': media_entries.get('thumbnail'),
|
||||||
'description': clean_html(get_element_html_by_class('content-pane-description', page)),
|
'description': clean_html(get_element_html_by_class('content-pane-description', page)),
|
||||||
'timestamp': unified_timestamp(get_element_by_class('date', page)),
|
'timestamp': unified_timestamp(get_element_by_class('date', page)),
|
||||||
'channel': channel_name,
|
'channel': re.sub(r'([^A-Z]+)([A-Z]+)', r'\1 \2', channel_name) if channel_name else None,
|
||||||
'channel_id': channel_id,
|
'channel_id': channel_id,
|
||||||
'channel_url': format_field(channel_id, None, 'https://members.nubiles-porn.com/video/website/%s'),
|
'channel_url': format_field(channel_id, None, 'https://members.nubiles-porn.com/video/website/%s'),
|
||||||
'like_count': int_or_none(get_element_by_id('likecount', page)),
|
'like_count': int_or_none(get_element_by_id('likecount', page)),
|
||||||
'average_rating': float_or_none(get_element_by_class('score', page)),
|
'average_rating': float_or_none(get_element_by_class('score', page)),
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
'categories': try_call(lambda: list(map(clean_html, get_elements_by_class('btn', get_element_by_class('categories', page))))),
|
'categories': traverse_obj(page, ({find_element(cls='categories')}, {find_elements(cls='btn')}, ..., {clean_html})),
|
||||||
'tags': try_call(lambda: list(map(clean_html, get_elements_by_class('btn', get_elements_by_class('tags', page)[1])))),
|
'tags': traverse_obj(page, ({find_elements(cls='tags')}, 1, {find_elements(cls='btn')}, ..., {clean_html})),
|
||||||
'cast': get_elements_by_class('content-pane-performer', page),
|
'cast': get_elements_by_class('content-pane-performer', page),
|
||||||
'availability': 'needs_auth',
|
'availability': 'needs_auth',
|
||||||
'series': channel_name,
|
'series': channel_name,
|
||||||
|
|
|
@ -235,7 +235,7 @@ def _extract_content_from_block(self, block):
|
||||||
details = traverse_obj(block, {
|
details = traverse_obj(block, {
|
||||||
'id': ('sourceId', {str}),
|
'id': ('sourceId', {str}),
|
||||||
'uploader': ('bylines', ..., 'renderedRepresentation', {str}),
|
'uploader': ('bylines', ..., 'renderedRepresentation', {str}),
|
||||||
'duration': (None, (('duration', {lambda x: float_or_none(x, scale=1000)}), ('length', {int_or_none}))),
|
'duration': (None, (('duration', {float_or_none(scale=1000)}), ('length', {int_or_none}))),
|
||||||
'timestamp': ('firstPublished', {parse_iso8601}),
|
'timestamp': ('firstPublished', {parse_iso8601}),
|
||||||
'series': ('podcastSeries', {str}),
|
'series': ('podcastSeries', {str}),
|
||||||
}, get_all=False)
|
}, get_all=False)
|
||||||
|
|
|
@ -115,7 +115,7 @@ def if_series(key=None):
|
||||||
**traverse_obj(data, {
|
**traverse_obj(data, {
|
||||||
'thumbnail': ('episode', 'images', 'thumbnail', {url_or_none}),
|
'thumbnail': ('episode', 'images', 'thumbnail', {url_or_none}),
|
||||||
'release_date': ('episode', 'release_date', {lambda x: x.replace('-', '')}, {unified_strdate}),
|
'release_date': ('episode', 'release_date', {lambda x: x.replace('-', '')}, {unified_strdate}),
|
||||||
'duration': ('duration', {functools.partial(float_or_none, scale=1000)}),
|
'duration': ('duration', {float_or_none(scale=1000)}),
|
||||||
'age_limit': ('age_rating', 'name', {lambda x: x.replace('R', '')}, {parse_age_limit}),
|
'age_limit': ('age_rating', 'name', {lambda x: x.replace('R', '')}, {parse_age_limit}),
|
||||||
'series': ('episode', {if_series(key='program')}, 'title'),
|
'series': ('episode', {if_series(key='program')}, 'title'),
|
||||||
'series_id': ('episode', {if_series(key='program')}, 'id', {str_or_none}),
|
'series_id': ('episode', {if_series(key='program')}, 'id', {str_or_none}),
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import base64
|
import base64
|
||||||
import functools
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
@ -192,7 +191,7 @@ def _real_extract(self, url):
|
||||||
'ext': ('enclosures', 0, 'type', {mimetype2ext}),
|
'ext': ('enclosures', 0, 'type', {mimetype2ext}),
|
||||||
'title': 'title',
|
'title': 'title',
|
||||||
'description': ('description', {clean_html}),
|
'description': ('description', {clean_html}),
|
||||||
'duration': ('duration', {functools.partial(float_or_none, scale=1000)}),
|
'duration': ('duration', {float_or_none(scale=1000)}),
|
||||||
'series': ('podcast', 'title'),
|
'series': ('podcast', 'title'),
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
|
@ -494,7 +493,7 @@ def _parse_metadata(api_json):
|
||||||
return traverse_obj(api_json, {
|
return traverse_obj(api_json, {
|
||||||
'id': ('id', {int}, {str_or_none}),
|
'id': ('id', {int}, {str_or_none}),
|
||||||
'age_limit': ('age_classification', {parse_age_limit}),
|
'age_limit': ('age_classification', {parse_age_limit}),
|
||||||
'duration': ('exact_duration', {functools.partial(float_or_none, scale=1000)}),
|
'duration': ('exact_duration', {float_or_none(scale=1000)}),
|
||||||
'title': (('title', 'headline'), {str}),
|
'title': (('title', 'headline'), {str}),
|
||||||
'description': (('description', 'teaser_text'), {str}),
|
'description': (('description', 'teaser_text'), {str}),
|
||||||
'media_type': ('video_type', {str}),
|
'media_type': ('video_type', {str}),
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import functools
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from .youtube import YoutubeIE
|
from .youtube import YoutubeIE
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
@ -83,7 +81,7 @@ def _real_extract(self, url):
|
||||||
'timestamp': ('date_created', {unified_timestamp}),
|
'timestamp': ('date_created', {unified_timestamp}),
|
||||||
'uploader': ('user', 'name', {strip_or_none}),
|
'uploader': ('user', 'name', {strip_or_none}),
|
||||||
'uploader_id': ('user', 'username', {str}),
|
'uploader_id': ('user', 'username', {str}),
|
||||||
'uploader_url': ('user', 'username', {functools.partial(urljoin, 'https://parler.com/')}),
|
'uploader_url': ('user', 'username', {urljoin('https://parler.com/')}),
|
||||||
'view_count': ('views', {int_or_none}),
|
'view_count': ('views', {int_or_none}),
|
||||||
'comment_count': ('total_comments', {int_or_none}),
|
'comment_count': ('total_comments', {int_or_none}),
|
||||||
'repost_count': ('echos', {int_or_none}),
|
'repost_count': ('echos', {int_or_none}),
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import functools
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
@ -105,7 +104,7 @@ def _real_extract(self, url):
|
||||||
get_quality = qualities(['web', 'vga', 'hd', '1080p', '4k', '8k'])
|
get_quality = qualities(['web', 'vga', 'hd', '1080p', '4k', '8k'])
|
||||||
metadata['formats'] = traverse_obj(stream_data, ('qualities', lambda _, v: v['src'], {
|
metadata['formats'] = traverse_obj(stream_data, ('qualities', lambda _, v: v['src'], {
|
||||||
'url': 'src',
|
'url': 'src',
|
||||||
'vbr': ('bitrate', {functools.partial(int_or_none, scale=1000)}),
|
'vbr': ('bitrate', {int_or_none(scale=1000)}),
|
||||||
'format_id': ('quality', {str_or_none}),
|
'format_id': ('quality', {str_or_none}),
|
||||||
'quality': ('quality', {get_quality}),
|
'quality': ('quality', {get_quality}),
|
||||||
'width': ('size', {lambda x: int(x[:-1])}),
|
'width': ('size', {lambda x: int(x[:-1])}),
|
||||||
|
|
|
@ -198,6 +198,6 @@ def _real_extract(self, url):
|
||||||
'dislike_count': ('down', {int}),
|
'dislike_count': ('down', {int}),
|
||||||
'timestamp': ('created', {int}),
|
'timestamp': ('created', {int}),
|
||||||
'upload_date': ('created', {int}, {dt.date.fromtimestamp}, {lambda x: x.strftime('%Y%m%d')}),
|
'upload_date': ('created', {int}, {dt.date.fromtimestamp}, {lambda x: x.strftime('%Y%m%d')}),
|
||||||
'thumbnail': ('thumb', {lambda x: urljoin('https://thumb.pr0gramm.com', x)}),
|
'thumbnail': ('thumb', {urljoin('https://thumb.pr0gramm.com')}),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,7 +140,7 @@ def extract_availability(level):
|
||||||
'description': ('description', {str.strip}),
|
'description': ('description', {str.strip}),
|
||||||
'display_id': ('slug', {str}),
|
'display_id': ('slug', {str}),
|
||||||
'thumbnail': ('thumbnail', {url_or_none}),
|
'thumbnail': ('thumbnail', {url_or_none}),
|
||||||
'duration': ('durationInSeconds', {int_or_none}, {lambda x: x or None}),
|
'duration': ('durationInSeconds', {int_or_none}, filter),
|
||||||
'availability': ('subscription', 'level', {extract_availability}),
|
'availability': ('subscription', 'level', {extract_availability}),
|
||||||
'is_live': ('type', {lambda x: x.lower() == 'live'}),
|
'is_live': ('type', {lambda x: x.lower() == 'live'}),
|
||||||
'artist': ('acts', ..., {str}),
|
'artist': ('acts', ..., {str}),
|
||||||
|
|
|
@ -211,10 +211,10 @@ def _real_extract(self, url):
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
**traverse_obj(info_data, {
|
**traverse_obj(info_data, {
|
||||||
'title': ('title', {str}),
|
'title': ('title', {str}),
|
||||||
'album': ('album', 'title', {str}, {lambda x: x or None}),
|
'album': ('album', 'title', {str}, filter),
|
||||||
'release_date': ('time_public', {lambda x: x.replace('-', '') or None}),
|
'release_date': ('time_public', {lambda x: x.replace('-', '') or None}),
|
||||||
'creators': ('singer', ..., 'name', {str}),
|
'creators': ('singer', ..., 'name', {str}),
|
||||||
'alt_title': ('subtitle', {str}, {lambda x: x or None}),
|
'alt_title': ('subtitle', {str}, filter),
|
||||||
'duration': ('interval', {int_or_none}),
|
'duration': ('interval', {int_or_none}),
|
||||||
}),
|
}),
|
||||||
**traverse_obj(init_data, ('detail', {
|
**traverse_obj(init_data, ('detail', {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import functools
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..networking import HEADRequest
|
from ..networking import HEADRequest
|
||||||
|
@ -118,7 +117,7 @@ def livx_mode(mode):
|
||||||
|
|
||||||
time_scale = traverse_obj(ism_doc, ('@TimeScale', {int_or_none})) or 10000000
|
time_scale = traverse_obj(ism_doc, ('@TimeScale', {int_or_none})) or 10000000
|
||||||
duration = traverse_obj(
|
duration = traverse_obj(
|
||||||
ism_doc, ('@Duration', {functools.partial(float_or_none, scale=time_scale)})) or None
|
ism_doc, ('@Duration', {float_or_none(scale=time_scale)})) or None
|
||||||
|
|
||||||
live_status = None
|
live_status = None
|
||||||
if traverse_obj(ism_doc, '@IsLive') == 'TRUE':
|
if traverse_obj(ism_doc, '@IsLive') == 'TRUE':
|
||||||
|
|
|
@ -187,4 +187,4 @@ def _real_extract(self, url):
|
||||||
return self.playlist_from_matches(
|
return self.playlist_from_matches(
|
||||||
re.findall(r'<a [^>]*\bhref="(/arhiv/[^"]+)"', webpage),
|
re.findall(r'<a [^>]*\bhref="(/arhiv/[^"]+)"', webpage),
|
||||||
playlist_id, self._html_extract_title(webpage),
|
playlist_id, self._html_extract_title(webpage),
|
||||||
getter=lambda x: urljoin('https://365.rtvslo.si', x), ie=RTVSLOIE)
|
getter=urljoin('https://365.rtvslo.si'), ie=RTVSLOIE)
|
||||||
|
|
|
@ -56,13 +56,13 @@ def _real_extract(self, url):
|
||||||
**traverse_obj(video_data, ('videoMetadata', {
|
**traverse_obj(video_data, ('videoMetadata', {
|
||||||
'title': ('name', {str}),
|
'title': ('name', {str}),
|
||||||
'description': ('description', {str}),
|
'description': ('description', {str}),
|
||||||
'timestamp': ('uploadDateMs', {lambda x: float_or_none(x, 1000)}),
|
'timestamp': ('uploadDateMs', {float_or_none(scale=1000)}),
|
||||||
'view_count': ('viewCount', {int_or_none}, {lambda x: None if x == -1 else x}),
|
'view_count': ('viewCount', {int_or_none}, {lambda x: None if x == -1 else x}),
|
||||||
'repost_count': ('shareCount', {int_or_none}),
|
'repost_count': ('shareCount', {int_or_none}),
|
||||||
'url': ('contentUrl', {url_or_none}),
|
'url': ('contentUrl', {url_or_none}),
|
||||||
'width': ('width', {int_or_none}),
|
'width': ('width', {int_or_none}),
|
||||||
'height': ('height', {int_or_none}),
|
'height': ('height', {int_or_none}),
|
||||||
'duration': ('durationMs', {lambda x: float_or_none(x, 1000)}),
|
'duration': ('durationMs', {float_or_none(scale=1000)}),
|
||||||
'thumbnail': ('thumbnailUrl', {url_or_none}),
|
'thumbnail': ('thumbnailUrl', {url_or_none}),
|
||||||
'uploader': ('creator', 'personCreator', 'username', {str}),
|
'uploader': ('creator', 'personCreator', 'username', {str}),
|
||||||
'uploader_url': ('creator', 'personCreator', 'url', {url_or_none}),
|
'uploader_url': ('creator', 'personCreator', 'url', {url_or_none}),
|
||||||
|
|
|
@ -3,14 +3,12 @@
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
clean_html,
|
clean_html,
|
||||||
get_element_text_and_html_by_tag,
|
|
||||||
int_or_none,
|
int_or_none,
|
||||||
str_or_none,
|
str_or_none,
|
||||||
traverse_obj,
|
|
||||||
try_call,
|
|
||||||
unified_timestamp,
|
unified_timestamp,
|
||||||
urljoin,
|
urljoin,
|
||||||
)
|
)
|
||||||
|
from ..utils.traversal import find_element, traverse_obj
|
||||||
|
|
||||||
|
|
||||||
class TBSJPEpisodeIE(InfoExtractor):
|
class TBSJPEpisodeIE(InfoExtractor):
|
||||||
|
@ -64,7 +62,7 @@ def _real_extract(self, url):
|
||||||
self._merge_subtitles(subs, target=subtitles)
|
self._merge_subtitles(subs, target=subtitles)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'title': try_call(lambda: clean_html(get_element_text_and_html_by_tag('h3', webpage)[0])),
|
'title': traverse_obj(webpage, ({find_element(tag='h3')}, {clean_html})),
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
**traverse_obj(episode, {
|
**traverse_obj(episode, {
|
||||||
'categories': ('keywords', {list}),
|
'categories': ('keywords', {list}),
|
||||||
|
|
|
@ -136,7 +136,7 @@ def _real_extract(self, url):
|
||||||
'blocks', lambda _, v: v['name'] in ('meta-tags', 'video-player', 'video-info'), 'props', {dict})))
|
'blocks', lambda _, v: v['name'] in ('meta-tags', 'video-player', 'video-info'), 'props', {dict})))
|
||||||
|
|
||||||
thumbnail = traverse_obj(
|
thumbnail = traverse_obj(
|
||||||
info, (('image', 'poster'), {lambda x: urljoin('https://teamcoco.com/', x)}), get_all=False)
|
info, (('image', 'poster'), {urljoin('https://teamcoco.com/')}), get_all=False)
|
||||||
video_id = traverse_obj(parse_qs(thumbnail), ('id', 0)) or display_id
|
video_id = traverse_obj(parse_qs(thumbnail), ('id', 0)) or display_id
|
||||||
|
|
||||||
formats, subtitles = self._get_formats_and_subtitles(info, video_id)
|
formats, subtitles = self._get_formats_and_subtitles(info, video_id)
|
||||||
|
|
|
@ -10,10 +10,11 @@
|
||||||
|
|
||||||
|
|
||||||
def _fmt_url(url):
|
def _fmt_url(url):
|
||||||
return functools.partial(format_field, template=url, default=None)
|
return format_field(template=url, default=None)
|
||||||
|
|
||||||
|
|
||||||
class TelewebionIE(InfoExtractor):
|
class TelewebionIE(InfoExtractor):
|
||||||
|
_WORKING = False
|
||||||
_VALID_URL = r'https?://(?:www\.)?telewebion\.com/episode/(?P<id>(?:0x[a-fA-F\d]+|\d+))'
|
_VALID_URL = r'https?://(?:www\.)?telewebion\.com/episode/(?P<id>(?:0x[a-fA-F\d]+|\d+))'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.telewebion.com/episode/0x1b3139c/',
|
'url': 'http://www.telewebion.com/episode/0x1b3139c/',
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import functools
|
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import string
|
import string
|
||||||
|
@ -278,7 +277,7 @@ def _real_extract(self, url):
|
||||||
webpage)]
|
webpage)]
|
||||||
|
|
||||||
return self.playlist_from_matches(
|
return self.playlist_from_matches(
|
||||||
episode_paths, series_id, ie=VQQVideoIE, getter=functools.partial(urljoin, url),
|
episode_paths, series_id, ie=VQQVideoIE, getter=urljoin(url),
|
||||||
title=self._get_clean_title(traverse_obj(webpage_metadata, ('coverInfo', 'title'))
|
title=self._get_clean_title(traverse_obj(webpage_metadata, ('coverInfo', 'title'))
|
||||||
or self._og_search_title(webpage)),
|
or self._og_search_title(webpage)),
|
||||||
description=(traverse_obj(webpage_metadata, ('coverInfo', 'description'))
|
description=(traverse_obj(webpage_metadata, ('coverInfo', 'description'))
|
||||||
|
@ -328,7 +327,7 @@ def _extract_series(self, url, ie):
|
||||||
or re.findall(r'<a[^>]+class="play-video__link"[^>]+href="(?P<path>[^"]+)', webpage))
|
or re.findall(r'<a[^>]+class="play-video__link"[^>]+href="(?P<path>[^"]+)', webpage))
|
||||||
|
|
||||||
return self.playlist_from_matches(
|
return self.playlist_from_matches(
|
||||||
episode_paths, series_id, ie=ie, getter=functools.partial(urljoin, url),
|
episode_paths, series_id, ie=ie, getter=urljoin(url),
|
||||||
title=self._get_clean_title(traverse_obj(webpage_metadata, ('coverInfo', 'title'))
|
title=self._get_clean_title(traverse_obj(webpage_metadata, ('coverInfo', 'title'))
|
||||||
or self._og_search_title(webpage)),
|
or self._og_search_title(webpage)),
|
||||||
description=(traverse_obj(webpage_metadata, ('coverInfo', 'description'))
|
description=(traverse_obj(webpage_metadata, ('coverInfo', 'description'))
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import functools
|
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
@ -161,4 +160,4 @@ def _real_extract(self, url):
|
||||||
return self.playlist_from_matches(
|
return self.playlist_from_matches(
|
||||||
self._entries(urljoin(url, episodes_carousel['loadMoreUrl']), playlist_id),
|
self._entries(urljoin(url, episodes_carousel['loadMoreUrl']), playlist_id),
|
||||||
playlist_id, traverse_obj(season_info, ('content', 0, 'title', {str})),
|
playlist_id, traverse_obj(season_info, ('content', 0, 'title', {str})),
|
||||||
getter=functools.partial(urljoin, url))
|
getter=urljoin(url))
|
||||||
|
|
|
@ -131,4 +131,4 @@ def _real_extract(self, url):
|
||||||
|
|
||||||
return self.playlist_from_matches(
|
return self.playlist_from_matches(
|
||||||
self._entries(url, podcast_id), podcast_id, title, description=description,
|
self._entries(url, podcast_id), podcast_id, title, description=description,
|
||||||
ie=TheGuardianPodcastIE, getter=lambda x: urljoin('https://www.theguardian.com', x))
|
ie=TheGuardianPodcastIE, getter=urljoin('https://www.theguardian.com'))
|
||||||
|
|
|
@ -469,7 +469,7 @@ def extract_addr(addr, add_meta={}):
|
||||||
aweme_detail, aweme_id, traverse_obj(author_info, 'uploader', 'uploader_id', 'channel_id')),
|
aweme_detail, aweme_id, traverse_obj(author_info, 'uploader', 'uploader_id', 'channel_id')),
|
||||||
'thumbnails': thumbnails,
|
'thumbnails': thumbnails,
|
||||||
'duration': (traverse_obj(video_info, (
|
'duration': (traverse_obj(video_info, (
|
||||||
(None, 'download_addr'), 'duration', {functools.partial(int_or_none, scale=1000)}, any))
|
(None, 'download_addr'), 'duration', {int_or_none(scale=1000)}, any))
|
||||||
or traverse_obj(music_info, ('duration', {int_or_none}))),
|
or traverse_obj(music_info, ('duration', {int_or_none}))),
|
||||||
'availability': self._availability(
|
'availability': self._availability(
|
||||||
is_private='Private' in labels,
|
is_private='Private' in labels,
|
||||||
|
@ -583,7 +583,7 @@ def _parse_aweme_video_web(self, aweme_detail, webpage_url, video_id, extract_fl
|
||||||
author_info, ['uploader', 'uploader_id'], self._UPLOADER_URL_FORMAT, default=None),
|
author_info, ['uploader', 'uploader_id'], self._UPLOADER_URL_FORMAT, default=None),
|
||||||
**traverse_obj(aweme_detail, ('music', {
|
**traverse_obj(aweme_detail, ('music', {
|
||||||
'track': ('title', {str}),
|
'track': ('title', {str}),
|
||||||
'album': ('album', {str}, {lambda x: x or None}),
|
'album': ('album', {str}, filter),
|
||||||
'artists': ('authorName', {str}, {lambda x: re.split(r'(?:, | & )', x) if x else None}),
|
'artists': ('authorName', {str}, {lambda x: re.split(r'(?:, | & )', x) if x else None}),
|
||||||
'duration': ('duration', {int_or_none}),
|
'duration': ('duration', {int_or_none}),
|
||||||
})),
|
})),
|
||||||
|
@ -591,7 +591,7 @@ def _parse_aweme_video_web(self, aweme_detail, webpage_url, video_id, extract_fl
|
||||||
'title': ('desc', {str}),
|
'title': ('desc', {str}),
|
||||||
'description': ('desc', {str}),
|
'description': ('desc', {str}),
|
||||||
# audio-only slideshows have a video duration of 0 and an actual audio duration
|
# audio-only slideshows have a video duration of 0 and an actual audio duration
|
||||||
'duration': ('video', 'duration', {int_or_none}, {lambda x: x or None}),
|
'duration': ('video', 'duration', {int_or_none}, filter),
|
||||||
'timestamp': ('createTime', {int_or_none}),
|
'timestamp': ('createTime', {int_or_none}),
|
||||||
}),
|
}),
|
||||||
**traverse_obj(aweme_detail, ('stats', {
|
**traverse_obj(aweme_detail, ('stats', {
|
||||||
|
@ -1493,7 +1493,7 @@ def _real_extract(self, url):
|
||||||
|
|
||||||
sdk_params = traverse_obj(stream, ('main', 'sdk_params', {parse_inner}, {
|
sdk_params = traverse_obj(stream, ('main', 'sdk_params', {parse_inner}, {
|
||||||
'vcodec': ('VCodec', {str}),
|
'vcodec': ('VCodec', {str}),
|
||||||
'tbr': ('vbitrate', {lambda x: int_or_none(x, 1000)}),
|
'tbr': ('vbitrate', {int_or_none(scale=1000)}),
|
||||||
'resolution': ('resolution', {lambda x: re.match(r'(?i)\d+x\d+|\d+p', x).group().lower()}),
|
'resolution': ('resolution', {lambda x: re.match(r'(?i)\d+x\d+|\d+p', x).group().lower()}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import functools
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .brightcove import BrightcoveNewIE
|
from .brightcove import BrightcoveNewIE
|
||||||
|
@ -68,7 +67,7 @@ def _real_extract(self, url):
|
||||||
'episode': episode,
|
'episode': episode,
|
||||||
**traverse_obj(entity, {
|
**traverse_obj(entity, {
|
||||||
'description': ('longDescription', {str}),
|
'description': ('longDescription', {str}),
|
||||||
'duration': ('durationMillis', {functools.partial(float_or_none, scale=1000)}),
|
'duration': ('durationMillis', {float_or_none(scale=1000)}),
|
||||||
'channel': ('knownEntities', 'channel', 'name', {str}),
|
'channel': ('knownEntities', 'channel', 'name', {str}),
|
||||||
'series': ('knownEntities', 'videoShow', 'name', {str}),
|
'series': ('knownEntities', 'videoShow', 'name', {str}),
|
||||||
'season_number': ('slug', {lambda x: re.search(r'/s(?:ai|ea)son-(\d+)/', x)}, 1, {int_or_none}),
|
'season_number': ('slug', {lambda x: re.search(r'/s(?:ai|ea)son-(\d+)/', x)}, 1, {int_or_none}),
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import functools
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
@ -72,9 +71,9 @@ def _process_video_json(self, json_data, video_id):
|
||||||
'id': ('facadeUuid', {str}),
|
'id': ('facadeUuid', {str}),
|
||||||
'display_id': ('videoId', {int}, {str_or_none}),
|
'display_id': ('videoId', {int}, {str_or_none}),
|
||||||
'title': ('name', {str}),
|
'title': ('name', {str}),
|
||||||
'description': ('description', {str}, {unescapeHTML}, {lambda x: x or None}),
|
'description': ('description', {str}, {unescapeHTML}, filter),
|
||||||
'duration': ((
|
'duration': ((
|
||||||
('milliseconds', {functools.partial(float_or_none, scale=1000)}),
|
('milliseconds', {float_or_none(scale=1000)}),
|
||||||
('seconds', {int_or_none})), any),
|
('seconds', {int_or_none})), any),
|
||||||
'thumbnails': ('thumbnailUrls', ('small', 'normal'), {'url': {url_or_none}}),
|
'thumbnails': ('thumbnailUrls', ('small', 'normal'), {'url': {url_or_none}}),
|
||||||
'tags': ('tags', ..., 'name', {str}),
|
'tags': ('tags', ..., 'name', {str}),
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import functools
|
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
@ -171,7 +170,7 @@ def _real_extract(self, url):
|
||||||
**traverse_obj(data, {
|
**traverse_obj(data, {
|
||||||
'title': ('title', {str}),
|
'title': ('title', {str}),
|
||||||
'description': ('shortDescription', {str}),
|
'description': ('shortDescription', {str}),
|
||||||
'duration': ('duration', {functools.partial(float_or_none, scale=1000)}),
|
'duration': ('duration', {float_or_none(scale=1000)}),
|
||||||
'thumbnail': ('posterImageUrl', {url_or_none}),
|
'thumbnail': ('posterImageUrl', {url_or_none}),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ def _extract_formats(self, video_info):
|
||||||
'format': ('quality_desc', {str}),
|
'format': ('quality_desc', {str}),
|
||||||
'format_id': ('label', {str}),
|
'format_id': ('label', {str}),
|
||||||
'ext': ('mime', {mimetype2ext}),
|
'ext': ('mime', {mimetype2ext}),
|
||||||
'tbr': ('bitrate', {int_or_none}, {lambda x: x or None}),
|
'tbr': ('bitrate', {int_or_none}, filter),
|
||||||
'vcodec': ('video_codecs', {str}),
|
'vcodec': ('video_codecs', {str}),
|
||||||
'fps': ('fps', {int_or_none}),
|
'fps': ('fps', {int_or_none}),
|
||||||
'width': ('width', {int_or_none}),
|
'width': ('width', {int_or_none}),
|
||||||
|
@ -107,14 +107,14 @@ def _parse_video_info(self, video_info, video_id=None):
|
||||||
**traverse_obj(video_info, {
|
**traverse_obj(video_info, {
|
||||||
'id': (('id', 'id_str', 'mid'), {str_or_none}),
|
'id': (('id', 'id_str', 'mid'), {str_or_none}),
|
||||||
'display_id': ('mblogid', {str_or_none}),
|
'display_id': ('mblogid', {str_or_none}),
|
||||||
'title': ('page_info', 'media_info', ('video_title', 'kol_title', 'name'), {str}, {lambda x: x or None}),
|
'title': ('page_info', 'media_info', ('video_title', 'kol_title', 'name'), {str}, filter),
|
||||||
'description': ('text_raw', {str}),
|
'description': ('text_raw', {str}),
|
||||||
'duration': ('page_info', 'media_info', 'duration', {int_or_none}),
|
'duration': ('page_info', 'media_info', 'duration', {int_or_none}),
|
||||||
'timestamp': ('page_info', 'media_info', 'video_publish_time', {int_or_none}),
|
'timestamp': ('page_info', 'media_info', 'video_publish_time', {int_or_none}),
|
||||||
'thumbnail': ('page_info', 'page_pic', {url_or_none}),
|
'thumbnail': ('page_info', 'page_pic', {url_or_none}),
|
||||||
'uploader': ('user', 'screen_name', {str}),
|
'uploader': ('user', 'screen_name', {str}),
|
||||||
'uploader_id': ('user', ('id', 'id_str'), {str_or_none}),
|
'uploader_id': ('user', ('id', 'id_str'), {str_or_none}),
|
||||||
'uploader_url': ('user', 'profile_url', {lambda x: urljoin('https://weibo.com/', x)}),
|
'uploader_url': ('user', 'profile_url', {urljoin('https://weibo.com/')}),
|
||||||
'view_count': ('page_info', 'media_info', 'online_users_number', {int_or_none}),
|
'view_count': ('page_info', 'media_info', 'online_users_number', {int_or_none}),
|
||||||
'like_count': ('attitudes_count', {int_or_none}),
|
'like_count': ('attitudes_count', {int_or_none}),
|
||||||
'repost_count': ('reposts_count', {int_or_none}),
|
'repost_count': ('reposts_count', {int_or_none}),
|
||||||
|
|
|
@ -159,8 +159,8 @@ def _parse_post_meta(self, metadata):
|
||||||
'creators': ('community', 'communityName', {str}, all),
|
'creators': ('community', 'communityName', {str}, all),
|
||||||
'channel_id': (('community', 'author'), 'communityId', {str_or_none}),
|
'channel_id': (('community', 'author'), 'communityId', {str_or_none}),
|
||||||
'duration': ('extension', 'video', 'playTime', {float_or_none}),
|
'duration': ('extension', 'video', 'playTime', {float_or_none}),
|
||||||
'timestamp': ('publishedAt', {lambda x: int_or_none(x, 1000)}),
|
'timestamp': ('publishedAt', {int_or_none(scale=1000)}),
|
||||||
'release_timestamp': ('extension', 'video', 'onAirStartAt', {lambda x: int_or_none(x, 1000)}),
|
'release_timestamp': ('extension', 'video', 'onAirStartAt', {int_or_none(scale=1000)}),
|
||||||
'thumbnail': ('extension', (('mediaInfo', 'thumbnail', 'url'), ('video', 'thumb')), {url_or_none}),
|
'thumbnail': ('extension', (('mediaInfo', 'thumbnail', 'url'), ('video', 'thumb')), {url_or_none}),
|
||||||
'view_count': ('extension', 'video', 'playCount', {int_or_none}),
|
'view_count': ('extension', 'video', 'playCount', {int_or_none}),
|
||||||
'like_count': ('extension', 'video', 'likeCount', {int_or_none}),
|
'like_count': ('extension', 'video', 'likeCount', {int_or_none}),
|
||||||
|
@ -469,7 +469,7 @@ def _real_extract(self, url):
|
||||||
'creator': (('community', 'author'), 'communityName', {str}),
|
'creator': (('community', 'author'), 'communityName', {str}),
|
||||||
'channel_id': (('community', 'author'), 'communityId', {str_or_none}),
|
'channel_id': (('community', 'author'), 'communityId', {str_or_none}),
|
||||||
'duration': ('extension', 'moment', 'video', 'uploadInfo', 'playTime', {float_or_none}),
|
'duration': ('extension', 'moment', 'video', 'uploadInfo', 'playTime', {float_or_none}),
|
||||||
'timestamp': ('publishedAt', {lambda x: int_or_none(x, 1000)}),
|
'timestamp': ('publishedAt', {int_or_none(scale=1000)}),
|
||||||
'thumbnail': ('extension', 'moment', 'video', 'uploadInfo', 'imageUrl', {url_or_none}),
|
'thumbnail': ('extension', 'moment', 'video', 'uploadInfo', 'imageUrl', {url_or_none}),
|
||||||
'like_count': ('emotionCount', {int_or_none}),
|
'like_count': ('emotionCount', {int_or_none}),
|
||||||
'comment_count': ('commentCount', {int_or_none}),
|
'comment_count': ('commentCount', {int_or_none}),
|
||||||
|
|
|
@ -78,7 +78,7 @@ def _extract_formats(self, wvplayer_props):
|
||||||
}
|
}
|
||||||
|
|
||||||
src_path = f'{wvplayer_props["srcVID"]}/{wvplayer_props["srcUID"]}/{wvplayer_props["srcNAME"]}'
|
src_path = f'{wvplayer_props["srcVID"]}/{wvplayer_props["srcUID"]}/{wvplayer_props["srcNAME"]}'
|
||||||
for res in traverse_obj(wvplayer_props, ('resolutions', ..., {int}, {lambda x: x or None})):
|
for res in traverse_obj(wvplayer_props, ('resolutions', ..., {int}, filter)):
|
||||||
format_id = str(-(res // -2) - 1)
|
format_id = str(-(res // -2) - 1)
|
||||||
yield {
|
yield {
|
||||||
'acodec': 'mp4a.40.2',
|
'acodec': 'mp4a.40.2',
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import functools
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
@ -51,7 +50,7 @@ def _real_extract(self, url):
|
||||||
'tbr': ('avgBitrate', {int_or_none}),
|
'tbr': ('avgBitrate', {int_or_none}),
|
||||||
'format': ('qualityType', {str}),
|
'format': ('qualityType', {str}),
|
||||||
'filesize': ('size', {int_or_none}),
|
'filesize': ('size', {int_or_none}),
|
||||||
'duration': ('duration', {functools.partial(float_or_none, scale=1000)}),
|
'duration': ('duration', {float_or_none(scale=1000)}),
|
||||||
})
|
})
|
||||||
|
|
||||||
formats.extend(traverse_obj(info, (('mediaUrl', ('backupUrls', ...)), {
|
formats.extend(traverse_obj(info, (('mediaUrl', ('backupUrls', ...)), {
|
||||||
|
|
|
@ -247,7 +247,7 @@ def _entries(self, url, pl_id, html=None, page_num=None):
|
||||||
if not html:
|
if not html:
|
||||||
return
|
return
|
||||||
for element in get_elements_html_by_class('video-title', html):
|
for element in get_elements_html_by_class('video-title', html):
|
||||||
if video_url := traverse_obj(element, ({extract_attributes}, 'href', {lambda x: urljoin(url, x)})):
|
if video_url := traverse_obj(element, ({extract_attributes}, 'href', {urljoin(url)})):
|
||||||
yield self.url_result(video_url)
|
yield self.url_result(video_url)
|
||||||
|
|
||||||
if page_num is not None:
|
if page_num is not None:
|
||||||
|
|
|
@ -3611,7 +3611,7 @@ def _extract_heatmap(self, data):
|
||||||
'frameworkUpdates', 'entityBatchUpdate', 'mutations',
|
'frameworkUpdates', 'entityBatchUpdate', 'mutations',
|
||||||
lambda _, v: v['payload']['macroMarkersListEntity']['markersList']['markerType'] == 'MARKER_TYPE_HEATMAP',
|
lambda _, v: v['payload']['macroMarkersListEntity']['markersList']['markerType'] == 'MARKER_TYPE_HEATMAP',
|
||||||
'payload', 'macroMarkersListEntity', 'markersList', 'markers', ..., {
|
'payload', 'macroMarkersListEntity', 'markersList', 'markers', ..., {
|
||||||
'start_time': ('startMillis', {functools.partial(float_or_none, scale=1000)}),
|
'start_time': ('startMillis', {float_or_none(scale=1000)}),
|
||||||
'end_time': {lambda x: (int(x['startMillis']) + int(x['durationMillis'])) / 1000},
|
'end_time': {lambda x: (int(x['startMillis']) + int(x['durationMillis'])) / 1000},
|
||||||
'value': ('intensityScoreNormalized', {float_or_none}),
|
'value': ('intensityScoreNormalized', {float_or_none}),
|
||||||
})) or None
|
})) or None
|
||||||
|
@ -3637,7 +3637,7 @@ def _extract_comment(self, entities, parent=None):
|
||||||
'author_is_verified': ('author', 'isVerified', {bool}),
|
'author_is_verified': ('author', 'isVerified', {bool}),
|
||||||
'author_url': ('author', 'channelCommand', 'innertubeCommand', (
|
'author_url': ('author', 'channelCommand', 'innertubeCommand', (
|
||||||
('browseEndpoint', 'canonicalBaseUrl'), ('commandMetadata', 'webCommandMetadata', 'url'),
|
('browseEndpoint', 'canonicalBaseUrl'), ('commandMetadata', 'webCommandMetadata', 'url'),
|
||||||
), {lambda x: urljoin('https://www.youtube.com', x)}),
|
), {urljoin('https://www.youtube.com')}),
|
||||||
}, get_all=False),
|
}, get_all=False),
|
||||||
'is_favorited': (None if toolbar_entity_payload is None else
|
'is_favorited': (None if toolbar_entity_payload is None else
|
||||||
toolbar_entity_payload.get('heartState') == 'TOOLBAR_HEART_STATE_HEARTED'),
|
toolbar_entity_payload.get('heartState') == 'TOOLBAR_HEART_STATE_HEARTED'),
|
||||||
|
@ -4304,7 +4304,7 @@ def build_fragments(f):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
tbr = float_or_none(fmt.get('averageBitrate') or fmt.get('bitrate'), 1000)
|
tbr = float_or_none(fmt.get('averageBitrate') or fmt.get('bitrate'), 1000)
|
||||||
format_duration = traverse_obj(fmt, ('approxDurationMs', {lambda x: float_or_none(x, 1000)}))
|
format_duration = traverse_obj(fmt, ('approxDurationMs', {float_or_none(scale=1000)}))
|
||||||
# Some formats may have much smaller duration than others (possibly damaged during encoding)
|
# Some formats may have much smaller duration than others (possibly damaged during encoding)
|
||||||
# E.g. 2-nOtRESiUc Ref: https://github.com/yt-dlp/yt-dlp/issues/2823
|
# E.g. 2-nOtRESiUc Ref: https://github.com/yt-dlp/yt-dlp/issues/2823
|
||||||
# Make sure to avoid false positives with small duration differences.
|
# Make sure to avoid false positives with small duration differences.
|
||||||
|
|
|
@ -109,7 +109,7 @@ def _real_extract(self, url):
|
||||||
'uploader': ('profile', 'name', {str}),
|
'uploader': ('profile', 'name', {str}),
|
||||||
'uploader_id': ('profile', 'id', {str_or_none}),
|
'uploader_id': ('profile', 'id', {str_or_none}),
|
||||||
'release_timestamp': ('stream', 'start', 'timestamp', {int_or_none}),
|
'release_timestamp': ('stream', 'start', 'timestamp', {int_or_none}),
|
||||||
'categories': ('event', 'genres', ..., {lambda x: x or None}),
|
'categories': ('event', 'genres', ..., filter),
|
||||||
}),
|
}),
|
||||||
'alt_title': traverse_obj(initial_event_info, ('title', {str})),
|
'alt_title': traverse_obj(initial_event_info, ('title', {str})),
|
||||||
'thumbnails': [{'url': url, 'id': url_basename(url)} for url in thumbnail_urls if url_or_none(url)],
|
'thumbnails': [{'url': url, 'id': url_basename(url)} for url in thumbnail_urls if url_or_none(url)],
|
||||||
|
|
|
@ -700,7 +700,8 @@ def _alias_callback(option, opt_str, value, parser, opts, nargs):
|
||||||
selection.add_option(
|
selection.add_option(
|
||||||
'--break-on-existing',
|
'--break-on-existing',
|
||||||
action='store_true', dest='break_on_existing', default=False,
|
action='store_true', dest='break_on_existing', default=False,
|
||||||
help='Stop the download process when encountering a file that is in the archive')
|
help='Stop the download process when encountering a file that is in the archive '
|
||||||
|
'supplied with the --download-archive option')
|
||||||
selection.add_option(
|
selection.add_option(
|
||||||
'--no-break-on-existing',
|
'--no-break-on-existing',
|
||||||
action='store_false', dest='break_on_existing',
|
action='store_false', dest='break_on_existing',
|
||||||
|
|
|
@ -5142,6 +5142,7 @@ class _UnsafeExtensionError(Exception):
|
||||||
'rm',
|
'rm',
|
||||||
'swf',
|
'swf',
|
||||||
'ts',
|
'ts',
|
||||||
|
'vid',
|
||||||
'vob',
|
'vob',
|
||||||
'vp9',
|
'vp9',
|
||||||
|
|
||||||
|
@ -5174,6 +5175,7 @@ class _UnsafeExtensionError(Exception):
|
||||||
'heic',
|
'heic',
|
||||||
'ico',
|
'ico',
|
||||||
'image',
|
'image',
|
||||||
|
'jfif',
|
||||||
'jng',
|
'jng',
|
||||||
'jpe',
|
'jpe',
|
||||||
'jpeg',
|
'jpeg',
|
||||||
|
|
Loading…
Reference in a new issue