Skip to content

Commit

Permalink
[script.module.youtube.dl@matrix] 23.04.01+matrix.1 (#2451)
Browse files Browse the repository at this point in the history
  • Loading branch information
joaopa00 authored Nov 21, 2023
1 parent 40bfcf5 commit 8ea1c13
Show file tree
Hide file tree
Showing 151 changed files with 11,691 additions and 3,068 deletions.
4 changes: 2 additions & 2 deletions script.module.youtube.dl/addon.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="script.module.youtube.dl" name="youtube-dl Control" version="21.303.0+matrix.1" provider-name="ytdl-org,ruuk,sy6sy2,wwark">
<addon id="script.module.youtube.dl" name="youtube-dl Control" version="23.04.01+matrix.1" provider-name="ytdl-org,ruuk,sy6sy2,wwark">
<requires>
<import addon="xbmc.python" version="3.0.0"/>
<import addon="script.module.addon.signals" version="0.0.5+matrix.1"/>
Expand All @@ -19,7 +19,7 @@
<description lang="es_ES">Módulo que proporciona acceso a YouTube-dl para la extracción de flujos de video de cientos de sitios. La versión está basada en la versión de fecha youtube-dl: YY.MDD.V donde V es la subversión específica del complemento. También permite descargas con la opción de descarga en segundo plano con una cola y un administrador de colas.</description>
<license>LGPL-2.1-only</license>
<forum>https://forum.kodi.tv/showthread.php?tid=200877</forum>
<source>https://github.com/Catch-up-TV-and-More/script.module.youtube.dl</source>
<source>https://github.com/xbmc/repo-scripts/tree/matrix/script.module.youtube.dl</source>
<assets>
<icon>icon.png</icon>
</assets>
Expand Down
99 changes: 74 additions & 25 deletions script.module.youtube.dl/lib/youtube_dl/YoutubeDL.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
compat_str,
compat_tokenize_tokenize,
compat_urllib_error,
compat_urllib_parse,
compat_urllib_request,
compat_urllib_request_DataHandler,
)
Expand All @@ -60,6 +61,7 @@
format_bytes,
formatSeconds,
GeoRestrictedError,
HEADRequest,
int_or_none,
ISO3166Utils,
locked_file,
Expand All @@ -73,6 +75,8 @@
PostProcessingError,
preferredencoding,
prepend_extension,
process_communicate_or_kill,
PUTRequest,
register_socks_protocols,
render_table,
replace_extension,
Expand Down Expand Up @@ -720,7 +724,7 @@ def prepare_filename(self, info_dict):
filename = encodeFilename(filename, True).decode(preferredencoding())
return sanitize_path(filename)
except ValueError as err:
self.report_error('Error in output template: ' + str(err) + ' (encoding: ' + repr(preferredencoding()) + ')')
self.report_error('Error in output template: ' + error_to_compat_str(err) + ' (encoding: ' + repr(preferredencoding()) + ')')
return None

def _match_entry(self, info_dict, incomplete):
Expand Down Expand Up @@ -773,11 +777,20 @@ def add_extra_info(info_dict, extra_info):

def extract_info(self, url, download=True, ie_key=None, extra_info={},
process=True, force_generic_extractor=False):
'''
Returns a list with a dictionary for each video we find.
If 'download', also downloads the videos.
extra_info is a dict containing the extra values to add to each result
'''
"""
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
"""

if not ie_key and force_generic_extractor:
ie_key = 'Generic'
Expand Down Expand Up @@ -1511,14 +1524,18 @@ def sanitize_numeric_fields(info):
if 'display_id' not in info_dict and 'id' in info_dict:
info_dict['display_id'] = info_dict['id']

if info_dict.get('upload_date') is None and info_dict.get('timestamp') 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['timestamp'])
info_dict['upload_date'] = upload_date.strftime('%Y%m%d')
except (ValueError, OverflowError, OSError):
pass
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] = compat_str(upload_date.strftime('%Y%m%d'))
except (ValueError, OverflowError, OSError):
pass

# 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.
Expand Down Expand Up @@ -1556,9 +1573,6 @@ def sanitize_numeric_fields(info):
else:
formats = info_dict['formats']

if not formats:
raise ExtractorError('No video formats found!')

def is_wellformed(f):
url = f.get('url')
if not url:
Expand All @@ -1571,7 +1585,10 @@ def is_wellformed(f):
return True

