mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-08-22 22:23:36 -07:00
Merge branch 'nightly' into dependabot/pip/nightly/distro-1.8.0
This commit is contained in:
commit
2875e15ce2
25 changed files with 116 additions and 104 deletions
|
@ -226,7 +226,7 @@ DOCUMENTATION :: END
|
|||
getExportFields();
|
||||
|
||||
$('#export_file_format').on('change', function() {
|
||||
if ($(this).val() === 'm3u8') {
|
||||
if ($(this).val() === 'm3u') {
|
||||
$('#export_metadata_level').prop('disabled', true);
|
||||
$('#export_media_info_level').prop('disabled', true);
|
||||
$("#export_thumb_level").prop('disabled', true);
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import abc
|
||||
from copy import deepcopy
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
import numbers
|
||||
import certifi
|
||||
import os
|
||||
import re
|
||||
from copy import deepcopy
|
||||
from math import ceil
|
||||
|
||||
import certifi
|
||||
from six import python_2_unicode_compatible, add_metaclass
|
||||
|
||||
logger = logging.getLogger("Cloudinary")
|
||||
|
@ -23,7 +23,7 @@ from cloudinary.cache import responsive_breakpoints_cache
|
|||
from cloudinary.http_client import HttpClient
|
||||
from cloudinary.compat import urlparse, parse_qs
|
||||
|
||||
from platform import python_version
|
||||
from platform import python_version, platform
|
||||
|
||||
CERT_KWARGS = {
|
||||
'cert_reqs': 'CERT_REQUIRED',
|
||||
|
@ -38,15 +38,17 @@ CL_BLANK = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAA
|
|||
URI_SCHEME = "cloudinary"
|
||||
API_VERSION = "v1_1"
|
||||
|
||||
VERSION = "1.29.0"
|
||||
VERSION = "1.30.0"
|
||||
|
||||
USER_AGENT = "CloudinaryPython/{} (Python {})".format(VERSION, python_version())
|
||||
_USER_PLATFORM_DETAILS = "; ".join((platform(), "Python {}".format(python_version())))
|
||||
|
||||
USER_AGENT = "CloudinaryPython/{} ({})".format(VERSION, _USER_PLATFORM_DETAILS)
|
||||
""" :const: USER_AGENT """
|
||||
|
||||
USER_PLATFORM = ""
|
||||
"""
|
||||
Additional information to be passed with the USER_AGENT, e.g. "CloudinaryMagento/1.0.1".
|
||||
This value is set in platform-specific implementations that use cloudinary_php.
|
||||
Additional information to be passed with the USER_AGENT, e.g. "CloudinaryCLI/1.2.3".
|
||||
This value is set in platform-specific implementations that use pycloudinary.
|
||||
|
||||
The format of the value should be <ProductName>/Version[ (comment)].
|
||||
@see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
|
||||
|
|
|
@ -93,6 +93,23 @@ def resources_by_ids(public_ids, **options):
|
|||
return call_api("get", uri, params, **options)
|
||||
|
||||
|
||||
def resources_by_asset_folder(asset_folder, **options):
|
||||
"""
|
||||
Returns the details of the resources (assets) under a specified asset_folder.
|
||||
|
||||
:param asset_folder: The Asset Folder of the asset
|
||||
:type asset_folder: string
|
||||
:param options: Additional options
|
||||
:type options: dict, optional
|
||||
:return: Resources (assets) of a specific asset_folder
|
||||
:rtype: Response
|
||||
"""
|
||||
uri = ["resources", "by_asset_folder"]
|
||||
params = only(options, "max_results", "tags", "moderations", "context", "next_cursor")
|
||||
params["asset_folder"] = asset_folder
|
||||
return call_api("get", uri, params, **options)
|
||||
|
||||
|
||||
def resources_by_asset_ids(asset_ids, **options):
|
||||
"""Retrieves the resources (assets) indicated in the asset IDs.
|
||||
This method does not return deleted assets even if they have been backed up.
|
||||
|
@ -194,10 +211,18 @@ def update(public_id, **options):
|
|||
options.get("custom_coordinates"))
|
||||
if "context" in options:
|
||||
params["context"] = utils.encode_context(options.get("context"))
|
||||
if "metadata" in options:
|
||||
params["metadata"] = utils.encode_context(options.get("metadata"))
|
||||
if "auto_tagging" in options:
|
||||
params["auto_tagging"] = str(options.get("auto_tagging"))
|
||||
if "access_control" in options:
|
||||
params["access_control"] = utils.json_encode(utils.build_list_of_dicts(options.get("access_control")))
|
||||
if "asset_folder" in options:
|
||||
params["asset_folder"] = options.get("asset_folder")
|
||||
if "display_name" in options:
|
||||
params["display_name"] = options.get("display_name")
|
||||
if "unique_display_name" in options:
|
||||
params["unique_display_name"] = options.get("unique_display_name")
|
||||
|
||||
return call_api("post", uri, params, **options)
|
||||
|
||||
|
|
|
@ -30,7 +30,9 @@ def generate(url=None, acl=None, start_time=None, duration=None,
|
|||
token_parts.append("st=%d" % start_time)
|
||||
token_parts.append("exp=%d" % expiration)
|
||||
if acl is not None:
|
||||
token_parts.append("acl=%s" % _escape_to_lower(acl))
|
||||
acl_list = acl if type(acl) is list else [acl]
|
||||
acl_list = [_escape_to_lower(a) for a in acl_list]
|
||||
token_parts.append("acl=%s" % "!".join(acl_list))
|
||||
to_sign = list(token_parts)
|
||||
if url is not None and acl is None:
|
||||
to_sign.append("url=%s" % _escape_to_lower(url))
|
||||
|
|
|
@ -106,7 +106,7 @@ class CloudinaryField(models.Field):
|
|||
value = super(CloudinaryField, self).pre_save(model_instance, add)
|
||||
if isinstance(value, UploadedFile):
|
||||
options = {"type": self.type, "resource_type": self.resource_type}
|
||||
options.update(self.options)
|
||||
options.update({key: val(model_instance) if callable(val) else val for key, val in self.options.items()})
|
||||
if hasattr(value, 'seekable') and value.seekable():
|
||||
value.seek(0)
|
||||
instance_value = uploader.upload_resource(value, **options)
|
||||
|
|
|
@ -65,7 +65,7 @@ def create_sub_account(name, cloud_name=None, custom_attributes=None, enabled=No
|
|||
"cloud_name": cloud_name,
|
||||
"custom_attributes": custom_attributes,
|
||||
"enabled": enabled,
|
||||
"base_account": base_account}
|
||||
"base_sub_account_id": base_account}
|
||||
return _call_account_api("POST", uri, params=params, **options)
|
||||
|
||||
|
||||
|
|
|
@ -1093,11 +1093,11 @@ var slice = [].slice,
|
|||
*
|
||||
* If the parameter is an object,
|
||||
* @param {(string|Object)} param - the video codec as either a String or a Hash
|
||||
* @return {string} the video codec string in the format codec:profile:level
|
||||
* @return {string} the video codec string in the format codec:profile:level:b_frames
|
||||
* @example
|
||||
* vc_[ :profile : [level]]
|
||||
* vc_[ :profile : [level : [b_frames]]]
|
||||
* or
|
||||
{ codec: 'h264', profile: 'basic', level: '3.1' }
|
||||
{ codec: 'h264', profile: 'basic', level: '3.1', b_frames: false }
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
|
@ -1112,6 +1112,9 @@ var slice = [].slice,
|
|||
video += ":" + param['profile'];
|
||||
if ('level' in param) {
|
||||
video += ":" + param['level'];
|
||||
if ('b_frames' in param && param['b_frames'] === false) {
|
||||
video += ":bframes_no";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
{% load static %}
|
||||
|
||||
<script src="{% static "js/jquery.ui.widget.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "js/jquery.iframe-transport.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "js/jquery.fileupload.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "js/jquery.cloudinary.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "cloudinary/js/jquery.ui.widget.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "cloudinary/js/jquery.iframe-transport.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "cloudinary/js/jquery.fileupload.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "cloudinary/js/jquery.cloudinary.js" %}" type="text/javascript"></script>
|
||||
|
||||
{% if processing %}
|
||||
<script src="{% static "js/load-image.all.min.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "js/canvas-to-blob.min.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "js/jquery.fileupload-process.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "js/jquery.fileupload-image.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "js/jquery.fileupload-validate.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "cloudinary/js/load-image.all.min.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "cloudinary/js/canvas-to-blob.min.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "cloudinary/js/jquery.fileupload-process.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "cloudinary/js/jquery.fileupload-image.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "cloudinary/js/jquery.fileupload-validate.js" %}" type="text/javascript"></script>
|
||||
{% endif %}
|
||||
|
|
|
@ -94,6 +94,8 @@ __SIMPLE_UPLOAD_PARAMS = [
|
|||
"proxy",
|
||||
"folder",
|
||||
"asset_folder",
|
||||
"use_asset_folder_as_public_id_prefix",
|
||||
"unique_display_name",
|
||||
"overwrite",
|
||||
"moderation",
|
||||
"raw_convert",
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import io
|
||||
import keyword
|
||||
|
@ -6,20 +8,17 @@ import sys
|
|||
import tokenize
|
||||
from typing import Generator
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Pattern
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
|
||||
# this is a performance hack. see https://bugs.python.org/issue43014
|
||||
if (
|
||||
if ( # pragma: no branch
|
||||
sys.version_info < (3, 10) and
|
||||
callable(getattr(tokenize, '_compile', None))
|
||||
): # pragma: no cover (<py310)
|
||||
): # pragma: <3.10 cover
|
||||
from functools import lru_cache
|
||||
tokenize._compile = lru_cache()(tokenize._compile) # type: ignore
|
||||
tokenize._compile = lru_cache()(tokenize._compile)
|
||||
|
||||
ESCAPED_NL = 'ESCAPED_NL'
|
||||
UNIMPORTANT_WS = 'UNIMPORTANT_WS'
|
||||
|
@ -27,15 +26,15 @@ NON_CODING_TOKENS = frozenset(('COMMENT', ESCAPED_NL, 'NL', UNIMPORTANT_WS))
|
|||
|
||||
|
||||
class Offset(NamedTuple):
|
||||
line: Optional[int] = None
|
||||
utf8_byte_offset: Optional[int] = None
|
||||
line: int | None = None
|
||||
utf8_byte_offset: int | None = None
|
||||
|
||||
|
||||
class Token(NamedTuple):
|
||||
name: str
|
||||
src: str
|
||||
line: Optional[int] = None
|
||||
utf8_byte_offset: Optional[int] = None
|
||||
line: int | None = None
|
||||
utf8_byte_offset: int | None = None
|
||||
|
||||
@property
|
||||
def offset(self) -> Offset:
|
||||
|
@ -43,11 +42,10 @@ class Token(NamedTuple):
|
|||
|
||||
|
||||
_string_re = re.compile('^([^\'"]*)(.*)$', re.DOTALL)
|
||||
_string_prefixes = frozenset('bfru')
|
||||
_escaped_nl_re = re.compile(r'\\(\n|\r\n|\r)')
|
||||
|
||||
|
||||
def _re_partition(regex: Pattern[str], s: str) -> Tuple[str, str, str]:
|
||||
def _re_partition(regex: Pattern[str], s: str) -> tuple[str, str, str]:
|
||||
match = regex.search(s)
|
||||
if match:
|
||||
return s[:match.start()], s[slice(*match.span())], s[match.end():]
|
||||
|
@ -55,7 +53,7 @@ def _re_partition(regex: Pattern[str], s: str) -> Tuple[str, str, str]:
|
|||
return (s, '', '')
|
||||
|
||||
|
||||
def src_to_tokens(src: str) -> List[Token]:
|
||||
def src_to_tokens(src: str) -> list[Token]:
|
||||
tokenize_target = io.StringIO(src)
|
||||
lines = ('',) + tuple(tokenize_target)
|
||||
|
||||
|
@ -98,32 +96,6 @@ def src_to_tokens(src: str) -> List[Token]:
|
|||
end_offset += len(newtok.encode())
|
||||
|
||||
tok_name = tokenize.tok_name[tok_type]
|
||||
# when a string prefix is not recognized, the tokenizer produces a
|
||||
# NAME token followed by a STRING token
|
||||
if (
|
||||
tok_name == 'STRING' and
|
||||
tokens and
|
||||
tokens[-1].name == 'NAME' and
|
||||
frozenset(tokens[-1].src.lower()) <= _string_prefixes
|
||||
):
|
||||
newsrc = tokens[-1].src + tok_text
|
||||
tokens[-1] = tokens[-1]._replace(src=newsrc, name=tok_name)
|
||||
# produce octal literals as a single token in python 3 as well
|
||||
elif (
|
||||
tok_name == 'NUMBER' and
|
||||
tokens and
|
||||
tokens[-1].name == 'NUMBER'
|
||||
):
|
||||
tokens[-1] = tokens[-1]._replace(src=tokens[-1].src + tok_text)
|
||||
# produce long literals as a single token in python 3 as well
|
||||
elif (
|
||||
tok_name == 'NAME' and
|
||||
tok_text.lower() == 'l' and
|
||||
tokens and
|
||||
tokens[-1].name == 'NUMBER'
|
||||
):
|
||||
tokens[-1] = tokens[-1]._replace(src=tokens[-1].src + tok_text)
|
||||
else:
|
||||
tokens.append(Token(tok_name, tok_text, sline, end_offset))
|
||||
last_line, last_col = eline, ecol
|
||||
if sline != eline:
|
||||
|
@ -140,19 +112,19 @@ def tokens_to_src(tokens: Iterable[Token]) -> str:
|
|||
|
||||
def reversed_enumerate(
|
||||
tokens: Sequence[Token],
|
||||
) -> Generator[Tuple[int, Token], None, None]:
|
||||
) -> Generator[tuple[int, Token], None, None]:
|
||||
for i in reversed(range(len(tokens))):
|
||||
yield i, tokens[i]
|
||||
|
||||
|
||||
def parse_string_literal(src: str) -> Tuple[str, str]:
|
||||
def parse_string_literal(src: str) -> tuple[str, str]:
|
||||
"""parse a string literal's source into (prefix, string)"""
|
||||
match = _string_re.match(src)
|
||||
assert match is not None
|
||||
return match.group(1), match.group(2)
|
||||
|
||||
|
||||
def rfind_string_parts(tokens: Sequence[Token], i: int) -> Tuple[int, ...]:
|
||||
def rfind_string_parts(tokens: Sequence[Token], i: int) -> tuple[int, ...]:
|
||||
"""find the indicies of the string parts of a (joined) string literal
|
||||
|
||||
- `i` should start at the end of the string literal
|
||||
|
@ -195,7 +167,7 @@ def rfind_string_parts(tokens: Sequence[Token], i: int) -> Tuple[int, ...]:
|
|||
return tuple(reversed(ret))
|
||||
|
||||
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filename')
|
||||
args = parser.parse_args(argv)
|
||||
|
@ -210,4 +182,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(main())
|
||||
raise SystemExit(main())
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
apscheduler==3.9.1
|
||||
apscheduler==3.9.1.post1
|
||||
importlib-metadata==5.0.0
|
||||
importlib-resources==5.10.0
|
||||
pyinstaller==5.6.2
|
||||
pyopenssl==22.0.0
|
||||
pyopenssl==22.1.0
|
||||
pycryptodomex==3.15.0
|
||||
|
||||
pyobjc-framework-Cocoa==8.5; platform_system == "Darwin"
|
||||
pyobjc-framework-Cocoa==9.0; platform_system == "Darwin"
|
||||
pyobjc-core==9.0; platform_system == "Darwin"
|
||||
|
||||
pywin32==304; platform_system == "Windows"
|
||||
pywin32==305; platform_system == "Windows"
|
||||
|
|
|
@ -126,6 +126,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'HTTPS_KEY': (str, 'General', ''),
|
||||
'HTTPS_DOMAIN': (str, 'General', 'localhost'),
|
||||
'HTTPS_IP': (str, 'General', '127.0.0.1'),
|
||||
'HTTPS_MIN_TLS_VERSION': (str, 'Advanced', 'TLSv1.2'),
|
||||
'HTTP_BASIC_AUTH': (int, 'General', 0),
|
||||
'HTTP_ENVIRONMENT': (str, 'General', 'production'),
|
||||
'HTTP_HASH_PASSWORD': (int, 'General', 1),
|
||||
|
|
|
@ -102,7 +102,7 @@ class Export(object):
|
|||
METADATA_LEVELS = (0, 1, 2, 3, 9)
|
||||
MEDIA_INFO_LEVELS = (0, 1, 2, 3, 9)
|
||||
IMAGE_LEVELS = (0, 1, 2, 9)
|
||||
FILE_FORMATS = ('csv', 'json', 'xml', 'm3u8')
|
||||
FILE_FORMATS = ('csv', 'json', 'xml', 'm3u')
|
||||
EXPORT_TYPES = ('all', 'collection', 'playlist')
|
||||
|
||||
def __init__(self, section_id=None, user_id=None, rating_key=None, file_format='csv',
|
||||
|
@ -141,8 +141,8 @@ class Export(object):
|
|||
self.exported_items = 0
|
||||
self.success = False
|
||||
|
||||
# Reset export options for m3u8
|
||||
if self.file_format == 'm3u8':
|
||||
# Reset export options for m3u
|
||||
if self.file_format == 'm3u':
|
||||
self.metadata_level = 1
|
||||
self.media_info_level = 1
|
||||
self.thumb_level = 0
|
||||
|
@ -1960,10 +1960,10 @@ class Export(object):
|
|||
with open(filepath, 'w', encoding='utf-8') as outfile:
|
||||
outfile.write(xml_data)
|
||||
|
||||
elif self.file_format == 'm3u8':
|
||||
m3u8_data = self.data_to_m3u8(result, obj)
|
||||
elif self.file_format == 'm3u':
|
||||
m3u_data = self.data_to_m3u(result, obj)
|
||||
with open(filepath, 'w', encoding='utf-8') as outfile:
|
||||
outfile.write(m3u8_data)
|
||||
outfile.write(m3u_data)
|
||||
|
||||
self.file_size += os.path.getsize(filepath)
|
||||
|
||||
|
@ -2119,36 +2119,36 @@ class Export(object):
|
|||
|
||||
return helpers.dict_to_xml(xml_metadata, root_node='export', indent=4)
|
||||
|
||||
def data_to_m3u8(self, data, obj):
|
||||
items = self._get_m3u8_items(data)
|
||||
def data_to_m3u(self, data, obj):
|
||||
items = self._get_m3u_items(data)
|
||||
|
||||
m3u8_metadata = {'title': obj.title, 'type': obj.media_type}
|
||||
m3u_metadata = {'title': obj.title, 'type': obj.media_type}
|
||||
if obj.rating_key:
|
||||
m3u8_metadata['ratingKey'] = obj.rating_key
|
||||
m3u_metadata['ratingKey'] = obj.rating_key
|
||||
if obj.user_id:
|
||||
m3u8_metadata['userID'] = obj.user_id
|
||||
m3u_metadata['userID'] = obj.user_id
|
||||
if obj.section_id:
|
||||
m3u8_metadata['sectionID'] = obj.section_id
|
||||
m3u_metadata['sectionID'] = obj.section_id
|
||||
|
||||
m3u8 = '#EXTM3U\n'
|
||||
m3u8 += '# Playlist: {title}\n# {metadata}\n\n'.format(title=obj.title, metadata=json.dumps(m3u8_metadata))
|
||||
m3u8_item_template = '# {metadata}\n#EXTINF:{duration},{title}\n{location}\n'
|
||||
m3u8_items = []
|
||||
m3u = '#EXTM3U\n'
|
||||
m3u += '# Playlist: {title}\n# {metadata}\n\n'.format(title=obj.title, metadata=json.dumps(m3u_metadata))
|
||||
m3u_item_template = '# {metadata}\n#EXTINF:{duration},{title}\n{location}\n'
|
||||
m3u_items = []
|
||||
|
||||
for item in items:
|
||||
m3u8_values = {
|
||||
m3u_values = {
|
||||
'duration': item.pop('duration'),
|
||||
'title': item.pop('title'),
|
||||
'location': item.pop('location'),
|
||||
'metadata': json.dumps(item)
|
||||
}
|
||||
m3u8_items.append(m3u8_item_template.format(**m3u8_values))
|
||||
m3u_items.append(m3u_item_template.format(**m3u_values))
|
||||
|
||||
m3u8 = m3u8 + '\n'.join(m3u8_items)
|
||||
m3u = m3u + '\n'.join(m3u_items)
|
||||
|
||||
return m3u8
|
||||
return m3u
|
||||
|
||||
def _get_m3u8_items(self, data):
|
||||
def _get_m3u_items(self, data):
|
||||
items = []
|
||||
|
||||
for d in data:
|
||||
|
@ -2167,7 +2167,7 @@ class Export(object):
|
|||
items.append(metadata)
|
||||
|
||||
for child_media_type in self.CHILD_MEDIA_TYPES[d['type']]:
|
||||
child_locations = self._get_m3u8_items(d[self.PLURAL_MEDIA_TYPES[child_media_type]])
|
||||
child_locations = self._get_m3u_items(d[self.PLURAL_MEDIA_TYPES[child_media_type]])
|
||||
items.extend(child_locations)
|
||||
|
||||
return items
|
||||
|
|
|
@ -7003,7 +7003,7 @@ class WebInterface(object):
|
|||
rating_key (int): The rating key of the media item to export
|
||||
|
||||
Optional parameters:
|
||||
file_format (str): csv (default), json, xml, or m3u8
|
||||
file_format (str): csv (default), json, xml, or m3u
|
||||
metadata_level (int): The level of metadata to export (default 1)
|
||||
media_info_level (int): The level of media info to export (default 1)
|
||||
thumb_level (int): The level of poster/cover images to export (default 0)
|
||||
|
@ -7084,7 +7084,7 @@ class WebInterface(object):
|
|||
elif result['file_format'] == 'xml':
|
||||
return serve_file(filepath, name=result['filename'], content_type='application/xml;charset=UTF-8')
|
||||
|
||||
elif result['file_format'] == 'm3u8':
|
||||
elif result['file_format'] == 'm3u':
|
||||
return serve_file(filepath, name=result['filename'], content_type='text/plain;charset=UTF-8')
|
||||
|
||||
else:
|
||||
|
|
|
@ -46,6 +46,7 @@ def start():
|
|||
'https_cert': plexpy.CONFIG.HTTPS_CERT,
|
||||
'https_cert_chain': plexpy.CONFIG.HTTPS_CERT_CHAIN,
|
||||
'https_key': plexpy.CONFIG.HTTPS_KEY,
|
||||
'https_min_tls_version': plexpy.CONFIG.HTTPS_MIN_TLS_VERSION,
|
||||
'http_username': plexpy.CONFIG.HTTP_USERNAME,
|
||||
'http_password': plexpy.CONFIG.HTTP_PASSWORD,
|
||||
'http_basic_auth': plexpy.CONFIG.HTTP_BASIC_AUTH
|
||||
|
@ -106,7 +107,11 @@ def initialize(options):
|
|||
purpose=ssl.Purpose.CLIENT_AUTH,
|
||||
cafile=https_cert_chain
|
||||
)
|
||||
context.minimum_version = ssl.TLSVersion.TLSv1_2
|
||||
|
||||
min_tls_version = options['https_min_tls_version'].replace('.', '_')
|
||||
context.minimum_version = getattr(ssl.TLSVersion, min_tls_version, ssl.TLSVersion.TLSv1_2)
|
||||
logger.debug("Tautulli WebStart :: Web server minimum TLS version set to %s.", context.minimum_version.name)
|
||||
|
||||
context.load_cert_chain(https_cert, https_key)
|
||||
|
||||
options_dict['server.ssl_context'] = context
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
appdirs==1.4.4
|
||||
apscheduler==3.9.1
|
||||
apscheduler==3.9.1.post1
|
||||
arrow==1.2.3
|
||||
backports.csv==1.0.7
|
||||
backports.functools-lru-cache==1.6.4
|
||||
|
@ -9,7 +9,7 @@ bleach==5.0.1
|
|||
certifi==2022.9.24
|
||||
cheroot==8.6.0
|
||||
cherrypy==18.8.0
|
||||
cloudinary==1.29.0
|
||||
cloudinary==1.30.0
|
||||
distro==1.8.0
|
||||
dnspython==2.2.1
|
||||
facebook-sdk==3.1.0
|
||||
|
@ -42,7 +42,7 @@ simplejson==3.17.6
|
|||
six==1.16.0
|
||||
soupsieve==2.3.2.post1
|
||||
tempora==5.0.2
|
||||
tokenize-rt==4.2.1
|
||||
tokenize-rt==5.0.0
|
||||
tzdata==2022.6
|
||||
tzlocal==4.2
|
||||
urllib3==1.26.12
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue