mirror of
https://github.com/ytdl-org/youtube-dl.git
synced 2024-12-20 23:52:07 +00:00
Compare commits
No commits in common. "9f4d83ff4255d8840c0fa9b367722c129ebecdb2" and "6fece0a96b3cd8677f5c1185a57c6e21403fcb44" have entirely different histories.
9f4d83ff42
...
6fece0a96b
24
.github/workflows/ci.yml
vendored
24
.github/workflows/ci.yml
vendored
@ -7,10 +7,9 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-20.04]
|
os: [ubuntu-18.04]
|
||||||
# TODO: python 2.6
|
# TODO: python 2.6
|
||||||
# TODO: restore support for 3.3, 3.4
|
python-version: [2.7, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, pypy-2.7, pypy-3.6, pypy-3.7]
|
||||||
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, pypy-2.7, pypy-3.6, pypy-3.7]
|
|
||||||
python-impl: [cpython]
|
python-impl: [cpython]
|
||||||
ytdl-test-set: [core, download]
|
ytdl-test-set: [core, download]
|
||||||
run-tests-ext: [sh]
|
run-tests-ext: [sh]
|
||||||
@ -27,27 +26,26 @@ jobs:
|
|||||||
ytdl-test-set: download
|
ytdl-test-set: download
|
||||||
run-tests-ext: bat
|
run-tests-ext: bat
|
||||||
# jython
|
# jython
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-18.04
|
||||||
python-impl: jython
|
python-impl: jython
|
||||||
ytdl-test-set: core
|
ytdl-test-set: core
|
||||||
run-tests-ext: sh
|
run-tests-ext: sh
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-18.04
|
||||||
python-impl: jython
|
python-impl: jython
|
||||||
ytdl-test-set: download
|
ytdl-test-set: download
|
||||||
run-tests-ext: sh
|
run-tests-ext: sh
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v2
|
||||||
- name: Set up supported Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v2
|
||||||
if: ${{ matrix.python-impl == 'cpython' && ! contains(fromJSON('["3.3", "3.4"]'), matrix.python-version) }}
|
if: ${{ matrix.python-impl == 'cpython' }}
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Set up Java 8
|
- name: Set up Java 8
|
||||||
if: ${{ matrix.python-impl == 'jython' }}
|
if: ${{ matrix.python-impl == 'jython' }}
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 8
|
java-version: 8
|
||||||
distribution: 'zulu'
|
|
||||||
- name: Install Jython
|
- name: Install Jython
|
||||||
if: ${{ matrix.python-impl == 'jython' }}
|
if: ${{ matrix.python-impl == 'jython' }}
|
||||||
run: |
|
run: |
|
||||||
@ -72,9 +70,9 @@ jobs:
|
|||||||
name: Linter
|
name: Linter
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.9
|
||||||
- name: Install flake8
|
- name: Install flake8
|
||||||
|
@ -49,34 +49,15 @@ def cli_to_api(*opts):
|
|||||||
|
|
||||||
# from https://github.com/yt-dlp/yt-dlp/issues/5859#issuecomment-1363938900
|
# from https://github.com/yt-dlp/yt-dlp/issues/5859#issuecomment-1363938900
|
||||||
default = parsed_options([])
|
default = parsed_options([])
|
||||||
|
diff = dict((k, v) for k, v in parsed_options(opts).items() if default[k] != v)
|
||||||
def neq_opt(a, b):
|
|
||||||
if a == b:
|
|
||||||
return False
|
|
||||||
if a is None and repr(type(object)).endswith(".utils.DateRange'>"):
|
|
||||||
return '0001-01-01 - 9999-12-31' != '{0}'.format(b)
|
|
||||||
return a != b
|
|
||||||
|
|
||||||
diff = dict((k, v) for k, v in parsed_options(opts).items() if neq_opt(default[k], v))
|
|
||||||
if 'postprocessors' in diff:
|
if 'postprocessors' in diff:
|
||||||
diff['postprocessors'] = [pp for pp in diff['postprocessors'] if pp not in default['postprocessors']]
|
diff['postprocessors'] = [pp for pp in diff['postprocessors'] if pp not in default['postprocessors']]
|
||||||
return diff
|
return diff
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
from pprint import PrettyPrinter
|
from pprint import pprint
|
||||||
|
pprint(cli_to_api(*sys.argv))
|
||||||
pprint = PrettyPrinter()
|
|
||||||
super_format = pprint.format
|
|
||||||
|
|
||||||
def format(object, context, maxlevels, level):
|
|
||||||
if repr(type(object)).endswith(".utils.DateRange'>"):
|
|
||||||
return '{0}: {1}>'.format(repr(object)[:-2], object), True, False
|
|
||||||
return super_format(object, context, maxlevels, level)
|
|
||||||
|
|
||||||
pprint.format = format
|
|
||||||
|
|
||||||
pprint.pprint(cli_to_api(*sys.argv))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -88,7 +88,7 @@ class TestHttpFD(unittest.TestCase):
|
|||||||
self.assertTrue(downloader.real_download(filename, {
|
self.assertTrue(downloader.real_download(filename, {
|
||||||
'url': 'http://127.0.0.1:%d/%s' % (self.port, ep),
|
'url': 'http://127.0.0.1:%d/%s' % (self.port, ep),
|
||||||
}))
|
}))
|
||||||
self.assertEqual(os.path.getsize(encodeFilename(filename)), TEST_SIZE, ep)
|
self.assertEqual(os.path.getsize(encodeFilename(filename)), TEST_SIZE)
|
||||||
try_rm(encodeFilename(filename))
|
try_rm(encodeFilename(filename))
|
||||||
|
|
||||||
def download_all(self, params):
|
def download_all(self, params):
|
||||||
|
@ -1563,7 +1563,6 @@ Line 1
|
|||||||
self.assertEqual(variadic(None), (None, ))
|
self.assertEqual(variadic(None), (None, ))
|
||||||
self.assertEqual(variadic('spam'), ('spam', ))
|
self.assertEqual(variadic('spam'), ('spam', ))
|
||||||
self.assertEqual(variadic('spam', allowed_types=dict), 'spam')
|
self.assertEqual(variadic('spam', allowed_types=dict), 'spam')
|
||||||
self.assertEqual(variadic('spam', allowed_types=[dict]), 'spam')
|
|
||||||
|
|
||||||
def test_traverse_obj(self):
|
def test_traverse_obj(self):
|
||||||
_TEST_DATA = {
|
_TEST_DATA = {
|
||||||
|
@ -3127,16 +3127,6 @@ else:
|
|||||||
return ctypes.WINFUNCTYPE(*args, **kwargs)
|
return ctypes.WINFUNCTYPE(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info < (3, 0):
|
|
||||||
# open(file, mode='r', buffering=- 1, encoding=None, errors=None, newline=None, closefd=True) not: opener=None
|
|
||||||
def compat_open(file_, *args, **kwargs):
|
|
||||||
if len(args) > 6 or 'opener' in kwargs:
|
|
||||||
raise ValueError('open: unsupported argument "opener"')
|
|
||||||
return io.open(file_, *args, **kwargs)
|
|
||||||
else:
|
|
||||||
compat_open = open
|
|
||||||
|
|
||||||
|
|
||||||
legacy = [
|
legacy = [
|
||||||
'compat_HTMLParseError',
|
'compat_HTMLParseError',
|
||||||
'compat_HTMLParser',
|
'compat_HTMLParser',
|
||||||
@ -3195,7 +3185,6 @@ __all__ = [
|
|||||||
'compat_kwargs',
|
'compat_kwargs',
|
||||||
'compat_map',
|
'compat_map',
|
||||||
'compat_numeric_types',
|
'compat_numeric_types',
|
||||||
'compat_open',
|
|
||||||
'compat_ord',
|
'compat_ord',
|
||||||
'compat_os_name',
|
'compat_os_name',
|
||||||
'compat_os_path_expanduser',
|
'compat_os_path_expanduser',
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import itertools
|
|
||||||
|
|
||||||
from .fragment import FragmentFD
|
from .fragment import FragmentFD
|
||||||
from ..compat import compat_urllib_error
|
from ..compat import compat_urllib_error
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@ -32,13 +30,15 @@ class DashSegmentsFD(FragmentFD):
|
|||||||
fragment_retries = self.params.get('fragment_retries', 0)
|
fragment_retries = self.params.get('fragment_retries', 0)
|
||||||
skip_unavailable_fragments = self.params.get('skip_unavailable_fragments', True)
|
skip_unavailable_fragments = self.params.get('skip_unavailable_fragments', True)
|
||||||
|
|
||||||
for frag_index, fragment in enumerate(fragments, 1):
|
frag_index = 0
|
||||||
|
for i, fragment in enumerate(fragments):
|
||||||
|
frag_index += 1
|
||||||
if frag_index <= ctx['fragment_index']:
|
if frag_index <= ctx['fragment_index']:
|
||||||
continue
|
continue
|
||||||
# In DASH, the first segment contains necessary headers to
|
# In DASH, the first segment contains necessary headers to
|
||||||
# generate a valid MP4 file, so always abort for the first segment
|
# generate a valid MP4 file, so always abort for the first segment
|
||||||
fatal = frag_index == 1 or not skip_unavailable_fragments
|
fatal = i == 0 or not skip_unavailable_fragments
|
||||||
for count in itertools.count():
|
for count in range(fragment_retries + 1):
|
||||||
try:
|
try:
|
||||||
fragment_url = fragment.get('url')
|
fragment_url = fragment.get('url')
|
||||||
if not fragment_url:
|
if not fragment_url:
|
||||||
@ -48,6 +48,7 @@ class DashSegmentsFD(FragmentFD):
|
|||||||
if not success:
|
if not success:
|
||||||
return False
|
return False
|
||||||
self._append_fragment(ctx, frag_content)
|
self._append_fragment(ctx, frag_content)
|
||||||
|
break
|
||||||
except compat_urllib_error.HTTPError as err:
|
except compat_urllib_error.HTTPError as err:
|
||||||
# YouTube may often return 404 HTTP error for a fragment causing the
|
# YouTube may often return 404 HTTP error for a fragment causing the
|
||||||
# whole download to fail. However if the same fragment is immediately
|
# whole download to fail. However if the same fragment is immediately
|
||||||
@ -57,14 +58,13 @@ class DashSegmentsFD(FragmentFD):
|
|||||||
# HTTP error.
|
# HTTP error.
|
||||||
if count < fragment_retries:
|
if count < fragment_retries:
|
||||||
self.report_retry_fragment(err, frag_index, count + 1, fragment_retries)
|
self.report_retry_fragment(err, frag_index, count + 1, fragment_retries)
|
||||||
continue
|
|
||||||
except DownloadError:
|
except DownloadError:
|
||||||
# Don't retry fragment if error occurred during HTTP downloading
|
# Don't retry fragment if error occurred during HTTP downloading
|
||||||
# itself since it has its own retry settings
|
# itself since it has own retry settings
|
||||||
if fatal:
|
if not fatal:
|
||||||
raise
|
|
||||||
self.report_skip_fragment(frag_index)
|
self.report_skip_fragment(frag_index)
|
||||||
break
|
break
|
||||||
|
raise
|
||||||
|
|
||||||
if count >= fragment_retries:
|
if count >= fragment_retries:
|
||||||
if not fatal:
|
if not fatal:
|
||||||
|
@ -141,7 +141,6 @@ class HttpFD(FileDownloader):
|
|||||||
# 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
|
||||||
if range_start > 0:
|
|
||||||
self.report_unable_to_resume()
|
self.report_unable_to_resume()
|
||||||
ctx.resume_len = 0
|
ctx.resume_len = 0
|
||||||
ctx.open_mode = 'wb'
|
ctx.open_mode = 'wb'
|
||||||
|
@ -31,7 +31,6 @@ from ..utils import (
|
|||||||
get_element_by_attribute,
|
get_element_by_attribute,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
js_to_json,
|
js_to_json,
|
||||||
LazyList,
|
|
||||||
merge_dicts,
|
merge_dicts,
|
||||||
mimetype2ext,
|
mimetype2ext,
|
||||||
parse_codecs,
|
parse_codecs,
|
||||||
@ -1987,19 +1986,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
itags = []
|
itags = []
|
||||||
itag_qualities = {}
|
itag_qualities = {}
|
||||||
q = qualities(['tiny', 'small', 'medium', 'large', 'hd720', 'hd1080', 'hd1440', 'hd2160', 'hd2880', 'highres'])
|
q = qualities(['tiny', 'small', 'medium', 'large', 'hd720', 'hd1080', 'hd1440', 'hd2160', 'hd2880', 'highres'])
|
||||||
CHUNK_SIZE = 10 << 20
|
|
||||||
|
|
||||||
streaming_data = player_response.get('streamingData') or {}
|
streaming_data = player_response.get('streamingData') or {}
|
||||||
streaming_formats = streaming_data.get('formats') or []
|
streaming_formats = streaming_data.get('formats') or []
|
||||||
streaming_formats.extend(streaming_data.get('adaptiveFormats') or [])
|
streaming_formats.extend(streaming_data.get('adaptiveFormats') or [])
|
||||||
|
|
||||||
def build_fragments(f):
|
|
||||||
return LazyList({
|
|
||||||
'url': update_url_query(f['url'], {
|
|
||||||
'range': '{0}-{1}'.format(range_start, min(range_start + CHUNK_SIZE - 1, f['filesize']))
|
|
||||||
})
|
|
||||||
} for range_start in range(0, f['filesize'], CHUNK_SIZE))
|
|
||||||
|
|
||||||
for fmt in streaming_formats:
|
for fmt in streaming_formats:
|
||||||
if fmt.get('targetDurationSec') or fmt.get('drmFamilies'):
|
if fmt.get('targetDurationSec') or fmt.get('drmFamilies'):
|
||||||
continue
|
continue
|
||||||
@ -2052,18 +2041,28 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
if mobj:
|
if mobj:
|
||||||
dct['ext'] = mimetype2ext(mobj.group(1))
|
dct['ext'] = mimetype2ext(mobj.group(1))
|
||||||
dct.update(parse_codecs(mobj.group(2)))
|
dct.update(parse_codecs(mobj.group(2)))
|
||||||
single_stream = 'none' in (dct.get(c) for c in ('acodec', 'vcodec'))
|
no_audio = dct.get('acodec') == 'none'
|
||||||
if single_stream and dct.get('ext'):
|
no_video = dct.get('vcodec') == 'none'
|
||||||
dct['container'] = dct['ext'] + '_dash'
|
if no_audio:
|
||||||
if single_stream or itag == '17':
|
dct['vbr'] = tbr
|
||||||
|
if no_video:
|
||||||
|
dct['abr'] = tbr
|
||||||
|
if no_audio or no_video:
|
||||||
|
CHUNK_SIZE = 10 << 20
|
||||||
# avoid Youtube throttling
|
# avoid Youtube throttling
|
||||||
dct.update({
|
dct.update({
|
||||||
'protocol': 'http_dash_segments',
|
'protocol': 'http_dash_segments',
|
||||||
'fragments': build_fragments(dct),
|
'fragments': [{
|
||||||
|
'url': update_url_query(dct['url'], {
|
||||||
|
'range': '{0}-{1}'.format(range_start, min(range_start + CHUNK_SIZE - 1, dct['filesize']))
|
||||||
|
})
|
||||||
|
} for range_start in range(0, dct['filesize'], CHUNK_SIZE)]
|
||||||
} if dct['filesize'] else {
|
} if dct['filesize'] else {
|
||||||
'downloader_options': {'http_chunk_size': CHUNK_SIZE} # No longer useful?
|
'downloader_options': {'http_chunk_size': CHUNK_SIZE} # No longer useful?
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if dct.get('ext'):
|
||||||
|
dct['container'] = dct['ext'] + '_dash'
|
||||||
formats.append(dct)
|
formats.append(dct)
|
||||||
|
|
||||||
hls_manifest_url = streaming_data.get('hlsManifestUrl')
|
hls_manifest_url = streaming_data.get('hlsManifestUrl')
|
||||||
|
@ -11,7 +11,6 @@ from .compat import (
|
|||||||
compat_get_terminal_size,
|
compat_get_terminal_size,
|
||||||
compat_getenv,
|
compat_getenv,
|
||||||
compat_kwargs,
|
compat_kwargs,
|
||||||
compat_open as open,
|
|
||||||
compat_shlex_split,
|
compat_shlex_split,
|
||||||
)
|
)
|
||||||
from .utils import (
|
from .utils import (
|
||||||
@ -42,11 +41,14 @@ def _hide_login_info(opts):
|
|||||||
def parseOpts(overrideArguments=None):
|
def parseOpts(overrideArguments=None):
|
||||||
def _readOptions(filename_bytes, default=[]):
|
def _readOptions(filename_bytes, default=[]):
|
||||||
try:
|
try:
|
||||||
optionf = open(filename_bytes, encoding=preferredencoding())
|
optionf = open(filename_bytes)
|
||||||
except IOError:
|
except IOError:
|
||||||
return default # silently skip if file is not present
|
return default # silently skip if file is not present
|
||||||
try:
|
try:
|
||||||
|
# FIXME: https://github.com/ytdl-org/youtube-dl/commit/dfe5fa49aed02cf36ba9f743b11b0903554b5e56
|
||||||
contents = optionf.read()
|
contents = optionf.read()
|
||||||
|
if sys.version_info < (3,):
|
||||||
|
contents = contents.decode(preferredencoding())
|
||||||
res = compat_shlex_split(contents, comments=True)
|
res = compat_shlex_split(contents, comments=True)
|
||||||
finally:
|
finally:
|
||||||
optionf.close()
|
optionf.close()
|
||||||
@ -731,13 +733,9 @@ def parseOpts(overrideArguments=None):
|
|||||||
'--no-part',
|
'--no-part',
|
||||||
action='store_true', dest='nopart', default=False,
|
action='store_true', dest='nopart', default=False,
|
||||||
help='Do not use .part files - write directly into output file')
|
help='Do not use .part files - write directly into output file')
|
||||||
filesystem.add_option(
|
|
||||||
'--mtime',
|
|
||||||
action='store_true', dest='updatetime', default=True,
|
|
||||||
help='Use the Last-modified header to set the file modification time (default)')
|
|
||||||
filesystem.add_option(
|
filesystem.add_option(
|
||||||
'--no-mtime',
|
'--no-mtime',
|
||||||
action='store_false', dest='updatetime',
|
action='store_false', dest='updatetime', default=True,
|
||||||
help='Do not use the Last-modified header to set the file modification time')
|
help='Do not use the Last-modified header to set the file modification time')
|
||||||
filesystem.add_option(
|
filesystem.add_option(
|
||||||
'--write-description',
|
'--write-description',
|
||||||
|
@ -3190,10 +3190,6 @@ class DateRange(object):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s - %s' % (self.start.isoformat(), self.end.isoformat())
|
return '%s - %s' % (self.start.isoformat(), self.end.isoformat())
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return (isinstance(other, DateRange)
|
|
||||||
and self.start == other.start and self.end == other.end)
|
|
||||||
|
|
||||||
|
|
||||||
def platform_name():
|
def platform_name():
|
||||||
""" Returns the platform name as a compat_str """
|
""" Returns the platform name as a compat_str """
|
||||||
@ -4217,8 +4213,6 @@ def multipart_encode(data, boundary=None):
|
|||||||
|
|
||||||
|
|
||||||
def variadic(x, allowed_types=(compat_str, bytes, dict)):
|
def variadic(x, allowed_types=(compat_str, bytes, dict)):
|
||||||
if not isinstance(allowed_types, tuple) and isinstance(allowed_types, compat_collections_abc.Iterable):
|
|
||||||
allowed_types = tuple(allowed_types)
|
|
||||||
return x if isinstance(x, compat_collections_abc.Iterable) and not isinstance(x, allowed_types) else (x,)
|
return x if isinstance(x, compat_collections_abc.Iterable) and not isinstance(x, allowed_types) else (x,)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user