# Filter out malformed formats for better extraction robustness
formats = list(filter(is_wellformed, formats))
formats = list(filter(is_wellformed, formats or []))

if not formats:
raise ExtractorError('No video formats found!')

formats_dict = {}

Expand Down Expand Up @@ -1765,10 +1782,9 @@ def process_info(self, info_dict):

assert info_dict.get('_type', 'video') == 'video'

max_downloads = self.params.get('max_downloads')
if max_downloads is not None:
if self._num_downloads >= int(max_downloads):
raise MaxDownloadsReached()
max_downloads = int_or_none(self.params.get('max_downloads')) or float('inf')
if self._num_downloads >= max_downloads:
raise MaxDownloadsReached()

# TODO: backward compatibility, to be removed
info_dict['fulltitle'] = info_dict['title']
Expand Down Expand Up @@ -1893,8 +1909,17 @@ def ensure_dir_exists(path):

if not self.params.get('skip_download', False):
try:
def checked_get_suitable_downloader(info_dict, params):
ed_args = params.get('external_downloader_args')
dler = get_suitable_downloader(info_dict, params)
if ed_args and not params.get('external_downloader_args'):
# external_downloader_args was cleared because external_downloader was rejected
self.report_warning('Requested external downloader cannot be used: '
'ignoring --external-downloader-args.')
return dler

def dl(name, info):
fd = get_suitable_downloader(info, self.params)(self, self.params)
fd = checked_get_suitable_downloader(info, self.params)(self, self.params)
for ph in self._progress_hooks:
fd.add_progress_hook(ph)
if self.params.get('verbose'):
Expand Down Expand Up @@ -2036,9 +2061,12 @@ def compatible_formats(formats):
try:
self.post_process(filename, info_dict)
except (PostProcessingError) as err:
self.report_error('postprocessing: %s' % str(err))
self.report_error('postprocessing: %s' % error_to_compat_str(err))
return
self.record_download_archive(info_dict)
# avoid possible nugatory search for further items (PR #26638)
if self._num_downloads >= max_downloads:
raise MaxDownloadsReached()

def download(self, url_list):
"""Download a given list of URLs."""
Expand Down Expand Up @@ -2272,6 +2300,27 @@ def urlopen(self, req):
""" Start an HTTP download """
if isinstance(req, compat_basestring):
req = sanitized_Request(req)
# an embedded /../ sequence is not automatically handled by urllib2
# see https://github.com/yt-dlp/yt-dlp/issues/3355
url = req.get_full_url()
parts = url.partition('/../')
if parts[1]:
url = compat_urllib_parse.urljoin(parts[0] + parts[1][:1], parts[1][1:] + parts[2])
if url:
# worse, URL path may have initial /../ against RFCs: work-around
# by stripping such prefixes, like eg Firefox
parts = compat_urllib_parse.urlsplit(url)
path = parts.path
while path.startswith('/../'):
path = path[3:]
url = parts._replace(path=path).geturl()
# get a new Request with the munged URL
if url != req.get_full_url():
req_type = {'HEAD': HEADRequest, 'PUT': PUTRequest}.get(
req.get_method(), compat_urllib_request.Request)
req = req_type(
url, data=req.data, headers=dict(req.header_items()),
origin_req_host=req.origin_req_host, unverifiable=req.unverifiable)
return self._opener.open(req, timeout=self._socket_timeout)

def print_debug_header(self):
Expand Down Expand Up @@ -2301,7 +2350,7 @@ def print_debug_header(self):
['git', 'rev-parse', '--short', 'HEAD'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
cwd=os.path.dirname(os.path.abspath(__file__)))
out, err = sp.communicate()
out, err = process_communicate_or_kill(sp)
out = out.decode().strip()
if re.match('[0-9a-f]+', out):
self._write_string('[debug] Git HEAD: ' + out + '\n')
Expand Down
39 changes: 36 additions & 3 deletions script.module.youtube.dl/lib/youtube_dl/aes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@
BLOCK_SIZE_BYTES = 16


def pkcs7_padding(data):
"""
PKCS#7 padding
@param {int[]} data cleartext
@returns {int[]} padding data
"""

remaining_length = BLOCK_SIZE_BYTES - len(data) % BLOCK_SIZE_BYTES
return data + [remaining_length] * remaining_length


