2021-06-03 11:43:42 +02:00
#!/usr/bin/env python3
2016-10-02 13:39:18 +02:00
# coding: utf-8
2013-06-18 22:14:21 +02:00
2014-01-05 01:52:03 +01:00
from __future__ import absolute_import , unicode_literals
2013-06-18 22:14:21 +02:00
2013-12-09 22:00:42 +01:00
import collections
2015-03-01 11:46:57 +01:00
import contextlib
2016-07-15 19:55:43 +02:00
import copy
2014-03-13 15:30:25 +01:00
import datetime
2013-10-06 04:27:09 +02:00
import errno
2015-03-01 11:46:57 +01:00
import fileinput
2013-06-18 22:14:21 +02:00
import io
2014-12-06 14:02:19 +01:00
import itertools
2013-11-20 06:18:24 +01:00
import json
2014-03-30 06:02:41 +02:00
import locale
2015-01-23 00:04:05 +01:00
import operator
2013-06-18 22:14:21 +02:00
import os
2013-11-22 19:57:52 +01:00
import platform
2013-06-18 22:14:21 +02:00
import re
import shutil
2013-11-22 19:57:52 +01:00
import subprocess
2013-06-18 22:14:21 +02:00
import sys
import time
2015-06-28 22:08:29 +02:00
import tokenize
2013-06-18 22:14:21 +02:00
import traceback
2017-01-31 10:03:31 +01:00
import random
2013-06-18 22:14:21 +02:00
2017-07-15 02:02:14 +02:00
from string import ascii_letters
2021-02-14 18:10:54 +01:00
from zipimport import zipimporter
2017-07-15 02:02:14 +02:00
2014-11-02 11:23:40 +01:00
from . compat import (
2015-11-19 22:08:34 +01:00
compat_basestring ,
2013-11-22 19:57:52 +01:00
compat_cookiejar ,
2015-02-28 21:42:16 +01:00
compat_get_terminal_size ,
2014-12-15 01:06:25 +01:00
compat_kwargs ,
2016-03-05 22:52:42 +01:00
compat_numeric_types ,
2016-03-03 12:24:24 +01:00
compat_os_name ,
2013-11-17 16:47:52 +01:00
compat_str ,
2015-06-28 22:08:29 +02:00
compat_tokenize_tokenize ,
2013-11-17 16:47:52 +01:00
compat_urllib_error ,
compat_urllib_request ,
2015-10-17 17:16:40 +02:00
compat_urllib_request_DataHandler ,
2014-11-02 11:23:40 +01:00
)
from . utils import (
2016-03-26 14:40:33 +01:00
age_restricted ,
args_to_str ,
2013-11-17 16:47:52 +01:00
ContentTooShortError ,
date_from_str ,
DateRange ,
2014-04-30 10:02:03 +02:00
DEFAULT_OUTTMPL ,
2013-11-17 16:47:52 +01:00
determine_ext ,
2016-01-16 05:10:28 +01:00
determine_protocol ,
2020-10-27 11:37:21 +01:00
DOT_DESKTOP_LINK_TEMPLATE ,
DOT_URL_LINK_TEMPLATE ,
DOT_WEBLOC_LINK_TEMPLATE ,
2013-11-17 16:47:52 +01:00
DownloadError ,
2015-12-20 01:29:36 +01:00
encode_compat_str ,
2013-11-17 16:47:52 +01:00
encodeFilename ,
2021-03-23 20:45:53 +01:00
EntryNotInPlaylist ,
2021-05-17 14:23:08 +02:00
error_to_compat_str ,
2021-01-13 02:01:01 +01:00
ExistingVideoReached ,
2017-03-25 20:31:16 +01:00
expand_path ,
2013-11-17 16:47:52 +01:00
ExtractorError ,
2021-02-02 22:15:00 +01:00
float_or_none ,
2013-11-25 03:12:26 +01:00
format_bytes ,
2020-12-13 15:29:09 +01:00
format_field ,
2021-06-03 20:00:38 +02:00
STR_FORMAT_RE ,
2013-12-16 04:15:10 +01:00
formatSeconds ,
2017-02-04 12:49:58 +01:00
GeoRestrictedError ,
2017-06-08 17:53:14 +02:00
int_or_none ,
2020-10-27 11:37:21 +01:00
iri_to_uri ,
2017-02-04 12:49:58 +01:00
ISO3166Utils ,
2021-05-28 18:37:11 +02:00
LazyList ,
2013-11-17 16:47:52 +01:00
locked_file ,
2021-01-23 13:18:12 +01:00
make_dir ,
2013-11-22 19:57:52 +01:00
make_HTTPS_handler ,
2013-11-17 16:47:52 +01:00
MaxDownloadsReached ,
2021-05-04 19:06:18 +02:00
network_exceptions ,
[YoutubeDL] Ignore duplicates in --playlist-items
E.g. '--playlist-items 2-4,3-4,3' should result in '[2,3,4]', not '[2,3,4,3,4,3]'
2017-10-06 18:46:57 +02:00
orderedSet ,
2021-05-17 14:23:08 +02:00
OUTTMPL_TYPES ,
2014-01-20 11:36:47 +01:00
PagedList ,
2015-01-23 00:04:05 +01:00
parse_filesize ,
2015-03-03 00:03:06 +01:00
PerRequestProxyHandler ,
2013-11-22 19:57:52 +01:00
platform_name ,
2016-03-26 14:40:33 +01:00
PostProcessingError ,
2013-11-17 16:47:52 +01:00
preferredencoding ,
2016-03-26 14:40:33 +01:00
prepend_extension ,
2021-05-17 14:23:08 +02:00
process_communicate_or_kill ,
2021-05-04 17:54:00 +02:00
random_uuidv4 ,
2016-05-03 09:15:32 +02:00
register_socks_protocols ,
2021-05-17 14:23:08 +02:00
RejectedVideoReached ,
2015-01-25 02:38:47 +01:00
render_table ,
2016-03-26 14:40:33 +01:00
replace_extension ,
2013-11-17 16:47:52 +01:00
SameFileError ,
sanitize_filename ,
2015-03-08 15:57:30 +01:00
sanitize_path ,
2016-03-26 14:37:41 +01:00
sanitize_url ,
2015-11-20 15:33:49 +01:00
sanitized_Request ,
2015-01-24 18:52:26 +01:00
std_headers ,
2019-02-07 19:08:48 +01:00
str_or_none ,
2021-02-02 22:15:00 +01:00
strftime_or_none ,
2013-11-17 16:47:52 +01:00
subtitles_filename ,
2020-10-27 11:37:21 +01:00
to_high_limit_path ,
2021-06-08 10:53:56 +02:00
traverse_obj ,
2013-11-17 16:47:52 +01:00
UnavailableVideoError ,
2013-12-17 04:13:36 +01:00
url_basename ,
2015-01-10 21:02:27 +01:00
version_tuple ,
2013-11-17 16:47:52 +01:00
write_json_file ,
write_string ,
2018-12-09 00:00:32 +01:00
YoutubeDLCookieJar ,
2015-09-06 02:21:33 +02:00
YoutubeDLCookieProcessor ,
2013-11-22 19:57:52 +01:00
YoutubeDLHandler ,
2020-02-29 13:08:44 +01:00
YoutubeDLRedirectHandler ,
2013-11-17 16:47:52 +01:00
)
2014-09-03 12:41:05 +02:00
from . cache import Cache
2021-04-10 17:08:33 +02:00
from . extractor import (
gen_extractor_classes ,
get_info_extractor ,
_LAZY_LOADER ,
_PLUGIN_CLASSES
)
2017-09-23 19:08:27 +02:00
from . extractor . openload import PhantomJSwrapper
2021-04-10 17:08:33 +02:00
from . downloader import (
get_suitable_downloader ,
shorten_protocol_name
)
2014-11-02 10:55:36 +01:00
from . downloader . rtmp import rtmpdump_version
2014-12-15 01:06:25 +01:00
from . postprocessor import (
2016-03-01 21:08:50 +01:00
FFmpegFixupM3u8PP ,
2015-01-23 18:39:12 +01:00
FFmpegFixupM4aPP ,
2015-01-10 05:45:51 +01:00
FFmpegFixupStretchedPP ,
2014-12-15 01:06:25 +01:00
FFmpegMergerPP ,
FFmpegPostProcessor ,
2021-01-23 13:18:12 +01:00
# FFmpegSubtitlesConvertorPP,
2014-12-15 01:06:25 +01:00
get_postprocessor ,
2021-01-23 13:18:12 +01:00
MoveFilesAfterDownloadPP ,
2014-12-15 01:06:25 +01:00
)
2013-11-22 19:57:52 +01:00
from . version import __version__
2013-06-18 22:14:21 +02:00
2016-03-03 12:24:24 +01:00
if compat_os_name == ' nt ' :
import ctypes
2020-09-18 15:35:21 +02:00
2013-06-18 22:14:21 +02:00
class YoutubeDL ( object ) :
""" YoutubeDL class.
YoutubeDL objects are the ones responsible of downloading the
actual video file and writing it to disk if the user has requested
it , among some other tasks . In most cases there should be one per
program . As , given a video URL , the downloader doesn ' t know how to
extract all the needed information , task that InfoExtractors do , it
has to pass the URL to one of them .
For this , YoutubeDL objects have a method that allows
InfoExtractors to be registered in a given order . When it is passed
a URL , the YoutubeDL object handles it to the first InfoExtractor it
finds that reports being able to handle it . The InfoExtractor extracts
all the information about the video or videos the URL refers to , and
YoutubeDL process the extracted information , possibly using a File
Downloader to download the video .
YoutubeDL objects accept a lot of parameters . In order not to saturate
the object constructor with arguments , it receives a dictionary of
options instead . These options are available through the params
attribute for the InfoExtractors to use . The YoutubeDL also
registers itself as the downloader in charge for the InfoExtractors
that are added to it , so this is a " mutual registration " .
Available options :
username : Username for authentication purposes .
password : Password for authentication purposes .
2015-06-14 07:49:42 +02:00
videopassword : Password for accessing a video .
2016-09-15 17:24:55 +02:00
ap_mso : Adobe Pass multiple - system operator identifier .
ap_username : Multiple - system operator account username .
ap_password : Multiple - system operator account password .
2013-06-18 22:14:21 +02:00
usenetrc : Use netrc for authentication instead .
verbose : Print additional info to stdout .
quiet : Do not print messages to stdout .
2014-03-26 00:43:46 +01:00
no_warnings : Do not print out anything for warnings .
2021-05-14 09:44:38 +02:00
forceprint : A list of templates to force print
forceurl : Force printing final URL . ( Deprecated )
forcetitle : Force printing title . ( Deprecated )
forceid : Force printing ID . ( Deprecated )
forcethumbnail : Force printing thumbnail URL . ( Deprecated )
forcedescription : Force printing description . ( Deprecated )
forcefilename : Force printing final filename . ( Deprecated )
forceduration : Force printing duration . ( Deprecated )
2013-11-20 06:18:24 +01:00
forcejson : Force printing info_dict as JSON .
2014-10-25 00:30:57 +02:00
dump_single_json : Force printing the info_dict of the whole playlist
( or video ) as a single JSON line .
2021-02-04 23:53:04 +01:00
force_write_download_archive : Force writing download archive regardless
of ' skip_download ' or ' simulate ' .
2013-06-18 22:14:21 +02:00
simulate : Do not download the video files .
Better Format Sorting (Squashed)
* Added --format-sort (-S height,filesize)
* Made fields reversible (-S +height)
* Added --format-sort-force, --no-format-sort-force
* Added limit (-S height:720)
* Added codec preference (-S vcodec,acodec)
* Correct handling of preference<-1000
* Rebased to yt-dlc
* Automatically determine missing bitrates
* aext, vext, protocol, acodec, vcodec can now takes priority as string, not number (-S vext:webm)
* Correct handling of None in codec, audio_codec (None means the codec is unknown while 'none' means it doesn't exist)
* Correctly parse filesize (-S filesize:200M)
* Generalized preference calculation
* Rewrote entire code into the class FormatSort
* Correctly handle user input errors
* Combined fields (-S +ext:webm:webm)
* Closest mode (-S filesize~50M)
* Aliases (framerate=fps, br=bitrate etc)
* Documentation
2020-10-26 16:50:09 +01:00
format : Video format code . see " FORMAT SELECTION " for more details .
2021-02-12 04:51:59 +01:00
allow_unplayable_formats : Allow unplayable formats to be extracted and downloaded .
2021-04-17 02:09:58 +02:00
ignore_no_formats_error : Ignore " No video formats " error . Usefull for
extracting metadata even if the video is not actually
available for download ( experimental )
2021-02-04 23:53:04 +01:00
format_sort : How to sort the video formats . see " Sorting Formats "
for more details .
format_sort_force : Force the given format_sort . see " Sorting Formats "
for more details .
allow_multiple_video_streams : Allow multiple video streams to be merged
into a single file
allow_multiple_audio_streams : Allow multiple audio streams to be merged
into a single file
2021-02-19 22:33:17 +01:00
paths : Dictionary of output paths . The allowed keys are ' home '
' temp ' and the keys of OUTTMPL_TYPES ( in utils . py )
2021-02-03 14:36:09 +01:00
outtmpl : Dictionary of templates for output names . Allowed keys
2021-02-19 22:33:17 +01:00
are ' default ' and the keys of OUTTMPL_TYPES ( in utils . py ) .
A string a also accepted for backward compatibility
2021-01-16 18:12:05 +01:00
outtmpl_na_placeholder : Placeholder for unavailable meta fields .
restrictfilenames : Do not allow " & " and spaces in file names
trim_file_name : Limit length of filename ( extension excluded )
2021-02-19 22:33:17 +01:00
windowsfilenames : Force the filenames to be windows compatible
2021-01-16 18:12:05 +01:00
ignoreerrors : Do not stop on download errors
2021-02-24 19:45:56 +01:00
( Default True when running yt - dlp ,
2021-01-16 18:12:05 +01:00
but False when directly accessing YoutubeDL class )
2021-04-21 08:00:43 +02:00
skip_playlist_after_errors : Number of allowed failures until the rest of
the playlist is skipped
2015-06-12 15:20:12 +02:00
force_generic_extractor : Force downloader to use the generic extractor
2019-10-13 18:00:48 +02:00
overwrites : Overwrite all video and metadata files if True ,
overwrite only non - video files if None
and don ' t overwrite any file if False
2013-06-18 22:14:21 +02:00
playliststart : Playlist item to start at .
playlistend : Playlist item to end at .
2015-01-25 04:24:55 +01:00
playlist_items : Specific indices of playlist to download .
2014-07-11 05:11:11 +02:00
playlistreverse : Download playlist items in reverse order .
2017-01-31 10:03:31 +01:00
playlistrandom : Download playlist items in random order .
2013-06-18 22:14:21 +02:00
matchtitle : Download only matching titles .
rejecttitle : Reject downloads for matching titles .
2013-11-24 06:08:11 +01:00
logger : Log messages to a logging . Logger instance .
2013-06-18 22:14:21 +02:00
logtostderr : Log messages to stderr instead of stdout .
writedescription : Write the video description to a . description file
writeinfojson : Write the video description to a . info . json file
2021-03-18 16:27:20 +01:00
clean_infojson : Remove private fields from the infojson
2021-01-27 16:02:51 +01:00
writecomments : Extract video comments . This will not be written to disk
unless writeinfojson is also given
2013-10-14 07:18:58 +02:00
writeannotations : Write the video annotations to a . annotations . xml file
2013-06-18 22:14:21 +02:00
writethumbnail : Write the thumbnail image to a file
2021-02-04 23:53:04 +01:00
allow_playlist_files : Whether to write playlists ' description, infojson etc
also to disk when using the ' write* ' options
2015-01-25 03:11:12 +01:00
write_all_thumbnails : Write all thumbnail formats to files
2020-10-27 11:37:21 +01:00
writelink : Write an internet shortcut file , depending on the
current platform ( . url / . webloc / . desktop )
writeurllink : Write a Windows internet shortcut file ( . url )
writewebloclink : Write a macOS internet shortcut file ( . webloc )
writedesktoplink : Write a Linux internet shortcut file ( . desktop )
2013-06-18 22:14:21 +02:00
writesubtitles : Write the video subtitles to a file
2015-11-16 15:15:25 +01:00
writeautomaticsub : Write the automatically generated subtitles to a file
2021-04-19 23:17:09 +02:00
allsubtitles : Deprecated - Use subtitlelangs = [ ' all ' ]
Downloads all the subtitles of the video
2013-09-14 11:14:40 +02:00
( requires writesubtitles or writeautomaticsub )
2013-06-18 22:14:21 +02:00
listsubtitles : Lists all available subtitles for the video
2015-02-15 18:03:41 +01:00
subtitlesformat : The format code for subtitles
2021-04-19 23:17:09 +02:00
subtitleslangs : List of languages of the subtitles to download ( can be regex ) .
The list may contain " all " to refer to all the available
subtitles . The language can be prefixed with a " - " to
exclude it from the requested languages . Eg : [ ' all ' , ' -live_chat ' ]
2013-06-18 22:14:21 +02:00
keepvideo : Keep the video file after post - processing
daterange : A DateRange object , download only if the upload_date is in the range .
skip_download : Skip the actual download of the video file
2013-09-22 11:09:25 +02:00
cachedir : Location of the cache files in the filesystem .
2014-09-03 12:41:05 +02:00
False to disable filesystem cache .
2013-09-30 22:26:25 +02:00
noplaylist : Download single video instead of a playlist if in doubt .
2013-10-06 06:06:30 +02:00
age_limit : An integer representing the user ' s age in years.
Unsuitable videos for the given age are skipped .
2013-12-16 03:09:49 +01:00
min_views : An integer representing the minimum view count the video
must have in order to not be skipped .
Videos without view count information are always
downloaded . None for no limit .
max_views : An integer representing the maximum view count .
Videos that are more popular than that are not
downloaded .
Videos without view count information are always
downloaded . None for no limit .
download_archive : File name of a file where all downloads are recorded .
2013-10-06 04:27:09 +02:00
Videos already present in the file are not downloaded
again .
2021-01-18 20:17:48 +01:00
break_on_existing : Stop the download process after attempting to download a
file that is in the archive .
break_on_reject : Stop the download process when encountering a video that
has been filtered out .
cookiefile : File name where cookies should be read from and dumped to
2013-11-24 15:03:25 +01:00
nocheckcertificate : Do not verify SSL certificates
2014-03-21 00:33:53 +01:00
prefer_insecure : Use HTTP instead of HTTPS to retrieve information .
At the moment , this is only supported by YouTube .
2013-11-24 15:03:25 +01:00
proxy : URL of the proxy server to use
2016-07-03 17:23:48 +02:00
geo_verification_proxy : URL of the proxy to use for IP address verification
2018-05-19 18:53:24 +02:00
on geo - restricted sites .
2013-12-01 11:42:02 +01:00
socket_timeout : Time to wait for unresponsive hosts , in seconds
2013-12-09 04:08:51 +01:00
bidi_workaround : Work around buggy terminals without bidirectional text
support , using fridibi
2013-12-29 15:28:32 +01:00
debug_printtraffic : Print out sent and received HTTP traffic
2014-01-21 02:09:49 +01:00
include_ads : Download ads as well
2014-01-22 14:16:43 +01:00
default_search : Prepend this string if an input url is not valid .
' auto ' for elaborate guessing
2014-03-30 06:02:41 +02:00
encoding : Use this encoding instead of the system - specified .
2014-08-21 11:52:07 +02:00
extract_flat : Do not resolve URLs , return the immediate result .
2014-10-24 14:48:12 +02:00
Pass in ' in_playlist ' to only show this behavior for
playlist items .
2014-12-15 01:06:25 +01:00
postprocessors : A list of dictionaries , each with an entry
2014-12-15 01:26:18 +01:00
* key : The name of the postprocessor . See
2021-02-24 19:45:56 +01:00
yt_dlp / postprocessor / __init__ . py for a list .
2021-04-11 00:18:07 +02:00
* when : When to run the postprocessor . Can be one of
pre_process | before_dl | post_process | after_move .
Assumed to be ' post_process ' if not given
2020-12-29 16:03:07 +01:00
post_hooks : A list of functions that get called as the final step
for each video file , after all postprocessors have been
called . The filename will be passed as the only argument .
2014-12-15 01:26:18 +01:00
progress_hooks : A list of functions that get called on download
progress , with a dictionary with the entries
2015-02-17 21:37:48 +01:00
* status : One of " downloading " , " error " , or " finished " .
2015-01-25 06:15:51 +01:00
Check this first and ignore unknown values .
2014-12-15 01:26:18 +01:00
2015-02-17 21:37:48 +01:00
If status is one of " downloading " , or " finished " , the
2015-01-25 06:15:51 +01:00
following properties may also be present :
* filename : The final filename ( always present )
2015-02-17 21:37:48 +01:00
* tmpfilename : The filename we ' re currently writing to
2014-12-15 01:26:18 +01:00
* downloaded_bytes : Bytes on disk
* total_bytes : Size of the whole file , None if unknown
2015-02-17 21:37:48 +01:00
* total_bytes_estimate : Guess of the eventual file size ,
None if unavailable .
* elapsed : The number of seconds since download started .
2014-12-15 01:26:18 +01:00
* eta : The estimated time in seconds , None if unknown
* speed : The download speed in bytes / second , None if
unknown
2015-02-17 21:37:48 +01:00
* fragment_index : The counter of the currently
downloaded video fragment .
* fragment_count : The number of fragments ( = individual
files that will be merged )
2014-12-15 01:26:18 +01:00
Progress hooks are guaranteed to be called at least once
( with status " finished " ) if the download is successful .
2015-01-10 01:59:14 +01:00
merge_output_format : Extension to use when merging formats .
2021-01-28 06:18:36 +01:00
final_ext : Expected final extension ; used to detect when the file was
already downloaded and converted . " merge_output_format " is
replaced by this extension when given
2015-01-10 05:45:51 +01:00
fixup : Automatically correct known faults of the file .
One of :
- " never " : do nothing
- " warn " : only emit a warning
- " detect_or_warn " : check whether we can do anything
2015-01-23 18:39:12 +01:00
about it , warn otherwise ( default )
2018-05-19 18:53:24 +02:00
source_address : Client - side IP address to bind to .
2016-01-10 19:27:22 +01:00
call_home : Boolean , true iff we are allowed to contact the
2021-02-24 19:45:56 +01:00
yt - dlp servers for debugging . ( BROKEN )
2021-02-27 13:41:23 +01:00
sleep_interval_requests : Number of seconds to sleep between requests
during extraction
2016-08-08 22:46:52 +02:00
sleep_interval : Number of seconds to sleep before each download when
used alone or a lower bound of a range for randomized
sleep before each download ( minimum possible number
of seconds to sleep ) when used along with
max_sleep_interval .
max_sleep_interval : Upper bound of a range for randomized sleep before each
download ( maximum possible number of seconds to sleep ) .
Must only be used along with sleep_interval .
Actual sleep time will be a random float from range
[ sleep_interval ; max_sleep_interval ] .
2021-02-27 13:41:23 +01:00
sleep_interval_subtitles : Number of seconds to sleep before each subtitle download
2015-01-25 02:38:47 +01:00
listformats : Print an overview of available video formats and exit .
list_thumbnails : Print a table of all thumbnails and exit .
2015-02-10 03:32:21 +01:00
match_filter : A function that gets called with the info_dict of
every video .
If it returns a message , the video is ignored .
If it returns None , the video is downloaded .
match_filter_func in utils . py is one example for this .
2015-02-10 04:22:10 +01:00
no_color : Do not emit color codes in output .
2017-02-18 19:53:41 +01:00
geo_bypass : Bypass geographic restriction via faking X - Forwarded - For
2018-05-19 18:53:24 +02:00
HTTP header
2017-02-18 19:53:41 +01:00
geo_bypass_country :
2017-02-04 12:49:58 +01:00
Two - letter ISO 3166 - 2 country code that will be used for
explicit geographic restriction bypassing via faking
2018-05-19 18:53:24 +02:00
X - Forwarded - For HTTP header
2018-05-02 02:18:01 +02:00
geo_bypass_ip_block :
IP range in CIDR notation that will be used similarly to
2018-05-19 18:53:24 +02:00
geo_bypass_country
2014-12-15 01:26:18 +01:00
2015-02-17 12:09:12 +01:00
The following options determine which downloader is picked :
2021-04-10 17:08:33 +02:00
external_downloader : A dictionary of protocol keys and the executable of the
external downloader to use for it . The allowed protocols
are default | http | ftp | m3u8 | dash | rtsp | rtmp | mms .
Set the value to ' native ' to use the native downloader
hls_prefer_native : Deprecated - Use external_downloader = { ' m3u8 ' : ' native ' }
or { ' m3u8 ' : ' ffmpeg ' } instead .
Use the native HLS downloader instead of ffmpeg / avconv
2016-04-21 19:02:17 +02:00
if True , otherwise use ffmpeg / avconv if False , otherwise
use downloader suggested by extractor if None .
2021-05-11 10:00:48 +02:00
compat_opts : Compatibility options . See " Differences in default behavior " .
2021-05-23 00:17:44 +02:00
Note that only format - sort , format - spec , no - live - chat ,
no - attach - info - json , playlist - index , list - formats ,
no - direct - merge , no - youtube - channel - redirect ,
2021-05-11 10:00:48 +02:00
and no - youtube - unavailable - videos works when used via the API
2013-10-22 14:49:34 +02:00
2013-06-18 22:14:21 +02:00
The following parameters are not used by YoutubeDL itself , they are used by
2021-02-24 19:45:56 +01:00
the downloader ( see yt_dlp / downloader / common . py ) :
2013-06-18 22:14:21 +02:00
nopart , updatetime , buffersize , ratelimit , min_filesize , max_filesize , test ,
2015-01-25 04:49:44 +01:00
noresizebuffer , retries , continuedl , noprogress , consoletitle ,
2018-02-03 20:53:50 +01:00
xattr_set_filesize , external_downloader_args , hls_use_mpegts ,
2021-02-24 17:01:01 +01:00
http_chunk_size .
2014-01-08 17:53:34 +01:00
The following options are used by the post processors :
2018-06-28 20:09:14 +02:00
prefer_ffmpeg : If False , use avconv instead of ffmpeg if both are available ,
2021-01-26 18:57:32 +01:00
otherwise prefer ffmpeg . ( avconv support is deprecated )
2019-04-01 20:29:44 +02:00
ffmpeg_location : Location of the ffmpeg / avconv binary ; either the path
to the binary or its containing directory .
2021-01-20 17:07:40 +01:00
postprocessor_args : A dictionary of postprocessor / executable keys ( in lower case )
and a list of additional command - line arguments for the
postprocessor / executable . The dict can also have " PP+EXE " keys
which are used when the given exe is used by the given PP .
Use ' default ' as the name for arguments to passed to all PP
2021-02-24 17:01:01 +01:00
The following options are used by the extractors :
2021-03-01 00:48:37 +01:00
extractor_retries : Number of times to retry for known errors
dynamic_mpd : Whether to process dynamic DASH manifests ( default : True )
2021-02-24 17:01:01 +01:00
hls_split_discontinuity : Split HLS playlists to different formats at
2021-03-01 00:48:37 +01:00
discontinuities such as ad breaks ( default : False )
2017-09-27 19:46:48 +02:00
youtube_include_dash_manifest : If True ( default ) , DASH manifests and related
2021-03-01 00:48:37 +01:00
data will be downloaded and processed by extractor .
You can reduce network I / O by disabling it if you don ' t
care about DASH . ( only for youtube )
2021-02-24 17:01:01 +01:00
youtube_include_hls_manifest : If True ( default ) , HLS manifests and related
2021-03-01 00:48:37 +01:00
data will be downloaded and processed by extractor .
You can reduce network I / O by disabling it if you don ' t
care about HLS . ( only for youtube )
2013-06-18 22:14:21 +02:00
"""
2017-06-08 17:53:14 +02:00
_NUMERIC_FIELDS = set ( (
' width ' , ' height ' , ' tbr ' , ' abr ' , ' asr ' , ' vbr ' , ' fps ' , ' filesize ' , ' filesize_approx ' ,
' timestamp ' , ' upload_year ' , ' upload_month ' , ' upload_day ' ,
' duration ' , ' view_count ' , ' like_count ' , ' dislike_count ' , ' repost_count ' ,
' average_rating ' , ' comment_count ' , ' age_limit ' ,
' start_time ' , ' end_time ' ,
' chapter_number ' , ' season_number ' , ' episode_number ' ,
' track_number ' , ' disc_number ' , ' release_year ' ,
' playlist_index ' ,
) )
2013-06-18 22:14:21 +02:00
params = None
_ies = [ ]
2021-04-11 00:18:07 +02:00
_pps = { ' pre_process ' : [ ] , ' before_dl ' : [ ] , ' after_move ' : [ ] , ' post_process ' : [ ] }
2021-01-23 13:18:12 +01:00
__prepare_filename_warned = False
2021-02-27 13:41:23 +01:00
_first_webpage_request = True
2013-06-18 22:14:21 +02:00
_download_retcode = None
_num_downloads = None
2021-01-16 13:40:15 +01:00
_playlist_level = 0
_playlist_urls = set ( )
2013-06-18 22:14:21 +02:00
_screen_file = None
2014-10-28 12:54:29 +01:00
def __init__ ( self , params = None , auto_init = True ) :
2013-06-18 22:14:21 +02:00
""" Create a FileDownloader object with the given options. """
2013-12-31 13:34:52 +01:00
if params is None :
params = { }
2013-06-18 22:14:21 +02:00
self . _ies = [ ]
2013-07-08 15:14:27 +02:00
self . _ies_instances = { }
2021-04-11 00:18:07 +02:00
self . _pps = { ' pre_process ' : [ ] , ' before_dl ' : [ ] , ' after_move ' : [ ] , ' post_process ' : [ ] }
2021-01-23 13:18:12 +01:00
self . __prepare_filename_warned = False
2021-02-27 13:41:23 +01:00
self . _first_webpage_request = True
2020-12-29 16:03:07 +01:00
self . _post_hooks = [ ]
2013-12-23 10:37:27 +01:00
self . _progress_hooks = [ ]
2013-06-18 22:14:21 +02:00
self . _download_retcode = 0
self . _num_downloads = 0
self . _screen_file = [ sys . stdout , sys . stderr ] [ params . get ( ' logtostderr ' , False ) ]
2013-12-09 04:08:51 +01:00
self . _err_file = sys . stderr
2015-09-05 15:17:30 +02:00
self . params = {
# Default parameters
' nocheckcertificate ' : False ,
}
self . params . update ( params )
2014-09-03 12:41:05 +02:00
self . cache = Cache ( self )
2013-09-21 11:48:07 +02:00
2021-05-09 00:24:44 +02:00
if sys . version_info < ( 3 , 6 ) :
self . report_warning (
2021-06-09 12:05:17 +02:00
' Support for Python version %d . %d have been deprecated and will break in future versions of yt-dlp! '
' Update to Python 3.6 or above ' % sys . version_info [ : 2 ] )
2021-05-09 00:24:44 +02:00
2017-02-24 00:04:27 +01:00
def check_deprecated ( param , option , suggestion ) :
if self . params . get ( param ) is not None :
2021-05-11 10:00:48 +02:00
self . report_warning ( ' %s is deprecated. Use %s instead ' % ( option , suggestion ) )
2017-02-24 00:04:27 +01:00
return True
return False
if check_deprecated ( ' cn_verification_proxy ' , ' --cn-verification-proxy ' , ' --geo-verification-proxy ' ) :
2016-07-03 17:23:48 +02:00
if self . params . get ( ' geo_verification_proxy ' ) is None :
self . params [ ' geo_verification_proxy ' ] = self . params [ ' cn_verification_proxy ' ]
2021-05-03 11:41:59 +02:00
check_deprecated ( ' autonumber ' , ' --auto-number ' , ' -o " %(autonumber)s - %(title)s . %(ext)s " ' )
check_deprecated ( ' usetitle ' , ' --title ' , ' -o " %(title)s - %(id)s . %(ext)s " ' )
2021-05-11 10:00:48 +02:00
check_deprecated ( ' useid ' , ' --id ' , ' -o " %(id)s . %(ext)s " ' )
2021-05-03 11:41:59 +02:00
for msg in self . params . get ( ' warnings ' , [ ] ) :
self . report_warning ( msg )
2021-01-28 06:18:36 +01:00
if self . params . get ( ' final_ext ' ) :
if self . params . get ( ' merge_output_format ' ) :
self . report_warning ( ' --merge-output-format will be ignored since --remux-video or --recode-video is given ' )
self . params [ ' merge_output_format ' ] = self . params [ ' final_ext ' ]
2021-02-01 16:15:46 +01:00
if ' overwrites ' in self . params and self . params [ ' overwrites ' ] is None :
del self . params [ ' overwrites ' ]
2013-12-09 04:08:51 +01:00
if params . get ( ' bidi_workaround ' , False ) :
2013-12-09 18:29:07 +01:00
try :
import pty
master , slave = pty . openpty ( )
2015-02-28 21:42:16 +01:00
width = compat_get_terminal_size ( ) . columns
2013-12-09 18:29:07 +01:00
if width is None :
width_args = [ ]
else :
width_args = [ ' -w ' , str ( width ) ]
2013-12-23 04:19:20 +01:00
sp_kwargs = dict (
2013-12-09 18:29:07 +01:00
stdin = subprocess . PIPE ,
stdout = slave ,
stderr = self . _err_file )
2013-12-23 04:19:20 +01:00
try :
self . _output_process = subprocess . Popen (
[ ' bidiv ' ] + width_args , * * sp_kwargs
)
except OSError :
self . _output_process = subprocess . Popen (
[ ' fribidi ' , ' -c ' , ' UTF-8 ' ] + width_args , * * sp_kwargs )
self . _output_channel = os . fdopen ( master , ' rb ' )
2013-12-09 18:29:07 +01:00
except OSError as ose :
2016-05-14 13:41:41 +02:00
if ose . errno == errno . ENOENT :
2014-01-05 01:52:03 +01:00
self . report_warning ( ' Could not find fribidi executable, ignoring --bidi-workaround . Make sure that fribidi is an executable file in one of the directories in your $PATH. ' )
2013-12-09 18:29:07 +01:00
else :
raise
2013-12-09 04:08:51 +01:00
2019-05-10 22:56:22 +02:00
if ( sys . platform != ' win32 '
and sys . getfilesystemencoding ( ) in [ ' ascii ' , ' ANSI_X3.4-1968 ' ]
and not params . get ( ' restrictfilenames ' , False ) ) :
2017-05-08 20:14:02 +02:00
# Unicode filesystem API will throw errors (#1474, #13027)
2013-09-21 11:48:07 +02:00
self . report_warning (
2014-01-05 01:52:03 +01:00
' Assuming --restrict-filenames since file system encoding '
2014-10-09 17:00:24 +02:00
' cannot encode all characters. '
2014-01-05 01:52:03 +01:00
' Set the LC_ALL environment variable to fix this. ' )
2013-11-26 18:53:36 +01:00
self . params [ ' restrictfilenames ' ] = True
2013-09-21 11:48:07 +02:00
2021-02-03 14:36:09 +01:00
self . outtmpl_dict = self . parse_outtmpl ( )
2015-03-13 08:40:20 +01:00
2013-11-22 19:57:52 +01:00
self . _setup_opener ( )
2021-05-03 12:31:20 +02:00
""" Preload the archive, if any is specified """
def preload_download_archive ( fn ) :
if fn is None :
return False
2021-05-14 09:45:29 +02:00
self . write_debug ( ' Loading archive file %r \n ' % fn )
2021-05-03 12:31:20 +02:00
try :
with locked_file ( fn , ' r ' , encoding = ' utf-8 ' ) as archive_file :
for line in archive_file :
self . archive . add ( line . strip ( ) )
except IOError as ioe :
if ioe . errno != errno . ENOENT :
raise
return False
return True
self . archive = set ( )
preload_download_archive ( self . params . get ( ' download_archive ' ) )
2014-10-28 12:54:29 +01:00
if auto_init :
self . print_debug_header ( )
self . add_default_info_extractors ( )
2014-12-15 01:06:25 +01:00
for pp_def_raw in self . params . get ( ' postprocessors ' , [ ] ) :
pp_class = get_postprocessor ( pp_def_raw [ ' key ' ] )
pp_def = dict ( pp_def_raw )
del pp_def [ ' key ' ]
2021-01-26 11:20:20 +01:00
if ' when ' in pp_def :
when = pp_def [ ' when ' ]
del pp_def [ ' when ' ]
else :
2021-04-11 00:18:07 +02:00
when = ' post_process '
2014-12-15 01:06:25 +01:00
pp = pp_class ( self , * * compat_kwargs ( pp_def ) )
2021-01-26 11:20:20 +01:00
self . add_post_processor ( pp , when = when )
2014-12-15 01:06:25 +01:00
2020-12-29 16:03:07 +01:00
for ph in self . params . get ( ' post_hooks ' , [ ] ) :
self . add_post_hook ( ph )
2014-12-15 01:26:18 +01:00
for ph in self . params . get ( ' progress_hooks ' , [ ] ) :
self . add_progress_hook ( ph )
2016-05-03 09:15:32 +02:00
register_socks_protocols ( )
2014-11-23 10:49:19 +01:00
def warn_if_short_id ( self , argv ) :
# short YouTube ID starting with dash?
idxs = [
i for i , a in enumerate ( argv )
if re . match ( r ' ^-[0-9A-Za-z_-] {10} $ ' , a ) ]
if idxs :
correct_argv = (
2021-02-24 19:45:56 +01:00
[ ' yt-dlp ' ]
2019-05-10 22:56:22 +02:00
+ [ a for i , a in enumerate ( argv ) if i not in idxs ]
+ [ ' -- ' ] + [ argv [ i ] for i in idxs ]
2014-11-23 10:49:19 +01:00
)
self . report_warning (
' Long argument string detected. '
' Use -- to separate parameters and URLs, like this: \n %s \n ' %
args_to_str ( correct_argv ) )
2013-06-18 22:14:21 +02:00
def add_info_extractor ( self , ie ) :
""" Add an InfoExtractor object to the end of the list. """
self . _ies . append ( ie )
2016-02-10 13:16:18 +01:00
if not isinstance ( ie , type ) :
self . _ies_instances [ ie . ie_key ( ) ] = ie
ie . set_downloader ( self )
2013-06-18 22:14:21 +02:00
2013-07-08 15:14:27 +02:00
def get_info_extractor ( self , ie_key ) :
"""
Get an instance of an IE with name ie_key , it will try to get one from
the _ies list , if there ' s no instance it will create a new one and add
it to the extractor list .
"""
ie = self . _ies_instances . get ( ie_key )
if ie is None :
ie = get_info_extractor ( ie_key ) ( )
self . add_info_extractor ( ie )
return ie
2013-06-27 23:51:06 +02:00
def add_default_info_extractors ( self ) :
"""
Add the InfoExtractors returned by gen_extractors to the end of the list
"""
2016-02-10 13:16:18 +01:00
for ie in gen_extractor_classes ( ) :
2013-06-27 23:51:06 +02:00
self . add_info_extractor ( ie )
2021-04-11 00:18:07 +02:00
def add_post_processor ( self , pp , when = ' post_process ' ) :
2013-06-18 22:14:21 +02:00
""" Add a PostProcessor object to the end of the chain. """
2021-01-26 11:20:20 +01:00
self . _pps [ when ] . append ( pp )
2013-06-18 22:14:21 +02:00
pp . set_downloader ( self )
2020-12-29 16:03:07 +01:00
def add_post_hook ( self , ph ) :
""" Add the post hook """
self . _post_hooks . append ( ph )
2013-12-23 10:37:27 +01:00
def add_progress_hook ( self , ph ) :
""" Add the progress hook (currently only for the file downloader) """
self . _progress_hooks . append ( ph )
2013-09-23 18:09:28 +02:00
2013-12-09 18:29:07 +01:00
def _bidi_workaround ( self , message ) :
2013-12-23 04:19:20 +01:00
if not hasattr ( self , ' _output_channel ' ) :
2013-12-09 18:29:07 +01:00
return message
2013-12-23 04:19:20 +01:00
assert hasattr ( self , ' _output_process ' )
2014-07-25 23:37:32 +02:00
assert isinstance ( message , compat_str )
2014-01-05 01:52:03 +01:00
line_count = message . count ( ' \n ' ) + 1
self . _output_process . stdin . write ( ( message + ' \n ' ) . encode ( ' utf-8 ' ) )
2013-12-23 04:19:20 +01:00
self . _output_process . stdin . flush ( )
2014-01-05 01:52:03 +01:00
res = ' ' . join ( self . _output_channel . readline ( ) . decode ( ' utf-8 ' )
2014-11-23 21:39:15 +01:00
for _ in range ( line_count ) )
2014-01-05 01:52:03 +01:00
return res [ : - len ( ' \n ' ) ]
2013-12-09 18:29:07 +01:00
2014-04-07 19:57:42 +02:00
def _write_string ( self , s , out = None ) :
2014-04-07 22:48:13 +02:00
write_string ( s , out = out , encoding = self . params . get ( ' encoding ' ) )
2014-04-07 19:57:42 +02:00
2021-05-04 17:39:36 +02:00
def to_stdout ( self , message , skip_eol = False , quiet = False ) :
2021-05-14 09:45:29 +02:00
""" Print message to stdout """
2013-11-24 06:08:11 +01:00
if self . params . get ( ' logger ' ) :
2013-11-23 09:22:18 +01:00
self . params [ ' logger ' ] . debug ( message )
2021-05-28 23:01:10 +02:00
elif not quiet or self . params . get ( ' verbose ' ) :
self . _write_string (
' %s %s ' % ( self . _bidi_workaround ( message ) , ( ' ' if skip_eol else ' \n ' ) ) ,
self . _err_file if quiet else self . _screen_file )
2013-06-18 22:14:21 +02:00
def to_stderr ( self , message ) :
2021-05-14 09:45:29 +02:00
""" Print message to stderr """
2014-07-25 23:37:32 +02:00
assert isinstance ( message , compat_str )
2013-11-24 06:08:11 +01:00
if self . params . get ( ' logger ' ) :
2013-11-23 09:22:18 +01:00
self . params [ ' logger ' ] . error ( message )
else :
2021-05-28 23:01:10 +02:00
self . _write_string ( ' %s \n ' % self . _bidi_workaround ( message ) , self . _err_file )
2013-06-18 22:14:21 +02:00
2013-11-17 11:39:52 +01:00
def to_console_title ( self , message ) :
if not self . params . get ( ' consoletitle ' , False ) :
return
2017-06-03 14:14:23 +02:00
if compat_os_name == ' nt ' :
if ctypes . windll . kernel32 . GetConsoleWindow ( ) :
# c_wchar_p() might not be necessary if `message` is
# already of type unicode()
ctypes . windll . kernel32 . SetConsoleTitleW ( ctypes . c_wchar_p ( message ) )
2013-11-17 11:39:52 +01:00
elif ' TERM ' in os . environ :
2021-01-21 20:39:24 +01:00
self . _write_string ( ' \033 ]0; %s \007 ' % message , self . _screen_file )
2013-11-17 11:39:52 +01:00
2013-11-17 21:05:14 +01:00
def save_console_title ( self ) :
if not self . params . get ( ' consoletitle ' , False ) :
return
2018-04-08 20:03:55 +02:00
if self . params . get ( ' simulate ' , False ) :
return
2017-06-03 14:14:23 +02:00
if compat_os_name != ' nt ' and ' TERM ' in os . environ :
2013-11-18 16:35:41 +01:00
# Save the title on stack
2014-04-07 19:57:42 +02:00
self . _write_string ( ' \033 [22;0t ' , self . _screen_file )
2013-11-17 21:05:14 +01:00
def restore_console_title ( self ) :
if not self . params . get ( ' consoletitle ' , False ) :
return
2018-04-08 20:03:55 +02:00
if self . params . get ( ' simulate ' , False ) :
return
2017-06-03 14:14:23 +02:00
if compat_os_name != ' nt ' and ' TERM ' in os . environ :
2013-11-18 16:35:41 +01:00
# Restore the title from stack
2014-04-07 19:57:42 +02:00
self . _write_string ( ' \033 [23;0t ' , self . _screen_file )
2013-11-17 21:05:14 +01:00
def __enter__ ( self ) :
self . save_console_title ( )
return self
def __exit__ ( self , * args ) :
self . restore_console_title ( )
2014-01-25 12:02:43 +01:00
2013-11-22 19:57:52 +01:00
if self . params . get ( ' cookiefile ' ) is not None :
2018-12-09 00:00:32 +01:00
self . cookiejar . save ( ignore_discard = True , ignore_expires = True )
2013-11-17 21:05:14 +01:00
2013-06-18 22:14:21 +02:00
def trouble ( self , message = None , tb = None ) :
""" Determine action to take when a download problem appears.
Depending on if the downloader has been configured to ignore
download errors or not , this method may throw an exception or
not when errors are found , after printing the message .
tb , if given , is additional traceback information .
"""
if message is not None :
self . to_stderr ( message )
if self . params . get ( ' verbose ' ) :
if tb is None :
if sys . exc_info ( ) [ 0 ] : # if .trouble has been called from an except block
2014-01-05 01:52:03 +01:00
tb = ' '
2013-06-18 22:14:21 +02:00
if hasattr ( sys . exc_info ( ) [ 1 ] , ' exc_info ' ) and sys . exc_info ( ) [ 1 ] . exc_info [ 0 ] :
2014-01-05 01:52:03 +01:00
tb + = ' ' . join ( traceback . format_exception ( * sys . exc_info ( ) [ 1 ] . exc_info ) )
2015-12-20 01:29:36 +01:00
tb + = encode_compat_str ( traceback . format_exc ( ) )
2013-06-18 22:14:21 +02:00
else :
tb_data = traceback . format_list ( traceback . extract_stack ( ) )
2014-01-05 01:52:03 +01:00
tb = ' ' . join ( tb_data )
2021-05-25 21:43:08 +02:00
if tb :
self . to_stderr ( tb )
2013-06-18 22:14:21 +02:00
if not self . params . get ( ' ignoreerrors ' , False ) :
if sys . exc_info ( ) [ 0 ] and hasattr ( sys . exc_info ( ) [ 1 ] , ' exc_info ' ) and sys . exc_info ( ) [ 1 ] . exc_info [ 0 ] :
exc_info = sys . exc_info ( ) [ 1 ] . exc_info
else :
exc_info = sys . exc_info ( )
raise DownloadError ( message , exc_info )
self . _download_retcode = 1
2021-05-14 09:45:29 +02:00
def to_screen ( self , message , skip_eol = False ) :
""" Print message to stdout if not in quiet mode """
self . to_stdout (
message , skip_eol , quiet = self . params . get ( ' quiet ' , False ) )
2013-06-18 22:14:21 +02:00
def report_warning ( self , message ) :
'''
Print the message to stderr , it will be prefixed with ' WARNING: '
If stderr is a tty file the ' WARNING: ' will be colored
'''
2014-03-09 14:53:07 +01:00
if self . params . get ( ' logger ' ) is not None :
self . params [ ' logger ' ] . warning ( message )
2013-06-18 22:14:21 +02:00
else :
2014-03-26 00:43:46 +01:00
if self . params . get ( ' no_warnings ' ) :
return
2016-03-03 12:24:24 +01:00
if not self . params . get ( ' no_color ' ) and self . _err_file . isatty ( ) and compat_os_name != ' nt ' :
2014-03-09 14:53:07 +01:00
_msg_header = ' \033 [0;33mWARNING: \033 [0m '
else :
_msg_header = ' WARNING: '
warning_message = ' %s %s ' % ( _msg_header , message )
self . to_stderr ( warning_message )
2013-06-18 22:14:21 +02:00
def report_error ( self , message , tb = None ) :
'''
Do the same as trouble , but prefixes the message with ' ERROR: ' , colored
in red if stderr is a tty file .
'''
2016-03-03 12:24:24 +01:00
if not self . params . get ( ' no_color ' ) and self . _err_file . isatty ( ) and compat_os_name != ' nt ' :
2014-01-05 01:52:03 +01:00
_msg_header = ' \033 [0;31mERROR: \033 [0m '
2013-06-18 22:14:21 +02:00
else :
2014-01-05 01:52:03 +01:00
_msg_header = ' ERROR: '
error_message = ' %s %s ' % ( _msg_header , message )
2013-06-18 22:14:21 +02:00
self . trouble ( error_message , tb )
2021-05-14 09:45:29 +02:00
def write_debug ( self , message ) :
''' Log debug message or Print message to stderr '''
if not self . params . get ( ' verbose ' , False ) :
return
message = ' [debug] %s ' % message
if self . params . get ( ' logger ' ) :
self . params [ ' logger ' ] . debug ( message )
else :
self . _write_string ( ' %s \n ' % message )
2013-06-18 22:14:21 +02:00
def report_file_already_downloaded ( self , file_name ) :
""" Report file has already been fully downloaded. """
try :
2014-01-05 01:52:03 +01:00
self . to_screen ( ' [download] %s has already been downloaded ' % file_name )
2013-11-17 16:47:52 +01:00
except UnicodeEncodeError :
2014-01-05 01:52:03 +01:00
self . to_screen ( ' [download] The file has already been downloaded ' )
2013-06-18 22:14:21 +02:00
2019-10-13 18:00:48 +02:00
def report_file_delete ( self , file_name ) :
""" Report that existing file will be deleted. """
try :
2021-02-04 23:53:04 +01:00
self . to_screen ( ' Deleting existing file %s ' % file_name )
2019-10-13 18:00:48 +02:00
except UnicodeEncodeError :
2021-02-04 23:53:04 +01:00
self . to_screen ( ' Deleting existing file ' )
2019-10-13 18:00:48 +02:00
2021-02-03 14:36:09 +01:00
def parse_outtmpl ( self ) :
outtmpl_dict = self . params . get ( ' outtmpl ' , { } )
if not isinstance ( outtmpl_dict , dict ) :
outtmpl_dict = { ' default ' : outtmpl_dict }
outtmpl_dict . update ( {
k : v for k , v in DEFAULT_OUTTMPL . items ( )
if not outtmpl_dict . get ( k ) } )
for key , val in outtmpl_dict . items ( ) :
if isinstance ( val , bytes ) :
self . report_warning (
' Parameter outtmpl is bytes, but should be a unicode string. '
' Put from __future__ import unicode_literals at the top of your code file or consider switching to Python 3.x. ' )
return outtmpl_dict
2021-06-08 16:41:00 +02:00
@staticmethod
def validate_outtmpl ( tmpl ) :
''' @return None or Exception object '''
try :
re . sub (
STR_FORMAT_RE . format ( ' ' ) ,
lambda mobj : ( ' % ' if not mobj . group ( ' has_key ' ) else ' ' ) + mobj . group ( 0 ) ,
tmpl
) % collections . defaultdict ( int )
return None
except ValueError as err :
return err
2021-03-24 23:02:15 +01:00
def prepare_outtmpl ( self , outtmpl , info_dict , sanitize = None ) :
""" Make the template and info_dict suitable for substitution (outtmpl % i nfo_dict) """
2021-06-03 20:00:38 +02:00
info_dict = dict ( info_dict )
2021-04-15 14:31:16 +02:00
na = self . params . get ( ' outtmpl_na_placeholder ' , ' NA ' )
2021-03-24 23:02:15 +01:00
2021-06-03 20:00:38 +02:00
info_dict [ ' duration_string ' ] = ( # %(duration>%H-%M-%S)s is wrong if duration > 24hrs
2021-05-14 09:44:38 +02:00
formatSeconds ( info_dict [ ' duration ' ] , ' - ' if sanitize else ' : ' )
2021-03-24 23:02:15 +01:00
if info_dict . get ( ' duration ' , None ) is not None
else None )
2021-06-03 20:00:38 +02:00
info_dict [ ' epoch ' ] = int ( time . time ( ) )
info_dict [ ' autonumber ' ] = self . params . get ( ' autonumber_start ' , 1 ) - 1 + self . _num_downloads
if info_dict . get ( ' resolution ' ) is None :
info_dict [ ' resolution ' ] = self . format_resolution ( info_dict , default = None )
2021-03-24 23:02:15 +01:00
# For fields playlist_index and autonumber convert all occurrences
# of %(field)s to %(field)0Nd for backward compatibility
field_size_compat_map = {
2021-06-03 20:00:38 +02:00
' playlist_index ' : len ( str ( info_dict . get ( ' _last_playlist_index ' ) or ' ' ) ) ,
' autonumber ' : self . params . get ( ' autonumber_size ' ) or 5 ,
2021-03-24 23:02:15 +01:00
}
2021-06-03 20:00:38 +02:00
2021-06-09 16:17:50 +02:00
TMPL_DICT = { }
EXTERNAL_FORMAT_RE = re . compile ( STR_FORMAT_RE . format ( ' [^)]* ' ) )
MATH_FUNCTIONS = {
' + ' : float . __add__ ,
' - ' : float . __sub__ ,
}
2021-05-03 19:06:03 +02:00
# Field is of the form key1.key2...
# where keys (except first) can be string, int or slice
2021-06-09 16:17:50 +02:00
FIELD_RE = r ' \ w+(?: \ .(?: \ w+| {num} | {num} ?(?:: {num} ?) {{ 1,2}}))* ' . format ( num = r ' (?:-? \ d+) ' )
MATH_FIELD_RE = r ''' {field} | {num} ''' . format ( field = FIELD_RE , num = r ' -? \ d+(?:. \ d+)? ' )
MATH_OPERATORS_RE = r ' (?: %s ) ' % ' | ' . join ( map ( re . escape , MATH_FUNCTIONS . keys ( ) ) )
2021-05-03 19:06:03 +02:00
INTERNAL_FORMAT_RE = re . compile ( r ''' (?x)
( ? P < negate > - ) ?
2021-06-09 16:17:50 +02:00
( ? P < fields > { field } )
( ? P < maths > ( ? : { math_op } { math_field } ) * )
2021-05-03 19:06:03 +02:00
( ? : > ( ? P < strf_format > . + ? ) ) ?
( ? : \| ( ? P < default > . * ? ) ) ?
2021-06-09 16:17:50 +02:00
$ ''' .format(field=FIELD_RE, math_op=MATH_OPERATORS_RE, math_field=MATH_FIELD_RE))
2021-06-03 20:00:38 +02:00
2021-06-08 16:41:00 +02:00
get_key = lambda k : traverse_obj (
info_dict , k . split ( ' . ' ) , is_user_input = True , traverse_string = True )
2021-06-03 20:00:38 +02:00
def get_value ( mdict ) :
# Object traversal
2021-06-08 16:41:00 +02:00
value = get_key ( mdict [ ' fields ' ] )
2021-06-03 20:00:38 +02:00
# Negative
if mdict [ ' negate ' ] :
value = float_or_none ( value )
if value is not None :
value * = - 1
# Do maths
2021-06-09 16:17:50 +02:00
offset_key = mdict [ ' maths ' ]
if offset_key :
2021-06-03 20:00:38 +02:00
value = float_or_none ( value )
operator = None
2021-06-09 16:17:50 +02:00
while offset_key :
item = re . match (
MATH_FIELD_RE if operator else MATH_OPERATORS_RE ,
offset_key ) . group ( 0 )
offset_key = offset_key [ len ( item ) : ]
if operator is None :
2021-06-03 20:00:38 +02:00
operator = MATH_FUNCTIONS [ item ]
2021-06-09 16:17:50 +02:00
continue
item , multiplier = ( item [ 1 : ] , - 1 ) if item [ 0 ] == ' - ' else ( item , 1 )
offset = float_or_none ( item )
if offset is None :
offset = float_or_none ( get_key ( item ) )
try :
value = operator ( value , multiplier * offset )
except ( TypeError , ZeroDivisionError ) :
return None
operator = None
2021-06-03 20:00:38 +02:00
# Datetime formatting
if mdict [ ' strf_format ' ] :
value = strftime_or_none ( value , mdict [ ' strf_format ' ] )
return value
def create_key ( outer_mobj ) :
if not outer_mobj . group ( ' has_key ' ) :
return ' % {} ' . format ( outer_mobj . group ( 0 ) )
key = outer_mobj . group ( ' key ' )
fmt = outer_mobj . group ( ' format ' )
mobj = re . match ( INTERNAL_FORMAT_RE , key )
if mobj is None :
value , default = None , na
else :
2021-05-03 19:06:03 +02:00
mobj = mobj . groupdict ( )
2021-06-03 20:00:38 +02:00
default = mobj [ ' default ' ] if mobj [ ' default ' ] is not None else na
value = get_value ( mobj )
if fmt == ' s ' and value is not None and key in field_size_compat_map . keys ( ) :
fmt = ' 0 {:d} d ' . format ( field_size_compat_map [ key ] )
value = default if value is None else value
key + = ' \0 %s ' % fmt
2021-06-08 16:41:00 +02:00
if fmt == ' c ' :
value = compat_str ( value )
if value is None :
value , fmt = default , ' s '
else :
value = value [ 0 ]
elif fmt [ - 1 ] not in ' rs ' : # numeric
2021-04-15 14:31:16 +02:00
value = float_or_none ( value )
2021-06-03 20:00:38 +02:00
if value is None :
value , fmt = default , ' s '
if sanitize :
if fmt [ - 1 ] == ' r ' :
# If value is an object, sanitize might convert it to a string
# So we convert it to repr first
value , fmt = repr ( value ) , ' %s s ' % fmt [ : - 1 ]
2021-06-09 11:13:51 +02:00
if fmt [ - 1 ] in ' csr ' :
value = sanitize ( key , value )
2021-06-09 16:17:50 +02:00
TMPL_DICT [ key ] = value
2021-06-03 20:00:38 +02:00
return ' % ( {key} ) {fmt} ' . format ( key = key , fmt = fmt )
2021-06-09 16:17:50 +02:00
return EXTERNAL_FORMAT_RE . sub ( create_key , outtmpl ) , TMPL_DICT
2021-03-24 23:02:15 +01:00
2021-02-03 14:36:09 +01:00
def _prepare_filename ( self , info_dict , tmpl_type = ' default ' ) :
2013-06-18 22:14:21 +02:00
try :
2013-10-22 22:28:19 +02:00
sanitize = lambda k , v : sanitize_filename (
2013-12-10 11:23:35 +01:00
compat_str ( v ) ,
2015-03-08 15:57:30 +01:00
restricted = self . params . get ( ' restrictfilenames ' ) ,
2017-03-01 17:03:36 +01:00
is_id = ( k == ' id ' or k . endswith ( ' _id ' ) ) )
2021-02-03 14:36:09 +01:00
outtmpl = self . outtmpl_dict . get ( tmpl_type , self . outtmpl_dict [ ' default ' ] )
2021-03-24 23:02:15 +01:00
outtmpl , template_dict = self . prepare_outtmpl ( outtmpl , info_dict , sanitize )
2016-03-05 22:52:42 +01:00
2017-07-13 19:40:54 +02:00
# expand_path translates '%%' into '%' and '$$' into '$'
# correspondingly that is not what we want since we need to keep
# '%%' intact for template dict substitution step. Working around
# with boundary-alike separator hack.
2017-07-15 02:02:14 +02:00
sep = ' ' . join ( [ random . choice ( ascii_letters ) for _ in range ( 32 ) ] )
2017-07-13 19:40:54 +02:00
outtmpl = outtmpl . replace ( ' %% ' , ' % {0} % ' . format ( sep ) ) . replace ( ' $$ ' , ' $ {0} $ ' . format ( sep ) )
# outtmpl should be expand_path'ed before template dict substitution
# because meta fields may contain env variables we don't want to
# be expanded. For example, for outtmpl "%(title)s.%(ext)s" and
# title "Hello $PATH", we don't want `$PATH` to be expanded.
filename = expand_path ( outtmpl ) . replace ( sep , ' ' ) % template_dict
2021-03-24 23:02:15 +01:00
force_ext = OUTTMPL_TYPES . get ( tmpl_type )
2021-02-03 14:36:09 +01:00
if force_ext is not None :
2021-06-03 20:00:38 +02:00
filename = replace_extension ( filename , force_ext , info_dict . get ( ' ext ' ) )
2021-02-03 14:36:09 +01:00
2020-09-30 05:50:09 +02:00
# https://github.com/blackjack4494/youtube-dlc/issues/85
trim_file_name = self . params . get ( ' trim_file_name ' , False )
if trim_file_name :
fn_groups = filename . rsplit ( ' . ' )
ext = fn_groups [ - 1 ]
sub_ext = ' '
if len ( fn_groups ) > 2 :
sub_ext = fn_groups [ - 2 ]
filename = ' . ' . join ( filter ( None , [ fn_groups [ 0 ] [ : trim_file_name ] , sub_ext , ext ] ) )
2021-01-23 13:18:12 +01:00
return filename
2013-06-18 22:14:21 +02:00
except ValueError as err :
2014-01-05 01:52:03 +01:00
self . report_error ( ' Error in output template: ' + str ( err ) + ' (encoding: ' + repr ( preferredencoding ( ) ) + ' ) ' )
2013-06-18 22:14:21 +02:00
return None
2021-02-03 14:36:09 +01:00
def prepare_filename ( self , info_dict , dir_type = ' ' , warn = False ) :
""" Generate the output filename. """
2021-01-23 13:18:12 +01:00
paths = self . params . get ( ' paths ' , { } )
assert isinstance ( paths , dict )
2021-02-03 14:36:09 +01:00
filename = self . _prepare_filename ( info_dict , dir_type or ' default ' )
if warn and not self . __prepare_filename_warned :
if not paths :
pass
elif filename == ' - ' :
self . report_warning ( ' --paths is ignored when an outputting to stdout ' )
elif os . path . isabs ( filename ) :
self . report_warning ( ' --paths is ignored since an absolute path is given in output template ' )
self . __prepare_filename_warned = True
if filename == ' - ' or not filename :
return filename
2021-01-23 13:18:12 +01:00
homepath = expand_path ( paths . get ( ' home ' , ' ' ) . strip ( ) )
assert isinstance ( homepath , compat_str )
subdir = expand_path ( paths . get ( dir_type , ' ' ) . strip ( ) ) if dir_type else ' '
assert isinstance ( subdir , compat_str )
2021-02-17 20:09:38 +01:00
path = os . path . join ( homepath , subdir , filename )
# Temporary fix for #4787
# 'Treat' all problem characters by passing filename through preferredencoding
# to workaround encoding issues with subprocess on python2 @ Windows
if sys . version_info < ( 3 , 0 ) and sys . platform == ' win32 ' :
path = encodeFilename ( path , True ) . decode ( preferredencoding ( ) )
return sanitize_path ( path , force = self . params . get ( ' windowsfilenames ' ) )
2021-01-23 13:18:12 +01:00
2021-05-28 18:38:01 +02:00
def _match_entry ( self , info_dict , incomplete = False , silent = False ) :
2020-09-17 20:22:07 +02:00
""" Returns None if the file should be downloaded """
2013-06-18 22:14:21 +02:00
2021-05-28 22:42:07 +02:00
video_title = info_dict . get ( ' title ' , info_dict . get ( ' id ' , ' video ' ) )
2021-01-13 02:01:01 +01:00
def check_filter ( ) :
if ' title ' in info_dict :
# This can happen when we're just evaluating the playlist
title = info_dict [ ' title ' ]
matchtitle = self . params . get ( ' matchtitle ' , False )
if matchtitle :
if not re . search ( matchtitle , title , re . IGNORECASE ) :
return ' " ' + title + ' " title did not match pattern " ' + matchtitle + ' " '
rejecttitle = self . params . get ( ' rejecttitle ' , False )
if rejecttitle :
if re . search ( rejecttitle , title , re . IGNORECASE ) :
return ' " ' + title + ' " title matched reject pattern " ' + rejecttitle + ' " '
date = info_dict . get ( ' upload_date ' )
if date is not None :
dateRange = self . params . get ( ' daterange ' , DateRange ( ) )
if date not in dateRange :
return ' %s upload date is not in range %s ' % ( date_from_str ( date ) . isoformat ( ) , dateRange )
view_count = info_dict . get ( ' view_count ' )
if view_count is not None :
min_views = self . params . get ( ' min_views ' )
if min_views is not None and view_count < min_views :
return ' Skipping %s , because it has not reached minimum view count ( %d / %d ) ' % ( video_title , view_count , min_views )
max_views = self . params . get ( ' max_views ' )
if max_views is not None and view_count > max_views :
return ' Skipping %s , because it has exceeded the maximum view count ( %d / %d ) ' % ( video_title , view_count , max_views )
if age_restricted ( info_dict . get ( ' age_limit ' ) , self . params . get ( ' age_limit ' ) ) :
return ' Skipping " %s " because it is age restricted ' % video_title
if not incomplete :
match_filter = self . params . get ( ' match_filter ' )
if match_filter is not None :
ret = match_filter ( info_dict )
if ret is not None :
return ret
return None
2021-05-28 22:42:07 +02:00
if self . in_download_archive ( info_dict ) :
reason = ' %s has already been recorded in the archive ' % video_title
break_opt , break_err = ' break_on_existing ' , ExistingVideoReached
else :
reason = check_filter ( )
break_opt , break_err = ' break_on_reject ' , RejectedVideoReached
2021-01-13 02:01:01 +01:00
if reason is not None :
2021-05-28 18:38:01 +02:00
if not silent :
self . to_screen ( ' [download] ' + reason )
2021-05-28 22:42:07 +02:00
if self . params . get ( break_opt , False ) :
raise break_err ( )
2021-01-13 02:01:01 +01:00
return reason
2013-10-22 14:49:34 +02:00
2013-11-03 11:56:45 +01:00
@staticmethod
def add_extra_info ( info_dict , extra_info ) :
''' Set the keys from extra_info in info dict if they are missing '''
for key , value in extra_info . items ( ) :
info_dict . setdefault ( key , value )
2021-04-27 11:02:08 +02:00
def extract_info ( self , url , download = True , ie_key = None , extra_info = { } ,
2015-06-12 22:05:21 +02:00
process = True , force_generic_extractor = False ) :
2021-05-06 18:01:20 +02:00
"""
Return a list with a dictionary for each video extracted .
Arguments :
url - - URL to extract
Keyword arguments :
download - - whether to download videos during extraction
ie_key - - extractor key hint
extra_info - - dictionary containing the extra values to add to each result
process - - whether to resolve all unresolved references ( URLs , playlist items ) ,
must be True for download to work .
force_generic_extractor - - force using the generic extractor
"""
2013-10-22 14:49:34 +02:00
2015-06-12 22:05:21 +02:00
if not ie_key and force_generic_extractor :
2015-06-12 15:20:12 +02:00
ie_key = ' Generic '
2013-06-18 22:14:21 +02:00
if ie_key :
2013-07-08 15:14:27 +02:00
ies = [ self . get_info_extractor ( ie_key ) ]
2013-06-18 22:14:21 +02:00
else :
ies = self . _ies
for ie in ies :
if not ie . suitable ( url ) :
continue
2020-11-12 22:10:51 +01:00
ie_key = ie . ie_key ( )
ie = self . get_info_extractor ( ie_key )
2013-06-18 22:14:21 +02:00
if not ie . working ( ) :
2014-01-05 01:52:03 +01:00
self . report_warning ( ' The program functionality for this site has been marked as broken, '
' and will probably not work. ' )
2013-06-18 22:14:21 +02:00
try :
2021-01-21 13:06:42 +01:00
temp_id = str_or_none (
2021-01-21 13:50:09 +01:00
ie . extract_id ( url ) if callable ( getattr ( ie , ' extract_id ' , None ) )
else ie . _match_id ( url ) )
2020-11-21 15:50:42 +01:00
except ( AssertionError , IndexError , AttributeError ) :
temp_id = None
if temp_id is not None and self . in_download_archive ( { ' id ' : temp_id , ' ie_key ' : ie_key } ) :
self . to_screen ( " [ %s ] %s : has already been recorded in archive " % (
ie_key , temp_id ) )
break
2021-04-27 11:02:08 +02:00
return self . __extract_info ( url , ie , download , extra_info , process )
2020-11-21 15:50:42 +01:00
else :
self . report_error ( ' no suitable InfoExtractor for URL %s ' % url )
def __handle_extraction_exceptions ( func ) :
def wrapper ( self , * args , * * kwargs ) :
try :
return func ( self , * args , * * kwargs )
2017-02-04 12:49:58 +01:00
except GeoRestrictedError as e :
msg = e . msg
if e . countries :
msg + = ' \n This video is available in %s . ' % ' , ' . join (
map ( ISO3166Utils . short2full , e . countries ) )
msg + = ' \n You might want to use a VPN or a proxy server (with --proxy) to workaround. '
self . report_error ( msg )
2015-12-20 01:16:19 +01:00
except ExtractorError as e : # An error we somewhat expected
2015-12-20 01:35:58 +01:00
self . report_error ( compat_str ( e ) , e . format_traceback ( ) )
2021-01-13 02:01:01 +01:00
except ( MaxDownloadsReached , ExistingVideoReached , RejectedVideoReached ) :
2014-01-23 10:36:47 +01:00
raise
2013-06-18 22:14:21 +02:00
except Exception as e :
if self . params . get ( ' ignoreerrors ' , False ) :
2015-12-20 02:00:39 +01:00
self . report_error ( error_to_compat_str ( e ) , tb = encode_compat_str ( traceback . format_exc ( ) ) )
2013-06-18 22:14:21 +02:00
else :
raise
2020-11-21 15:50:42 +01:00
return wrapper
@__handle_extraction_exceptions
2021-04-27 11:02:08 +02:00
def __extract_info ( self , url , ie , download , extra_info , process ) :
2020-11-21 15:50:42 +01:00
ie_result = ie . extract ( url )
if ie_result is None : # Finished already (backwards compatibility; listformats and friends should be moved here)
return
if isinstance ( ie_result , list ) :
# Backwards compatibility: old IE result format
ie_result = {
' _type ' : ' compat_list ' ,
' entries ' : ie_result ,
}
self . add_default_extra_info ( ie_result , ie , url )
if process :
return self . process_ie_result ( ie_result , download , extra_info )
2013-06-18 22:14:21 +02:00
else :
2020-11-21 15:50:42 +01:00
return ie_result
2013-10-22 14:49:34 +02:00
2014-03-23 16:06:03 +01:00
def add_default_extra_info ( self , ie_result , ie , url ) :
self . add_extra_info ( ie_result , {
' extractor ' : ie . IE_NAME ,
' webpage_url ' : url ,
2021-06-07 20:50:06 +02:00
' original_url ' : url ,
2014-03-23 16:06:03 +01:00
' webpage_url_basename ' : url_basename ( url ) ,
' extractor_key ' : ie . ie_key ( ) ,
} )
2013-06-18 22:14:21 +02:00
def process_ie_result ( self , ie_result , download = True , extra_info = { } ) :
"""
Take the result of the ie ( may be modified ) and resolve all unresolved
references ( URLs , playlist items ) .
It will also download the videos if ' download ' .
Returns the resolved ie_result .
"""
2014-08-21 11:52:07 +02:00
result_type = ie_result . get ( ' _type ' , ' video ' )
2014-10-24 14:48:12 +02:00
if result_type in ( ' url ' , ' url_transparent ' ) :
2016-05-14 00:46:38 +02:00
ie_result [ ' url ' ] = sanitize_url ( ie_result [ ' url ' ] )
2014-10-24 14:48:12 +02:00
extract_flat = self . params . get ( ' extract_flat ' , False )
2019-05-10 22:56:22 +02:00
if ( ( extract_flat == ' in_playlist ' and ' playlist ' in extra_info )
or extract_flat is True ) :
2021-06-07 20:47:53 +02:00
info_copy = ie_result . copy ( )
self . add_extra_info ( info_copy , extra_info )
self . add_default_extra_info (
info_copy , self . get_info_extractor ( ie_result . get ( ' ie_key ' ) ) , ie_result [ ' url ' ] )
self . __forced_printings ( info_copy , self . prepare_filename ( info_copy ) , incomplete = True )
2014-08-21 11:52:07 +02:00
return ie_result
2013-06-18 22:14:21 +02:00
if result_type == ' video ' :
2013-11-03 11:56:45 +01:00
self . add_extra_info ( ie_result , extra_info )
2021-05-18 20:20:29 +02:00
ie_result = self . process_video_result ( ie_result , download = download )
2021-05-20 14:32:58 +02:00
additional_urls = ( ie_result or { } ) . get ( ' additional_urls ' )
2021-05-18 20:20:29 +02:00
if additional_urls :
# TODO: Improve MetadataFromFieldPP to allow setting a list
if isinstance ( additional_urls , compat_str ) :
additional_urls = [ additional_urls ]
self . to_screen (
' [info] %s : %d additional URL(s) requested ' % ( ie_result [ ' id ' ] , len ( additional_urls ) ) )
self . write_debug ( ' Additional URLs: " %s " ' % ' " , " ' . join ( additional_urls ) )
ie_result [ ' additional_entries ' ] = [
self . extract_info (
url , download , extra_info ,
force_generic_extractor = self . params . get ( ' force_generic_extractor ' ) )
for url in additional_urls
]
return ie_result
2013-06-18 22:14:21 +02:00
elif result_type == ' url ' :
# We have to add extra_info to the results because it may be
# contained in a playlist
2021-05-18 20:20:59 +02:00
return self . extract_info (
ie_result [ ' url ' ] , download ,
ie_key = ie_result . get ( ' ie_key ' ) ,
extra_info = extra_info )
2013-12-05 14:29:08 +01:00
elif result_type == ' url_transparent ' :
# Use the information from the embedding page
info = self . extract_info (
ie_result [ ' url ' ] , ie_key = ie_result . get ( ' ie_key ' ) ,
extra_info = extra_info , download = False , process = False )
2017-03-31 18:57:35 +02:00
# extract_info may return None when ignoreerrors is enabled and
# extraction failed with an error, don't crash and return early
# in this case
if not info :
return info
2014-12-12 15:55:55 +01:00
force_properties = dict (
( k , v ) for k , v in ie_result . items ( ) if v is not None )
2017-07-20 19:13:32 +02:00
for f in ( ' _type ' , ' url ' , ' id ' , ' extractor ' , ' extractor_key ' , ' ie_key ' ) :
2014-12-12 15:55:55 +01:00
if f in force_properties :
del force_properties [ f ]
new_result = info . copy ( )
new_result . update ( force_properties )
2013-12-05 14:29:08 +01:00
2017-04-15 19:56:53 +02:00
# Extracted info may not be a video result (i.e.
# info.get('_type', 'video') != video) but rather an url or
# url_transparent. In such cases outer metadata (from ie_result)
# should be propagated to inner one (info). For this to happen
# _type of info should be overridden with url_transparent. This
2019-03-09 13:14:41 +01:00
# fixes issue from https://github.com/ytdl-org/youtube-dl/pull/11163.
2017-04-15 19:56:53 +02:00
if new_result . get ( ' _type ' ) == ' url ' :
new_result [ ' _type ' ] = ' url_transparent '
2013-12-05 14:29:08 +01:00
return self . process_ie_result (
new_result , download = download , extra_info = extra_info )
2017-04-12 21:38:43 +02:00
elif result_type in ( ' playlist ' , ' multi_video ' ) :
2021-01-16 13:40:15 +01:00
# Protect from infinite recursion due to recursively nested playlists
# (see https://github.com/ytdl-org/youtube-dl/issues/27833)
webpage_url = ie_result [ ' webpage_url ' ]
if webpage_url in self . _playlist_urls :
2017-10-06 18:34:46 +02:00
self . to_screen (
2021-01-16 13:40:15 +01:00
' [download] Skipping already downloaded playlist: %s '
% ie_result . get ( ' title ' ) or ie_result . get ( ' id ' ) )
return
2017-10-06 18:34:46 +02:00
2021-01-16 13:40:15 +01:00
self . _playlist_level + = 1
self . _playlist_urls . add ( webpage_url )
2021-05-23 13:58:15 +02:00
self . _sanitize_thumbnails ( ie_result )
2021-01-16 13:40:15 +01:00
try :
return self . __process_playlist ( ie_result , download )
finally :
self . _playlist_level - = 1
if not self . _playlist_level :
self . _playlist_urls . clear ( )
2013-06-18 22:14:21 +02:00
elif result_type == ' compat_list ' :
2014-11-20 16:29:31 +01:00
self . report_warning (
' Extractor %s returned a compat_list result. '
' It needs to be updated. ' % ie_result . get ( ' extractor ' ) )
2014-11-23 20:41:03 +01:00
2013-06-18 22:14:21 +02:00
def _fixup ( r ) :
2014-11-23 21:39:15 +01:00
self . add_extra_info (
r ,
2013-11-03 12:11:13 +01:00
{
' extractor ' : ie_result [ ' extractor ' ] ,
' webpage_url ' : ie_result [ ' webpage_url ' ] ,
2013-12-17 04:13:36 +01:00
' webpage_url_basename ' : url_basename ( ie_result [ ' webpage_url ' ] ) ,
2013-11-03 12:14:44 +01:00
' extractor_key ' : ie_result [ ' extractor_key ' ] ,
2014-11-23 21:39:15 +01:00
}
)
2013-06-18 22:14:21 +02:00
return r
ie_result [ ' entries ' ] = [
2013-11-03 11:56:45 +01:00
self . process_ie_result ( _fixup ( r ) , download , extra_info )
2013-06-18 22:14:21 +02:00
for r in ie_result [ ' entries ' ]
]
return ie_result
else :
raise Exception ( ' Invalid result type: %s ' % result_type )
2021-03-09 03:17:21 +01:00
def _ensure_dir_exists ( self , path ) :
return make_dir ( path , self . report_error )
2021-01-16 13:40:15 +01:00
def __process_playlist ( self , ie_result , download ) :
# We process each entry in the playlist
playlist = ie_result . get ( ' title ' ) or ie_result . get ( ' id ' )
self . to_screen ( ' [download] Downloading playlist: %s ' % playlist )
2021-03-23 20:45:53 +01:00
if ' entries ' not in ie_result :
raise EntryNotInPlaylist ( )
incomplete_entries = bool ( ie_result . get ( ' requested_entries ' ) )
if incomplete_entries :
def fill_missing_entries ( entries , indexes ) :
ret = [ None ] * max ( * indexes )
for i , entry in zip ( indexes , entries ) :
ret [ i - 1 ] = entry
return ret
ie_result [ ' entries ' ] = fill_missing_entries ( ie_result [ ' entries ' ] , ie_result [ ' requested_entries ' ] )
2021-01-28 01:54:58 +01:00
2021-01-16 13:40:15 +01:00
playlist_results = [ ]
2021-05-28 18:37:11 +02:00
playliststart = self . params . get ( ' playliststart ' , 1 )
2021-01-16 13:40:15 +01:00
playlistend = self . params . get ( ' playlistend ' )
# For backwards compatibility, interpret -1 as whole list
if playlistend == - 1 :
playlistend = None
playlistitems_str = self . params . get ( ' playlist_items ' )
playlistitems = None
if playlistitems_str is not None :
def iter_playlistitems ( format ) :
for string_segment in format . split ( ' , ' ) :
if ' - ' in string_segment :
start , end = string_segment . split ( ' - ' )
for item in range ( int ( start ) , int ( end ) + 1 ) :
yield int ( item )
else :
yield int ( string_segment )
playlistitems = orderedSet ( iter_playlistitems ( playlistitems_str ) )
ie_entries = ie_result [ ' entries ' ]
2021-05-28 18:37:11 +02:00
msg = (
' Downloading %d videos ' if not isinstance ( ie_entries , list )
else ' Collected %d videos; downloading %% d of them ' % len ( ie_entries ) )
if not isinstance ( ie_entries , ( list , PagedList ) ) :
ie_entries = LazyList ( ie_entries )
entries = [ ]
for i in playlistitems or itertools . count ( playliststart ) :
if playlistitems is None and playlistend is not None and playlistend < i :
break
entry = None
try :
entry = ie_entries [ i - 1 ]
if entry is None :
2021-03-23 20:45:53 +01:00
raise EntryNotInPlaylist ( )
2021-05-28 18:37:11 +02:00
except ( IndexError , EntryNotInPlaylist ) :
if incomplete_entries :
raise EntryNotInPlaylist ( )
elif not playlistitems :
break
entries . append ( entry )
2021-05-28 18:38:01 +02:00
try :
if entry is not None :
self . _match_entry ( entry , incomplete = True , silent = True )
except ( ExistingVideoReached , RejectedVideoReached ) :
break
2021-05-28 18:37:11 +02:00
ie_result [ ' entries ' ] = entries
2021-01-16 13:40:15 +01:00
2021-05-28 18:37:11 +02:00
# Save playlist_index before re-ordering
entries = [
( ( playlistitems [ i - 1 ] if playlistitems else i ) , entry )
for i , entry in enumerate ( entries , 1 )
if entry is not None ]
n_entries = len ( entries )
2021-03-23 20:45:53 +01:00
if not playlistitems and ( playliststart or playlistend ) :
2021-05-28 18:37:11 +02:00
playlistitems = list ( range ( playliststart , playliststart + n_entries ) )
2021-03-23 20:45:53 +01:00
ie_result [ ' requested_entries ' ] = playlistitems
if self . params . get ( ' allow_playlist_files ' , True ) :
ie_copy = {
' playlist ' : playlist ,
' playlist_id ' : ie_result . get ( ' id ' ) ,
' playlist_title ' : ie_result . get ( ' title ' ) ,
' playlist_uploader ' : ie_result . get ( ' uploader ' ) ,
' playlist_uploader_id ' : ie_result . get ( ' uploader_id ' ) ,
2021-05-06 17:26:19 +02:00
' playlist_index ' : 0 ,
2021-03-23 20:45:53 +01:00
}
ie_copy . update ( dict ( ie_result ) )
if self . params . get ( ' writeinfojson ' , False ) :
infofn = self . prepare_filename ( ie_copy , ' pl_infojson ' )
if not self . _ensure_dir_exists ( encodeFilename ( infofn ) ) :
return
if not self . params . get ( ' overwrites ' , True ) and os . path . exists ( encodeFilename ( infofn ) ) :
self . to_screen ( ' [info] Playlist metadata is already present ' )
else :
self . to_screen ( ' [info] Writing playlist metadata as JSON to: ' + infofn )
try :
write_json_file ( self . filter_requested_info ( ie_result , self . params . get ( ' clean_infojson ' , True ) ) , infofn )
except ( OSError , IOError ) :
self . report_error ( ' Cannot write playlist metadata to JSON file ' + infofn )
2021-05-17 13:45:33 +02:00
# TODO: This should be passed to ThumbnailsConvertor if necessary
self . _write_thumbnails ( ie_copy , self . prepare_filename ( ie_copy , ' pl_thumbnail ' ) )
2021-03-23 20:45:53 +01:00
if self . params . get ( ' writedescription ' , False ) :
descfn = self . prepare_filename ( ie_copy , ' pl_description ' )
if not self . _ensure_dir_exists ( encodeFilename ( descfn ) ) :
return
if not self . params . get ( ' overwrites ' , True ) and os . path . exists ( encodeFilename ( descfn ) ) :
self . to_screen ( ' [info] Playlist description is already present ' )
elif ie_result . get ( ' description ' ) is None :
self . report_warning ( ' There \' s no playlist description to write. ' )
else :
try :
self . to_screen ( ' [info] Writing playlist description to: ' + descfn )
with io . open ( encodeFilename ( descfn ) , ' w ' , encoding = ' utf-8 ' ) as descfile :
descfile . write ( ie_result [ ' description ' ] )
except ( OSError , IOError ) :
self . report_error ( ' Cannot write playlist description file ' + descfn )
return
2021-01-16 13:40:15 +01:00
if self . params . get ( ' playlistreverse ' , False ) :
entries = entries [ : : - 1 ]
if self . params . get ( ' playlistrandom ' , False ) :
random . shuffle ( entries )
x_forwarded_for = ie_result . get ( ' __x_forwarded_for_ip ' )
2021-05-28 18:37:11 +02:00
self . to_screen ( ' [ %s ] playlist %s : %s ' % ( ie_result [ ' extractor ' ] , playlist , msg % n_entries ) )
2021-04-21 08:00:43 +02:00
failures = 0
max_failures = self . params . get ( ' skip_playlist_after_errors ' ) or float ( ' inf ' )
2021-05-06 17:26:19 +02:00
for i , entry_tuple in enumerate ( entries , 1 ) :
playlist_index , entry = entry_tuple
2021-05-11 10:00:48 +02:00
if ' playlist_index ' in self . params . get ( ' compat_options ' , [ ] ) :
playlist_index = playlistitems [ i - 1 ] if playlistitems else i
2021-01-16 13:40:15 +01:00
self . to_screen ( ' [download] Downloading video %s of %s ' % ( i , n_entries ) )
# This __x_forwarded_for_ip thing is a bit ugly but requires
# minimal changes
if x_forwarded_for :
entry [ ' __x_forwarded_for_ip ' ] = x_forwarded_for
extra = {
' n_entries ' : n_entries ,
2021-05-03 17:11:33 +02:00
' _last_playlist_index ' : max ( playlistitems ) if playlistitems else ( playlistend or n_entries ) ,
2021-05-06 17:26:19 +02:00
' playlist_index ' : playlist_index ,
' playlist_autonumber ' : i ,
2021-01-16 13:40:15 +01:00
' playlist ' : playlist ,
' playlist_id ' : ie_result . get ( ' id ' ) ,
' playlist_title ' : ie_result . get ( ' title ' ) ,
' playlist_uploader ' : ie_result . get ( ' uploader ' ) ,
' playlist_uploader_id ' : ie_result . get ( ' uploader_id ' ) ,
' extractor ' : ie_result [ ' extractor ' ] ,
' webpage_url ' : ie_result [ ' webpage_url ' ] ,
' webpage_url_basename ' : url_basename ( ie_result [ ' webpage_url ' ] ) ,
' extractor_key ' : ie_result [ ' extractor_key ' ] ,
}
if self . _match_entry ( entry , incomplete = True ) is not None :
continue
entry_result = self . __process_iterable_entry ( entry , download , extra )
2021-04-21 08:00:43 +02:00
if not entry_result :
failures + = 1
if failures > = max_failures :
self . report_error (
' Skipping the remaining entries in playlist " %s " since %d items failed extraction ' % ( playlist , failures ) )
break
2021-01-16 13:40:15 +01:00
# TODO: skip failed (empty) entries?
playlist_results . append ( entry_result )
ie_result [ ' entries ' ] = playlist_results
self . to_screen ( ' [download] Finished downloading playlist: %s ' % playlist )
return ie_result
2020-11-21 15:50:42 +01:00
@__handle_extraction_exceptions
def __process_iterable_entry ( self , entry , download , extra_info ) :
return self . process_ie_result (
entry , download = download , extra_info = extra_info )
2015-06-28 22:08:29 +02:00
def _build_format_filter ( self , filter_spec ) :
" Returns a function to filter the formats according to the filter_spec "
2015-01-23 00:04:05 +01:00
OPERATORS = {
' < ' : operator . lt ,
' <= ' : operator . le ,
' > ' : operator . gt ,
' >= ' : operator . ge ,
' = ' : operator . eq ,
' != ' : operator . ne ,
}
2015-06-28 22:08:29 +02:00
operator_rex = re . compile ( r ''' (?x) \ s*
2018-02-10 10:42:45 +01:00
( ? P < key > width | height | tbr | abr | vbr | asr | filesize | filesize_approx | fps )
2015-01-23 00:04:05 +01:00
\s * ( ? P < op > % s ) ( ? P < none_inclusive > \s * \? ) ? \s *
( ? P < value > [ 0 - 9. ] + ( ? : [ kKmMgGtTpPeEzZyY ] i ? [ Bb ] ? ) ? )
2015-06-28 22:08:29 +02:00
$
2015-01-23 00:04:05 +01:00
''' % ' | ' .join(map(re.escape, OPERATORS.keys())))
2015-06-28 22:08:29 +02:00
m = operator_rex . search ( filter_spec )
2015-02-08 20:07:43 +01:00
if m :
try :
comparison_value = int ( m . group ( ' value ' ) )
except ValueError :
comparison_value = parse_filesize ( m . group ( ' value ' ) )
if comparison_value is None :
comparison_value = parse_filesize ( m . group ( ' value ' ) + ' B ' )
if comparison_value is None :
raise ValueError (
' Invalid value %r in format specification %r ' % (
2015-06-28 22:08:29 +02:00
m . group ( ' value ' ) , filter_spec ) )
2015-02-08 20:07:43 +01:00
op = OPERATORS [ m . group ( ' op ' ) ]
2015-01-23 00:04:05 +01:00
if not m :
2015-02-08 20:07:43 +01:00
STR_OPERATORS = {
' = ' : operator . eq ,
2016-01-13 09:24:48 +01:00
' ^= ' : lambda attr , value : attr . startswith ( value ) ,
' $= ' : lambda attr , value : attr . endswith ( value ) ,
' *= ' : lambda attr , value : value in attr ,
2015-02-08 20:07:43 +01:00
}
2015-06-28 22:08:29 +02:00
str_operator_rex = re . compile ( r ''' (?x)
2020-10-26 16:44:00 +01:00
\s * ( ? P < key > [ a - zA - Z0 - 9. _ - ] + )
2019-01-20 07:48:09 +01:00
\s * ( ? P < negation > ! \s * ) ? ( ? P < op > % s ) ( ? P < none_inclusive > \s * \? ) ?
2016-01-28 12:07:15 +01:00
\s * ( ? P < value > [ a - zA - Z0 - 9. _ - ] + )
2015-06-28 22:08:29 +02:00
\s * $
2015-02-08 20:07:43 +01:00
''' % ' | ' .join(map(re.escape, STR_OPERATORS.keys())))
2015-06-28 22:08:29 +02:00
m = str_operator_rex . search ( filter_spec )
2015-02-08 20:07:43 +01:00
if m :
comparison_value = m . group ( ' value ' )
2019-01-20 07:48:09 +01:00
str_op = STR_OPERATORS [ m . group ( ' op ' ) ]
if m . group ( ' negation ' ) :
2019-01-23 19:34:41 +01:00
op = lambda attr , value : not str_op ( attr , value )
2019-01-20 07:48:09 +01:00
else :
op = str_op
2015-01-23 00:04:05 +01:00
2015-02-08 20:07:43 +01:00
if not m :
2015-06-28 22:08:29 +02:00
raise ValueError ( ' Invalid filter specification %r ' % filter_spec )
2015-01-23 00:04:05 +01:00
def _filter ( f ) :
actual_value = f . get ( m . group ( ' key ' ) )
if actual_value is None :
return m . group ( ' none_inclusive ' )
return op ( actual_value , comparison_value )
2015-06-28 22:08:29 +02:00
return _filter
2017-07-22 19:12:01 +02:00
def _default_format_spec ( self , info_dict , download = True ) :
2017-10-11 18:45:03 +02:00
def can_merge ( ) :
merger = FFmpegMergerPP ( self )
return merger . available and merger . can_merge ( )
Change defaults
* Enabled --ignore by default
* Disabled --video-multistreams and --audio-multistreams by default
* Changed default format selection to 'bv*+ba/b' when --audio-multistreams is disabled
* Changed default format sort order to 'res,fps,codec,size,br,asr,proto,ext,has_audio,source,format_id'
* Changed default output template to '%(title)s [%(id)s].%(ext)s'
* Enabled `--list-formats-as-table` by default
2021-01-04 17:40:47 +01:00
prefer_best = (
not self . params . get ( ' simulate ' , False )
and download
and (
not can_merge ( )
2021-01-07 12:41:39 +01:00
or info_dict . get ( ' is_live ' , False )
2021-02-03 14:36:09 +01:00
or self . outtmpl_dict [ ' default ' ] == ' - ' ) )
2021-05-11 10:00:48 +02:00
compat = (
prefer_best
or self . params . get ( ' allow_multiple_audio_streams ' , False )
or ' format-spec ' in self . params . get ( ' compat_opts ' , [ ] ) )
Change defaults
* Enabled --ignore by default
* Disabled --video-multistreams and --audio-multistreams by default
* Changed default format selection to 'bv*+ba/b' when --audio-multistreams is disabled
* Changed default format sort order to 'res,fps,codec,size,br,asr,proto,ext,has_audio,source,format_id'
* Changed default output template to '%(title)s [%(id)s].%(ext)s'
* Enabled `--list-formats-as-table` by default
2021-01-04 17:40:47 +01:00
return (
2021-05-11 10:00:48 +02:00
' best/bestvideo+bestaudio ' if prefer_best
else ' bestvideo*+bestaudio/best ' if not compat
Change defaults
* Enabled --ignore by default
* Disabled --video-multistreams and --audio-multistreams by default
* Changed default format selection to 'bv*+ba/b' when --audio-multistreams is disabled
* Changed default format sort order to 'res,fps,codec,size,br,asr,proto,ext,has_audio,source,format_id'
* Changed default output template to '%(title)s [%(id)s].%(ext)s'
* Enabled `--list-formats-as-table` by default
2021-01-04 17:40:47 +01:00
else ' bestvideo+bestaudio/best ' )
2017-07-22 19:12:01 +02:00
2015-06-28 22:08:29 +02:00
def build_format_selector ( self , format_spec ) :
def syntax_error ( note , start ) :
message = (
' Invalid format specification: '
' {0} \n \t {1} \n \t {2} ^ ' . format ( note , format_spec , ' ' * start [ 1 ] ) )
return SyntaxError ( message )
PICKFIRST = ' PICKFIRST '
MERGE = ' MERGE '
SINGLE = ' SINGLE '
2015-06-29 12:42:02 +02:00
GROUP = ' GROUP '
2015-06-28 22:08:29 +02:00
FormatSelector = collections . namedtuple ( ' FormatSelector ' , [ ' type ' , ' selector ' , ' filters ' ] )
Change defaults
* Enabled --ignore by default
* Disabled --video-multistreams and --audio-multistreams by default
* Changed default format selection to 'bv*+ba/b' when --audio-multistreams is disabled
* Changed default format sort order to 'res,fps,codec,size,br,asr,proto,ext,has_audio,source,format_id'
* Changed default output template to '%(title)s [%(id)s].%(ext)s'
* Enabled `--list-formats-as-table` by default
2021-01-04 17:40:47 +01:00
allow_multiple_streams = { ' audio ' : self . params . get ( ' allow_multiple_audio_streams ' , False ) ,
' video ' : self . params . get ( ' allow_multiple_video_streams ' , False ) }
Better Format Selection
* Added options: --video-multistreams, --no-video-multistreams, --audio-multistreams, --no-audio-multistreams
* New format selectors: best*, worst*, bestvideo*, bestaudio*, worstvideo*, worstaudio*
* Added b,w,v,a as alias for best, worst, video and audio respectively in format selection
* Changed video format sorting to show video only files and video+audio files together.
2020-11-05 16:35:36 +01:00
2021-05-04 17:54:00 +02:00
check_formats = self . params . get ( ' check_formats ' )
2015-06-28 22:08:29 +02:00
def _parse_filter ( tokens ) :
filter_parts = [ ]
for type , string , start , _ , _ in tokens :
if type == tokenize . OP and string == ' ] ' :
return ' ' . join ( filter_parts )
else :
filter_parts . append ( string )
2015-08-04 22:29:23 +02:00
def _remove_unused_ops ( tokens ) :
2015-11-20 18:21:46 +01:00
# Remove operators that we don't use and join them with the surrounding strings
2015-08-04 22:29:23 +02:00
# for example: 'mp4' '-' 'baseline' '-' '16x9' is converted to 'mp4-baseline-16x9'
ALLOWED_OPS = ( ' / ' , ' + ' , ' , ' , ' ( ' , ' ) ' )
last_string , last_start , last_end , last_line = None , None , None , None
for type , string , start , end , line in tokens :
if type == tokenize . OP and string == ' [ ' :
if last_string :
yield tokenize . NAME , last_string , last_start , last_end , last_line
last_string = None
yield type , string , start , end , line
# everything inside brackets will be handled by _parse_filter
for type , string , start , end , line in tokens :
yield type , string , start , end , line
if type == tokenize . OP and string == ' ] ' :
break
elif type == tokenize . OP and string in ALLOWED_OPS :
if last_string :
yield tokenize . NAME , last_string , last_start , last_end , last_line
last_string = None
yield type , string , start , end , line
elif type in [ tokenize . NAME , tokenize . NUMBER , tokenize . OP ] :
if not last_string :
last_string = string
last_start = start
last_end = end
else :
last_string + = string
if last_string :
yield tokenize . NAME , last_string , last_start , last_end , last_line
2015-06-30 19:45:42 +02:00
def _parse_format_selection ( tokens , inside_merge = False , inside_choice = False , inside_group = False ) :
2015-06-28 22:08:29 +02:00
selectors = [ ]
current_selector = None
for type , string , start , _ , _ in tokens :
# ENCODING is only defined in python 3.x
if type == getattr ( tokenize , ' ENCODING ' , None ) :
continue
elif type in [ tokenize . NAME , tokenize . NUMBER ] :
current_selector = FormatSelector ( SINGLE , string , [ ] )
elif type == tokenize . OP :
2015-06-30 19:45:42 +02:00
if string == ' ) ' :
if not inside_group :
# ')' will be handled by the parentheses group
tokens . restore_last_token ( )
2015-06-28 22:08:29 +02:00
break
2015-06-30 19:45:42 +02:00
elif inside_merge and string in [ ' / ' , ' , ' ] :
2015-06-29 12:42:02 +02:00
tokens . restore_last_token ( )
break
2015-06-30 19:45:42 +02:00
elif inside_choice and string == ' , ' :
tokens . restore_last_token ( )
break
elif string == ' , ' :
2015-07-10 22:46:25 +02:00
if not current_selector :
raise syntax_error ( ' " , " must follow a format selector ' , start )
2015-06-28 22:08:29 +02:00
selectors . append ( current_selector )
current_selector = None
elif string == ' / ' :
2015-08-03 23:04:11 +02:00
if not current_selector :
raise syntax_error ( ' " / " must follow a format selector ' , start )
2015-06-28 22:08:29 +02:00
first_choice = current_selector
2015-06-30 19:45:42 +02:00
second_choice = _parse_format_selection ( tokens , inside_choice = True )
2015-07-04 21:30:26 +02:00
current_selector = FormatSelector ( PICKFIRST , ( first_choice , second_choice ) , [ ] )
2015-06-28 22:08:29 +02:00
elif string == ' [ ' :
if not current_selector :
current_selector = FormatSelector ( SINGLE , ' best ' , [ ] )
format_filter = _parse_filter ( tokens )
current_selector . filters . append ( format_filter )
2015-06-29 12:42:02 +02:00
elif string == ' ( ' :
if current_selector :
raise syntax_error ( ' Unexpected " ( " ' , start )
2015-06-30 19:45:42 +02:00
group = _parse_format_selection ( tokens , inside_group = True )
current_selector = FormatSelector ( GROUP , group , [ ] )
2015-06-28 22:08:29 +02:00
elif string == ' + ' :
2015-08-04 09:07:44 +02:00
if not current_selector :
raise syntax_error ( ' Unexpected " + " ' , start )
selector_1 = current_selector
selector_2 = _parse_format_selection ( tokens , inside_merge = True )
if not selector_2 :
raise syntax_error ( ' Expected a selector ' , start )
current_selector = FormatSelector ( MERGE , ( selector_1 , selector_2 ) , [ ] )
2015-06-28 22:08:29 +02:00
else :
raise syntax_error ( ' Operator not recognized: " {0} " ' . format ( string ) , start )
elif type == tokenize . ENDMARKER :
break
if current_selector :
selectors . append ( current_selector )
return selectors
2021-04-10 16:40:30 +02:00
def _merge ( formats_pair ) :
format_1 , format_2 = formats_pair
formats_info = [ ]
formats_info . extend ( format_1 . get ( ' requested_formats ' , ( format_1 , ) ) )
formats_info . extend ( format_2 . get ( ' requested_formats ' , ( format_2 , ) ) )
if not allow_multiple_streams [ ' video ' ] or not allow_multiple_streams [ ' audio ' ] :
get_no_more = { " video " : False , " audio " : False }
for ( i , fmt_info ) in enumerate ( formats_info ) :
for aud_vid in [ " audio " , " video " ] :
if not allow_multiple_streams [ aud_vid ] and fmt_info . get ( aud_vid [ 0 ] + ' codec ' ) != ' none ' :
if get_no_more [ aud_vid ] :
formats_info . pop ( i )
get_no_more [ aud_vid ] = True
if len ( formats_info ) == 1 :
return formats_info [ 0 ]
video_fmts = [ fmt_info for fmt_info in formats_info if fmt_info . get ( ' vcodec ' ) != ' none ' ]
audio_fmts = [ fmt_info for fmt_info in formats_info if fmt_info . get ( ' acodec ' ) != ' none ' ]
the_only_video = video_fmts [ 0 ] if len ( video_fmts ) == 1 else None
the_only_audio = audio_fmts [ 0 ] if len ( audio_fmts ) == 1 else None
output_ext = self . params . get ( ' merge_output_format ' )
if not output_ext :
if the_only_video :
output_ext = the_only_video [ ' ext ' ]
elif the_only_audio and not video_fmts :
output_ext = the_only_audio [ ' ext ' ]
else :
output_ext = ' mkv '
new_dict = {
' requested_formats ' : formats_info ,
' format ' : ' + ' . join ( fmt_info . get ( ' format ' ) for fmt_info in formats_info ) ,
' format_id ' : ' + ' . join ( fmt_info . get ( ' format_id ' ) for fmt_info in formats_info ) ,
' ext ' : output_ext ,
}
if the_only_video :
new_dict . update ( {
' width ' : the_only_video . get ( ' width ' ) ,
' height ' : the_only_video . get ( ' height ' ) ,
' resolution ' : the_only_video . get ( ' resolution ' ) or self . format_resolution ( the_only_video ) ,
' fps ' : the_only_video . get ( ' fps ' ) ,
' vcodec ' : the_only_video . get ( ' vcodec ' ) ,
' vbr ' : the_only_video . get ( ' vbr ' ) ,
' stretched_ratio ' : the_only_video . get ( ' stretched_ratio ' ) ,
} )
if the_only_audio :
new_dict . update ( {
' acodec ' : the_only_audio . get ( ' acodec ' ) ,
' abr ' : the_only_audio . get ( ' abr ' ) ,
} )
return new_dict
2021-05-04 17:54:00 +02:00
def _check_formats ( formats ) :
for f in formats :
self . to_screen ( ' [info] Testing format %s ' % f [ ' format_id ' ] )
paths = self . params . get ( ' paths ' , { } )
temp_file = os . path . join (
expand_path ( paths . get ( ' home ' , ' ' ) . strip ( ) ) ,
expand_path ( paths . get ( ' temp ' , ' ' ) . strip ( ) ) ,
' ytdl. %s .f %s .check-format ' % ( random_uuidv4 ( ) , f [ ' format_id ' ] ) )
2021-05-15 15:42:26 +02:00
try :
dl , _ = self . dl ( temp_file , f , test = True )
except ( ExtractorError , IOError , OSError , ValueError ) + network_exceptions :
dl = False
finally :
if os . path . exists ( temp_file ) :
os . remove ( temp_file )
2021-05-04 17:54:00 +02:00
if dl :
yield f
else :
self . to_screen ( ' [info] Unable to download format %s . Skipping... ' % f [ ' format_id ' ] )
2015-06-28 22:08:29 +02:00
def _build_selector_function ( selector ) :
Better Format Selection
* Added options: --video-multistreams, --no-video-multistreams, --audio-multistreams, --no-audio-multistreams
* New format selectors: best*, worst*, bestvideo*, bestaudio*, worstvideo*, worstaudio*
* Added b,w,v,a as alias for best, worst, video and audio respectively in format selection
* Changed video format sorting to show video only files and video+audio files together.
2020-11-05 16:35:36 +01:00
if isinstance ( selector , list ) : # ,
2015-06-28 22:08:29 +02:00
fs = [ _build_selector_function ( s ) for s in selector ]
2016-07-15 19:55:43 +02:00
def selector_function ( ctx ) :
2015-06-28 22:08:29 +02:00
for f in fs :
2016-07-15 19:55:43 +02:00
for format in f ( ctx ) :
2015-06-28 22:08:29 +02:00
yield format
return selector_function
Better Format Selection
* Added options: --video-multistreams, --no-video-multistreams, --audio-multistreams, --no-audio-multistreams
* New format selectors: best*, worst*, bestvideo*, bestaudio*, worstvideo*, worstaudio*
* Added b,w,v,a as alias for best, worst, video and audio respectively in format selection
* Changed video format sorting to show video only files and video+audio files together.
2020-11-05 16:35:36 +01:00
elif selector . type == GROUP : # ()
2015-06-29 12:42:02 +02:00
selector_function = _build_selector_function ( selector . selector )
Better Format Selection
* Added options: --video-multistreams, --no-video-multistreams, --audio-multistreams, --no-audio-multistreams
* New format selectors: best*, worst*, bestvideo*, bestaudio*, worstvideo*, worstaudio*
* Added b,w,v,a as alias for best, worst, video and audio respectively in format selection
* Changed video format sorting to show video only files and video+audio files together.
2020-11-05 16:35:36 +01:00
elif selector . type == PICKFIRST : # /
2015-06-28 22:08:29 +02:00
fs = [ _build_selector_function ( s ) for s in selector . selector ]
2016-07-15 19:55:43 +02:00
def selector_function ( ctx ) :
2015-06-28 22:08:29 +02:00
for f in fs :
2016-07-15 19:55:43 +02:00
picked_formats = list ( f ( ctx ) )
2015-06-28 22:08:29 +02:00
if picked_formats :
return picked_formats
return [ ]
Better Format Selection
* Added options: --video-multistreams, --no-video-multistreams, --audio-multistreams, --no-audio-multistreams
* New format selectors: best*, worst*, bestvideo*, bestaudio*, worstvideo*, worstaudio*
* Added b,w,v,a as alias for best, worst, video and audio respectively in format selection
* Changed video format sorting to show video only files and video+audio files together.
2020-11-05 16:35:36 +01:00
elif selector . type == SINGLE : # atom
2021-04-26 07:19:22 +02:00
format_spec = selector . selector or ' best '
Better Format Selection
* Added options: --video-multistreams, --no-video-multistreams, --audio-multistreams, --no-audio-multistreams
* New format selectors: best*, worst*, bestvideo*, bestaudio*, worstvideo*, worstaudio*
* Added b,w,v,a as alias for best, worst, video and audio respectively in format selection
* Changed video format sorting to show video only files and video+audio files together.
2020-11-05 16:35:36 +01:00
2021-04-10 16:40:30 +02:00
# TODO: Add allvideo, allaudio etc by generalizing the code with best/worst selector
Better Format Selection
* Added options: --video-multistreams, --no-video-multistreams, --audio-multistreams, --no-audio-multistreams
* New format selectors: best*, worst*, bestvideo*, bestaudio*, worstvideo*, worstaudio*
* Added b,w,v,a as alias for best, worst, video and audio respectively in format selection
* Changed video format sorting to show video only files and video+audio files together.
2020-11-05 16:35:36 +01:00
if format_spec == ' all ' :
def selector_function ( ctx ) :
formats = list ( ctx [ ' formats ' ] )
2021-05-04 17:54:00 +02:00
if check_formats :
formats = _check_formats ( formats )
for f in formats :
yield f
2021-04-10 16:40:30 +02:00
elif format_spec == ' mergeall ' :
def selector_function ( ctx ) :
2021-05-04 17:54:00 +02:00
formats = list ( _check_formats ( ctx [ ' formats ' ] ) )
2021-04-10 18:59:58 +02:00
if not formats :
return
2021-04-13 07:23:25 +02:00
merged_format = formats [ - 1 ]
for f in formats [ - 2 : : - 1 ] :
2021-04-10 16:40:30 +02:00
merged_format = _merge ( ( merged_format , f ) )
yield merged_format
Better Format Selection
* Added options: --video-multistreams, --no-video-multistreams, --audio-multistreams, --no-audio-multistreams
* New format selectors: best*, worst*, bestvideo*, bestaudio*, worstvideo*, worstaudio*
* Added b,w,v,a as alias for best, worst, video and audio respectively in format selection
* Changed video format sorting to show video only files and video+audio files together.
2020-11-05 16:35:36 +01:00
else :
2021-05-04 17:54:00 +02:00
format_fallback , format_reverse , format_idx = False , True , 1
2021-04-02 18:42:42 +02:00
mobj = re . match (
r ' (?P<bw>best|worst|b|w)(?P<type>video|audio|v|a)?(?P<mod> \ *)?(?: \ .(?P<n>[1-9] \ d*))?$ ' ,
format_spec )
if mobj is not None :
format_idx = int_or_none ( mobj . group ( ' n ' ) , default = 1 )
2021-05-04 17:54:00 +02:00
format_reverse = mobj . group ( ' bw ' ) [ 0 ] == ' b '
2021-04-02 18:42:42 +02:00
format_type = ( mobj . group ( ' type ' ) or [ None ] ) [ 0 ]
not_format_type = { ' v ' : ' a ' , ' a ' : ' v ' } . get ( format_type )
format_modified = mobj . group ( ' mod ' ) is not None
Better Format Selection
* Added options: --video-multistreams, --no-video-multistreams, --audio-multistreams, --no-audio-multistreams
* New format selectors: best*, worst*, bestvideo*, bestaudio*, worstvideo*, worstaudio*
* Added b,w,v,a as alias for best, worst, video and audio respectively in format selection
* Changed video format sorting to show video only files and video+audio files together.
2020-11-05 16:35:36 +01:00
format_fallback = not format_type and not format_modified # for b, w
2021-04-02 18:42:42 +02:00
filter_f = (
( lambda f : f . get ( ' %s codec ' % format_type ) != ' none ' )
if format_type and format_modified # bv*, ba*, wv*, wa*
else ( lambda f : f . get ( ' %s codec ' % not_format_type ) == ' none ' )
if format_type # bv, ba, wv, wa
else ( lambda f : f . get ( ' vcodec ' ) != ' none ' and f . get ( ' acodec ' ) != ' none ' )
if not format_modified # b, w
else None ) # b*, w*
2015-06-28 22:08:29 +02:00
else :
Better Format Selection
* Added options: --video-multistreams, --no-video-multistreams, --audio-multistreams, --no-audio-multistreams
* New format selectors: best*, worst*, bestvideo*, bestaudio*, worstvideo*, worstaudio*
* Added b,w,v,a as alias for best, worst, video and audio respectively in format selection
* Changed video format sorting to show video only files and video+audio files together.
2020-11-05 16:35:36 +01:00
filter_f = ( ( lambda f : f . get ( ' ext ' ) == format_spec )
if format_spec in [ ' mp4 ' , ' flv ' , ' webm ' , ' 3gp ' , ' m4a ' , ' mp3 ' , ' ogg ' , ' aac ' , ' wav ' ] # extension
else ( lambda f : f . get ( ' format_id ' ) == format_spec ) ) # id
def selector_function ( ctx ) :
formats = list ( ctx [ ' formats ' ] )
if not formats :
return
matches = list ( filter ( filter_f , formats ) ) if filter_f is not None else formats
2021-05-04 17:54:00 +02:00
if format_fallback and ctx [ ' incomplete_formats ' ] and not matches :
Better Format Selection
* Added options: --video-multistreams, --no-video-multistreams, --audio-multistreams, --no-audio-multistreams
* New format selectors: best*, worst*, bestvideo*, bestaudio*, worstvideo*, worstaudio*
* Added b,w,v,a as alias for best, worst, video and audio respectively in format selection
* Changed video format sorting to show video only files and video+audio files together.
2020-11-05 16:35:36 +01:00
# for extractors with incomplete formats (audio only (soundcloud)
# or video only (imgur)) best/worst will fallback to
# best/worst {video,audio}-only format
2021-05-04 17:54:00 +02:00
matches = formats
if format_reverse :
matches = matches [ : : - 1 ]
if check_formats :
matches = list ( itertools . islice ( _check_formats ( matches ) , format_idx ) )
n = len ( matches )
if - n < = format_idx - 1 < n :
yield matches [ format_idx - 1 ]
Better Format Selection
* Added options: --video-multistreams, --no-video-multistreams, --audio-multistreams, --no-audio-multistreams
* New format selectors: best*, worst*, bestvideo*, bestaudio*, worstvideo*, worstaudio*
* Added b,w,v,a as alias for best, worst, video and audio respectively in format selection
* Changed video format sorting to show video only files and video+audio files together.
2020-11-05 16:35:36 +01:00
elif selector . type == MERGE : # +
2015-08-04 09:07:44 +02:00
selector_1 , selector_2 = map ( _build_selector_function , selector . selector )
2015-01-23 00:04:05 +01:00
2016-07-15 19:55:43 +02:00
def selector_function ( ctx ) :
for pair in itertools . product (
2015-08-04 09:07:44 +02:00
selector_1 ( copy . deepcopy ( ctx ) ) , selector_2 ( copy . deepcopy ( ctx ) ) ) :
2015-06-28 22:08:29 +02:00
yield _merge ( pair )
2015-01-23 00:04:05 +01:00
2015-06-28 22:08:29 +02:00
filters = [ self . _build_format_filter ( f ) for f in selector . filters ]
2015-01-23 00:04:05 +01:00
2016-07-15 19:55:43 +02:00
def final_selector ( ctx ) :
ctx_copy = copy . deepcopy ( ctx )
2015-06-28 22:08:29 +02:00
for _filter in filters :
2016-07-15 19:55:43 +02:00
ctx_copy [ ' formats ' ] = list ( filter ( _filter , ctx_copy [ ' formats ' ] ) )
return selector_function ( ctx_copy )
2015-06-28 22:08:29 +02:00
return final_selector
2015-01-23 00:04:05 +01:00
2015-06-28 22:08:29 +02:00
stream = io . BytesIO ( format_spec . encode ( ' utf-8 ' ) )
2015-06-29 12:42:02 +02:00
try :
2015-08-04 22:29:23 +02:00
tokens = list ( _remove_unused_ops ( compat_tokenize_tokenize ( stream . readline ) ) )
2015-06-29 12:42:02 +02:00
except tokenize . TokenError :
raise syntax_error ( ' Missing closing/opening brackets or parenthesis ' , ( 0 , len ( format_spec ) ) )
class TokenIterator ( object ) :
def __init__ ( self , tokens ) :
self . tokens = tokens
self . counter = 0
def __iter__ ( self ) :
return self
def __next__ ( self ) :
if self . counter > = len ( self . tokens ) :
raise StopIteration ( )
value = self . tokens [ self . counter ]
self . counter + = 1
return value
next = __next__
def restore_last_token ( self ) :
self . counter - = 1
parsed_selector = _parse_format_selection ( iter ( TokenIterator ( tokens ) ) )
2015-06-28 22:08:29 +02:00
return _build_selector_function ( parsed_selector )
2013-10-21 13:19:58 +02:00
2015-01-24 18:52:26 +01:00
def _calc_headers ( self , info_dict ) :
res = std_headers . copy ( )
add_headers = info_dict . get ( ' http_headers ' )
if add_headers :
res . update ( add_headers )
cookies = self . _calc_cookies ( info_dict )
if cookies :
res [ ' Cookie ' ] = cookies
2017-02-04 15:06:07 +01:00
if ' X-Forwarded-For ' not in res :
x_forwarded_for_ip = info_dict . get ( ' __x_forwarded_for_ip ' )
if x_forwarded_for_ip :
res [ ' X-Forwarded-For ' ] = x_forwarded_for_ip
2015-01-24 18:52:26 +01:00
return res
def _calc_cookies ( self , info_dict ) :
2015-11-21 17:18:17 +01:00
pr = sanitized_Request ( info_dict [ ' url ' ] )
2015-01-24 18:52:26 +01:00
self . cookiejar . add_cookie_header ( pr )
2015-02-17 16:29:24 +01:00
return pr . get_header ( ' Cookie ' )
2015-01-24 18:52:26 +01:00
2021-05-23 13:58:15 +02:00
@staticmethod
def _sanitize_thumbnails ( info_dict ) :
thumbnails = info_dict . get ( ' thumbnails ' )
if thumbnails is None :
thumbnail = info_dict . get ( ' thumbnail ' )
if thumbnail :
info_dict [ ' thumbnails ' ] = thumbnails = [ { ' url ' : thumbnail } ]
if thumbnails :
thumbnails . sort ( key = lambda t : (
t . get ( ' preference ' ) if t . get ( ' preference ' ) is not None else - 1 ,
t . get ( ' width ' ) if t . get ( ' width ' ) is not None else - 1 ,
t . get ( ' height ' ) if t . get ( ' height ' ) is not None else - 1 ,
t . get ( ' id ' ) if t . get ( ' id ' ) is not None else ' ' ,
t . get ( ' url ' ) ) )
for i , t in enumerate ( thumbnails ) :
t [ ' url ' ] = sanitize_url ( t [ ' url ' ] )
if t . get ( ' width ' ) and t . get ( ' height ' ) :
t [ ' resolution ' ] = ' %d x %d ' % ( t [ ' width ' ] , t [ ' height ' ] )
if t . get ( ' id ' ) is None :
t [ ' id ' ] = ' %d ' % i
2013-07-02 10:08:58 +02:00
def process_video_result ( self , info_dict , download = True ) :
assert info_dict . get ( ' _type ' , ' video ' ) == ' video '
2014-04-03 14:36:40 +02:00
if ' id ' not in info_dict :
raise ExtractorError ( ' Missing " id " field in extractor result ' )
if ' title ' not in info_dict :
raise ExtractorError ( ' Missing " title " field in extractor result ' )
2017-06-08 17:53:14 +02:00
def report_force_conversion ( field , field_not , conversion ) :
self . report_warning (
' " %s " field is not %s - forcing %s conversion, there is an error in extractor '
% ( field , field_not , conversion ) )
def sanitize_string_field ( info , string_field ) :
field = info . get ( string_field )
if field is None or isinstance ( field , compat_str ) :
return
report_force_conversion ( string_field , ' a string ' , ' string ' )
info [ string_field ] = compat_str ( field )
def sanitize_numeric_fields ( info ) :
for numeric_field in self . _NUMERIC_FIELDS :
field = info . get ( numeric_field )
if field is None or isinstance ( field , compat_numeric_types ) :
continue
report_force_conversion ( numeric_field , ' numeric ' , ' int ' )
info [ numeric_field ] = int_or_none ( field )
sanitize_string_field ( info_dict , ' id ' )
sanitize_numeric_fields ( info_dict )
2016-06-09 00:34:19 +02:00
2013-07-02 10:08:58 +02:00
if ' playlist ' not in info_dict :
# It isn't part of a playlist
info_dict [ ' playlist ' ] = None
info_dict [ ' playlist_index ' ] = None
2021-05-23 13:58:15 +02:00
self . _sanitize_thumbnails ( info_dict )
2014-06-07 15:33:45 +02:00
2016-03-10 20:17:35 +01:00
if self . params . get ( ' list_thumbnails ' ) :
self . list_thumbnails ( info_dict )
return
2016-04-07 20:17:47 +02:00
thumbnail = info_dict . get ( ' thumbnail ' )
2021-05-23 13:58:15 +02:00
thumbnails = info_dict . get ( ' thumbnails ' )
2016-04-07 20:17:47 +02:00
if thumbnail :
info_dict [ ' thumbnail ' ] = sanitize_url ( thumbnail )
elif thumbnails :
2014-06-07 15:33:45 +02:00
info_dict [ ' thumbnail ' ] = thumbnails [ - 1 ] [ ' url ' ]
2014-03-04 03:32:28 +01:00
if ' display_id ' not in info_dict and ' id ' in info_dict :
2014-03-03 12:06:28 +01:00
info_dict [ ' display_id ' ] = info_dict [ ' id ' ]
2021-03-15 00:22:06 +01:00
for ts_key , date_key in (
( ' timestamp ' , ' upload_date ' ) ,
( ' release_timestamp ' , ' release_date ' ) ,
) :
if info_dict . get ( date_key ) is None and info_dict . get ( ts_key ) is not None :
# Working around out-of-range timestamp values (e.g. negative ones on Windows,
# see http://bugs.python.org/issue1646728)
try :
upload_date = datetime . datetime . utcfromtimestamp ( info_dict [ ts_key ] )
info_dict [ date_key ] = upload_date . strftime ( ' % Y % m %d ' )
except ( ValueError , OverflowError , OSError ) :
pass
2014-03-13 15:30:25 +01:00
2016-01-15 19:09:54 +01:00
# Auto generate title fields corresponding to the *_number fields when missing
# in order to always have clean titles. This is very common for TV series.
for field in ( ' chapter ' , ' season ' , ' episode ' ) :
if info_dict . get ( ' %s _number ' % field ) is not None and not info_dict . get ( field ) :
info_dict [ field ] = ' %s %d ' % ( field . capitalize ( ) , info_dict [ ' %s _number ' % field ] )
2018-05-08 17:57:52 +02:00
for cc_kind in ( ' subtitles ' , ' automatic_captions ' ) :
cc = info_dict . get ( cc_kind )
if cc :
for _ , subtitle in cc . items ( ) :
for subtitle_format in subtitle :
if subtitle_format . get ( ' url ' ) :
subtitle_format [ ' url ' ] = sanitize_url ( subtitle_format [ ' url ' ] )
if subtitle_format . get ( ' ext ' ) is None :
subtitle_format [ ' ext ' ] = determine_ext ( subtitle_format [ ' url ' ] ) . lower ( )
automatic_captions = info_dict . get ( ' automatic_captions ' )
2015-10-04 16:33:42 +02:00
subtitles = info_dict . get ( ' subtitles ' )
2015-02-15 18:03:41 +01:00
if self . params . get ( ' listsubtitles ' , False ) :
2015-02-16 21:44:17 +01:00
if ' automatic_captions ' in info_dict :
2018-05-08 17:57:52 +02:00
self . list_subtitles (
info_dict [ ' id ' ] , automatic_captions , ' automatic captions ' )
2015-10-04 16:33:42 +02:00
self . list_subtitles ( info_dict [ ' id ' ] , subtitles , ' subtitles ' )
2015-02-15 18:03:41 +01:00
return
2018-05-08 17:57:52 +02:00
2015-02-16 21:44:17 +01:00
info_dict [ ' requested_subtitles ' ] = self . process_subtitles (
2018-05-08 17:57:52 +02:00
info_dict [ ' id ' ] , subtitles , automatic_captions )
2015-02-15 18:03:41 +01:00
2013-07-02 10:08:58 +02:00
# We now pick which formats have to be downloaded
if info_dict . get ( ' formats ' ) is None :
# There's only one format available
formats = [ info_dict ]
else :
formats = info_dict [ ' formats ' ]
2014-03-10 20:55:47 +01:00
if not formats :
2021-04-17 02:09:58 +02:00
if not self . params . get ( ' ignore_no_formats_error ' ) :
raise ExtractorError ( ' No video formats found! ' )
else :
self . report_warning ( ' No video formats found! ' )
2014-03-10 20:55:47 +01:00
2017-06-23 16:18:33 +02:00
def is_wellformed ( f ) :
url = f . get ( ' url ' )
2017-08-17 18:59:12 +02:00
if not url :
2017-06-23 16:18:33 +02:00
self . report_warning (
' " url " field is missing or empty - skipping format, '
' there is an error in extractor ' )
2017-08-17 18:59:12 +02:00
return False
if isinstance ( url , bytes ) :
sanitize_string_field ( f , ' url ' )
return True
2017-06-23 16:18:33 +02:00
# Filter out malformed formats for better extraction robustness
formats = list ( filter ( is_wellformed , formats ) )
2015-05-30 12:04:44 +02:00
formats_dict = { }
2013-07-02 10:08:58 +02:00
# We check that all the formats have the format and format_id fields
2014-03-10 20:55:47 +01:00
for i , format in enumerate ( formats ) :
2017-06-08 17:53:14 +02:00
sanitize_string_field ( format , ' format_id ' )
sanitize_numeric_fields ( format )
2016-03-26 14:37:41 +01:00
format [ ' url ' ] = sanitize_url ( format [ ' url ' ] )
2017-08-12 12:14:11 +02:00
if not format . get ( ' format_id ' ) :
2013-07-14 17:31:52 +02:00
format [ ' format_id ' ] = compat_str ( i )
2016-02-10 16:16:58 +01:00
else :
# Sanitize format_id from characters used in format selector expression
2017-01-02 13:08:07 +01:00
format [ ' format_id ' ] = re . sub ( r ' [ \ s,/+ \ [ \ ]()] ' , ' _ ' , format [ ' format_id ' ] )
2015-05-30 12:04:44 +02:00
format_id = format [ ' format_id ' ]
if format_id not in formats_dict :
formats_dict [ format_id ] = [ ]
formats_dict [ format_id ] . append ( format )
# Make sure all formats have unique format_id
for format_id , ambiguous_formats in formats_dict . items ( ) :
if len ( ambiguous_formats ) > 1 :
for i , format in enumerate ( ambiguous_formats ) :
format [ ' format_id ' ] = ' %s - %d ' % ( format_id , i )
for i , format in enumerate ( formats ) :
2013-10-21 14:09:38 +02:00
if format . get ( ' format ' ) is None :
2014-01-05 01:52:03 +01:00
format [ ' format ' ] = ' {id} - {res} {note} ' . format (
2013-10-21 14:09:38 +02:00
id = format [ ' format_id ' ] ,
res = self . format_resolution ( format ) ,
2014-01-05 01:52:03 +01:00
note = ' ( {0} ) ' . format ( format [ ' format_note ' ] ) if format . get ( ' format_note ' ) is not None else ' ' ,
2013-10-21 14:09:38 +02:00
)
2013-10-28 11:28:02 +01:00
# Automatically determine file extension if missing
2016-08-21 03:07:26 +02:00
if format . get ( ' ext ' ) is None :
2014-04-03 08:55:38 +02:00
format [ ' ext ' ] = determine_ext ( format [ ' url ' ] ) . lower ( )
2016-01-16 05:10:28 +01:00
# Automatically determine protocol if missing (useful for format
# selection purposes)
2017-01-15 00:09:32 +01:00
if format . get ( ' protocol ' ) is None :
2016-01-16 05:10:28 +01:00
format [ ' protocol ' ] = determine_protocol ( format )
2015-01-24 18:52:26 +01:00
# Add HTTP headers, so that external programs can use them from the
# json output
full_format_info = info_dict . copy ( )
full_format_info . update ( format )
format [ ' http_headers ' ] = self . _calc_headers ( full_format_info )
2017-02-04 15:06:07 +01:00
# Remove private housekeeping stuff
if ' __x_forwarded_for_ip ' in info_dict :
del info_dict [ ' __x_forwarded_for_ip ' ]
2013-07-02 10:08:58 +02:00
2013-12-24 12:25:22 +01:00
# TODO Central sorting goes here
2013-07-08 12:10:47 +02:00
2021-04-17 02:09:58 +02:00
if formats and formats [ 0 ] is not info_dict :
2013-12-23 10:23:13 +01:00
# only set the 'formats' fields if the original info_dict list them
# otherwise we end up with a circular reference, the first (and unique)
2014-01-25 12:02:43 +01:00
# element in the 'formats' field in info_dict is info_dict itself,
2016-01-10 16:17:47 +01:00
# which can't be exported to json
2013-12-23 10:23:13 +01:00
info_dict [ ' formats ' ] = formats
2021-05-18 20:25:32 +02:00
info_dict , _ = self . pre_process ( info_dict )
2015-01-25 02:38:47 +01:00
if self . params . get ( ' listformats ' ) :
2021-04-17 02:09:58 +02:00
if not info_dict . get ( ' formats ' ) :
raise ExtractorError ( ' No video formats found ' , expected = True )
2013-12-18 21:24:39 +01:00
self . list_formats ( info_dict )
return
2014-01-22 14:53:23 +01:00
req_format = self . params . get ( ' format ' )
2013-10-21 13:19:58 +02:00
if req_format is None :
2017-07-22 19:12:01 +02:00
req_format = self . _default_format_spec ( info_dict , download = download )
2021-05-14 09:45:29 +02:00
self . write_debug ( ' Default format spec: %s ' % req_format )
2017-07-22 19:12:01 +02:00
2015-06-28 22:48:02 +02:00
format_selector = self . build_format_selector ( req_format )
2016-07-15 19:55:43 +02:00
# While in format selection we may need to have an access to the original
# format set in order to calculate some metrics or do some processing.
# For now we need to be able to guess whether original formats provided
# by extractor are incomplete or not (i.e. whether extractor provides only
# video-only or audio-only formats) for proper formats selection for
# extractors with such incomplete formats (see
2019-03-09 13:14:41 +01:00
# https://github.com/ytdl-org/youtube-dl/pull/5556).
2016-07-15 19:55:43 +02:00
# Since formats may be filtered during format selection and may not match
# the original formats the results may be incorrect. Thus original formats
# or pre-calculated metrics should be passed to format selection routines
# as well.
# We will pass a context object containing all necessary additional data
# instead of just formats.
# This fixes incorrect format selection issue (see
2019-03-09 13:14:41 +01:00
# https://github.com/ytdl-org/youtube-dl/issues/10083).
2016-07-15 20:18:05 +02:00
incomplete_formats = (
2016-07-15 19:55:43 +02:00
# All formats are video-only or
2019-05-10 22:56:22 +02:00
all ( f . get ( ' vcodec ' ) != ' none ' and f . get ( ' acodec ' ) == ' none ' for f in formats )
2016-07-15 19:55:43 +02:00
# all formats are audio-only
2019-05-10 22:56:22 +02:00
or all ( f . get ( ' vcodec ' ) == ' none ' and f . get ( ' acodec ' ) != ' none ' for f in formats ) )
2016-07-15 19:55:43 +02:00
ctx = {
' formats ' : formats ,
' incomplete_formats ' : incomplete_formats ,
}
formats_to_download = list ( format_selector ( ctx ) )
2013-07-02 10:08:58 +02:00
if not formats_to_download :
2021-04-17 02:09:58 +02:00
if not self . params . get ( ' ignore_no_formats_error ' ) :
raise ExtractorError ( ' Requested format is not available ' , expected = True )
else :
self . report_warning ( ' Requested format is not available ' )
elif download :
self . to_screen (
2021-05-18 20:20:59 +02:00
' [info] %s : Downloading %d format(s): %s ' % (
info_dict [ ' id ' ] , len ( formats_to_download ) ,
" , " . join ( [ f [ ' format_id ' ] for f in formats_to_download ] ) ) )
2021-04-17 02:09:58 +02:00
for fmt in formats_to_download :
2013-07-02 10:08:58 +02:00
new_info = dict ( info_dict )
2021-05-18 20:25:32 +02:00
# Save a reference to the original info_dict so that it can be modified in process_info if needed
new_info [ ' __original_infodict ' ] = info_dict
2021-04-17 02:09:58 +02:00
new_info . update ( fmt )
2013-07-02 10:08:58 +02:00
self . process_info ( new_info )
# We update the info dict with the best quality format (backwards compatibility)
2021-04-17 02:09:58 +02:00
if formats_to_download :
info_dict . update ( formats_to_download [ - 1 ] )
2013-07-02 10:08:58 +02:00
return info_dict
2015-02-22 11:37:27 +01:00
def process_subtitles ( self , video_id , normal_subtitles , automatic_captions ) :
2015-02-15 18:03:41 +01:00
""" Select the requested subtitles and their format """
2015-02-22 11:37:27 +01:00
available_subs = { }
if normal_subtitles and self . params . get ( ' writesubtitles ' ) :
available_subs . update ( normal_subtitles )
if automatic_captions and self . params . get ( ' writeautomaticsub ' ) :
for lang , cap_info in automatic_captions . items ( ) :
2015-02-16 21:44:17 +01:00
if lang not in available_subs :
available_subs [ lang ] = cap_info
2015-02-21 22:31:53 +01:00
if ( not self . params . get ( ' writesubtitles ' ) and not
self . params . get ( ' writeautomaticsub ' ) or not
available_subs ) :
return None
2015-02-15 18:03:41 +01:00
2021-04-19 23:17:09 +02:00
all_sub_langs = available_subs . keys ( )
2015-02-15 18:03:41 +01:00
if self . params . get ( ' allsubtitles ' , False ) :
2021-04-19 23:17:09 +02:00
requested_langs = all_sub_langs
elif self . params . get ( ' subtitleslangs ' , False ) :
requested_langs = set ( )
for lang in self . params . get ( ' subtitleslangs ' ) :
if lang == ' all ' :
requested_langs . update ( all_sub_langs )
continue
discard = lang [ 0 ] == ' - '
if discard :
lang = lang [ 1 : ]
current_langs = filter ( re . compile ( lang + ' $ ' ) . match , all_sub_langs )
if discard :
for lang in current_langs :
requested_langs . discard ( lang )
else :
requested_langs . update ( current_langs )
elif ' en ' in available_subs :
requested_langs = [ ' en ' ]
2015-02-15 18:03:41 +01:00
else :
2021-04-19 23:17:09 +02:00
requested_langs = [ list ( all_sub_langs ) [ 0 ] ]
2021-05-13 14:21:22 +02:00
self . write_debug ( ' Downloading subtitles: %s ' % ' , ' . join ( requested_langs ) )
2015-02-15 18:03:41 +01:00
formats_query = self . params . get ( ' subtitlesformat ' , ' best ' )
formats_preference = formats_query . split ( ' / ' ) if formats_query else [ ]
subs = { }
for lang in requested_langs :
formats = available_subs . get ( lang )
if formats is None :
self . report_warning ( ' %s subtitles not available for %s ' % ( lang , video_id ) )
continue
for ext in formats_preference :
if ext == ' best ' :
f = formats [ - 1 ]
break
matches = list ( filter ( lambda f : f [ ' ext ' ] == ext , formats ) )
if matches :
f = matches [ - 1 ]
break
else :
f = formats [ - 1 ]
self . report_warning (
' No subtitle format found matching " %s " for language %s , '
' using %s ' % ( formats_query , lang , f [ ' ext ' ] ) )
subs [ lang ] = f
return subs
2019-09-24 21:08:46 +02:00
def __forced_printings ( self , info_dict , filename , incomplete ) :
2021-05-14 09:44:38 +02:00
def print_mandatory ( field , actual_field = None ) :
if actual_field is None :
actual_field = field
2019-09-24 21:08:46 +02:00
if ( self . params . get ( ' force %s ' % field , False )
2021-05-14 09:44:38 +02:00
and ( not incomplete or info_dict . get ( actual_field ) is not None ) ) :
self . to_stdout ( info_dict [ actual_field ] )
2019-09-24 21:08:46 +02:00
def print_optional ( field ) :
if ( self . params . get ( ' force %s ' % field , False )
and info_dict . get ( field ) is not None ) :
self . to_stdout ( info_dict [ field ] )
2021-05-14 09:44:38 +02:00
info_dict = info_dict . copy ( )
if filename is not None :
info_dict [ ' filename ' ] = filename
if info_dict . get ( ' requested_formats ' ) is not None :
# For RTMP URLs, also include the playpath
info_dict [ ' urls ' ] = ' \n ' . join ( f [ ' url ' ] + f . get ( ' play_path ' , ' ' ) for f in info_dict [ ' requested_formats ' ] )
elif ' url ' in info_dict :
info_dict [ ' urls ' ] = info_dict [ ' url ' ] + info_dict . get ( ' play_path ' , ' ' )
for tmpl in self . params . get ( ' forceprint ' , [ ] ) :
if re . match ( r ' \ w+$ ' , tmpl ) :
tmpl = ' % ( {} )s ' . format ( tmpl )
tmpl , info_copy = self . prepare_outtmpl ( tmpl , info_dict )
self . to_stdout ( tmpl % info_copy )
2019-09-24 21:08:46 +02:00
print_mandatory ( ' title ' )
print_mandatory ( ' id ' )
2021-05-14 09:44:38 +02:00
print_mandatory ( ' url ' , ' urls ' )
2019-09-24 21:08:46 +02:00
print_optional ( ' thumbnail ' )
print_optional ( ' description ' )
2021-05-14 09:44:38 +02:00
print_optional ( ' filename ' )
2019-09-24 21:08:46 +02:00
if self . params . get ( ' forceduration ' , False ) and info_dict . get ( ' duration ' ) is not None :
self . to_stdout ( formatSeconds ( info_dict [ ' duration ' ] ) )
print_mandatory ( ' format ' )
2021-05-14 09:44:38 +02:00
2019-09-24 21:08:46 +02:00
if self . params . get ( ' forcejson ' , False ) :
2021-02-28 15:56:08 +01:00
self . post_extract ( info_dict )
2021-03-18 16:27:20 +01:00
self . to_stdout ( json . dumps ( info_dict , default = repr ) )
2019-09-24 21:08:46 +02:00
2021-05-04 17:54:00 +02:00
def dl ( self , name , info , subtitle = False , test = False ) :
if test :
verbose = self . params . get ( ' verbose ' )
params = {
' test ' : True ,
' quiet ' : not verbose ,
' verbose ' : verbose ,
' noprogress ' : not verbose ,
' nopart ' : True ,
' skip_unavailable_fragments ' : False ,
' keep_fragments ' : False ,
' overwrites ' : True ,
' _no_ytdl_file ' : True ,
}
else :
params = self . params
fd = get_suitable_downloader ( info , params ) ( self , params )
if not test :
for ph in self . _progress_hooks :
fd . add_progress_hook ( ph )
2021-05-23 00:17:44 +02:00
urls = ' " , " ' . join ( [ f [ ' url ' ] for f in info . get ( ' requested_formats ' , [ ] ) ] or [ info [ ' url ' ] ] )
self . write_debug ( ' Invoking downloader on " %s " ' % urls )
2021-05-04 17:54:00 +02:00
new_info = dict ( info )
if new_info . get ( ' http_headers ' ) is None :
new_info [ ' http_headers ' ] = self . _calc_headers ( new_info )
return fd . download ( name , new_info , subtitle )
2013-06-18 22:14:21 +02:00
def process_info ( self , info_dict ) :
""" Process a single resolved IE result. """
assert info_dict . get ( ' _type ' , ' video ' ) == ' video '
2014-01-23 18:56:36 +01:00
2021-01-23 13:18:12 +01:00
info_dict . setdefault ( ' __postprocessors ' , [ ] )
2014-01-23 18:56:36 +01:00
max_downloads = self . params . get ( ' max_downloads ' )
if max_downloads is not None :
if self . _num_downloads > = int ( max_downloads ) :
raise MaxDownloadsReached ( )
2013-06-18 22:14:21 +02:00
2019-09-24 21:08:46 +02:00
# TODO: backward compatibility, to be removed
2013-06-18 22:14:21 +02:00
info_dict [ ' fulltitle ' ] = info_dict [ ' title ' ]
2014-07-25 23:37:32 +02:00
if ' format ' not in info_dict :
2013-06-18 22:14:21 +02:00
info_dict [ ' format ' ] = info_dict [ ' ext ' ]
2021-05-28 22:42:07 +02:00
if self . _match_entry ( info_dict ) is not None :
2013-06-18 22:14:21 +02:00
return
2021-02-28 15:56:08 +01:00
self . post_extract ( info_dict )
2014-01-23 18:56:36 +01:00
self . _num_downloads + = 1
2013-06-18 22:14:21 +02:00
2021-03-18 16:24:53 +01:00
# info_dict['_filename'] needs to be set for backward compatibility
2021-02-03 14:36:09 +01:00
info_dict [ ' _filename ' ] = full_filename = self . prepare_filename ( info_dict , warn = True )
temp_filename = self . prepare_filename ( info_dict , ' temp ' )
2021-01-23 13:18:12 +01:00
files_to_move = { }
2013-06-18 22:14:21 +02:00
# Forced printings
2021-01-23 13:18:12 +01:00
self . __forced_printings ( info_dict , full_filename , incomplete = False )
2013-06-18 22:14:21 +02:00
if self . params . get ( ' simulate ' , False ) :
2020-11-05 18:43:21 +01:00
if self . params . get ( ' force_write_download_archive ' , False ) :
self . record_download_archive ( info_dict )
# Do nothing else if in simulate mode
2013-06-18 22:14:21 +02:00
return
2021-02-03 14:36:09 +01:00
if full_filename is None :
2013-06-18 22:14:21 +02:00
return
2021-03-09 03:17:21 +01:00
if not self . _ensure_dir_exists ( encodeFilename ( full_filename ) ) :
2021-01-23 13:18:12 +01:00
return
2021-03-09 03:17:21 +01:00
if not self . _ensure_dir_exists ( encodeFilename ( temp_filename ) ) :
2013-06-18 22:14:21 +02:00
return
if self . params . get ( ' writedescription ' , False ) :
2021-02-03 14:36:09 +01:00
descfn = self . prepare_filename ( info_dict , ' description ' )
2021-03-09 03:17:21 +01:00
if not self . _ensure_dir_exists ( encodeFilename ( descfn ) ) :
2021-01-23 13:18:12 +01:00
return
2019-10-13 18:00:48 +02:00
if not self . params . get ( ' overwrites ' , True ) and os . path . exists ( encodeFilename ( descfn ) ) :
2014-01-05 01:52:03 +01:00
self . to_screen ( ' [info] Video description is already present ' )
2014-12-21 20:49:14 +01:00
elif info_dict . get ( ' description ' ) is None :
self . report_warning ( ' There \' s no description to write. ' )
2013-12-16 04:39:04 +01:00
else :
try :
2014-01-05 01:52:03 +01:00
self . to_screen ( ' [info] Writing video description to: ' + descfn )
2013-12-16 04:39:04 +01:00
with io . open ( encodeFilename ( descfn ) , ' w ' , encoding = ' utf-8 ' ) as descfile :
descfile . write ( info_dict [ ' description ' ] )
except ( OSError , IOError ) :
2014-01-05 01:52:03 +01:00
self . report_error ( ' Cannot write description file ' + descfn )
2013-12-16 04:39:04 +01:00
return
2013-06-18 22:14:21 +02:00
2013-10-14 07:18:58 +02:00
if self . params . get ( ' writeannotations ' , False ) :
2021-02-03 14:36:09 +01:00
annofn = self . prepare_filename ( info_dict , ' annotation ' )
2021-03-09 03:17:21 +01:00
if not self . _ensure_dir_exists ( encodeFilename ( annofn ) ) :
2021-01-23 13:18:12 +01:00
return
2019-10-13 18:00:48 +02:00
if not self . params . get ( ' overwrites ' , True ) and os . path . exists ( encodeFilename ( annofn ) ) :
2014-01-05 01:52:03 +01:00
self . to_screen ( ' [info] Video annotations are already present ' )
2019-08-09 09:19:41 +02:00
elif not info_dict . get ( ' annotations ' ) :
self . report_warning ( ' There are no annotations to write. ' )
2013-12-16 04:39:04 +01:00
else :
try :
2014-01-05 01:52:03 +01:00
self . to_screen ( ' [info] Writing video annotations to: ' + annofn )
2013-12-16 04:39:04 +01:00
with io . open ( encodeFilename ( annofn ) , ' w ' , encoding = ' utf-8 ' ) as annofile :
annofile . write ( info_dict [ ' annotations ' ] )
except ( KeyError , TypeError ) :
2014-01-05 01:52:03 +01:00
self . report_warning ( ' There are no annotations to write. ' )
2013-12-16 04:39:04 +01:00
except ( OSError , IOError ) :
2014-01-05 01:52:03 +01:00
self . report_error ( ' Cannot write annotations file: ' + annofn )
2013-12-16 04:39:04 +01:00
return
2013-10-14 07:18:58 +02:00
2013-06-26 00:02:15 +02:00
subtitles_are_requested = any ( [ self . params . get ( ' writesubtitles ' , False ) ,
2013-09-14 11:14:40 +02:00
self . params . get ( ' writeautomaticsub ' ) ] )
2013-06-26 00:02:15 +02:00
2015-02-16 21:12:31 +01:00
if subtitles_are_requested and info_dict . get ( ' requested_subtitles ' ) :
2013-06-18 22:14:21 +02:00
# subtitles download errors are already managed as troubles in relevant IE
# that way it will silently go on when used with unsupporting IE
2015-02-16 21:12:31 +01:00
subtitles = info_dict [ ' requested_subtitles ' ]
2020-10-31 08:57:55 +01:00
# ie = self.get_info_extractor(info_dict['extractor_key'])
2015-02-15 18:03:41 +01:00
for sub_lang , sub_info in subtitles . items ( ) :
sub_format = sub_info [ ' ext ' ]
2021-04-11 00:18:07 +02:00
sub_filename = subtitles_filename ( temp_filename , sub_lang , sub_format , info_dict . get ( ' ext ' ) )
sub_filename_final = subtitles_filename (
self . prepare_filename ( info_dict , ' subtitle ' ) , sub_lang , sub_format , info_dict . get ( ' ext ' ) )
2019-10-13 18:00:48 +02:00
if not self . params . get ( ' overwrites ' , True ) and os . path . exists ( encodeFilename ( sub_filename ) ) :
2017-04-28 23:25:20 +02:00
self . to_screen ( ' [info] Video subtitle %s . %s is already present ' % ( sub_lang , sub_format ) )
2021-03-18 16:24:53 +01:00
sub_info [ ' filepath ' ] = sub_filename
2021-01-23 13:18:12 +01:00
files_to_move [ sub_filename ] = sub_filename_final
2015-02-15 18:03:41 +01:00
else :
2020-09-29 05:11:32 +02:00
self . to_screen ( ' [info] Writing video subtitles to: ' + sub_filename )
2017-04-28 23:25:20 +02:00
if sub_info . get ( ' data ' ) is not None :
try :
# Use newline='' to prevent conversion of newline characters
2019-03-09 13:14:41 +01:00
# See https://github.com/ytdl-org/youtube-dl/issues/10268
2017-04-28 23:25:20 +02:00
with io . open ( encodeFilename ( sub_filename ) , ' w ' , encoding = ' utf-8 ' , newline = ' ' ) as subfile :
subfile . write ( sub_info [ ' data ' ] )
2021-03-18 16:24:53 +01:00
sub_info [ ' filepath ' ] = sub_filename
2021-01-23 13:18:12 +01:00
files_to_move [ sub_filename ] = sub_filename_final
2017-04-28 23:25:20 +02:00
except ( OSError , IOError ) :
self . report_error ( ' Cannot write subtitles file ' + sub_filename )
return
2013-12-16 04:39:04 +01:00
else :
2017-04-28 23:25:20 +02:00
try :
2021-05-04 17:54:00 +02:00
self . dl ( sub_filename , sub_info . copy ( ) , subtitle = True )
2021-03-18 16:24:53 +01:00
sub_info [ ' filepath ' ] = sub_filename
2021-01-23 13:18:12 +01:00
files_to_move [ sub_filename ] = sub_filename_final
2021-05-15 15:42:26 +02:00
except ( ExtractorError , IOError , OSError , ValueError ) + network_exceptions as err :
2017-04-28 23:25:20 +02:00
self . report_warning ( ' Unable to download subtitle for " %s " : %s ' %
( sub_lang , error_to_compat_str ( err ) ) )
continue
2013-06-18 22:14:21 +02:00
if self . params . get ( ' writeinfojson ' , False ) :
2021-02-03 14:36:09 +01:00
infofn = self . prepare_filename ( info_dict , ' infojson ' )
2021-03-09 03:17:21 +01:00
if not self . _ensure_dir_exists ( encodeFilename ( infofn ) ) :
2021-01-23 13:18:12 +01:00
return
2019-10-13 18:00:48 +02:00
if not self . params . get ( ' overwrites ' , True ) and os . path . exists ( encodeFilename ( infofn ) ) :
2021-01-28 20:32:37 +01:00
self . to_screen ( ' [info] Video metadata is already present ' )
2013-12-16 04:39:04 +01:00
else :
2021-01-28 20:32:37 +01:00
self . to_screen ( ' [info] Writing video metadata as JSON to: ' + infofn )
2013-12-16 04:39:04 +01:00
try :
2021-03-18 16:27:20 +01:00
write_json_file ( self . filter_requested_info ( info_dict , self . params . get ( ' clean_infojson ' , True ) ) , infofn )
2013-12-16 04:39:04 +01:00
except ( OSError , IOError ) :
2021-01-28 20:32:37 +01:00
self . report_error ( ' Cannot write video metadata to JSON file ' + infofn )
2013-12-16 04:39:04 +01:00
return
2021-02-03 14:36:09 +01:00
info_dict [ ' __infojson_filename ' ] = infofn
2013-06-18 22:14:21 +02:00
2021-04-11 00:18:07 +02:00
for thumb_ext in self . _write_thumbnails ( info_dict , temp_filename ) :
thumb_filename_temp = replace_extension ( temp_filename , thumb_ext , info_dict . get ( ' ext ' ) )
thumb_filename = replace_extension (
self . prepare_filename ( info_dict , ' thumbnail ' ) , thumb_ext , info_dict . get ( ' ext ' ) )
2021-03-18 16:24:53 +01:00
files_to_move [ thumb_filename_temp ] = thumb_filename
2013-06-18 22:14:21 +02:00
2020-10-27 11:37:21 +01:00
# Write internet shortcut files
url_link = webloc_link = desktop_link = False
if self . params . get ( ' writelink ' , False ) :
if sys . platform == " darwin " : # macOS.
webloc_link = True
elif sys . platform . startswith ( " linux " ) :
desktop_link = True
else : # if sys.platform in ['win32', 'cygwin']:
url_link = True
if self . params . get ( ' writeurllink ' , False ) :
url_link = True
if self . params . get ( ' writewebloclink ' , False ) :
webloc_link = True
if self . params . get ( ' writedesktoplink ' , False ) :
desktop_link = True
if url_link or webloc_link or desktop_link :
if ' webpage_url ' not in info_dict :
self . report_error ( ' Cannot write internet shortcut file because the " webpage_url " field is missing in the media information ' )
return
ascii_url = iri_to_uri ( info_dict [ ' webpage_url ' ] )
def _write_link_file ( extension , template , newline , embed_filename ) :
2021-01-23 13:18:12 +01:00
linkfn = replace_extension ( full_filename , extension , info_dict . get ( ' ext ' ) )
2021-01-23 16:52:15 +01:00
if self . params . get ( ' overwrites ' , True ) and os . path . exists ( encodeFilename ( linkfn ) ) :
2020-10-27 11:37:21 +01:00
self . to_screen ( ' [info] Internet shortcut is already present ' )
else :
try :
self . to_screen ( ' [info] Writing internet shortcut to: ' + linkfn )
with io . open ( encodeFilename ( to_high_limit_path ( linkfn ) ) , ' w ' , encoding = ' utf-8 ' , newline = newline ) as linkfile :
template_vars = { ' url ' : ascii_url }
if embed_filename :
template_vars [ ' filename ' ] = linkfn [ : - ( len ( extension ) + 1 ) ]
linkfile . write ( template % template_vars )
except ( OSError , IOError ) :
self . report_error ( ' Cannot write internet shortcut ' + linkfn )
return False
return True
if url_link :
if not _write_link_file ( ' url ' , DOT_URL_LINK_TEMPLATE , ' \r \n ' , embed_filename = False ) :
return
if webloc_link :
if not _write_link_file ( ' webloc ' , DOT_WEBLOC_LINK_TEMPLATE , ' \n ' , embed_filename = False ) :
return
if desktop_link :
if not _write_link_file ( ' desktop ' , DOT_DESKTOP_LINK_TEMPLATE , ' \n ' , embed_filename = True ) :
return
2021-04-11 00:18:07 +02:00
try :
info_dict , files_to_move = self . pre_process ( info_dict , ' before_dl ' , files_to_move )
except PostProcessingError as err :
self . report_error ( ' Preprocessing: %s ' % str ( err ) )
return
2020-10-27 11:37:21 +01:00
must_record_download_archive = False
2021-04-11 00:18:07 +02:00
if self . params . get ( ' skip_download ' , False ) :
info_dict [ ' filepath ' ] = temp_filename
info_dict [ ' __finaldir ' ] = os . path . dirname ( os . path . abspath ( encodeFilename ( full_filename ) ) )
info_dict [ ' __files_to_move ' ] = files_to_move
info_dict = self . run_pp ( MoveFilesAfterDownloadPP ( self , False ) , info_dict )
else :
# Download
2014-09-25 18:37:20 +02:00
try :
2021-01-23 13:18:12 +01:00
2021-01-28 06:18:36 +01:00
def existing_file ( * filepaths ) :
ext = info_dict . get ( ' ext ' )
final_ext = self . params . get ( ' final_ext ' , ext )
existing_files = [ ]
for file in orderedSet ( filepaths ) :
if final_ext != ext :
converted = replace_extension ( file , final_ext , ext )
if os . path . exists ( encodeFilename ( converted ) ) :
existing_files . append ( converted )
if os . path . exists ( encodeFilename ( file ) ) :
existing_files . append ( file )
if not existing_files or self . params . get ( ' overwrites ' , False ) :
for file in orderedSet ( existing_files ) :
self . report_file_delete ( file )
os . remove ( encodeFilename ( file ) )
return None
self . report_file_already_downloaded ( existing_files [ 0 ] )
info_dict [ ' ext ' ] = os . path . splitext ( existing_files [ 0 ] ) [ 1 ] [ 1 : ]
return existing_files [ 0 ]
2021-01-23 13:18:12 +01:00
success = True
2014-09-25 18:37:20 +02:00
if info_dict . get ( ' requested_formats ' ) is not None :
2015-04-17 23:00:35 +02:00
def compatible_formats ( formats ) :
2015-08-04 09:07:44 +02:00
# TODO: some formats actually allow this (mkv, webm, ogg, mp4), but not all of them.
video_formats = [ format for format in formats if format . get ( ' vcodec ' ) != ' none ' ]
audio_formats = [ format for format in formats if format . get ( ' acodec ' ) != ' none ' ]
if len ( video_formats ) > 2 or len ( audio_formats ) > 2 :
return False
2015-04-17 23:00:35 +02:00
# Check extension
2015-08-04 09:07:44 +02:00
exts = set ( format . get ( ' ext ' ) for format in formats )
COMPATIBLE_EXTS = (
set ( ( ' mp3 ' , ' mp4 ' , ' m4a ' , ' m4p ' , ' m4b ' , ' m4r ' , ' m4v ' , ' ismv ' , ' isma ' ) ) ,
set ( ( ' webm ' , ) ) ,
)
for ext_sets in COMPATIBLE_EXTS :
if ext_sets . issuperset ( exts ) :
return True
2015-04-17 23:00:35 +02:00
# TODO: Check acodec/vcodec
return False
requested_formats = info_dict [ ' requested_formats ' ]
2021-01-23 13:18:12 +01:00
old_ext = info_dict [ ' ext ' ]
2021-03-19 12:01:22 +01:00
if self . params . get ( ' merge_output_format ' ) is None :
if not compatible_formats ( requested_formats ) :
info_dict [ ' ext ' ] = ' mkv '
self . report_warning (
' Requested formats are incompatible for merge and will be merged into mkv. ' )
if ( info_dict [ ' ext ' ] == ' webm '
and self . params . get ( ' writethumbnail ' , False )
and info_dict . get ( ' thumbnails ' ) ) :
info_dict [ ' ext ' ] = ' mkv '
self . report_warning (
' webm doesn \' t support embedding a thumbnail, mkv will be used. ' )
2021-01-23 13:18:12 +01:00
def correct_ext ( filename ) :
filename_real_ext = os . path . splitext ( filename ) [ 1 ] [ 1 : ]
filename_wo_ext = (
os . path . splitext ( filename ) [ 0 ]
if filename_real_ext == old_ext
else filename )
return ' %s . %s ' % ( filename_wo_ext , info_dict [ ' ext ' ] )
2015-05-02 18:52:21 +02:00
# Ensure filename always has a correct extension for successful merge
2021-01-23 13:18:12 +01:00
full_filename = correct_ext ( full_filename )
temp_filename = correct_ext ( temp_filename )
dl_filename = existing_file ( full_filename , temp_filename )
2021-02-12 05:40:31 +01:00
info_dict [ ' __real_download ' ] = False
2021-05-23 00:17:44 +02:00
_protocols = set ( determine_protocol ( f ) for f in requested_formats )
if len ( _protocols ) == 1 :
info_dict [ ' protocol ' ] = _protocols . pop ( )
directly_mergable = (
' no-direct-merge ' not in self . params . get ( ' compat_opts ' , [ ] )
and info_dict . get ( ' protocol ' ) is not None # All requested formats have same protocol
and not self . params . get ( ' allow_unplayable_formats ' )
and get_suitable_downloader ( info_dict , self . params ) . __name__ == ' FFmpegFD ' )
if directly_mergable :
info_dict [ ' url ' ] = requested_formats [ 0 ] [ ' url ' ]
# Treat it as a single download
dl_filename = existing_file ( full_filename , temp_filename )
if dl_filename is None :
success , real_download = self . dl ( temp_filename , info_dict )
info_dict [ ' __real_download ' ] = real_download
else :
downloaded = [ ]
merger = FFmpegMergerPP ( self )
if self . params . get ( ' allow_unplayable_formats ' ) :
self . report_warning (
' You have requested merging of multiple formats '
' while also allowing unplayable formats to be downloaded. '
' The formats won \' t be merged to prevent data corruption. ' )
elif not merger . available :
self . report_warning (
' You have requested merging of multiple formats but ffmpeg is not installed. '
' The formats won \' t be merged. ' )
if dl_filename is None :
for f in requested_formats :
new_info = dict ( info_dict )
del new_info [ ' requested_formats ' ]
new_info . update ( f )
fname = prepend_extension (
self . prepare_filename ( new_info , ' temp ' ) ,
' f %s ' % f [ ' format_id ' ] , new_info [ ' ext ' ] )
if not self . _ensure_dir_exists ( fname ) :
return
downloaded . append ( fname )
partial_success , real_download = self . dl ( fname , new_info )
info_dict [ ' __real_download ' ] = info_dict [ ' __real_download ' ] or real_download
success = success and partial_success
if merger . available and not self . params . get ( ' allow_unplayable_formats ' ) :
info_dict [ ' __postprocessors ' ] . append ( merger )
info_dict [ ' __files_to_merge ' ] = downloaded
# Even if there were no downloads, it is being merged only now
info_dict [ ' __real_download ' ] = True
else :
for file in downloaded :
files_to_move [ file ] = None
2014-09-25 18:37:20 +02:00
else :
# Just a single file
2021-01-23 13:18:12 +01:00
dl_filename = existing_file ( full_filename , temp_filename )
if dl_filename is None :
2021-05-04 17:54:00 +02:00
success , real_download = self . dl ( temp_filename , info_dict )
2021-01-23 13:18:12 +01:00
info_dict [ ' __real_download ' ] = real_download
dl_filename = dl_filename or temp_filename
2021-01-23 16:25:45 +01:00
info_dict [ ' __finaldir ' ] = os . path . dirname ( os . path . abspath ( encodeFilename ( full_filename ) ) )
2021-01-23 13:18:12 +01:00
2021-05-04 19:06:18 +02:00
except network_exceptions as err :
2016-05-02 14:35:50 +02:00
self . report_error ( ' unable to download video data: %s ' % error_to_compat_str ( err ) )
2014-09-25 18:37:20 +02:00
return
except ( OSError , IOError ) as err :
raise UnavailableVideoError ( err )
except ( ContentTooShortError , ) as err :
self . report_error ( ' content too short (expected %s bytes and served %s ) ' % ( err . expected , err . downloaded ) )
return
2013-06-18 22:14:21 +02:00
2021-02-03 14:36:09 +01:00
if success and full_filename != ' - ' :
2015-01-10 05:45:51 +01:00
# Fixup content
2015-01-23 18:39:12 +01:00
fixup_policy = self . params . get ( ' fixup ' )
if fixup_policy is None :
fixup_policy = ' detect_or_warn '
2021-01-26 18:57:32 +01:00
INSTALL_FFMPEG_MESSAGE = ' Install ffmpeg to fix this automatically. '
2016-03-05 23:32:18 +01:00
2015-01-10 05:45:51 +01:00
stretched_ratio = info_dict . get ( ' stretched_ratio ' )
if stretched_ratio is not None and stretched_ratio != 1 :
if fixup_policy == ' warn ' :
self . report_warning ( ' %s : Non-uniform pixel ratio ( %s ) ' % (
info_dict [ ' id ' ] , stretched_ratio ) )
elif fixup_policy == ' detect_or_warn ' :
stretched_pp = FFmpegFixupStretchedPP ( self )
if stretched_pp . available :
info_dict [ ' __postprocessors ' ] . append ( stretched_pp )
else :
self . report_warning (
2016-03-05 23:32:18 +01:00
' %s : Non-uniform pixel ratio ( %s ). %s '
% ( info_dict [ ' id ' ] , stretched_ratio , INSTALL_FFMPEG_MESSAGE ) )
2015-01-10 05:45:51 +01:00
else :
2015-01-23 18:39:12 +01:00
assert fixup_policy in ( ' ignore ' , ' never ' )
2019-05-10 22:56:22 +02:00
if ( info_dict . get ( ' requested_formats ' ) is None
2021-01-28 06:18:36 +01:00
and info_dict . get ( ' container ' ) == ' m4a_dash '
and info_dict . get ( ' ext ' ) == ' m4a ' ) :
2015-01-23 18:39:12 +01:00
if fixup_policy == ' warn ' :
2016-03-05 23:32:18 +01:00
self . report_warning (
' %s : writing DASH m4a. '
' Only some players support this container. '
% info_dict [ ' id ' ] )
2015-01-23 18:39:12 +01:00
elif fixup_policy == ' detect_or_warn ' :
fixup_pp = FFmpegFixupM4aPP ( self )
if fixup_pp . available :
info_dict [ ' __postprocessors ' ] . append ( fixup_pp )
else :
self . report_warning (
2016-03-05 23:32:18 +01:00
' %s : writing DASH m4a. '
' Only some players support this container. %s '
% ( info_dict [ ' id ' ] , INSTALL_FFMPEG_MESSAGE ) )
2015-01-23 18:39:12 +01:00
else :
assert fixup_policy in ( ' ignore ' , ' never ' )
2015-01-10 05:45:51 +01:00
2021-03-10 16:26:24 +01:00
if ( ' protocol ' in info_dict
and get_suitable_downloader ( info_dict , self . params ) . __name__ == ' HlsFD ' ) :
2016-03-01 21:08:50 +01:00
if fixup_policy == ' warn ' :
2017-07-09 12:09:44 +02:00
self . report_warning ( ' %s : malformed AAC bitstream detected. ' % (
2016-03-01 21:08:50 +01:00
info_dict [ ' id ' ] ) )
elif fixup_policy == ' detect_or_warn ' :
fixup_pp = FFmpegFixupM3u8PP ( self )
if fixup_pp . available :
info_dict [ ' __postprocessors ' ] . append ( fixup_pp )
else :
self . report_warning (
2017-07-09 12:09:44 +02:00
' %s : malformed AAC bitstream detected. %s '
2016-03-05 23:32:18 +01:00
% ( info_dict [ ' id ' ] , INSTALL_FFMPEG_MESSAGE ) )
2016-03-01 21:08:50 +01:00
else :
assert fixup_policy in ( ' ignore ' , ' never ' )
2013-06-18 22:14:21 +02:00
try :
2021-03-19 11:35:32 +01:00
info_dict = self . post_process ( dl_filename , info_dict , files_to_move )
2021-01-30 13:07:05 +01:00
except PostProcessingError as err :
self . report_error ( ' Postprocessing: %s ' % str ( err ) )
2013-06-18 22:14:21 +02:00
return
2020-12-29 16:03:07 +01:00
try :
for ph in self . _post_hooks :
2021-03-19 11:35:32 +01:00
ph ( info_dict [ ' filepath ' ] )
2020-12-29 16:03:07 +01:00
except Exception as err :
self . report_error ( ' post hooks: %s ' % str ( err ) )
return
2020-11-05 18:43:21 +01:00
must_record_download_archive = True
if must_record_download_archive or self . params . get ( ' force_write_download_archive ' , False ) :
self . record_download_archive ( info_dict )
2021-01-09 13:08:12 +01:00
max_downloads = self . params . get ( ' max_downloads ' )
if max_downloads is not None and self . _num_downloads > = int ( max_downloads ) :
raise MaxDownloadsReached ( )
2013-06-18 22:14:21 +02:00
def download ( self , url_list ) :
""" Download a given list of URLs. """
2021-02-03 14:36:09 +01:00
outtmpl = self . outtmpl_dict [ ' default ' ]
2019-05-10 22:56:22 +02:00
if ( len ( url_list ) > 1
and outtmpl != ' - '
and ' % ' not in outtmpl
and self . params . get ( ' max_downloads ' ) != 1 ) :
2014-04-30 10:02:03 +02:00
raise SameFileError ( outtmpl )
2013-06-18 22:14:21 +02:00
for url in url_list :
try :
2014-11-23 20:41:03 +01:00
# It also downloads the videos
2015-06-12 22:05:21 +02:00
res = self . extract_info (
url , force_generic_extractor = self . params . get ( ' force_generic_extractor ' , False ) )
2013-06-18 22:14:21 +02:00
except UnavailableVideoError :
2014-01-05 01:52:03 +01:00
self . report_error ( ' unable to download video ' )
2013-06-18 22:14:21 +02:00
except MaxDownloadsReached :
2021-01-13 02:01:01 +01:00
self . to_screen ( ' [info] Maximum number of downloaded files reached ' )
raise
except ExistingVideoReached :
2021-01-13 16:24:13 +01:00
self . to_screen ( ' [info] Encountered a file that is already in the archive, stopping due to --break-on-existing ' )
2021-01-13 02:01:01 +01:00
raise
except RejectedVideoReached :
2021-01-13 16:24:13 +01:00
self . to_screen ( ' [info] Encountered a file that did not match filter, stopping due to --break-on-reject ' )
2013-06-18 22:14:21 +02:00
raise
2014-10-25 00:30:57 +02:00
else :
if self . params . get ( ' dump_single_json ' , False ) :
2021-02-28 15:56:08 +01:00
self . post_extract ( res )
2021-03-18 16:27:20 +01:00
self . to_stdout ( json . dumps ( res , default = repr ) )
2013-06-18 22:14:21 +02:00
return self . _download_retcode
2013-11-22 14:57:53 +01:00
def download_with_info_file ( self , info_filename ) :
2015-03-01 11:46:57 +01:00
with contextlib . closing ( fileinput . FileInput (
[ info_filename ] , mode = ' r ' ,
openhook = fileinput . hook_encoded ( ' utf-8 ' ) ) ) as f :
# FileInput doesn't have a read method, we can't call json.load
2021-03-23 20:45:53 +01:00
info = self . filter_requested_info ( json . loads ( ' \n ' . join ( f ) ) , self . params . get ( ' clean_infojson ' , True ) )
2013-12-03 20:16:52 +01:00
try :
self . process_ie_result ( info , download = True )
2021-03-23 20:45:53 +01:00
except ( DownloadError , EntryNotInPlaylist ) :
2013-12-03 20:16:52 +01:00
webpage_url = info . get ( ' webpage_url ' )
if webpage_url is not None :
2014-01-05 01:52:03 +01:00
self . report_warning ( ' The info failed to download, trying with " %s " ' % webpage_url )
2013-12-03 20:16:52 +01:00
return self . download ( [ webpage_url ] )
else :
raise
return self . _download_retcode
2013-11-22 14:57:53 +01:00
2015-04-30 20:44:34 +02:00
@staticmethod
2021-03-18 16:27:20 +01:00
def filter_requested_info ( info_dict , actually_filter = True ) :
2021-05-28 18:15:06 +02:00
remove_keys = [ ' __original_infodict ' ] # Always remove this since this may contain a copy of the entire dict
keep_keys = [ ' _type ' ] , # Always keep this to facilitate load-info-json
if actually_filter :
2021-06-07 20:50:06 +02:00
remove_keys + = ( ' requested_formats ' , ' requested_subtitles ' , ' requested_entries ' , ' filepath ' , ' entries ' , ' original_url ' )
2021-05-28 18:15:06 +02:00
empty_values = ( None , { } , [ ] , set ( ) , tuple ( ) )
reject = lambda k , v : k not in keep_keys and (
k . startswith ( ' _ ' ) or k in remove_keys or v in empty_values )
else :
2021-03-23 20:04:33 +01:00
info_dict [ ' epoch ' ] = int ( time . time ( ) )
2021-05-28 18:15:06 +02:00
reject = lambda k , v : k in remove_keys
2021-03-18 16:25:16 +01:00
filter_fn = lambda obj : (
2021-05-28 18:15:06 +02:00
list ( map ( filter_fn , obj ) ) if isinstance ( obj , ( list , tuple , set ) )
2021-03-18 17:48:02 +01:00
else obj if not isinstance ( obj , dict )
2021-05-28 18:15:06 +02:00
else dict ( ( k , filter_fn ( v ) ) for k , v in obj . items ( ) if not reject ( k , v ) ) )
2021-03-18 16:25:16 +01:00
return filter_fn ( info_dict )
2015-04-30 20:44:34 +02:00
2021-03-18 16:24:53 +01:00
def run_pp ( self , pp , infodict ) :
2021-01-26 11:20:20 +01:00
files_to_delete = [ ]
2021-03-18 16:24:53 +01:00
if ' __files_to_move ' not in infodict :
infodict [ ' __files_to_move ' ] = { }
2021-01-30 13:07:05 +01:00
files_to_delete , infodict = pp . run ( infodict )
2021-01-26 11:20:20 +01:00
if not files_to_delete :
2021-03-18 16:24:53 +01:00
return infodict
2021-01-26 11:20:20 +01:00
if self . params . get ( ' keepvideo ' , False ) :
for f in files_to_delete :
2021-03-18 16:24:53 +01:00
infodict [ ' __files_to_move ' ] . setdefault ( f , ' ' )
2021-01-26 11:20:20 +01:00
else :
for old_filename in set ( files_to_delete ) :
self . to_screen ( ' Deleting original file %s (pass -k to keep) ' % old_filename )
try :
os . remove ( encodeFilename ( old_filename ) )
except ( IOError , OSError ) :
self . report_warning ( ' Unable to remove downloaded original file ' )
2021-03-18 16:24:53 +01:00
if old_filename in infodict [ ' __files_to_move ' ] :
del infodict [ ' __files_to_move ' ] [ old_filename ]
return infodict
2021-01-26 11:20:20 +01:00
2021-02-28 15:56:08 +01:00
@staticmethod
def post_extract ( info_dict ) :
def actual_post_extract ( info_dict ) :
if info_dict . get ( ' _type ' ) in ( ' playlist ' , ' multi_video ' ) :
for video_dict in info_dict . get ( ' entries ' , { } ) :
2021-04-01 10:46:10 +02:00
actual_post_extract ( video_dict or { } )
2021-02-28 15:56:08 +01:00
return
2021-05-18 20:20:59 +02:00
post_extractor = info_dict . get ( ' __post_extractor ' ) or ( lambda : { } )
2021-05-18 20:25:32 +02:00
extra = post_extractor ( ) . items ( )
info_dict . update ( extra )
2021-05-18 20:20:59 +02:00
info_dict . pop ( ' __post_extractor ' , None )
2021-02-28 15:56:08 +01:00
2021-05-18 20:25:32 +02:00
original_infodict = info_dict . get ( ' __original_infodict ' ) or { }
original_infodict . update ( extra )
original_infodict . pop ( ' __post_extractor ' , None )
2021-04-01 10:46:10 +02:00
actual_post_extract ( info_dict or { } )
2021-02-28 15:56:08 +01:00
2021-04-11 00:18:07 +02:00
def pre_process ( self , ie_info , key = ' pre_process ' , files_to_move = None ) :
2021-01-26 11:20:20 +01:00
info = dict ( ie_info )
2021-04-11 00:18:07 +02:00
info [ ' __files_to_move ' ] = files_to_move or { }
for pp in self . _pps [ key ] :
2021-03-18 16:24:53 +01:00
info = self . run_pp ( pp , info )
2021-04-11 00:18:07 +02:00
return info , info . pop ( ' __files_to_move ' , None )
2021-01-26 11:20:20 +01:00
2021-03-18 16:24:53 +01:00
def post_process ( self , filename , ie_info , files_to_move = None ) :
2013-06-18 22:14:21 +02:00
""" Run all the postprocessors on the given file. """
info = dict ( ie_info )
info [ ' filepath ' ] = filename
2021-03-18 16:24:53 +01:00
info [ ' __files_to_move ' ] = files_to_move or { }
2021-01-23 13:18:12 +01:00
2021-04-11 00:18:07 +02:00
for pp in ie_info . get ( ' __postprocessors ' , [ ] ) + self . _pps [ ' post_process ' ] :
2021-03-18 16:24:53 +01:00
info = self . run_pp ( pp , info )
info = self . run_pp ( MoveFilesAfterDownloadPP ( self ) , info )
del info [ ' __files_to_move ' ]
2021-04-11 00:18:07 +02:00
for pp in self . _pps [ ' after_move ' ] :
2021-03-18 16:24:53 +01:00
info = self . run_pp ( pp , info )
2021-03-19 11:35:32 +01:00
return info
2013-10-06 04:27:09 +02:00
2013-11-25 15:46:54 +01:00
def _make_archive_id ( self , info_dict ) :
2019-02-01 23:44:31 +01:00
video_id = info_dict . get ( ' id ' )
if not video_id :
return
2013-11-25 15:46:54 +01:00
# Future-proof against any change in case
# and backwards compatibility with prior versions
2019-02-01 23:44:31 +01:00
extractor = info_dict . get ( ' extractor_key ' ) or info_dict . get ( ' ie_key ' ) # key in a playlist
2013-11-22 22:46:46 +01:00
if extractor is None :
2019-02-07 19:08:48 +01:00
url = str_or_none ( info_dict . get ( ' url ' ) )
if not url :
return
2019-02-01 23:44:31 +01:00
# Try to find matching extractor for the URL and take its ie_key
for ie in self . _ies :
2019-02-07 19:08:48 +01:00
if ie . suitable ( url ) :
2019-02-01 23:44:31 +01:00
extractor = ie . ie_key ( )
break
else :
return
2021-01-21 13:06:42 +01:00
return ' %s %s ' % ( extractor . lower ( ) , video_id )
2013-11-25 15:46:54 +01:00
def in_download_archive ( self , info_dict ) :
fn = self . params . get ( ' download_archive ' )
if fn is None :
return False
vid_id = self . _make_archive_id ( info_dict )
2019-02-01 23:44:31 +01:00
if not vid_id :
2013-11-22 22:46:46 +01:00
return False # Incomplete video information
2013-11-25 15:46:54 +01:00
2020-09-19 03:18:23 +02:00
return vid_id in self . archive
2013-10-06 04:27:09 +02:00
def record_download_archive ( self , info_dict ) :
fn = self . params . get ( ' download_archive ' )
if fn is None :
return
2013-11-25 15:46:54 +01:00
vid_id = self . _make_archive_id ( info_dict )
assert vid_id
2013-10-06 04:27:09 +02:00
with locked_file ( fn , ' a ' , encoding = ' utf-8 ' ) as archive_file :
2014-01-05 01:52:03 +01:00
archive_file . write ( vid_id + ' \n ' )
2020-09-19 03:18:23 +02:00
self . archive . add ( vid_id )
2013-07-02 10:08:58 +02:00
2013-10-21 14:09:38 +02:00
@staticmethod
2013-10-28 11:31:12 +01:00
def format_resolution ( format , default = ' unknown ' ) :
2013-11-25 22:34:56 +01:00
if format . get ( ' vcodec ' ) == ' none ' :
return ' audio only '
2013-12-24 11:56:02 +01:00
if format . get ( ' resolution ' ) is not None :
return format [ ' resolution ' ]
2021-03-15 18:17:29 +01:00
if format . get ( ' width ' ) and format . get ( ' height ' ) :
res = ' %d x %d ' % ( format [ ' width ' ] , format [ ' height ' ] )
elif format . get ( ' height ' ) :
res = ' %s p ' % format [ ' height ' ]
elif format . get ( ' width ' ) :
2016-02-11 17:46:13 +01:00
res = ' %d x? ' % format [ ' width ' ]
2013-10-21 14:09:38 +02:00
else :
2013-10-28 11:31:12 +01:00
res = default
2013-10-21 14:09:38 +02:00
return res
2014-04-30 02:02:41 +02:00
def _format_note ( self , fdict ) :
res = ' '
if fdict . get ( ' ext ' ) in [ ' f4f ' , ' f4m ' ] :
res + = ' (unsupported) '
2016-01-01 13:28:45 +01:00
if fdict . get ( ' language ' ) :
if res :
res + = ' '
2016-03-20 17:01:45 +01:00
res + = ' [ %s ] ' % fdict [ ' language ' ]
2014-04-30 02:02:41 +02:00
if fdict . get ( ' format_note ' ) is not None :
res + = fdict [ ' format_note ' ] + ' '
if fdict . get ( ' tbr ' ) is not None :
res + = ' %4d k ' % fdict [ ' tbr ' ]
if fdict . get ( ' container ' ) is not None :
if res :
res + = ' , '
res + = ' %s container ' % fdict [ ' container ' ]
2019-05-10 22:56:22 +02:00
if ( fdict . get ( ' vcodec ' ) is not None
and fdict . get ( ' vcodec ' ) != ' none ' ) :
2014-04-30 02:02:41 +02:00
if res :
res + = ' , '
res + = fdict [ ' vcodec ' ]
2013-11-16 01:08:43 +01:00
if fdict . get ( ' vbr ' ) is not None :
2014-04-30 02:02:41 +02:00
res + = ' @ '
elif fdict . get ( ' vbr ' ) is not None and fdict . get ( ' abr ' ) is not None :
res + = ' video@ '
if fdict . get ( ' vbr ' ) is not None :
res + = ' %4d k ' % fdict [ ' vbr ' ]
2014-10-30 09:34:13 +01:00
if fdict . get ( ' fps ' ) is not None :
2016-03-09 20:03:18 +01:00
if res :
res + = ' , '
res + = ' %s fps ' % fdict [ ' fps ' ]
2014-04-30 02:02:41 +02:00
if fdict . get ( ' acodec ' ) is not None :
if res :
res + = ' , '
if fdict [ ' acodec ' ] == ' none ' :
res + = ' video only '
else :
res + = ' %-5s ' % fdict [ ' acodec ' ]
elif fdict . get ( ' abr ' ) is not None :
if res :
res + = ' , '
res + = ' audio '
if fdict . get ( ' abr ' ) is not None :
res + = ' @ %3d k ' % fdict [ ' abr ' ]
if fdict . get ( ' asr ' ) is not None :
res + = ' ( %5d Hz) ' % fdict [ ' asr ' ]
if fdict . get ( ' filesize ' ) is not None :
if res :
res + = ' , '
res + = format_bytes ( fdict [ ' filesize ' ] )
2014-07-21 12:02:44 +02:00
elif fdict . get ( ' filesize_approx ' ) is not None :
if res :
res + = ' , '
res + = ' ~ ' + format_bytes ( fdict [ ' filesize_approx ' ] )
2014-04-30 02:02:41 +02:00
return res
2013-11-16 01:08:43 +01:00
2020-12-13 15:29:09 +01:00
def _format_note_table ( self , f ) :
def join_fields ( * vargs ) :
return ' , ' . join ( ( val for val in vargs if val != ' ' ) )
return join_fields (
' UNSUPPORTED ' if f . get ( ' ext ' ) in ( ' f4f ' , ' f4m ' ) else ' ' ,
format_field ( f , ' language ' , ' [ %s ] ' ) ,
format_field ( f , ' format_note ' ) ,
format_field ( f , ' container ' , ignore = ( None , f . get ( ' ext ' ) ) ) ,
format_field ( f , ' asr ' , ' %5d Hz ' ) )
2014-04-30 02:02:41 +02:00
def list_formats ( self , info_dict ) :
2013-10-30 01:09:26 +01:00
formats = info_dict . get ( ' formats ' , [ info_dict ] )
2021-05-11 10:00:48 +02:00
new_format = (
' list-formats ' not in self . params . get ( ' compat_opts ' , [ ] )
and self . params . get ( ' list_formats_as_table ' , True ) is not False )
2020-12-13 15:29:09 +01:00
if new_format :
table = [
[
format_field ( f , ' format_id ' ) ,
format_field ( f , ' ext ' ) ,
self . format_resolution ( f ) ,
format_field ( f , ' fps ' , ' %d ' ) ,
' | ' ,
format_field ( f , ' filesize ' , ' %s ' , func = format_bytes ) + format_field ( f , ' filesize_approx ' , ' ~ %s ' , func = format_bytes ) ,
format_field ( f , ' tbr ' , ' %4d k ' ) ,
2021-04-10 17:08:33 +02:00
shorten_protocol_name ( f . get ( ' protocol ' , ' ' ) . replace ( " native " , " n " ) ) ,
2020-12-13 15:29:09 +01:00
' | ' ,
format_field ( f , ' vcodec ' , default = ' unknown ' ) . replace ( ' none ' , ' ' ) ,
format_field ( f , ' vbr ' , ' %4d k ' ) ,
format_field ( f , ' acodec ' , default = ' unknown ' ) . replace ( ' none ' , ' ' ) ,
format_field ( f , ' abr ' , ' %3d k ' ) ,
format_field ( f , ' asr ' , ' %5d Hz ' ) ,
self . _format_note_table ( f ) ]
for f in formats
if f . get ( ' preference ' ) is None or f [ ' preference ' ] > = - 1000 ]
header_line = [ ' ID ' , ' EXT ' , ' RESOLUTION ' , ' FPS ' , ' | ' , ' FILESIZE ' , ' TBR ' , ' PROTO ' ,
' | ' , ' VCODEC ' , ' VBR ' , ' ACODEC ' , ' ABR ' , ' ASR ' , ' NOTE ' ]
else :
table = [
[
format_field ( f , ' format_id ' ) ,
format_field ( f , ' ext ' ) ,
self . format_resolution ( f ) ,
self . _format_note ( f ) ]
for f in formats
if f . get ( ' preference ' ) is None or f [ ' preference ' ] > = - 1000 ]
header_line = [ ' format code ' , ' extension ' , ' resolution ' , ' note ' ]
2013-10-29 15:09:45 +01:00
2015-01-25 02:38:47 +01:00
self . to_screen (
2020-12-13 15:29:09 +01:00
' [info] Available formats for %s : \n %s ' % ( info_dict [ ' id ' ] , render_table (
header_line ,
table ,
delim = new_format ,
extraGap = ( 0 if new_format else 1 ) ,
hideEmpty = new_format ) ) )
2015-01-25 02:38:47 +01:00
def list_thumbnails ( self , info_dict ) :
thumbnails = info_dict . get ( ' thumbnails ' )
if not thumbnails :
2016-03-10 20:17:35 +01:00
self . to_screen ( ' [info] No thumbnails present for %s ' % info_dict [ ' id ' ] )
return
2015-01-25 02:38:47 +01:00
self . to_screen (
' [info] Thumbnails for %s : ' % info_dict [ ' id ' ] )
self . to_screen ( render_table (
[ ' ID ' , ' width ' , ' height ' , ' URL ' ] ,
[ [ t [ ' id ' ] , t . get ( ' width ' , ' unknown ' ) , t . get ( ' height ' , ' unknown ' ) , t [ ' url ' ] ] for t in thumbnails ] ) )
2013-11-22 19:57:52 +01:00
2015-02-16 21:44:17 +01:00
def list_subtitles ( self , video_id , subtitles , name = ' subtitles ' ) :
2015-02-15 18:03:41 +01:00
if not subtitles :
2015-02-16 21:44:17 +01:00
self . to_screen ( ' %s has no %s ' % ( video_id , name ) )
2015-02-15 18:03:41 +01:00
return
self . to_screen (
2015-02-17 22:59:19 +01:00
' Available %s for %s : ' % ( name , video_id ) )
2021-05-12 21:37:58 +02:00
def _row ( lang , formats ) :
2021-05-17 12:41:07 +02:00
exts , names = zip ( * ( ( f [ ' ext ' ] , f . get ( ' name ' , ' unknown ' ) ) for f in reversed ( formats ) ) )
2021-05-12 21:37:58 +02:00
if len ( set ( names ) ) == 1 :
2021-05-17 12:41:07 +02:00
names = [ ] if names [ 0 ] == ' unknown ' else names [ : 1 ]
2021-05-12 21:37:58 +02:00
return [ lang , ' , ' . join ( names ) , ' , ' . join ( exts ) ]
2015-02-17 22:59:19 +01:00
self . to_screen ( render_table (
2021-05-12 21:37:58 +02:00
[ ' Language ' , ' Name ' , ' Formats ' ] ,
[ _row ( lang , formats ) for lang , formats in subtitles . items ( ) ] ,
hideEmpty = True ) )
2015-02-15 18:03:41 +01:00
2013-11-22 19:57:52 +01:00
def urlopen ( self , req ) :
""" Start an HTTP download """
2015-11-19 22:08:34 +01:00
if isinstance ( req , compat_basestring ) :
2015-11-20 15:33:49 +01:00
req = sanitized_Request ( req )
2014-03-10 19:01:29 +01:00
return self . _opener . open ( req , timeout = self . _socket_timeout )
2013-11-22 19:57:52 +01:00
def print_debug_header ( self ) :
if not self . params . get ( ' verbose ' ) :
return
2014-03-30 06:02:41 +02:00
2014-07-24 13:29:44 +02:00
if type ( ' ' ) is not compat_str :
2019-03-09 13:14:41 +01:00
# Python 2.6 on SLES11 SP1 (https://github.com/ytdl-org/youtube-dl/issues/3326)
2014-07-24 13:29:44 +02:00
self . report_warning (
' Your Python is broken! Update to a newer and supported version ' )
2014-11-12 15:30:26 +01:00
stdout_encoding = getattr (
sys . stdout , ' encoding ' , ' missing ( %s ) ' % type ( sys . stdout ) . __name__ )
2014-07-23 02:24:50 +02:00
encoding_str = (
2014-04-07 19:57:42 +02:00
' [debug] Encodings: locale %s , fs %s , out %s , pref %s \n ' % (
locale . getpreferredencoding ( ) ,
sys . getfilesystemencoding ( ) ,
2014-11-12 15:30:26 +01:00
stdout_encoding ,
2014-07-23 02:24:50 +02:00
self . get_encoding ( ) ) )
2014-07-24 13:29:44 +02:00
write_string ( encoding_str , encoding = None )
2014-04-07 19:57:42 +02:00
2021-02-14 18:10:54 +01:00
source = (
' (exe) ' if hasattr ( sys , ' frozen ' )
else ' (zip) ' if isinstance ( globals ( ) . get ( ' __loader__ ' ) , zipimporter )
else ' (source) ' if os . path . basename ( sys . argv [ 0 ] ) == ' __main__.py '
else ' ' )
self . _write_string ( ' [debug] yt-dlp version %s %s \n ' % ( __version__ , source ) )
2016-02-21 12:28:58 +01:00
if _LAZY_LOADER :
2021-01-24 14:40:02 +01:00
self . _write_string ( ' [debug] Lazy loading extractors enabled \n ' )
if _PLUGIN_CLASSES :
self . _write_string (
' [debug] Plugin Extractors: %s \n ' % [ ie . ie_key ( ) for ie in _PLUGIN_CLASSES ] )
2021-05-11 10:00:48 +02:00
if self . params . get ( ' compat_opts ' ) :
self . _write_string (
' [debug] Compatibility options: %s \n ' % ' , ' . join ( self . params . get ( ' compat_opts ' ) ) )
2013-11-22 19:57:52 +01:00
try :
sp = subprocess . Popen (
[ ' git ' , ' rev-parse ' , ' --short ' , ' HEAD ' ] ,
stdout = subprocess . PIPE , stderr = subprocess . PIPE ,
cwd = os . path . dirname ( os . path . abspath ( __file__ ) ) )
2021-01-09 13:26:12 +01:00
out , err = process_communicate_or_kill ( sp )
2013-11-22 19:57:52 +01:00
out = out . decode ( ) . strip ( )
if re . match ( ' [0-9a-f]+ ' , out ) :
2021-01-24 14:40:02 +01:00
self . _write_string ( ' [debug] Git HEAD: %s \n ' % out )
2015-03-27 13:02:20 +01:00
except Exception :
2013-11-22 19:57:52 +01:00
try :
sys . exc_clear ( )
2015-03-27 13:02:20 +01:00
except Exception :
2013-11-22 19:57:52 +01:00
pass
2018-01-01 15:52:24 +01:00
def python_implementation ( ) :
impl_name = platform . python_implementation ( )
if impl_name == ' PyPy ' and hasattr ( sys , ' pypy_version_info ' ) :
return impl_name + ' version %d . %d . %d ' % sys . pypy_version_info [ : 3 ]
return impl_name
2021-02-14 18:10:54 +01:00
self . _write_string ( ' [debug] Python version %s ( %s %s ) - %s \n ' % (
platform . python_version ( ) ,
python_implementation ( ) ,
platform . architecture ( ) [ 0 ] ,
2018-01-01 15:52:24 +01:00
platform_name ( ) ) )
2014-10-26 16:31:52 +01:00
2015-02-13 11:14:01 +01:00
exe_versions = FFmpegPostProcessor . get_versions ( self )
2014-11-02 10:55:36 +01:00
exe_versions [ ' rtmpdump ' ] = rtmpdump_version ( )
2017-08-03 14:17:25 +02:00
exe_versions [ ' phantomjs ' ] = PhantomJSwrapper . _version ( )
2014-10-26 16:31:52 +01:00
exe_str = ' , ' . join (
' %s %s ' % ( exe , v )
for exe , v in sorted ( exe_versions . items ( ) )
if v
)
if not exe_str :
exe_str = ' none '
self . _write_string ( ' [debug] exe versions: %s \n ' % exe_str )
2013-11-22 19:57:52 +01:00
proxy_map = { }
for handler in self . _opener . handlers :
if hasattr ( handler , ' proxies ' ) :
proxy_map . update ( handler . proxies )
2014-04-07 19:57:42 +02:00
self . _write_string ( ' [debug] Proxy map: ' + compat_str ( proxy_map ) + ' \n ' )
2013-11-22 19:57:52 +01:00
2015-01-10 21:02:27 +01:00
if self . params . get ( ' call_home ' , False ) :
ipaddr = self . urlopen ( ' https://yt-dl.org/ip ' ) . read ( ) . decode ( ' utf-8 ' )
self . _write_string ( ' [debug] Public IP address: %s \n ' % ipaddr )
2021-01-12 16:53:31 +01:00
return
2015-01-10 21:02:27 +01:00
latest_version = self . urlopen (
' https://yt-dl.org/latest/version ' ) . read ( ) . decode ( ' utf-8 ' )
if version_tuple ( latest_version ) > version_tuple ( __version__ ) :
self . report_warning (
' You are using an outdated version (newest version: %s )! '
' See https://yt-dl.org/update if you need help updating. ' %
latest_version )
2013-12-01 11:42:02 +01:00
def _setup_opener ( self ) :
2013-12-02 13:37:05 +01:00
timeout_val = self . params . get ( ' socket_timeout ' )
2014-03-10 19:01:29 +01:00
self . _socket_timeout = 600 if timeout_val is None else float ( timeout_val )
2013-12-02 13:37:05 +01:00
2013-11-22 19:57:52 +01:00
opts_cookiefile = self . params . get ( ' cookiefile ' )
opts_proxy = self . params . get ( ' proxy ' )
if opts_cookiefile is None :
self . cookiejar = compat_cookiejar . CookieJar ( )
else :
2017-03-25 20:31:16 +01:00
opts_cookiefile = expand_path ( opts_cookiefile )
2018-12-09 00:00:32 +01:00
self . cookiejar = YoutubeDLCookieJar ( opts_cookiefile )
2013-11-22 19:57:52 +01:00
if os . access ( opts_cookiefile , os . R_OK ) :
2017-04-30 07:56:33 +02:00
self . cookiejar . load ( ignore_discard = True , ignore_expires = True )
2013-11-22 19:57:52 +01:00
2015-09-06 02:21:33 +02:00
cookie_processor = YoutubeDLCookieProcessor ( self . cookiejar )
2013-11-22 19:57:52 +01:00
if opts_proxy is not None :
if opts_proxy == ' ' :
proxies = { }
else :
proxies = { ' http ' : opts_proxy , ' https ' : opts_proxy }
else :
proxies = compat_urllib_request . getproxies ( )
2019-03-09 13:14:41 +01:00
# Set HTTPS proxy to HTTP one if given (https://github.com/ytdl-org/youtube-dl/issues/805)
2013-11-22 19:57:52 +01:00
if ' http ' in proxies and ' https ' not in proxies :
proxies [ ' https ' ] = proxies [ ' http ' ]
2015-03-03 00:03:06 +01:00
proxy_handler = PerRequestProxyHandler ( proxies )
2013-12-29 15:28:32 +01:00
debuglevel = 1 if self . params . get ( ' debug_printtraffic ' ) else 0
2015-01-10 19:55:36 +01:00
https_handler = make_HTTPS_handler ( self . params , debuglevel = debuglevel )
ydlh = YoutubeDLHandler ( self . params , debuglevel = debuglevel )
2020-02-29 13:08:44 +01:00
redirect_handler = YoutubeDLRedirectHandler ( )
2015-10-17 17:16:40 +02:00
data_handler = compat_urllib_request_DataHandler ( )
2016-01-14 08:14:01 +01:00
# When passing our own FileHandler instance, build_opener won't add the
# default FileHandler and allows us to disable the file protocol, which
# can be used for malicious purposes (see
2019-03-09 13:14:41 +01:00
# https://github.com/ytdl-org/youtube-dl/issues/8227)
2016-01-14 08:14:01 +01:00
file_handler = compat_urllib_request . FileHandler ( )
def file_open ( * args , * * kwargs ) :
2021-02-24 19:45:56 +01:00
raise compat_urllib_error . URLError ( ' file:// scheme is explicitly disabled in yt-dlp for security reasons ' )
2016-01-14 08:14:01 +01:00
file_handler . file_open = file_open
opener = compat_urllib_request . build_opener (
2020-02-29 13:08:44 +01:00
proxy_handler , https_handler , cookie_processor , ydlh , redirect_handler , data_handler , file_handler )
2015-03-03 13:56:06 +01:00
2013-11-22 19:57:52 +01:00
# Delete the default user-agent header, which would otherwise apply in
# cases where our custom HTTP handler doesn't come into play
2019-03-09 13:14:41 +01:00
# (See https://github.com/ytdl-org/youtube-dl/issues/1309 for details)
2013-11-22 19:57:52 +01:00
opener . addheaders = [ ]
self . _opener = opener
2014-03-30 06:02:41 +02:00
def encode ( self , s ) :
if isinstance ( s , bytes ) :
return s # Already encoded
try :
return s . encode ( self . get_encoding ( ) )
except UnicodeEncodeError as err :
err . reason = err . reason + ' . Check your system encoding configuration or use the --encoding option. '
raise
def get_encoding ( self ) :
encoding = self . params . get ( ' encoding ' )
if encoding is None :
encoding = preferredencoding ( )
return encoding
2015-01-25 03:11:12 +01:00
2021-02-03 14:36:09 +01:00
def _write_thumbnails ( self , info_dict , filename ) : # return the extensions
2021-02-09 18:42:32 +01:00
write_all = self . params . get ( ' write_all_thumbnails ' , False )
thumbnails = [ ]
if write_all or self . params . get ( ' writethumbnail ' , False ) :
2021-01-23 13:18:12 +01:00
thumbnails = info_dict . get ( ' thumbnails ' ) or [ ]
2021-02-09 18:42:32 +01:00
multiple = write_all and len ( thumbnails ) > 1
2015-01-25 03:11:12 +01:00
2021-01-23 13:18:12 +01:00
ret = [ ]
2021-02-09 18:42:32 +01:00
for t in thumbnails [ : : 1 if write_all else - 1 ] :
2015-01-25 03:11:12 +01:00
thumb_ext = determine_ext ( t [ ' url ' ] , ' jpg ' )
2021-02-09 18:42:32 +01:00
suffix = ' %s . ' % t [ ' id ' ] if multiple else ' '
thumb_display_id = ' %s ' % t [ ' id ' ] if multiple else ' '
2021-05-28 22:08:02 +02:00
thumb_filename = replace_extension ( filename , suffix + thumb_ext , info_dict . get ( ' ext ' ) )
2015-01-25 03:11:12 +01:00
2019-10-13 18:00:48 +02:00
if not self . params . get ( ' overwrites ' , True ) and os . path . exists ( encodeFilename ( thumb_filename ) ) :
2021-02-03 14:36:09 +01:00
ret . append ( suffix + thumb_ext )
2015-01-25 03:11:12 +01:00
self . to_screen ( ' [ %s ] %s : Thumbnail %s is already present ' %
( info_dict [ ' extractor ' ] , info_dict [ ' id ' ] , thumb_display_id ) )
else :
2021-03-01 01:09:50 +01:00
self . to_screen ( ' [ %s ] %s : Downloading thumbnail %s ... ' %
2015-01-25 03:11:12 +01:00
( info_dict [ ' extractor ' ] , info_dict [ ' id ' ] , thumb_display_id ) )
try :
uf = self . urlopen ( t [ ' url ' ] )
2015-08-30 22:01:13 +02:00
with open ( encodeFilename ( thumb_filename ) , ' wb ' ) as thumbf :
2015-01-25 03:11:12 +01:00
shutil . copyfileobj ( uf , thumbf )
2021-02-03 14:36:09 +01:00
ret . append ( suffix + thumb_ext )
2015-01-25 03:11:12 +01:00
self . to_screen ( ' [ %s ] %s : Writing thumbnail %s to: %s ' %
( info_dict [ ' extractor ' ] , info_dict [ ' id ' ] , thumb_display_id , thumb_filename ) )
2021-05-28 22:08:02 +02:00
t [ ' filepath ' ] = thumb_filename
2021-05-04 19:06:18 +02:00
except network_exceptions as err :
2015-01-25 03:11:12 +01:00
self . report_warning ( ' Unable to download thumbnail " %s " : %s ' %
2015-12-20 02:00:39 +01:00
( t [ ' url ' ] , error_to_compat_str ( err ) ) )
2021-02-09 18:42:32 +01:00
if ret and not write_all :
break
2021-01-23 13:18:12 +01:00
return ret