mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2024-11-22 02:15:12 +00:00
[cleanup] Misc
This commit is contained in:
parent
415f8d51a8
commit
c487cf0010
20 changed files with 171 additions and 139 deletions
12
Makefile
12
Makefile
|
@ -9,7 +9,9 @@ tar: yt-dlp.tar.gz
|
||||||
# Keep this list in sync with MANIFEST.in
|
# Keep this list in sync with MANIFEST.in
|
||||||
# intended use: when building a source distribution,
|
# intended use: when building a source distribution,
|
||||||
# make pypi-files && python setup.py sdist
|
# make pypi-files && python setup.py sdist
|
||||||
pypi-files: AUTHORS Changelog.md LICENSE README.md README.txt supportedsites completions yt-dlp.1 devscripts/* test/*
|
pypi-files:
|
||||||
|
AUTHORS Changelog.md LICENSE README.md README.txt supportedsites \
|
||||||
|
completions yt-dlp.1 requirements.txt devscripts/* test/*
|
||||||
|
|
||||||
.PHONY: all clean install test tar pypi-files completions ot offlinetest codetest supportedsites
|
.PHONY: all clean install test tar pypi-files completions ot offlinetest codetest supportedsites
|
||||||
|
|
||||||
|
@ -91,10 +93,10 @@ yt-dlp: yt_dlp/*.py yt_dlp/*/*.py
|
||||||
rm yt-dlp.zip
|
rm yt-dlp.zip
|
||||||
chmod a+x yt-dlp
|
chmod a+x yt-dlp
|
||||||
|
|
||||||
README.md: yt_dlp/*.py yt_dlp/*/*.py
|
README.md: yt_dlp/*.py yt_dlp/*/*.py devscripts/make_readme.py
|
||||||
COLUMNS=80 $(PYTHON) yt_dlp/__main__.py --ignore-config --help | $(PYTHON) devscripts/make_readme.py
|
COLUMNS=80 $(PYTHON) yt_dlp/__main__.py --ignore-config --help | $(PYTHON) devscripts/make_readme.py
|
||||||
|
|
||||||
CONTRIBUTING.md: README.md
|
CONTRIBUTING.md: README.md devscripts/make_contributing.py
|
||||||
$(PYTHON) devscripts/make_contributing.py README.md CONTRIBUTING.md
|
$(PYTHON) devscripts/make_contributing.py README.md CONTRIBUTING.md
|
||||||
|
|
||||||
issuetemplates: devscripts/make_issue_template.py .github/ISSUE_TEMPLATE_tmpl/1_broken_site.yml .github/ISSUE_TEMPLATE_tmpl/2_site_support_request.yml .github/ISSUE_TEMPLATE_tmpl/3_site_feature_request.yml .github/ISSUE_TEMPLATE_tmpl/4_bug_report.yml .github/ISSUE_TEMPLATE_tmpl/5_feature_request.yml yt_dlp/version.py
|
issuetemplates: devscripts/make_issue_template.py .github/ISSUE_TEMPLATE_tmpl/1_broken_site.yml .github/ISSUE_TEMPLATE_tmpl/2_site_support_request.yml .github/ISSUE_TEMPLATE_tmpl/3_site_feature_request.yml .github/ISSUE_TEMPLATE_tmpl/4_bug_report.yml .github/ISSUE_TEMPLATE_tmpl/5_feature_request.yml yt_dlp/version.py
|
||||||
|
@ -111,7 +113,7 @@ supportedsites:
|
||||||
README.txt: README.md
|
README.txt: README.md
|
||||||
pandoc -f $(MARKDOWN) -t plain README.md -o README.txt
|
pandoc -f $(MARKDOWN) -t plain README.md -o README.txt
|
||||||
|
|
||||||
yt-dlp.1: README.md
|
yt-dlp.1: README.md devscripts/prepare_manpage.py
|
||||||
$(PYTHON) devscripts/prepare_manpage.py yt-dlp.1.temp.md
|
$(PYTHON) devscripts/prepare_manpage.py yt-dlp.1.temp.md
|
||||||
pandoc -s -f $(MARKDOWN) -t man yt-dlp.1.temp.md -o yt-dlp.1
|
pandoc -s -f $(MARKDOWN) -t man yt-dlp.1.temp.md -o yt-dlp.1
|
||||||
rm -f yt-dlp.1.temp.md
|
rm -f yt-dlp.1.temp.md
|
||||||
|
@ -147,7 +149,7 @@ yt-dlp.tar.gz: all
|
||||||
CONTRIBUTING.md Collaborators.md CONTRIBUTORS AUTHORS \
|
CONTRIBUTING.md Collaborators.md CONTRIBUTORS AUTHORS \
|
||||||
Makefile MANIFEST.in yt-dlp.1 README.txt completions \
|
Makefile MANIFEST.in yt-dlp.1 README.txt completions \
|
||||||
setup.py setup.cfg yt-dlp yt_dlp requirements.txt \
|
setup.py setup.cfg yt-dlp yt_dlp requirements.txt \
|
||||||
devscripts test tox.ini pytest.ini
|
devscripts test
|
||||||
|
|
||||||
AUTHORS: .mailmap
|
AUTHORS: .mailmap
|
||||||
git shortlog -s -n | cut -f2 | sort > AUTHORS
|
git shortlog -s -n | cut -f2 | sort > AUTHORS
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import io
|
|
||||||
import optparse
|
import optparse
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
# yt-dlp --help | make_readme.py
|
# yt-dlp --help | make_readme.py
|
||||||
# This must be run in a console of correct width
|
# This must be run in a console of correct width
|
||||||
|
import functools
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -12,19 +13,44 @@
|
||||||
EPILOG_START = 'See full documentation'
|
EPILOG_START = 'See full documentation'
|
||||||
|
|
||||||
|
|
||||||
helptext = sys.stdin.read()
|
def take_section(text, start=None, end=None, *, shift=0):
|
||||||
if isinstance(helptext, bytes):
|
return text[
|
||||||
helptext = helptext.decode()
|
text.index(start) + shift if start else None:
|
||||||
|
text.index(end) + shift if end else None
|
||||||
|
]
|
||||||
|
|
||||||
start, end = helptext.index(f'\n {OPTIONS_START}'), helptext.index(f'\n{EPILOG_START}')
|
|
||||||
options = re.sub(r'(?m)^ (\w.+)$', r'## \1', helptext[start + 1: end + 1])
|
def apply_patch(text, patch):
|
||||||
|
return re.sub(*patch, text)
|
||||||
|
|
||||||
|
|
||||||
|
options = take_section(sys.stdin.read(), f'\n {OPTIONS_START}', f'\n{EPILOG_START}', shift=1)
|
||||||
|
|
||||||
|
switch_col_width = len(re.search(r'(?m)^\s{5,}', options).group())
|
||||||
|
delim = f'\n{" " * switch_col_width}'
|
||||||
|
|
||||||
|
PATCHES = (
|
||||||
|
( # Headings
|
||||||
|
r'(?m)^ (\w.+\n)( (?=\w))?',
|
||||||
|
r'## \1'
|
||||||
|
),
|
||||||
|
( # Do not split URLs
|
||||||
|
rf'({delim[:-1]})? (?P<label>\[\S+\] )?(?P<url>https?({delim})?:({delim})?/({delim})?/(({delim})?\S+)+)\s',
|
||||||
|
lambda mobj: ''.join((delim, mobj.group('label') or '', re.sub(r'\s+', '', mobj.group('url')), '\n'))
|
||||||
|
),
|
||||||
|
# This creates issues with prepare_manpage
|
||||||
|
# ( # Avoid newline when a space is available b/w switch and description
|
||||||
|
# r'(?m)^(\s{4}-.{%d})(%s)' % (switch_col_width - 6, delim),
|
||||||
|
# r'\1 '
|
||||||
|
# ),
|
||||||
|
)
|
||||||
|
|
||||||
with open(README_FILE, encoding='utf-8') as f:
|
with open(README_FILE, encoding='utf-8') as f:
|
||||||
readme = f.read()
|
readme = f.read()
|
||||||
|
|
||||||
header = readme[:readme.index(f'## {OPTIONS_START}')]
|
|
||||||
footer = readme[readme.index(f'# {OPTIONS_END}'):]
|
|
||||||
|
|
||||||
with open(README_FILE, 'w', encoding='utf-8') as f:
|
with open(README_FILE, 'w', encoding='utf-8') as f:
|
||||||
for part in (header, options, footer):
|
f.write(''.join((
|
||||||
f.write(part)
|
take_section(readme, end=f'## {OPTIONS_START}'),
|
||||||
|
functools.reduce(apply_patch, PATCHES, options),
|
||||||
|
take_section(readme, f'# {OPTIONS_END}'),
|
||||||
|
)))
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
[pytest]
|
|
||||||
addopts = -ra -v --strict-markers
|
|
||||||
markers =
|
|
||||||
download
|
|
32
setup.cfg
32
setup.cfg
|
@ -1,6 +1,34 @@
|
||||||
[wheel]
|
[wheel]
|
||||||
universal = True
|
universal = true
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
exclude = devscripts/lazy_load_template.py,devscripts/make_issue_template.py,setup.py,build,.git,venv
|
exclude = build,venv,.tox,.git
|
||||||
ignore = E402,E501,E731,E741,W503
|
ignore = E402,E501,E731,E741,W503
|
||||||
|
per_file_ignores =
|
||||||
|
./devscripts/lazy_load_template.py: F401
|
||||||
|
|
||||||
|
[tool:pytest]
|
||||||
|
addopts = -ra -v --strict-markers
|
||||||
|
markers =
|
||||||
|
download
|
||||||
|
|
||||||
|
[tox:tox]
|
||||||
|
skipsdist = true
|
||||||
|
envlist = py{36,37,38,39,310},pypy{36,37,38,39}
|
||||||
|
skip_missing_interpreters = true
|
||||||
|
|
||||||
|
[testenv] # tox
|
||||||
|
deps =
|
||||||
|
pytest
|
||||||
|
commands = pytest {posargs:"-m not download"}
|
||||||
|
passenv = HOME # For test_compat_expanduser
|
||||||
|
setenv =
|
||||||
|
# PYTHONWARNINGS = error # Catches PIP's warnings too
|
||||||
|
|
||||||
|
[isort]
|
||||||
|
py_version = 36
|
||||||
|
multi_line_output = VERTICAL_HANGING_INDENT
|
||||||
|
line_length = 80
|
||||||
|
reverse_relative = true
|
||||||
|
ensure_newline_before_comments = true
|
||||||
|
include_trailing_comma = true
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -36,7 +36,7 @@ def read_version(fname):
|
||||||
|
|
||||||
|
|
||||||
if sys.argv[1:2] == ['py2exe']:
|
if sys.argv[1:2] == ['py2exe']:
|
||||||
import py2exe
|
import py2exe # noqa: F401
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
'py2exe builds do not support pycryptodomex and needs VC++14 to run. '
|
'py2exe builds do not support pycryptodomex and needs VC++14 to run. '
|
||||||
'The recommended way is to use "pyinst.py" to build using pyinstaller')
|
'The recommended way is to use "pyinst.py" to build using pyinstaller')
|
||||||
|
|
16
tox.ini
16
tox.ini
|
@ -1,16 +0,0 @@
|
||||||
[tox]
|
|
||||||
envlist = py26,py27,py33,py34,py35
|
|
||||||
|
|
||||||
# Needed?
|
|
||||||
[testenv]
|
|
||||||
deps =
|
|
||||||
nose
|
|
||||||
coverage
|
|
||||||
# We need a valid $HOME for test_compat_expanduser
|
|
||||||
passenv = HOME
|
|
||||||
defaultargs = test --exclude test_download.py --exclude test_age_restriction.py
|
|
||||||
--exclude test_subtitles.py --exclude test_write_annotations.py
|
|
||||||
--exclude test_youtube_lists.py --exclude test_iqiyi_sdk_interpreter.py
|
|
||||||
--exclude test_socks.py
|
|
||||||
commands = nosetests --verbose {posargs:{[testenv]defaultargs}} # --with-coverage --cover-package=yt_dlp --cover-html
|
|
||||||
# test.test_download:TestDownload.test_NowVideo
|
|
|
@ -2276,7 +2276,7 @@ def restore_last_token(self):
|
||||||
def _calc_headers(self, info_dict):
|
def _calc_headers(self, info_dict):
|
||||||
res = merge_headers(self.params['http_headers'], info_dict.get('http_headers') or {})
|
res = merge_headers(self.params['http_headers'], info_dict.get('http_headers') or {})
|
||||||
|
|
||||||
cookies = self._calc_cookies(info_dict)
|
cookies = self._calc_cookies(info_dict['url'])
|
||||||
if cookies:
|
if cookies:
|
||||||
res['Cookie'] = cookies
|
res['Cookie'] = cookies
|
||||||
|
|
||||||
|
@ -2287,8 +2287,8 @@ def _calc_headers(self, info_dict):
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _calc_cookies(self, info_dict):
|
def _calc_cookies(self, url):
|
||||||
pr = sanitized_Request(info_dict['url'])
|
pr = sanitized_Request(url)
|
||||||
self.cookiejar.add_cookie_header(pr)
|
self.cookiejar.add_cookie_header(pr)
|
||||||
return pr.get_header('Cookie')
|
return pr.get_header('Cookie')
|
||||||
|
|
||||||
|
@ -2596,7 +2596,7 @@ def is_wellformed(f):
|
||||||
if list_only:
|
if list_only:
|
||||||
# Without this printing, -F --print-json will not work
|
# Without this printing, -F --print-json will not work
|
||||||
self.__forced_printings(info_dict, self.prepare_filename(info_dict), incomplete=True)
|
self.__forced_printings(info_dict, self.prepare_filename(info_dict), incomplete=True)
|
||||||
return
|
return info_dict
|
||||||
|
|
||||||
format_selector = self.format_selector
|
format_selector = self.format_selector
|
||||||
if format_selector is None:
|
if format_selector is None:
|
||||||
|
@ -3052,7 +3052,7 @@ def compatible_formats(formats):
|
||||||
and info_dict.get('thumbnails')
|
and info_dict.get('thumbnails')
|
||||||
# check with type instead of pp_key, __name__, or isinstance
|
# check with type instead of pp_key, __name__, or isinstance
|
||||||
# since we dont want any custom PPs to trigger this
|
# since we dont want any custom PPs to trigger this
|
||||||
and any(type(pp) == EmbedThumbnailPP for pp in self._pps['post_process'])):
|
and any(type(pp) == EmbedThumbnailPP for pp in self._pps['post_process'])): # noqa: E721
|
||||||
info_dict['ext'] = 'mkv'
|
info_dict['ext'] = 'mkv'
|
||||||
self.report_warning(
|
self.report_warning(
|
||||||
'webm doesn\'t support embedding a thumbnail, mkv will be used')
|
'webm doesn\'t support embedding a thumbnail, mkv will be used')
|
||||||
|
@ -3227,11 +3227,9 @@ def ffmpeg_fixup(cndn, msg, cls):
|
||||||
return
|
return
|
||||||
info_dict['__write_download_archive'] = True
|
info_dict['__write_download_archive'] = True
|
||||||
|
|
||||||
|
assert info_dict is original_infodict # Make sure the info_dict was modified in-place
|
||||||
if self.params.get('force_write_download_archive'):
|
if self.params.get('force_write_download_archive'):
|
||||||
info_dict['__write_download_archive'] = True
|
info_dict['__write_download_archive'] = True
|
||||||
|
|
||||||
# Make sure the info_dict was modified in-place
|
|
||||||
assert info_dict is original_infodict
|
|
||||||
check_max_downloads()
|
check_max_downloads()
|
||||||
|
|
||||||
def __download_wrapper(self, func):
|
def __download_wrapper(self, func):
|
||||||
|
|
|
@ -865,6 +865,7 @@ def _real_main(argv=None):
|
||||||
'You must provide at least one URL.\n'
|
'You must provide at least one URL.\n'
|
||||||
'Type yt-dlp --help to see a list of all options.')
|
'Type yt-dlp --help to see a list of all options.')
|
||||||
|
|
||||||
|
parser.destroy()
|
||||||
try:
|
try:
|
||||||
if opts.load_info_filename is not None:
|
if opts.load_info_filename is not None:
|
||||||
return ydl.download_with_info_file(expand_path(opts.load_info_filename))
|
return ydl.download_with_info_file(expand_path(opts.load_info_filename))
|
||||||
|
|
|
@ -43,6 +43,7 @@ class FileDownloader:
|
||||||
verbose: Print additional info to stdout.
|
verbose: Print additional info to stdout.
|
||||||
quiet: Do not print messages to stdout.
|
quiet: Do not print messages to stdout.
|
||||||
ratelimit: Download speed limit, in bytes/sec.
|
ratelimit: Download speed limit, in bytes/sec.
|
||||||
|
continuedl: Attempt to continue downloads if possible
|
||||||
throttledratelimit: Assume the download is being throttled below this speed (bytes/sec)
|
throttledratelimit: Assume the download is being throttled below this speed (bytes/sec)
|
||||||
retries: Number of times to retry for HTTP error 5xx
|
retries: Number of times to retry for HTTP error 5xx
|
||||||
file_access_retries: Number of times to retry on file access error
|
file_access_retries: Number of times to retry on file access error
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from . import get_suitable_downloader
|
||||||
from .fragment import FragmentFD
|
from .fragment import FragmentFD
|
||||||
from ..downloader import get_suitable_downloader
|
|
||||||
from ..utils import urljoin
|
from ..utils import urljoin
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import enum
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
@ -5,8 +6,8 @@
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from .fragment import FragmentFD
|
from .fragment import FragmentFD
|
||||||
from ..compat import functools
|
from ..compat import functools # isort: split
|
||||||
from ..compat import compat_setenv, compat_str
|
from ..compat import compat_setenv
|
||||||
from ..postprocessor.ffmpeg import EXT_TO_OUT_FORMATS, FFmpegPostProcessor
|
from ..postprocessor.ffmpeg import EXT_TO_OUT_FORMATS, FFmpegPostProcessor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
Popen,
|
Popen,
|
||||||
|
@ -25,9 +26,14 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Features(enum.Enum):
|
||||||
|
TO_STDOUT = enum.auto()
|
||||||
|
MULTIPLE_FORMATS = enum.auto()
|
||||||
|
|
||||||
|
|
||||||
class ExternalFD(FragmentFD):
|
class ExternalFD(FragmentFD):
|
||||||
SUPPORTED_PROTOCOLS = ('http', 'https', 'ftp', 'ftps')
|
SUPPORTED_PROTOCOLS = ('http', 'https', 'ftp', 'ftps')
|
||||||
can_download_to_stdout = False
|
SUPPORTED_FEATURES = ()
|
||||||
|
|
||||||
def real_download(self, filename, info_dict):
|
def real_download(self, filename, info_dict):
|
||||||
self.report_destination(filename)
|
self.report_destination(filename)
|
||||||
|
@ -91,9 +97,11 @@ def available(cls, path=None):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def supports(cls, info_dict):
|
def supports(cls, info_dict):
|
||||||
return (
|
return all((
|
||||||
(cls.can_download_to_stdout or not info_dict.get('to_stdout'))
|
not info_dict.get('to_stdout') or Features.TO_STDOUT in cls.SUPPORTED_FEATURES,
|
||||||
and info_dict['protocol'] in cls.SUPPORTED_PROTOCOLS)
|
'+' not in info_dict['protocol'] or Features.MULTIPLE_FORMATS in cls.SUPPORTED_FEATURES,
|
||||||
|
all(proto in cls.SUPPORTED_PROTOCOLS for proto in info_dict['protocol'].split('+')),
|
||||||
|
))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def can_download(cls, info_dict, path=None):
|
def can_download(cls, info_dict, path=None):
|
||||||
|
@ -324,7 +332,7 @@ def _make_cmd(self, tmpfilename, info_dict):
|
||||||
|
|
||||||
class FFmpegFD(ExternalFD):
|
class FFmpegFD(ExternalFD):
|
||||||
SUPPORTED_PROTOCOLS = ('http', 'https', 'ftp', 'ftps', 'm3u8', 'm3u8_native', 'rtsp', 'rtmp', 'rtmp_ffmpeg', 'mms', 'http_dash_segments')
|
SUPPORTED_PROTOCOLS = ('http', 'https', 'ftp', 'ftps', 'm3u8', 'm3u8_native', 'rtsp', 'rtmp', 'rtmp_ffmpeg', 'mms', 'http_dash_segments')
|
||||||
can_download_to_stdout = True
|
SUPPORTED_FEATURES = (Features.TO_STDOUT, Features.MULTIPLE_FORMATS)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def available(cls, path=None):
|
def available(cls, path=None):
|
||||||
|
@ -332,10 +340,6 @@ def available(cls, path=None):
|
||||||
# Fixme: This may be wrong when --ffmpeg-location is used
|
# Fixme: This may be wrong when --ffmpeg-location is used
|
||||||
return FFmpegPostProcessor().available
|
return FFmpegPostProcessor().available
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def supports(cls, info_dict):
|
|
||||||
return all(proto in cls.SUPPORTED_PROTOCOLS for proto in info_dict['protocol'].split('+'))
|
|
||||||
|
|
||||||
def on_process_started(self, proc, stdin):
|
def on_process_started(self, proc, stdin):
|
||||||
""" Override this in subclasses """
|
""" Override this in subclasses """
|
||||||
pass
|
pass
|
||||||
|
@ -382,10 +386,10 @@ def _call_downloader(self, tmpfilename, info_dict):
|
||||||
|
|
||||||
# start_time = info_dict.get('start_time') or 0
|
# start_time = info_dict.get('start_time') or 0
|
||||||
# if start_time:
|
# if start_time:
|
||||||
# args += ['-ss', compat_str(start_time)]
|
# args += ['-ss', str(start_time)]
|
||||||
# end_time = info_dict.get('end_time')
|
# end_time = info_dict.get('end_time')
|
||||||
# if end_time:
|
# if end_time:
|
||||||
# args += ['-t', compat_str(end_time - start_time)]
|
# args += ['-t', str(end_time - start_time)]
|
||||||
|
|
||||||
http_headers = None
|
http_headers = None
|
||||||
if info_dict.get('http_headers'):
|
if info_dict.get('http_headers'):
|
||||||
|
@ -444,7 +448,7 @@ def _call_downloader(self, tmpfilename, info_dict):
|
||||||
if isinstance(conn, list):
|
if isinstance(conn, list):
|
||||||
for entry in conn:
|
for entry in conn:
|
||||||
args += ['-rtmp_conn', entry]
|
args += ['-rtmp_conn', entry]
|
||||||
elif isinstance(conn, compat_str):
|
elif isinstance(conn, str):
|
||||||
args += ['-rtmp_conn', conn]
|
args += ['-rtmp_conn', conn]
|
||||||
|
|
||||||
for i, url in enumerate(urls):
|
for i, url in enumerate(urls):
|
||||||
|
@ -462,7 +466,7 @@ def _call_downloader(self, tmpfilename, info_dict):
|
||||||
args.extend(['-map', f'{i}:{stream_number}'])
|
args.extend(['-map', f'{i}:{stream_number}'])
|
||||||
|
|
||||||
if self.params.get('test', False):
|
if self.params.get('test', False):
|
||||||
args += ['-fs', compat_str(self._TEST_FILE_SIZE)]
|
args += ['-fs', str(self._TEST_FILE_SIZE)]
|
||||||
|
|
||||||
ext = info_dict['ext']
|
ext = info_dict['ext']
|
||||||
if protocol in ('m3u8', 'm3u8_native'):
|
if protocol in ('m3u8', 'm3u8_native'):
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
import io
|
import io
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from . import get_suitable_downloader
|
||||||
from .external import FFmpegFD
|
from .external import FFmpegFD
|
||||||
from .fragment import FragmentFD
|
from .fragment import FragmentFD
|
||||||
from .. import webvtt
|
from .. import webvtt
|
||||||
from ..compat import compat_urlparse
|
from ..compat import compat_urlparse
|
||||||
from ..dependencies import Cryptodome_AES
|
from ..dependencies import Cryptodome_AES
|
||||||
from ..downloader import get_suitable_downloader
|
|
||||||
from ..utils import bug_reports_message, parse_m3u8_attributes, update_url_query
|
from ..utils import bug_reports_message, parse_m3u8_attributes, update_url_query
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -136,20 +136,18 @@ def establish_connection():
|
||||||
if has_range:
|
if has_range:
|
||||||
content_range = ctx.data.headers.get('Content-Range')
|
content_range = ctx.data.headers.get('Content-Range')
|
||||||
content_range_start, content_range_end, content_len = parse_http_range(content_range)
|
content_range_start, content_range_end, content_len = parse_http_range(content_range)
|
||||||
if content_range_start is not None and range_start == content_range_start:
|
# Content-Range is present and matches requested Range, resume is possible
|
||||||
# Content-Range is present and matches requested Range, resume is possible
|
if range_start == content_range_start and (
|
||||||
accept_content_len = (
|
|
||||||
# Non-chunked download
|
# Non-chunked download
|
||||||
not ctx.chunk_size
|
not ctx.chunk_size
|
||||||
# Chunked download and requested piece or
|
# Chunked download and requested piece or
|
||||||
# its part is promised to be served
|
# its part is promised to be served
|
||||||
or content_range_end == range_end
|
or content_range_end == range_end
|
||||||
or content_len < range_end)
|
or content_len < range_end):
|
||||||
if accept_content_len:
|
ctx.content_len = content_len
|
||||||
ctx.content_len = content_len
|
if content_len or req_end:
|
||||||
if content_len or req_end:
|
ctx.data_len = min(content_len or req_end, req_end or content_len) - (req_start or 0)
|
||||||
ctx.data_len = min(content_len or req_end, req_end or content_len) - (req_start or 0)
|
return
|
||||||
return
|
|
||||||
# Content-Range is either not present or invalid. Assuming remote webserver is
|
# Content-Range is either not present or invalid. Assuming remote webserver is
|
||||||
# trying to send the whole file, resume is not possible, so wiping the local file
|
# trying to send the whole file, resume is not possible, so wiping the local file
|
||||||
# and performing entire redownload
|
# and performing entire redownload
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
from . import get_suitable_downloader
|
||||||
from .common import FileDownloader
|
from .common import FileDownloader
|
||||||
from ..downloader import get_suitable_downloader
|
|
||||||
from ..extractor.niconico import NiconicoIE
|
|
||||||
from ..utils import sanitized_Request
|
from ..utils import sanitized_Request
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,8 +9,9 @@ class NiconicoDmcFD(FileDownloader):
|
||||||
""" Downloading niconico douga from DMC with heartbeat """
|
""" Downloading niconico douga from DMC with heartbeat """
|
||||||
|
|
||||||
def real_download(self, filename, info_dict):
|
def real_download(self, filename, info_dict):
|
||||||
self.to_screen('[%s] Downloading from DMC' % self.FD_NAME)
|
from ..extractor.niconico import NiconicoIE
|
||||||
|
|
||||||
|
self.to_screen('[%s] Downloading from DMC' % self.FD_NAME)
|
||||||
ie = NiconicoIE(self.ydl)
|
ie = NiconicoIE(self.ydl)
|
||||||
info_dict, heartbeat_info_dict = ie._get_heartbeat_info(info_dict)
|
info_dict, heartbeat_info_dict = ie._get_heartbeat_info(info_dict)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
from .fragment import FragmentFD
|
from .fragment import FragmentFD
|
||||||
from ..compat import compat_urllib_error
|
from ..compat import compat_urllib_error
|
||||||
from ..extractor.youtube import YoutubeBaseInfoExtractor as YT_BaseIE
|
|
||||||
from ..utils import RegexNotFoundError, dict_get, int_or_none, try_get
|
from ..utils import RegexNotFoundError, dict_get, int_or_none, try_get
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,7 +25,9 @@ def real_download(self, filename, info_dict):
|
||||||
'total_frags': None,
|
'total_frags': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
ie = YT_BaseIE(self.ydl)
|
from ..extractor.youtube import YoutubeBaseInfoExtractor
|
||||||
|
|
||||||
|
ie = YoutubeBaseInfoExtractor(self.ydl)
|
||||||
|
|
||||||
start_time = int(time.time() * 1000)
|
start_time = int(time.time() * 1000)
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
import time
|
import time
|
||||||
import xml.etree.ElementTree
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
from ..compat import functools, re
|
from ..compat import functools, re # isort: split
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_cookiejar_Cookie,
|
compat_cookiejar_Cookie,
|
||||||
compat_cookies_SimpleCookie,
|
compat_cookies_SimpleCookie,
|
||||||
|
@ -3602,9 +3602,7 @@ def _set_cookie(self, domain, name, value, expire_time=None, port=None,
|
||||||
|
|
||||||
def _get_cookies(self, url):
|
def _get_cookies(self, url):
|
||||||
""" Return a compat_cookies_SimpleCookie with the cookies for the url """
|
""" Return a compat_cookies_SimpleCookie with the cookies for the url """
|
||||||
req = sanitized_Request(url)
|
return compat_cookies_SimpleCookie(self._downloader._calc_cookies(url))
|
||||||
self._downloader.cookiejar.add_cookie_header(req)
|
|
||||||
return compat_cookies_SimpleCookie(req.get_header('Cookie'))
|
|
||||||
|
|
||||||
def _apply_first_set_cookie_header(self, url_handle, cookie):
|
def _apply_first_set_cookie_header(self, url_handle, cookie):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -11,7 +11,7 @@ class TestURLIE(InfoExtractor):
|
||||||
_VALID_URL = r'test(?:url)?:(?P<extractor>.+?)(?:_(?P<num>[0-9]+))?$'
|
_VALID_URL = r'test(?:url)?:(?P<extractor>.+?)(?:_(?P<num>[0-9]+))?$'
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
from ..extractor import gen_extractor_classes
|
from . import gen_extractor_classes
|
||||||
|
|
||||||
extractor_id, num = self._match_valid_url(url).group('extractor', 'num')
|
extractor_id, num = self._match_valid_url(url).group('extractor', 'num')
|
||||||
|
|
||||||
|
|
|
@ -13,19 +13,27 @@
|
||||||
|
|
||||||
|
|
||||||
@functools.cache
|
@functools.cache
|
||||||
def detect_variant():
|
def get_variant_and_executable_path():
|
||||||
|
"""@returns (variant, executable_path)"""
|
||||||
if hasattr(sys, 'frozen'):
|
if hasattr(sys, 'frozen'):
|
||||||
|
path = sys.executable
|
||||||
prefix = 'mac' if sys.platform == 'darwin' else 'win'
|
prefix = 'mac' if sys.platform == 'darwin' else 'win'
|
||||||
if getattr(sys, '_MEIPASS', None):
|
if getattr(sys, '_MEIPASS', None):
|
||||||
if sys._MEIPASS == os.path.dirname(sys.executable):
|
if sys._MEIPASS == os.path.dirname(sys.executable):
|
||||||
return f'{prefix}_dir'
|
return f'{prefix}_dir', path
|
||||||
return f'{prefix}_exe'
|
return f'{prefix}_exe', path
|
||||||
return 'py2exe'
|
return 'py2exe', path
|
||||||
elif isinstance(__loader__, zipimporter):
|
|
||||||
return 'zip'
|
path = os.path.join(os.path.dirname(__file__), '..')
|
||||||
|
if isinstance(__loader__, zipimporter):
|
||||||
|
return 'zip', os.path.join(path, '..')
|
||||||
elif os.path.basename(sys.argv[0]) == '__main__.py':
|
elif os.path.basename(sys.argv[0]) == '__main__.py':
|
||||||
return 'source'
|
return 'source', path
|
||||||
return 'unknown'
|
return 'unknown', path
|
||||||
|
|
||||||
|
|
||||||
|
def detect_variant():
|
||||||
|
return get_variant_and_executable_path()[0]
|
||||||
|
|
||||||
|
|
||||||
_NON_UPDATEABLE_REASONS = {
|
_NON_UPDATEABLE_REASONS = {
|
||||||
|
@ -53,7 +61,7 @@ def run_update(ydl):
|
||||||
JSON_URL = 'https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest'
|
JSON_URL = 'https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest'
|
||||||
|
|
||||||
def report_error(msg, expected=False):
|
def report_error(msg, expected=False):
|
||||||
ydl.report_error(msg, tb='' if expected else None)
|
ydl.report_error(msg, tb=False if expected else None)
|
||||||
|
|
||||||
def report_unable(action, expected=False):
|
def report_unable(action, expected=False):
|
||||||
report_error(f'Unable to {action}', expected)
|
report_error(f'Unable to {action}', expected)
|
||||||
|
@ -93,10 +101,9 @@ def version_tuple(version_str):
|
||||||
if err:
|
if err:
|
||||||
return report_error(err, True)
|
return report_error(err, True)
|
||||||
|
|
||||||
# sys.executable is set to the full pathname of the exe-file for py2exe
|
variant, filename = get_variant_and_executable_path()
|
||||||
# though symlinks are not followed so that we need to do this manually
|
filename = compat_realpath(filename) # Absolute path, following symlinks
|
||||||
# with help of realpath
|
|
||||||
filename = compat_realpath(sys.executable if hasattr(sys, 'frozen') else sys.argv[0])
|
|
||||||
ydl.to_screen(f'Current Build Hash {calc_sha256sum(filename)}')
|
ydl.to_screen(f'Current Build Hash {calc_sha256sum(filename)}')
|
||||||
ydl.to_screen(f'Updating to version {version_id} ...')
|
ydl.to_screen(f'Updating to version {version_id} ...')
|
||||||
|
|
||||||
|
@ -125,8 +132,6 @@ def get_sha256sum(bin_or_exe, version):
|
||||||
if not os.access(filename, os.W_OK):
|
if not os.access(filename, os.W_OK):
|
||||||
return report_permission_error(filename)
|
return report_permission_error(filename)
|
||||||
|
|
||||||
# PyInstaller
|
|
||||||
variant = detect_variant()
|
|
||||||
if variant in ('win_exe', 'py2exe'):
|
if variant in ('win_exe', 'py2exe'):
|
||||||
directory = os.path.dirname(filename)
|
directory = os.path.dirname(filename)
|
||||||
if not os.access(directory, os.W_OK):
|
if not os.access(directory, os.W_OK):
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
import xml.etree.ElementTree
|
import xml.etree.ElementTree
|
||||||
import zlib
|
import zlib
|
||||||
|
|
||||||
from .compat import asyncio, functools # Modules
|
from .compat import asyncio, functools # isort: split
|
||||||
from .compat import (
|
from .compat import (
|
||||||
compat_chr,
|
compat_chr,
|
||||||
compat_cookiejar,
|
compat_cookiejar,
|
||||||
|
@ -362,14 +362,14 @@ def xpath_attr(node, xpath, key, name=None, fatal=False, default=NO_DEFAULT):
|
||||||
return n.attrib[key]
|
return n.attrib[key]
|
||||||
|
|
||||||
|
|
||||||
def get_element_by_id(id, html):
|
def get_element_by_id(id, html, **kwargs):
|
||||||
"""Return the content of the tag with the specified ID in the passed HTML document"""
|
"""Return the content of the tag with the specified ID in the passed HTML document"""
|
||||||
return get_element_by_attribute('id', id, html)
|
return get_element_by_attribute('id', id, html, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def get_element_html_by_id(id, html):
|
def get_element_html_by_id(id, html, **kwargs):
|
||||||
"""Return the html of the tag with the specified ID in the passed HTML document"""
|
"""Return the html of the tag with the specified ID in the passed HTML document"""
|
||||||
return get_element_html_by_attribute('id', id, html)
|
return get_element_html_by_attribute('id', id, html, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def get_element_by_class(class_name, html):
|
def get_element_by_class(class_name, html):
|
||||||
|
@ -384,17 +384,17 @@ def get_element_html_by_class(class_name, html):
|
||||||
return retval[0] if retval else None
|
return retval[0] if retval else None
|
||||||
|
|
||||||
|
|
||||||
def get_element_by_attribute(attribute, value, html, escape_value=True):
|
def get_element_by_attribute(attribute, value, html, **kwargs):
|
||||||
retval = get_elements_by_attribute(attribute, value, html, escape_value)
|
retval = get_elements_by_attribute(attribute, value, html, **kwargs)
|
||||||
return retval[0] if retval else None
|
return retval[0] if retval else None
|
||||||
|
|
||||||
|
|
||||||
def get_element_html_by_attribute(attribute, value, html, escape_value=True):
|
def get_element_html_by_attribute(attribute, value, html, **kargs):
|
||||||
retval = get_elements_html_by_attribute(attribute, value, html, escape_value)
|
retval = get_elements_html_by_attribute(attribute, value, html, **kargs)
|
||||||
return retval[0] if retval else None
|
return retval[0] if retval else None
|
||||||
|
|
||||||
|
|
||||||
def get_elements_by_class(class_name, html):
|
def get_elements_by_class(class_name, html, **kargs):
|
||||||
"""Return the content of all tags with the specified class in the passed HTML document as a list"""
|
"""Return the content of all tags with the specified class in the passed HTML document as a list"""
|
||||||
return get_elements_by_attribute(
|
return get_elements_by_attribute(
|
||||||
'class', r'[^\'"]*\b%s\b[^\'"]*' % re.escape(class_name),
|
'class', r'[^\'"]*\b%s\b[^\'"]*' % re.escape(class_name),
|
||||||
|
@ -1899,15 +1899,14 @@ def write_string(s, out=None, encoding=None):
|
||||||
if compat_os_name == 'nt' and supports_terminal_sequences(out):
|
if compat_os_name == 'nt' and supports_terminal_sequences(out):
|
||||||
s = re.sub(r'([\r\n]+)', r' \1', s)
|
s = re.sub(r'([\r\n]+)', r' \1', s)
|
||||||
|
|
||||||
|
enc = None
|
||||||
if 'b' in getattr(out, 'mode', ''):
|
if 'b' in getattr(out, 'mode', ''):
|
||||||
byt = s.encode(encoding or preferredencoding(), 'ignore')
|
enc = encoding or preferredencoding()
|
||||||
out.write(byt)
|
|
||||||
elif hasattr(out, 'buffer'):
|
elif hasattr(out, 'buffer'):
|
||||||
|
out = out.buffer
|
||||||
enc = encoding or getattr(out, 'encoding', None) or preferredencoding()
|
enc = encoding or getattr(out, 'encoding', None) or preferredencoding()
|
||||||
byt = s.encode(enc, 'ignore')
|
|
||||||
out.buffer.write(byt)
|
out.write(s.encode(enc, 'ignore') if enc else s)
|
||||||
else:
|
|
||||||
out.write(s)
|
|
||||||
out.flush()
|
out.flush()
|
||||||
|
|
||||||
|
|
||||||
|
@ -2970,7 +2969,7 @@ def encode_compat_str(string, encoding=preferredencoding(), errors='strict'):
|
||||||
|
|
||||||
def parse_age_limit(s):
|
def parse_age_limit(s):
|
||||||
# isinstance(False, int) is True. So type() must be used instead
|
# isinstance(False, int) is True. So type() must be used instead
|
||||||
if type(s) is int:
|
if type(s) is int: # noqa: E721
|
||||||
return s if 0 <= s <= 21 else None
|
return s if 0 <= s <= 21 else None
|
||||||
elif not isinstance(s, str):
|
elif not isinstance(s, str):
|
||||||
return None
|
return None
|
||||||
|
@ -3656,26 +3655,21 @@ def parse_node(node):
|
||||||
return ''.join(out)
|
return ''.join(out)
|
||||||
|
|
||||||
|
|
||||||
def cli_option(params, command_option, param):
|
def cli_option(params, command_option, param, separator=None):
|
||||||
param = params.get(param)
|
param = params.get(param)
|
||||||
if param:
|
return ([] if param is None
|
||||||
param = compat_str(param)
|
else [command_option, str(param)] if separator is None
|
||||||
return [command_option, param] if param is not None else []
|
else [f'{command_option}{separator}{param}'])
|
||||||
|
|
||||||
|
|
||||||
def cli_bool_option(params, command_option, param, true_value='true', false_value='false', separator=None):
|
def cli_bool_option(params, command_option, param, true_value='true', false_value='false', separator=None):
|
||||||
param = params.get(param)
|
param = params.get(param)
|
||||||
if param is None:
|
assert param in (True, False, None)
|
||||||
return []
|
return cli_option({True: true_value, False: false_value}, command_option, param, separator)
|
||||||
assert isinstance(param, bool)
|
|
||||||
if separator:
|
|
||||||
return [command_option + separator + (true_value if param else false_value)]
|
|
||||||
return [command_option, true_value if param else false_value]
|
|
||||||
|
|
||||||
|
|
||||||
def cli_valueless_option(params, command_option, param, expected_value=True):
|
def cli_valueless_option(params, command_option, param, expected_value=True):
|
||||||
param = params.get(param)
|
return [command_option] if params.get(param) == expected_value else []
|
||||||
return [command_option] if param == expected_value else []
|
|
||||||
|
|
||||||
|
|
||||||
def cli_configuration_args(argdict, keys, default=[], use_compat=True):
|
def cli_configuration_args(argdict, keys, default=[], use_compat=True):
|
||||||
|
@ -4910,14 +4904,9 @@ def make_dir(path, to_screen=None):
|
||||||
|
|
||||||
|
|
||||||
def get_executable_path():
|
def get_executable_path():
|
||||||
from zipimport import zipimporter
|
from .update import get_variant_and_executable_path
|
||||||
if hasattr(sys, 'frozen'): # Running from PyInstaller
|
|
||||||
path = os.path.dirname(sys.executable)
|
return os.path.abspath(get_variant_and_executable_path()[1])
|
||||||
elif isinstance(__loader__, zipimporter): # Running from ZIP
|
|
||||||
path = os.path.join(os.path.dirname(__file__), '../..')
|
|
||||||
else:
|
|
||||||
path = os.path.join(os.path.dirname(__file__), '..')
|
|
||||||
return os.path.abspath(path)
|
|
||||||
|
|
||||||
|
|
||||||
def load_plugins(name, suffix, namespace):
|
def load_plugins(name, suffix, namespace):
|
||||||
|
@ -5344,12 +5333,14 @@ def merge_headers(*dicts):
|
||||||
|
|
||||||
|
|
||||||
class classproperty:
|
class classproperty:
|
||||||
def __init__(self, f):
|
"""classmethod(property(func)) that works in py < 3.9"""
|
||||||
functools.update_wrapper(self, f)
|
|
||||||
self.f = f
|
def __init__(self, func):
|
||||||
|
functools.update_wrapper(self, func)
|
||||||
|
self.func = func
|
||||||
|
|
||||||
def __get__(self, _, cls):
|
def __get__(self, _, cls):
|
||||||
return self.f(cls)
|
return self.func(cls)
|
||||||
|
|
||||||
|
|
||||||
class Namespace:
|
class Namespace:
|
||||||
|
|
Loading…
Reference in a new issue