def aes_ctr_decrypt(data, key, counter):
"""
Decrypt with aes in counter mode
Expand Down Expand Up @@ -76,8 +88,7 @@ def aes_cbc_encrypt(data, key, iv):
previous_cipher_block = iv
for i in range(block_count):
block = data[i * BLOCK_SIZE_BYTES: (i + 1) * BLOCK_SIZE_BYTES]
remaining_length = BLOCK_SIZE_BYTES - len(block)
block += [remaining_length] * remaining_length
block = pkcs7_padding(block)
mixed_block = xor(block, previous_cipher_block)

encrypted_block = aes_encrypt(mixed_block, expanded_key)
Expand All @@ -88,6 +99,28 @@ def aes_cbc_encrypt(data, key, iv):
return encrypted_data


def aes_ecb_encrypt(data, key):
"""
Encrypt with aes in ECB mode. Using PKCS#7 padding
@param {int[]} data cleartext
@param {int[]} key 16/24/32-Byte cipher key
@returns {int[]} encrypted data
"""
expanded_key = key_expansion(key)
block_count = int(ceil(float(len(data)) / BLOCK_SIZE_BYTES))

encrypted_data = []
for i in range(block_count):
block = data[i * BLOCK_SIZE_BYTES: (i + 1) * BLOCK_SIZE_BYTES]
block = pkcs7_padding(block)

encrypted_block = aes_encrypt(block, expanded_key)
encrypted_data += encrypted_block

return encrypted_data


def key_expansion(data):
"""
Generate key schedule
Expand Down Expand Up @@ -303,7 +336,7 @@ def xor(data1, data2):


def rijndael_mul(a, b):
if(a == 0 or b == 0):
if (a == 0 or b == 0):
return 0
return RIJNDAEL_EXP_TABLE[(RIJNDAEL_LOG_TABLE[a] + RIJNDAEL_LOG_TABLE[b]) % 0xFF]

Expand Down
28 changes: 23 additions & 5 deletions script.module.youtube.dl/lib/youtube_dl/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,29 @@

from .compat import compat_getenv
from .utils import (
error_to_compat_str,
expand_path,
is_outdated_version,
try_get,
write_json_file,
)
from .version import __version__


class Cache(object):

_YTDL_DIR = 'youtube-dl'
_VERSION_KEY = _YTDL_DIR + '_version'
_DEFAULT_VERSION = '2021.12.17'

def __init__(self, ydl):
self._ydl = ydl

def _get_root_dir(self):
res = self._ydl.params.get('cachedir')
if res is None:
cache_root = compat_getenv('XDG_CACHE_HOME', '~/.cache')
res = os.path.join(cache_root, 'youtube-dl')
res = os.path.join(cache_root, self._YTDL_DIR)
return expand_path(res)

def _get_cache_fn(self, section, key, dtype):
Expand All @@ -50,13 +59,22 @@ def store(self, section, key, data, dtype='json'):
except OSError as ose:
if ose.errno != errno.EEXIST:
raise
write_json_file(data, fn)
write_json_file({self._VERSION_KEY: __version__, 'data': data}, fn)
except Exception:
tb = traceback.format_exc()
self._ydl.report_warning(
'Writing cache to %r failed: %s' % (fn, tb))

def load(self, section, key, dtype='json', default=None):
def _validate(self, data, min_ver):
version = try_get(data, lambda x: x[self._VERSION_KEY])
if not version: # Backward compatibility
data, version = {'data': data}, self._DEFAULT_VERSION
if not is_outdated_version(version, min_ver or '0', assume_new=False):
return data['data']
self._ydl.to_screen(
'Discarding old cache from version {version} (needs {min_ver})'.format(**locals()))

def load(self, section, key, dtype='json', default=None, min_ver=None):
assert dtype in ('json',)

if not self.enabled:
Expand All @@ -66,12 +84,12 @@ def load(self, section, key, dtype='json', default=None):
try:
try:
with io.open(cache_fn, 'r', encoding='utf-8') as cachef:
return json.load(cachef)
return self._validate(json.load(cachef), min_ver)
except ValueError:
try:
file_size = os.path.getsize(cache_fn)
except (OSError, IOError) as oe:
file_size = str(oe)
file_size = error_to_compat_str(oe)
self._ydl.report_warning(
'Cache retrieval from %s failed (%s)' % (cache_fn, file_size))
except IOError:
Expand Down
Loading

0 comments on commit 8ea1c13

Please sign in to comment.