mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2024-11-27 10:31:29 +00:00
parent
fc5fa964c7
commit
6970b6005e
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -14,7 +14,10 @@ cookies
|
||||||
*.frag.urls
|
*.frag.urls
|
||||||
*.info.json
|
*.info.json
|
||||||
*.live_chat.json
|
*.live_chat.json
|
||||||
|
*.meta
|
||||||
*.part*
|
*.part*
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
*.unknown_video
|
*.unknown_video
|
||||||
*.ytdl
|
*.ytdl
|
||||||
.cache/
|
.cache/
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -15,7 +15,7 @@ pypi-files: AUTHORS Changelog.md LICENSE README.md README.txt supportedsites com
|
||||||
|
|
||||||
clean-test:
|
clean-test:
|
||||||
rm -rf test/testdata/player-*.js tmp/ *.annotations.xml *.aria2 *.description *.dump *.frag \
|
rm -rf test/testdata/player-*.js tmp/ *.annotations.xml *.aria2 *.description *.dump *.frag \
|
||||||
*.frag.aria2 *.frag.urls *.info.json *.live_chat.json *.part* *.unknown_video *.ytdl \
|
*.frag.aria2 *.frag.urls *.info.json *.live_chat.json *.meta *.part* *.tmp *.temp *.unknown_video *.ytdl \
|
||||||
*.3gp *.ape *.avi *.desktop *.flac *.flv *.jpeg *.jpg *.m4a *.m4v *.mhtml *.mkv *.mov *.mp3 \
|
*.3gp *.ape *.avi *.desktop *.flac *.flv *.jpeg *.jpg *.m4a *.m4v *.mhtml *.mkv *.mov *.mp3 \
|
||||||
*.mp4 *.ogg *.opus *.png *.sbv *.srt *.swf *.swp *.ttml *.url *.vtt *.wav *.webloc *.webm *.webp
|
*.mp4 *.ogg *.opus *.png *.sbv *.srt *.swf *.swp *.ttml *.url *.vtt *.wav *.webloc *.webm *.webp
|
||||||
clean-dist:
|
clean-dist:
|
||||||
|
|
|
@ -235,7 +235,7 @@ def sanitize(key, value):
|
||||||
}
|
}
|
||||||
|
|
||||||
# display_id may be generated from id
|
# display_id may be generated from id
|
||||||
if test_info_dict.get('display_id') == test_info_dict['id']:
|
if test_info_dict.get('display_id') == test_info_dict.get('id'):
|
||||||
test_info_dict.pop('display_id')
|
test_info_dict.pop('display_id')
|
||||||
|
|
||||||
return test_info_dict
|
return test_info_dict
|
||||||
|
|
|
@ -2682,7 +2682,7 @@ def process_subtitles(self, video_id, normal_subtitles, automatic_captions):
|
||||||
def _forceprint(self, tmpl, info_dict):
|
def _forceprint(self, tmpl, info_dict):
|
||||||
mobj = re.match(r'\w+(=?)$', tmpl)
|
mobj = re.match(r'\w+(=?)$', tmpl)
|
||||||
if mobj and mobj.group(1):
|
if mobj and mobj.group(1):
|
||||||
tmpl = f'{tmpl[:-1]} = %({tmpl[:-1]})s'
|
tmpl = f'{tmpl[:-1]} = %({tmpl[:-1]})r'
|
||||||
elif mobj:
|
elif mobj:
|
||||||
tmpl = '%({})s'.format(tmpl)
|
tmpl = '%({})s'.format(tmpl)
|
||||||
|
|
||||||
|
@ -3486,7 +3486,7 @@ def render_thumbnails_table(self, info_dict):
|
||||||
return None
|
return None
|
||||||
return render_table(
|
return render_table(
|
||||||
self._list_format_headers('ID', 'Width', 'Height', 'URL'),
|
self._list_format_headers('ID', 'Width', 'Height', 'URL'),
|
||||||
[[t['id'], t.get('width', 'unknown'), t.get('height', 'unknown'), t['url']] for t in thumbnails])
|
[[t.get('id'), t.get('width', 'unknown'), t.get('height', 'unknown'), t['url']] for t in thumbnails])
|
||||||
|
|
||||||
def render_subtitles_table(self, video_id, subtitles):
|
def render_subtitles_table(self, video_id, subtitles):
|
||||||
def _row(lang, formats):
|
def _row(lang, formats):
|
||||||
|
|
|
@ -78,11 +78,11 @@ def _real_extract(self, url):
|
||||||
'height': try_get(video, lambda x: x['res']['height'], expected_type=int),
|
'height': try_get(video, lambda x: x['res']['height'], expected_type=int),
|
||||||
} for video in try_get(data_json, lambda x: x['video']['mp4'], expected_type=list) or [] if video.get('$url')]
|
} for video in try_get(data_json, lambda x: x['video']['mp4'], expected_type=list) or [] if video.get('$url')]
|
||||||
if manifests.get('hls'):
|
if manifests.get('hls'):
|
||||||
m3u8_frmts, m3u8_subs = self._parse_m3u8_formats_and_subtitles(manifests['hls'], id)
|
m3u8_frmts, m3u8_subs = self._parse_m3u8_formats_and_subtitles(manifests['hls'], video_id=id)
|
||||||
formats.extend(m3u8_frmts)
|
formats.extend(m3u8_frmts)
|
||||||
subtitles = self._merge_subtitles(subtitles, m3u8_subs)
|
subtitles = self._merge_subtitles(subtitles, m3u8_subs)
|
||||||
if manifests.get('dash'):
|
if manifests.get('dash'):
|
||||||
dash_frmts, dash_subs = self._parse_mpd_formats_and_subtitles(manifests['dash'], id)
|
dash_frmts, dash_subs = self._parse_mpd_formats_and_subtitles(manifests['dash'])
|
||||||
formats.extend(dash_frmts)
|
formats.extend(dash_frmts)
|
||||||
subtitles = self._merge_subtitles(subtitles, dash_subs)
|
subtitles = self._merge_subtitles(subtitles, dash_subs)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
|
@ -3504,8 +3504,6 @@ def _live_title(self, name):
|
||||||
|
|
||||||
def _int(self, v, name, fatal=False, **kwargs):
|
def _int(self, v, name, fatal=False, **kwargs):
|
||||||
res = int_or_none(v, **kwargs)
|
res = int_or_none(v, **kwargs)
|
||||||
if 'get_attr' in kwargs:
|
|
||||||
print(getattr(v, kwargs['get_attr']))
|
|
||||||
if res is None:
|
if res is None:
|
||||||
msg = 'Failed to extract %s: Could not parse value %r' % (name, v)
|
msg = 'Failed to extract %s: Could not parse value %r' % (name, v)
|
||||||
if fatal:
|
if fatal:
|
||||||
|
|
|
@ -74,13 +74,11 @@ def _parse_mp4(self, metadata):
|
||||||
tbr = int_or_none(bitrate)
|
tbr = int_or_none(bitrate)
|
||||||
vbr = int_or_none(self._search_regex(
|
vbr = int_or_none(self._search_regex(
|
||||||
r'-(\d+)\.mp4', video_path, 'vbr', default=None))
|
r'-(\d+)\.mp4', video_path, 'vbr', default=None))
|
||||||
abr = tbr - vbr if tbr and vbr else None
|
|
||||||
video_formats.append({
|
video_formats.append({
|
||||||
'format_id': bitrate,
|
'format_id': bitrate,
|
||||||
'url': url,
|
'url': url,
|
||||||
'tbr': tbr,
|
'tbr': tbr,
|
||||||
'vbr': vbr,
|
'vbr': vbr,
|
||||||
'abr': abr,
|
|
||||||
})
|
})
|
||||||
return video_formats
|
return video_formats
|
||||||
|
|
||||||
|
@ -121,6 +119,7 @@ def _real_extract(self, url):
|
||||||
video_formats = self._parse_mp4(metadata)
|
video_formats = self._parse_mp4(metadata)
|
||||||
if video_formats is None:
|
if video_formats is None:
|
||||||
video_formats = self._parse_flv(metadata)
|
video_formats = self._parse_flv(metadata)
|
||||||
|
self._sort_formats(video_formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
|
|
@ -3804,6 +3804,7 @@ def _real_extract(self, url):
|
||||||
json_ld['formats'], json_ld['subtitles'] = self._extract_m3u8_formats_and_subtitles(
|
json_ld['formats'], json_ld['subtitles'] = self._extract_m3u8_formats_and_subtitles(
|
||||||
json_ld['url'], video_id, 'mp4')
|
json_ld['url'], video_id, 'mp4')
|
||||||
json_ld.pop('url')
|
json_ld.pop('url')
|
||||||
|
self._sort_formats(json_ld['formats'])
|
||||||
return merge_dicts(json_ld, info_dict)
|
return merge_dicts(json_ld, info_dict)
|
||||||
|
|
||||||
def check_video(vurl):
|
def check_video(vurl):
|
||||||
|
@ -3858,7 +3859,7 @@ def filter_video(urls):
|
||||||
protocol, _, _ = url.partition('/')
|
protocol, _, _ = url.partition('/')
|
||||||
thumbnail = protocol + thumbnail
|
thumbnail = protocol + thumbnail
|
||||||
|
|
||||||
url_keys = list(filter(re.compile(r'video_url|video_alt_url\d+').fullmatch, flashvars.keys()))
|
url_keys = list(filter(re.compile(r'video_url|video_alt_url\d*').fullmatch, flashvars.keys()))
|
||||||
formats = []
|
formats = []
|
||||||
for key in url_keys:
|
for key in url_keys:
|
||||||
if '/get_file/' not in flashvars[key]:
|
if '/get_file/' not in flashvars[key]:
|
||||||
|
|
|
@ -177,9 +177,6 @@ def build_player_url(cls, video_id, integration, origin_url=None):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _extract_urls(cls, webpage, origin_url):
|
def _extract_urls(cls, webpage, origin_url):
|
||||||
# in comparison with _VALID_URL:
|
|
||||||
# * make the scheme optional
|
|
||||||
# * simplify the query string part; after extracting iframe src, the URL will be matched again
|
|
||||||
VALID_SRC = rf'(?:https?:)?{cls._BASE_PLAYER_URL_RE}\?(?:(?!(?P=_q1)).)+'
|
VALID_SRC = rf'(?:https?:)?{cls._BASE_PLAYER_URL_RE}\?(?:(?!(?P=_q1)).)+'
|
||||||
|
|
||||||
# https://docs.glomex.com/publisher/video-player-integration/javascript-api/
|
# https://docs.glomex.com/publisher/video-player-integration/javascript-api/
|
||||||
|
|
|
@ -257,7 +257,7 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
||||||
|
|
||||||
_RESERVED_NAMES = (
|
_RESERVED_NAMES = (
|
||||||
r'channel|c|user|playlist|watch|w|v|embed|e|watch_popup|clip|'
|
r'channel|c|user|playlist|watch|w|v|embed|e|watch_popup|clip|'
|
||||||
r'shorts|movies|results|shared|hashtag|trending|feed|feeds|'
|
r'shorts|movies|results|shared|hashtag|trending|explore|feed|feeds|'
|
||||||
r'browse|oembed|get_video_info|iframe_api|s/player|'
|
r'browse|oembed|get_video_info|iframe_api|s/player|'
|
||||||
r'storefront|oops|index|account|reporthistory|t/terms|about|upload|signin|logout')
|
r'storefront|oops|index|account|reporthistory|t/terms|about|upload|signin|logout')
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
from .exec import ExecPP, ExecAfterDownloadPP
|
from .exec import ExecPP, ExecAfterDownloadPP
|
||||||
from .ffmpeg import (
|
from .ffmpeg import (
|
||||||
FFmpegPostProcessor,
|
FFmpegPostProcessor,
|
||||||
|
FFmpegCopyStreamPP,
|
||||||
FFmpegConcatPP,
|
FFmpegConcatPP,
|
||||||
FFmpegEmbedSubtitlePP,
|
FFmpegEmbedSubtitlePP,
|
||||||
FFmpegExtractAudioPP,
|
FFmpegExtractAudioPP,
|
||||||
|
|
|
@ -568,7 +568,7 @@ def run(self, info):
|
||||||
else f'already is in target format {source_ext}' if source_ext == target_ext
|
else f'already is in target format {source_ext}' if source_ext == target_ext
|
||||||
else None)
|
else None)
|
||||||
if _skip_msg:
|
if _skip_msg:
|
||||||
self.to_screen(f'Not {self._ACTION} media file {filename!r}; {_skip_msg}')
|
self.to_screen(f'Not {self._ACTION} media file "{filename}"; {_skip_msg}')
|
||||||
return [], info
|
return [], info
|
||||||
|
|
||||||
outpath = replace_extension(filename, target_ext, source_ext)
|
outpath = replace_extension(filename, target_ext, source_ext)
|
||||||
|
@ -917,7 +917,7 @@ def run(self, info):
|
||||||
return [], info
|
return [], info
|
||||||
|
|
||||||
|
|
||||||
class FFmpegCopyStreamPostProcessor(FFmpegFixupPostProcessor):
|
class FFmpegCopyStreamPP(FFmpegFixupPostProcessor):
|
||||||
MESSAGE = 'Copying stream'
|
MESSAGE = 'Copying stream'
|
||||||
|
|
||||||
@PostProcessor._restrict_to(images=False)
|
@PostProcessor._restrict_to(images=False)
|
||||||
|
@ -926,11 +926,11 @@ def run(self, info):
|
||||||
return [], info
|
return [], info
|
||||||
|
|
||||||
|
|
||||||
class FFmpegFixupDurationPP(FFmpegCopyStreamPostProcessor):
|
class FFmpegFixupDurationPP(FFmpegCopyStreamPP):
|
||||||
MESSAGE = 'Fixing video duration'
|
MESSAGE = 'Fixing video duration'
|
||||||
|
|
||||||
|
|
||||||
class FFmpegFixupDuplicateMoovPP(FFmpegCopyStreamPostProcessor):
|
class FFmpegFixupDuplicateMoovPP(FFmpegCopyStreamPP):
|
||||||
MESSAGE = 'Fixing duplicate MOOV atoms'
|
MESSAGE = 'Fixing duplicate MOOV atoms'
|
||||||
|
|
||||||
|
|
||||||
|
@ -1132,15 +1132,20 @@ def __init__(self, downloader, only_multi_video=False):
|
||||||
|
|
||||||
def concat_files(self, in_files, out_file):
|
def concat_files(self, in_files, out_file):
|
||||||
if len(in_files) == 1:
|
if len(in_files) == 1:
|
||||||
|
if os.path.realpath(in_files[0]) != os.path.realpath(out_file):
|
||||||
|
self.to_screen(f'Moving "{in_files[0]}" to "{out_file}"')
|
||||||
os.replace(in_files[0], out_file)
|
os.replace(in_files[0], out_file)
|
||||||
return
|
return []
|
||||||
|
|
||||||
codecs = [traverse_obj(self.get_metadata_object(file), ('streams', ..., 'codec_name')) for file in in_files]
|
codecs = [traverse_obj(self.get_metadata_object(file), ('streams', ..., 'codec_name')) for file in in_files]
|
||||||
if len(set(map(tuple, codecs))) > 1:
|
if len(set(map(tuple, codecs))) > 1:
|
||||||
raise PostProcessingError(
|
raise PostProcessingError(
|
||||||
'The files have different streams/codecs and cannot be concatenated. '
|
'The files have different streams/codecs and cannot be concatenated. '
|
||||||
'Either select different formats or --recode-video them to a common format')
|
'Either select different formats or --recode-video them to a common format')
|
||||||
|
|
||||||
|
self.to_screen(f'Concatenating {len(in_files)} files; Destination: {out_file}')
|
||||||
super().concat_files(in_files, out_file)
|
super().concat_files(in_files, out_file)
|
||||||
|
return in_files
|
||||||
|
|
||||||
@PostProcessor._restrict_to(images=False)
|
@PostProcessor._restrict_to(images=False)
|
||||||
def run(self, info):
|
def run(self, info):
|
||||||
|
@ -1161,10 +1166,10 @@ def run(self, info):
|
||||||
ie_copy['ext'] = exts[0] if len(set(exts)) == 1 else 'mkv'
|
ie_copy['ext'] = exts[0] if len(set(exts)) == 1 else 'mkv'
|
||||||
out_file = self._downloader.prepare_filename(ie_copy, 'pl_video')
|
out_file = self._downloader.prepare_filename(ie_copy, 'pl_video')
|
||||||
|
|
||||||
self.concat_files(in_files, out_file)
|
files_to_delete = self.concat_files(in_files, out_file)
|
||||||
|
|
||||||
info['requested_downloads'] = [{
|
info['requested_downloads'] = [{
|
||||||
'filepath': out_file,
|
'filepath': out_file,
|
||||||
'ext': ie_copy['ext'],
|
'ext': ie_copy['ext'],
|
||||||
}]
|
}]
|
||||||
return in_files, info
|
return files_to_delete, info
|
||||||
|
|
Loading…
Reference in a new issue