@ -1,213 +0,0 @@ | |||||
#!/usr/bin/python | |||||
# coding: utf-8 | |||||
from ConfigParser import RawConfigParser, NoOptionError, NoSectionError | |||||
from os.path import dirname, splitext, basename, isfile | |||||
import re | |||||
from os import devnull | |||||
from subprocess import check_call, CalledProcessError, STDOUT | |||||
import unidecode | |||||
import logging | |||||
### CATEGORIES ### | |||||
YOUTUBE_CATEGORY = { | |||||
"music": 10, | |||||
"films": 1, | |||||
"vehicles": 2, | |||||
"sport": 17, | |||||
"travels": 19, | |||||
"gaming": 20, | |||||
"people": 22, | |||||
"comedy": 23, | |||||
"entertainment": 24, | |||||
"news": 25, | |||||
"how to": 26, | |||||
"education": 27, | |||||
"activism": 29, | |||||
"science & technology": 28, | |||||
"science": 28, | |||||
"technology": 28, | |||||
"animals": 15 | |||||
} | |||||
PEERTUBE_CATEGORY = { | |||||
"music": 1, | |||||
"films": 2, | |||||
"vehicles": 3, | |||||
"sport": 5, | |||||
"travels": 6, | |||||
"gaming": 7, | |||||
"people": 8, | |||||
"comedy": 9, | |||||
"entertainment": 10, | |||||
"news": 11, | |||||
"how to": 12, | |||||
"education": 13, | |||||
"activism": 14, | |||||
"science & technology": 15, | |||||
"science": 15, | |||||
"technology": 15, | |||||
"animals": 16 | |||||
} | |||||
### LANGUAGES ### | |||||
YOUTUBE_LANGUAGE = { | |||||
"arabic": 'ar', | |||||
"english": 'en', | |||||
"french": 'fr', | |||||
"german": 'de', | |||||
"hindi": 'hi', | |||||
"italian": 'it', | |||||
"japanese": 'ja', | |||||
"korean": 'ko', | |||||
"mandarin": 'zh-CN', | |||||
"portuguese": 'pt-PT', | |||||
"punjabi": 'pa', | |||||
"russian": 'ru', | |||||
"spanish": 'es' | |||||
} | |||||
PEERTUBE_LANGUAGE = { | |||||
"arabic": "ar", | |||||
"english": "en", | |||||
"french": "fr", | |||||
"german": "de", | |||||
"hindi": "hi", | |||||
"italian": "it", | |||||
"japanese": "ja", | |||||
"korean": "ko", | |||||
"mandarin": "zh", | |||||
"portuguese": "pt", | |||||
"punjabi": "pa", | |||||
"russian": "ru", | |||||
"spanish": "es" | |||||
} | |||||
###################### | |||||
def getCategory(category, platform): | |||||
if platform == "youtube": | |||||
return YOUTUBE_CATEGORY[category.lower()] | |||||
else: | |||||
return PEERTUBE_CATEGORY[category.lower()] | |||||
def getLanguage(language, platform): | |||||
if platform == "youtube": | |||||
return YOUTUBE_LANGUAGE[language.lower()] | |||||
else: | |||||
return PEERTUBE_LANGUAGE[language.lower()] | |||||
def remove_empty_kwargs(**kwargs): | |||||
good_kwargs = {} | |||||
if kwargs is not None: | |||||
for key, value in kwargs.iteritems(): | |||||
if value: | |||||
good_kwargs[key] = value | |||||
return good_kwargs | |||||
def searchThumbnail(options): | |||||
video_directory = dirname(options.get('--file')) + "/" | |||||
# First, check for thumbnail based on videoname | |||||
if options.get('--name'): | |||||
if isfile(video_directory + options.get('--name') + ".jpg"): | |||||
options['--thumbnail'] = video_directory + options.get('--name') + ".jpg" | |||||
elif isfile(video_directory + options.get('--name') + ".jpeg"): | |||||
options['--thumbnail'] = video_directory + options.get('--name') + ".jpeg" | |||||
# Then, if we still not have thumbnail, check for thumbnail based on videofile name | |||||
if not options.get('--thumbnail'): | |||||
video_file = splitext(basename(options.get('--file')))[0] | |||||
if isfile(video_directory + video_file + ".jpg"): | |||||
options['--thumbnail'] = video_directory + video_file + ".jpg" | |||||
elif isfile(video_directory + video_file + ".jpeg"): | |||||
options['--thumbnail'] = video_directory + video_file + ".jpeg" | |||||
return options | |||||
# return the nfo as a RawConfigParser object | |||||
def loadNFO(options): | |||||
video_directory = dirname(options.get('--file')) + "/" | |||||
if options.get('--nfo'): | |||||
try: | |||||
logging.info("Using " + options.get('--nfo') + " as NFO, loading...") | |||||
if isfile(options.get('--nfo')): | |||||
nfo = RawConfigParser() | |||||
nfo.read(options.get('--nfo')) | |||||
return nfo | |||||
else: | |||||
logging.error("Given NFO file does not exist, please check your path.") | |||||
exit(1) | |||||
except Exception as e: | |||||
logging.error("Problem with NFO file: " + str(e)) | |||||
exit(1) | |||||
else: | |||||
if options.get('--name'): | |||||
nfo_file = video_directory + options.get('--name') + ".txt" | |||||
if isfile(nfo_file): | |||||
try: | |||||
logging.info("Using " + nfo_file + " as NFO, loading...") | |||||
nfo = RawConfigParser() | |||||
nfo.read(nfo_file) | |||||
return nfo | |||||
except Exception as e: | |||||
logging.error("Problem with NFO file: " + str(e)) | |||||
exit(1) | |||||
# if --nfo and --name does not exist, use --file as default | |||||
video_file = splitext(basename(options.get('--file')))[0] | |||||
nfo_file = video_directory + video_file + ".txt" | |||||
if isfile(nfo_file): | |||||
try: | |||||
logging.info("Using " + nfo_file + " as NFO, loading...") | |||||
nfo = RawConfigParser() | |||||
nfo.read(nfo_file) | |||||
return nfo | |||||
except Exception as e: | |||||
logging.error("Problem with nfo file: " + str(e)) | |||||
exit(1) | |||||
logging.info("No suitable NFO found, skipping.") | |||||
return False | |||||
def parseNFO(options): | |||||
nfo = loadNFO(options) | |||||
if nfo: | |||||
# We need to check all options and replace it with the nfo value if not defined (None or False) | |||||
for key, value in options.iteritems(): | |||||
key = key.replace("-", "") | |||||
try: | |||||
# get string options | |||||
if value is None and nfo.get('video', key): | |||||
options['--' + key] = nfo.get('video', key) | |||||
# get boolean options | |||||
elif value is False and nfo.getboolean('video', key): | |||||
options['--' + key] = nfo.getboolean('video', key) | |||||
except NoOptionError: | |||||
continue | |||||
except NoSectionError: | |||||
logging.error("Given NFO file miss section [video], please check syntax of your NFO.") | |||||
exit(1) | |||||
return options | |||||
def upcaseFirstLetter(s): | |||||
return s[0].upper() + s[1:] | |||||
def cleanString(toclean): | |||||
toclean = toclean.decode('utf-8') | |||||
toclean = unidecode.unidecode(toclean) | |||||
cleaned = re.sub('[^A-Za-z0-9]+', '', toclean) | |||||
return cleaned | |||||
def decodeArgumentStrings(options, encoding): | |||||
# Python crash when decoding from UTF-8 to UTF-8, so we prevent this | |||||
if "utf-8" == encoding.lower(): | |||||
return; | |||||
if options["--name"] is not None: | |||||
options["--name"] = options["--name"].decode(encoding) | |||||
if options["--description"] is not None: | |||||
options["--description"] = options["--description"].decode(encoding) | |||||
if options["--tags"] is not None: | |||||
options["--tags"] = options["--tags"].decode(encoding) |
@ -0,0 +1,681 @@ | |||||
[[package]] | |||||
category = "main" | |||||
description = "Command Arguments for Humans." | |||||
name = "args" | |||||
optional = false | |||||
python-versions = "*" | |||||
version = "0.1.0" | |||||
[[package]] | |||||
category = "main" | |||||
description = "Backport of the standard library zoneinfo module" | |||||
marker = "python_version >= \"3.6\" and python_version < \"3.9\" or python_version < \"3.9\"" | |||||
name = "backports.zoneinfo" | |||||
optional = false | |||||
python-versions = ">=3.6" | |||||
version = "0.2.1" | |||||
[package.dependencies] | |||||
[package.dependencies.importlib-resources] | |||||
python = "<3.7" | |||||
version = "*" | |||||
[package.extras] | |||||
tzdata = ["tzdata"] | |||||
[[package]] | |||||
category = "main" | |||||
description = "Extensible memoizing collections and decorators" | |||||
name = "cachetools" | |||||
optional = false | |||||
python-versions = "*" | |||||
version = "3.1.1" | |||||
[[package]] | |||||
category = "main" | |||||
description = "Python package for providing Mozilla's CA Bundle." | |||||
name = "certifi" | |||||
optional = false | |||||
python-versions = "*" | |||||
version = "2021.10.8" | |||||
[[package]] | |||||
category = "main" | |||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." | |||||
marker = "python_version >= \"3\"" | |||||
name = "charset-normalizer" | |||||
optional = false | |||||
python-versions = ">=3.5.0" | |||||
version = "2.0.12" | |||||
[package.extras] | |||||
unicode_backport = ["unicodedata2"] | |||||
[[package]] | |||||
category = "main" | |||||
description = "Python Command Line Interface Tools" | |||||
name = "clint" | |||||
optional = false | |||||
python-versions = "*" | |||||
version = "0.5.1" | |||||
[package.dependencies] | |||||
args = "*" | |||||
[[package]] | |||||
category = "main" | |||||
description = "Updated configparser from Python 3.8 for Python 2.6+." | |||||
name = "configparser" | |||||
optional = false | |||||
python-versions = ">=3.6" | |||||
version = "5.2.0" | |||||
[package.extras] | |||||
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] | |||||
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "types-backports", "pytest-black (>=0.3.7)", "pytest-mypy"] | |||||
[[package]] | |||||
category = "main" | |||||
description = "Backports and enhancements for the contextlib module" | |||||
name = "contextlib2" | |||||
optional = false | |||||
python-versions = ">=3.6" | |||||
version = "21.6.0" | |||||
[[package]] | |||||
category = "main" | |||||
description = "Pythonic argument parser, that will make you smile" | |||||
name = "docopt" | |||||
optional = false | |||||
python-versions = "*" | |||||
version = "0.6.2" | |||||
[[package]] | |||||
category = "main" | |||||
description = "Clean single-source support for Python 3 and 2" | |||||
name = "future" | |||||
optional = false | |||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" | |||||
version = "0.18.2" | |||||
[[package]] | |||||
category = "main" | |||||
description = "Google API client core library" | |||||
name = "google-api-core" | |||||
optional = false | |||||
python-versions = ">=3.6" | |||||
version = "2.7.2" | |||||
[package.dependencies] | |||||
google-auth = ">=1.25.0,<3.0dev" | |||||
googleapis-common-protos = ">=1.52.0,<2.0dev" | |||||
protobuf = ">=3.12.0" | |||||
requests = ">=2.18.0,<3.0.0dev" | |||||
[package.extras] | |||||
grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio-status (>=1.33.2,<2.0dev)"] | |||||
grpcgcp = ["grpcio-gcp (>=0.2.2)"] | |||||
grpcio-gcp = ["grpcio-gcp (>=0.2.2)"] | |||||
[[package]] | |||||
category = "main" | |||||
description = "Google API Client Library for Python" | |||||
name = "google-api-python-client" | |||||
optional = false | |||||
python-versions = ">=3.6" | |||||
version = "2.44.0" | |||||
[package.dependencies] | |||||
google-api-core = ">=1.31.5,<2.0.0 || >2.3.0,<3.0.0dev" | |||||
google-auth = ">=1.16.0,<3.0.0dev" | |||||
google-auth-httplib2 = ">=0.1.0" | |||||
httplib2 = ">=0.15.0,<1dev" | |||||
uritemplate = ">=3.0.1,<5" | |||||
[[package]] | |||||
category = "main" | |||||
description = "Google Authentication Library" | |||||
name = "google-auth" | |||||
optional = false | |||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" | |||||
version = "2.6.5" | |||||
[package.dependencies] | |||||
cachetools = ">=2.0.0,<6.0" | |||||
pyasn1-modules = ">=0.2.1" | |||||
six = ">=1.9.0" | |||||
[package.dependencies.rsa] | |||||
python = ">=3.6" | |||||
version = ">=3.1.4,<5" | |||||
[package.extras] | |||||
aiohttp = ["requests (>=2.20.0,<3.0.0dev)", "aiohttp (>=3.6.2,<4.0.0dev)"] | |||||
pyopenssl = ["pyopenssl (>=20.0.0)"] | |||||
reauth = ["pyu2f (>=0.1.5)"] | |||||
[[package]] | |||||
category = "main" | |||||
description = "Google Authentication Library: httplib2 transport" | |||||
name = "google-auth-httplib2" | |||||
optional = false | |||||
python-versions = "*" | |||||
version = "0.1.0" | |||||
[package.dependencies] | |||||
google-auth = "*" | |||||
httplib2 = ">=0.15.0" | |||||
six = "*" | |||||
[[package]] | |||||
category = "main" | |||||
description = "Google Authentication Library" | |||||
name = "google-auth-oauthlib" | |||||
optional = false | |||||
python-versions = ">=3.6" | |||||
version = "0.5.1" | |||||
[package.dependencies] | |||||
google-auth = ">=1.0.0" | |||||
requests-oauthlib = ">=0.7.0" | |||||
[package.extras] | |||||
tool = ["click (>=6.0.0)"] | |||||
[[package]] | |||||
category = "main" | |||||
description = "Common protobufs used in Google APIs" | |||||
name = "googleapis-common-protos" | |||||
optional = false | |||||
python-versions = ">=3.6" | |||||
version = "1.56.0" | |||||
[package.dependencies] | |||||
protobuf = ">=3.12.0" | |||||
[package.extras] | |||||
grpc = ["grpcio (>=1.0.0)"] | |||||
[[package]] | |||||
category = "main" | |||||
description = "A comprehensive HTTP client library." | |||||
name = "httplib2" | |||||
optional = false | |||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | |||||
version = "0.20.4" | |||||
[package.dependencies] | |||||
[package.dependencies.pyparsing] | |||||
python = ">=3.1" | |||||
version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4" | |||||
[[package]] | |||||
category = "main" | |||||
description = "Internationalized Domain Names in Applications (IDNA)" | |||||
marker = "python_version >= \"3\"" | |||||
name = "idna" | |||||
optional = false | |||||
python-versions = ">=3.5" | |||||
version = "3.3" | |||||
[[package]] | |||||
category = "main" | |||||
description = "Read resources from Python packages" | |||||
marker = "python_version >= \"3.6\" and python_version < \"3.7\" or python_version < \"3.7\"" | |||||
name = "importlib-resources" | |||||
optional = false | |||||
python-versions = ">=3.6" | |||||
version = "5.4.0" | |||||
[package.dependencies] | |||||
[package.dependencies.zipp] | |||||
python = "<3.10" | |||||
version = ">=3.1.0" | |||||
[package.extras] | |||||
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] | |||||
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"] | |||||
[[package]] | |||||
category = "main" | |||||
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" | |||||
name = "oauthlib" | |||||
optional = false | |||||
python-versions = "*" | |||||
version = "2.1.0" | |||||
[package.extras] | |||||
rsa = ["cryptography"] | |||||
signals = ["blinker"] | |||||
signedtoken = ["cryptography", "pyjwt (>=1.0.0)"] | |||||
test = ["nose", "unittest2", "cryptography", "mock", "pyjwt (>=1.0.0)", "blinker"] | |||||
[[package]] | |||||
category = "main" | |||||
description = "Protocol Buffers" | |||||
name = "protobuf" | |||||
optional = false | |||||
python-versions = ">=3.5" | |||||
version = "3.19.4" | |||||
[[package]] | |||||
category = "main" | |||||
description = "ASN.1 types and codecs" | |||||
name = "pyasn1" | |||||
optional = false | |||||
python-versions = "*" | |||||
version = "0.4.8" | |||||
[[package]] | |||||
category = "main" | |||||
description = "A collection of ASN.1-based protocols modules." | |||||
name = "pyasn1-modules" | |||||
optional = false | |||||
python-versions = "*" | |||||
version = "0.2.8" | |||||
[package.dependencies] | |||||
pyasn1 = ">=0.4.6,<0.5.0" | |||||
[[package]] | |||||
category = "main" | |||||
description = "Python parsing module" | |||||
marker = "python_version > \"3.0\"" | |||||
name = "pyparsing" | |||||
optional = false | |||||
python-versions = ">=3.6" | |||||
version = "3.0.7" | |||||
[package.extras] | |||||
diagrams = ["jinja2", "railroad-diagrams"] | |||||
[[package]] | |||||
category = "main" | |||||
description = "World timezone definitions, modern and historical" | |||||
name = "pytz" | |||||
optional = false | |||||
python-versions = "*" | |||||
version = "2022.1" | |||||
[[package]] | |||||
category = "main" | |||||
description = "Shims to make deprecation of pytz easier" | |||||
name = "pytz-deprecation-shim" | |||||
optional = false | |||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" | |||||
version = "0.1.0.post0" | |||||
[package.dependencies] | |||||
[package.dependencies."backports.zoneinfo"] | |||||
python = ">=3.6,<3.9" | |||||
version = "*" | |||||
[package.dependencies.tzdata] | |||||
python = ">=3.6" | |||||
version = "*" | |||||
[[package]] | |||||
category = "main" | |||||
description = "Python HTTP for Humans." | |||||
name = "requests" | |||||
optional = false | |||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" | |||||
version = "2.27.1" | |||||
[package.dependencies] | |||||
certifi = ">=2017.4.17" | |||||
urllib3 = ">=1.21.1,<1.27" | |||||
[package.dependencies.charset-normalizer] | |||||
python = ">=3" | |||||
version = ">=2.0.0,<2.1.0" | |||||
[package.dependencies.idna] | |||||
python = ">=3" | |||||
version = ">=2.5,<4" | |||||
[package.extras] | |||||
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] | |||||
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] | |||||
[[package]] | |||||
category = "main" | |||||
description = "OAuthlib authentication support for Requests." | |||||
name = "requests-oauthlib" | |||||
optional = false | |||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | |||||
version = "1.1.0" | |||||
[package.dependencies] | |||||
oauthlib = ">=2.1.0,<3.0.0" | |||||
requests = ">=2.0.0" | |||||
[package.extras] | |||||
rsa = ["oauthlib (>=2.1.0,<3.0.0)"] | |||||
[[package]] | |||||
category = "main" | |||||
description = "A utility belt for advanced users of python-requests" | |||||
name = "requests-toolbelt" | |||||
optional = false | |||||
python-versions = "*" | |||||
version = "0.9.1" | |||||
[package.dependencies] | |||||
requests = ">=2.0.1,<3.0.0" | |||||
[[package]] | |||||
category = "main" | |||||
description = "Pure-Python RSA implementation" | |||||
marker = "python_version >= \"3.6\"" | |||||
name = "rsa" | |||||
optional = false | |||||
python-versions = "*" | |||||
version = "4.4" | |||||
[package.dependencies] | |||||
pyasn1 = ">=0.1.3" | |||||
[[package]] | |||||
category = "main" | |||||
description = "Simple data validation library" | |||||
name = "schema" | |||||
optional = false | |||||
python-versions = "*" | |||||
version = "0.7.5" | |||||
[package.dependencies] | |||||
contextlib2 = ">=0.5.5" | |||||
[[package]] | |||||
category = "main" | |||||
description = "Python 2 and 3 compatibility utilities" | |||||
name = "six" | |||||
optional = false | |||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" | |||||
version = "1.16.0" | |||||
[[package]] | |||||
category = "main" | |||||
description = "Provider of IANA time zone data" | |||||
marker = "python_version >= \"3.6\" or platform_system == \"Windows\"" | |||||
name = "tzdata" | |||||
optional = false | |||||
python-versions = ">=2" | |||||
version = "2022.1" | |||||
[[package]] | |||||
category = "main" | |||||
description = "tzinfo object for the local timezone" | |||||
name = "tzlocal" | |||||
optional = false | |||||
python-versions = ">=3.6" | |||||
version = "4.2" | |||||
[package.dependencies] | |||||
pytz-deprecation-shim = "*" | |||||
tzdata = "*" | |||||
[package.dependencies."backports.zoneinfo"] | |||||
python = "<3.9" | |||||
version = "*" | |||||
[package.extras] | |||||
devenv = ["black", "pyroma", "pytest-cov", "zest.releaser"] | |||||
test = ["pytest-mock (>=3.3)", "pytest (>=4.3)"] | |||||
[[package]] | |||||
category = "main" | |||||
description = "ASCII transliterations of Unicode text" | |||||
name = "unidecode" | |||||
optional = false | |||||
python-versions = ">=3.5" | |||||
version = "1.3.4" | |||||
[[package]] | |||||
category = "main" | |||||
description = "Implementation of RFC 6570 URI Templates" | |||||
name = "uritemplate" | |||||
optional = false | |||||
python-versions = ">=3.6" | |||||
version = "4.1.1" | |||||
[[package]] | |||||
category = "main" | |||||
description = "HTTP library with thread-safe connection pooling, file post, and more." | |||||
name = "urllib3" | |||||
optional = false | |||||
python-versions = "*" | |||||
version = "1.22" | |||||
[package.extras] | |||||
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] | |||||
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] | |||||
[[package]] | |||||
category = "main" | |||||
description = "Backport of pathlib-compatible object wrapper for zip files" | |||||
marker = "python_version >= \"3.6\" and python_version < \"3.7\" or python_version < \"3.7\"" | |||||
name = "zipp" | |||||
optional = false | |||||
python-versions = ">=3.6" | |||||
version = "3.6.0" | |||||
[package.extras] | |||||
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] | |||||
testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] | |||||
[metadata] | |||||
content-hash = "be6573f03c921c7e695d02a9d03b766a756d2c5a61619cbebea44c85a4f66bf6" | |||||
python-versions = ">=3.6" | |||||
[metadata.files] | |||||
args = [ | |||||
{file = "args-0.1.0.tar.gz", hash = "sha256:a785b8d837625e9b61c39108532d95b85274acd679693b71ebb5156848fcf814"}, | |||||
] | |||||
"backports.zoneinfo" = [ | |||||
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, | |||||
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, | |||||
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, | |||||
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, | |||||
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, | |||||
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, | |||||
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, | |||||
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, | |||||
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, | |||||
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, | |||||
{file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, | |||||
{file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, | |||||
{file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, | |||||
{file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, | |||||
{file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, | |||||
{file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, | |||||
] | |||||
cachetools = [ | |||||
{file = "cachetools-3.1.1-py2.py3-none-any.whl", hash = "sha256:428266a1c0d36dc5aca63a2d7c5942e88c2c898d72139fca0e97fdd2380517ae"}, | |||||
{file = "cachetools-3.1.1.tar.gz", hash = "sha256:8ea2d3ce97850f31e4a08b0e2b5e6c34997d7216a9d2c98e0f3978630d4da69a"}, | |||||
] | |||||
certifi = [ | |||||
{file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, | |||||
{file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, | |||||
] | |||||
charset-normalizer = [ | |||||
{file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, | |||||
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, | |||||
] | |||||
clint = [ | |||||
{file = "clint-0.5.1.tar.gz", hash = "sha256:05224c32b1075563d0b16d0015faaf9da43aa214e4a2140e51f08789e7a4c5aa"}, | |||||
] | |||||
configparser = [ | |||||
{file = "configparser-5.2.0-py3-none-any.whl", hash = "sha256:e8b39238fb6f0153a069aa253d349467c3c4737934f253ef6abac5fe0eca1e5d"}, | |||||
{file = "configparser-5.2.0.tar.gz", hash = "sha256:1b35798fdf1713f1c3139016cfcbc461f09edbf099d1fb658d4b7479fcaa3daa"}, | |||||
] | |||||
contextlib2 = [ | |||||
{file = "contextlib2-21.6.0-py2.py3-none-any.whl", hash = "sha256:3fbdb64466afd23abaf6c977627b75b6139a5a3e8ce38405c5b413aed7a0471f"}, | |||||
{file = "contextlib2-21.6.0.tar.gz", hash = "sha256:ab1e2bfe1d01d968e1b7e8d9023bc51ef3509bba217bb730cee3827e1ee82869"}, | |||||
] | |||||
docopt = [ | |||||
{file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, | |||||
] | |||||
future = [ | |||||
{file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, | |||||
] | |||||
google-api-core = [ | |||||
{file = "google-api-core-2.7.2.tar.gz", hash = "sha256:65480309a7437f739e4476da037af02a3ec8263f1d1f89f72bbdc8f54fe402d2"}, | |||||
{file = "google_api_core-2.7.2-py3-none-any.whl", hash = "sha256:8fcbe52dc129fd83dca4e638a76f22b3a11579c493e643134e50e9870b233302"}, | |||||
] | |||||
google-api-python-client = [ | |||||
{file = "google-api-python-client-2.44.0.tar.gz", hash = "sha256:fc9c31737b82a592d29636c6f77af9cf9666e45ed966d0a5ebe20711aa0df83c"}, | |||||
{file = "google_api_python_client-2.44.0-py2.py3-none-any.whl", hash = "sha256:0588acf65ea0569ece979c4eecdd5366c98ba314c1212a86a2808de0b7bcd02b"}, | |||||
] | |||||
google-auth = [ | |||||
{file = "google-auth-2.6.5.tar.gz", hash = "sha256:04e224f241c0566477bb35a8a93be8c635210de743bde454d49393cfb605266d"}, | |||||
{file = "google_auth-2.6.5-py2.py3-none-any.whl", hash = "sha256:9a88ee548f6fd49467e2e443dfbfe10344e5a270629a137a3a0b3437ec6b02a6"}, | |||||
] | |||||
google-auth-httplib2 = [ | |||||
{file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, | |||||
{file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"}, | |||||
] | |||||
google-auth-oauthlib = [ | |||||
{file = "google-auth-oauthlib-0.5.1.tar.gz", hash = "sha256:30596b824fc6808fdaca2f048e4998cc40fb4b3599eaea66d28dc7085b36c5b8"}, | |||||
{file = "google_auth_oauthlib-0.5.1-py2.py3-none-any.whl", hash = "sha256:24f67735513c4c7134dbde2f1dee5a1deb6acc8dfcb577d7bff30d213a28e7b0"}, | |||||
] | |||||
googleapis-common-protos = [ | |||||
{file = "googleapis-common-protos-1.56.0.tar.gz", hash = "sha256:4007500795bcfc269d279f0f7d253ae18d6dc1ff5d5a73613ffe452038b1ec5f"}, | |||||
{file = "googleapis_common_protos-1.56.0-py2.py3-none-any.whl", hash = "sha256:60220c89b8bd5272159bed4929ecdc1243ae1f73437883a499a44a1cbc084086"}, | |||||
] | |||||
httplib2 = [ | |||||
{file = "httplib2-0.20.4-py3-none-any.whl", hash = "sha256:8b6a905cb1c79eefd03f8669fd993c36dc341f7c558f056cb5a33b5c2f458543"}, | |||||
{file = "httplib2-0.20.4.tar.gz", hash = "sha256:58a98e45b4b1a48273073f905d2961666ecf0fbac4250ea5b47aef259eb5c585"}, | |||||
] | |||||
idna = [ | |||||
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, | |||||
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, | |||||
] | |||||
importlib-resources = [ | |||||
{file = "importlib_resources-5.4.0-py3-none-any.whl", hash = "sha256:33a95faed5fc19b4bc16b29a6eeae248a3fe69dd55d4d229d2b480e23eeaad45"}, | |||||
{file = "importlib_resources-5.4.0.tar.gz", hash = "sha256:d756e2f85dd4de2ba89be0b21dba2a3bbec2e871a42a3a16719258a11f87506b"}, | |||||
] | |||||
oauthlib = [ | |||||
{file = "oauthlib-2.1.0-py2.py3-none-any.whl", hash = "sha256:d883b36b21a6ad813953803edfa563b1b579d79ca758fe950d1bc9e8b326025b"}, | |||||
{file = "oauthlib-2.1.0.tar.gz", hash = "sha256:ac35665a61c1685c56336bda97d5eefa246f1202618a1d6f34fccb1bdd404162"}, | |||||
] | |||||
protobuf = [ | |||||
{file = "protobuf-3.19.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f51d5a9f137f7a2cec2d326a74b6e3fc79d635d69ffe1b036d39fc7d75430d37"}, | |||||
{file = "protobuf-3.19.4-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:09297b7972da685ce269ec52af761743714996b4381c085205914c41fcab59fb"}, | |||||
{file = "protobuf-3.19.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:072fbc78d705d3edc7ccac58a62c4c8e0cec856987da7df8aca86e647be4e35c"}, | |||||
{file = "protobuf-3.19.4-cp310-cp310-win32.whl", hash = "sha256:7bb03bc2873a2842e5ebb4801f5c7ff1bfbdf426f85d0172f7644fcda0671ae0"}, | |||||
{file = "protobuf-3.19.4-cp310-cp310-win_amd64.whl", hash = "sha256:f358aa33e03b7a84e0d91270a4d4d8f5df6921abe99a377828839e8ed0c04e07"}, | |||||
{file = "protobuf-3.19.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1c91ef4110fdd2c590effb5dca8fdbdcb3bf563eece99287019c4204f53d81a4"}, | |||||
{file = "protobuf-3.19.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c438268eebb8cf039552897d78f402d734a404f1360592fef55297285f7f953f"}, | |||||
{file = "protobuf-3.19.4-cp36-cp36m-win32.whl", hash = "sha256:835a9c949dc193953c319603b2961c5c8f4327957fe23d914ca80d982665e8ee"}, | |||||
{file = "protobuf-3.19.4-cp36-cp36m-win_amd64.whl", hash = "sha256:4276cdec4447bd5015453e41bdc0c0c1234eda08420b7c9a18b8d647add51e4b"}, | |||||
{file = "protobuf-3.19.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6cbc312be5e71869d9d5ea25147cdf652a6781cf4d906497ca7690b7b9b5df13"}, | |||||
{file = "protobuf-3.19.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:54a1473077f3b616779ce31f477351a45b4fef8c9fd7892d6d87e287a38df368"}, | |||||
{file = "protobuf-3.19.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:435bb78b37fc386f9275a7035fe4fb1364484e38980d0dd91bc834a02c5ec909"}, | |||||
{file = "protobuf-3.19.4-cp37-cp37m-win32.whl", hash = "sha256:16f519de1313f1b7139ad70772e7db515b1420d208cb16c6d7858ea989fc64a9"}, | |||||
{file = "protobuf-3.19.4-cp37-cp37m-win_amd64.whl", hash = "sha256:cdc076c03381f5c1d9bb1abdcc5503d9ca8b53cf0a9d31a9f6754ec9e6c8af0f"}, | |||||
{file = "protobuf-3.19.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:69da7d39e39942bd52848438462674c463e23963a1fdaa84d88df7fbd7e749b2"}, | |||||
{file = "protobuf-3.19.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:48ed3877fa43e22bcacc852ca76d4775741f9709dd9575881a373bd3e85e54b2"}, | |||||
{file = "protobuf-3.19.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd95d1dfb9c4f4563e6093a9aa19d9c186bf98fa54da5252531cc0d3a07977e7"}, | |||||
{file = "protobuf-3.19.4-cp38-cp38-win32.whl", hash = "sha256:b38057450a0c566cbd04890a40edf916db890f2818e8682221611d78dc32ae26"}, | |||||
{file = "protobuf-3.19.4-cp38-cp38-win_amd64.whl", hash = "sha256:7ca7da9c339ca8890d66958f5462beabd611eca6c958691a8fe6eccbd1eb0c6e"}, | |||||
{file = "protobuf-3.19.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:36cecbabbda242915529b8ff364f2263cd4de7c46bbe361418b5ed859677ba58"}, | |||||
{file = "protobuf-3.19.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c1068287025f8ea025103e37d62ffd63fec8e9e636246b89c341aeda8a67c934"}, | |||||
{file = "protobuf-3.19.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96bd766831596d6014ca88d86dc8fe0fb2e428c0b02432fd9db3943202bf8c5e"}, | |||||
{file = "protobuf-3.19.4-cp39-cp39-win32.whl", hash = "sha256:84123274d982b9e248a143dadd1b9815049f4477dc783bf84efe6250eb4b836a"}, | |||||
{file = "protobuf-3.19.4-cp39-cp39-win_amd64.whl", hash = "sha256:3112b58aac3bac9c8be2b60a9daf6b558ca3f7681c130dcdd788ade7c9ffbdca"}, | |||||
{file = "protobuf-3.19.4-py2.py3-none-any.whl", hash = "sha256:8961c3a78ebfcd000920c9060a262f082f29838682b1f7201889300c1fbe0616"}, | |||||
{file = "protobuf-3.19.4.tar.gz", hash = "sha256:9df0c10adf3e83015ced42a9a7bd64e13d06c4cf45c340d2c63020ea04499d0a"}, | |||||
] | |||||
pyasn1 = [ | |||||
{file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, | |||||
{file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, | |||||
{file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, | |||||
{file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, | |||||
{file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, | |||||
{file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, | |||||
{file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, | |||||
{file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, | |||||
{file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, | |||||
{file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, | |||||
{file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, | |||||
{file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, | |||||
{file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, | |||||
] | |||||
pyasn1-modules = [ | |||||
{file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"}, | |||||
{file = "pyasn1_modules-0.2.8-py2.4.egg", hash = "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199"}, | |||||
{file = "pyasn1_modules-0.2.8-py2.5.egg", hash = "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"}, | |||||
{file = "pyasn1_modules-0.2.8-py2.6.egg", hash = "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb"}, | |||||
{file = "pyasn1_modules-0.2.8-py2.7.egg", hash = "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8"}, | |||||
{file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"}, | |||||
{file = "pyasn1_modules-0.2.8-py3.1.egg", hash = "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d"}, | |||||
{file = "pyasn1_modules-0.2.8-py3.2.egg", hash = "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45"}, | |||||
{file = "pyasn1_modules-0.2.8-py3.3.egg", hash = "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4"}, | |||||
{file = "pyasn1_modules-0.2.8-py3.4.egg", hash = "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811"}, | |||||
{file = "pyasn1_modules-0.2.8-py3.5.egg", hash = "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed"}, | |||||
{file = "pyasn1_modules-0.2.8-py3.6.egg", hash = "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0"}, | |||||
{file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"}, | |||||
] | |||||
pyparsing = [ | |||||
{file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, | |||||
{file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, | |||||
] | |||||
pytz = [ | |||||
{file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, | |||||
{file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, | |||||
] | |||||
pytz-deprecation-shim = [ | |||||
{file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"}, | |||||
{file = "pytz_deprecation_shim-0.1.0.post0.tar.gz", hash = "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"}, | |||||
] | |||||
requests = [ | |||||
{file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, | |||||
{file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, | |||||
] | |||||
requests-oauthlib = [ | |||||
{file = "requests-oauthlib-1.1.0.tar.gz", hash = "sha256:eabd8eb700ebed81ba080c6ead96d39d6bdc39996094bd23000204f6965786b0"}, | |||||
{file = "requests_oauthlib-1.1.0-py2.py3-none-any.whl", hash = "sha256:be76f2bb72ca5525998e81d47913e09b1ca8b7957ae89b46f787a79e68ad5e61"}, | |||||
{file = "requests_oauthlib-1.1.0-py3.7.egg", hash = "sha256:490229d14a98e1b69612dcc1a22887ec14f5487dc1b8c6d7ba7f77a42ce7347b"}, | |||||
] | |||||
requests-toolbelt = [ | |||||
{file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, | |||||
{file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, | |||||
] | |||||
rsa = [ | |||||
{file = "rsa-4.4-py2.py3-none-any.whl", hash = "sha256:4afbaaecc3e9550c7351fdf0ab3fea1857ff616b85bab59215f00fb42e0e9582"}, | |||||
{file = "rsa-4.4.tar.gz", hash = "sha256:5d95293bbd0fbee1dd9cb4b72d27b723942eb50584abc8c4f5f00e4bcfa55307"}, | |||||
] | |||||
schema = [ | |||||
{file = "schema-0.7.5-py2.py3-none-any.whl", hash = "sha256:f3ffdeeada09ec34bf40d7d79996d9f7175db93b7a5065de0faa7f41083c1e6c"}, | |||||
{file = "schema-0.7.5.tar.gz", hash = "sha256:f06717112c61895cabc4707752b88716e8420a8819d71404501e114f91043197"}, | |||||
] | |||||
six = [ | |||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, | |||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, | |||||
] | |||||
tzdata = [ | |||||
{file = "tzdata-2022.1-py2.py3-none-any.whl", hash = "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9"}, | |||||
{file = "tzdata-2022.1.tar.gz", hash = "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3"}, | |||||
] | |||||
tzlocal = [ | |||||
{file = "tzlocal-4.2-py3-none-any.whl", hash = "sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745"}, | |||||
{file = "tzlocal-4.2.tar.gz", hash = "sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7"}, | |||||
] | |||||
unidecode = [ | |||||
{file = "Unidecode-1.3.4-py3-none-any.whl", hash = "sha256:afa04efcdd818a93237574791be9b2817d7077c25a068b00f8cff7baa4e59257"}, | |||||
{file = "Unidecode-1.3.4.tar.gz", hash = "sha256:8e4352fb93d5a735c788110d2e7ac8e8031eb06ccbfe8d324ab71735015f9342"}, | |||||
] | |||||
uritemplate = [ | |||||
{file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, | |||||
{file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, | |||||
] | |||||
urllib3 = [ | |||||
{file = "urllib3-1.22-py2.py3-none-any.whl", hash = "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b"}, | |||||
{file = "urllib3-1.22.tar.gz", hash = "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"}, | |||||
] | |||||
zipp = [ | |||||
{file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, | |||||
{file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, | |||||
] |
@ -0,0 +1,12 @@ | |||||
from future import standard_library | |||||
standard_library.install_aliases() | |||||
import logging | |||||
logger = logging.getLogger('Prismedia') | |||||
logger.setLevel(logging.INFO) | |||||
ch = logging.StreamHandler() | |||||
ch.setLevel(logging.INFO) | |||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s: %(message)s') | |||||
ch.setFormatter(formatter) | |||||
logger.addHandler(ch) | |||||
from . import upload |
@ -0,0 +1,2 @@ | |||||
from .upload import main | |||||
main() |
@ -0,0 +1,24 @@ | |||||
from os.path import join, abspath, isfile, dirname, exists | |||||
from os import listdir | |||||
from shutil import copyfile | |||||
import logging | |||||
logger = logging.getLogger('Prismedia') | |||||
from . import utils | |||||
def genconfig(): | |||||
path = join(dirname(__file__), 'config') | |||||
files = [f for f in listdir(path) if isfile(join(path, f))] | |||||
for f in files: | |||||
final_f = f.replace(".sample", "") | |||||
if exists(final_f) and not utils.ask_overwrite(final_f + " already exists. Do you want to overwrite it?"): | |||||
continue | |||||
copyfile(join(path, f), final_f) | |||||
logger.info(str(final_f) + " correctly generated, you may now edit it to fill your credentials.") | |||||
if __name__ == '__main__': | |||||
genconfig() |
@ -0,0 +1,12 @@ | |||||
### This NFO is aimed to be passed to prismedia through the --nfo cli option ### | |||||
### eg: | |||||
### python -m prismedia --file=/path/to/yourvideo.mp4 --nfo=/path/to/cli_nfo.txt ### | |||||
### It's the more priority NFO, only erased by direct cli options ### | |||||
[video] | |||||
disable-comments = False | |||||
nsfw = True | |||||
# Publish on Peertube at a specific date | |||||
peertubeAt = 2034-05-14T19:00:00 | |||||
platform = peertube | |||||
# debug to display all loaded options | |||||
debug = True |
@ -0,0 +1,10 @@ | |||||
### This NFO is named nfo.txt and is stored in the directory of your videos ### | |||||
### This is the less priority NFO, you may use it to set default generic options ### | |||||
[video] | |||||
# Some generic options for your videos | |||||
cca = True | |||||
privacy = private | |||||
disable-comments = False | |||||
channel = DefaultChannel | |||||
channelCreate = True | |||||
auto-originalDate = True |
@ -0,0 +1,14 @@ | |||||
### This NFO is named from the directory where your video are. ### | |||||
### While more specific than nfo.txt, it's less priority than other NFO ### | |||||
### You may use it for options specific to videos in this directory, but still globals ### | |||||
[video] | |||||
channel = MyMoreSpecificChannel | |||||
disable-comments = False | |||||
channelCreate = True | |||||
category = Films | |||||
playlist = Desserts Recipes playlist | |||||
playlistCreate = True | |||||
nsfw = False | |||||
platform = youtube, peertube | |||||
language = French | |||||
tags = list of tags, comma separated |
@ -0,0 +1,14 @@ | |||||
### This NFO is named from your video name (here let's say your video is named "yourvideo.mp4") ### | |||||
### It aims to give options specific to this videos ### | |||||
[video] | |||||
disable-comments = False | |||||
#thumbnail = /path/to/your/thumbnail.jpg # Set the absolute path to your thumbnail | |||||
name = videoname | |||||
description = Your complete video description | |||||
Multilines description | |||||
should be wrote with a blank space | |||||
at the beginning of the line :-) | |||||
publishAt = 2034-05-07T19:00:00 | |||||
# platformAt overrides the default publishAt for the corresponding platform | |||||
#peertubeAt = 2034-05-14T19:00:00 | |||||
#youtubeAt = 2034-05-21T19:00:00 |
@ -0,0 +1,422 @@ | |||||
#!/usr/bin/env python | |||||
# coding: utf-8 | |||||
""" | |||||
prismedia - tool to upload videos to Peertube and Youtube | |||||
Usage: | |||||
prismedia --file=<FILE> [options] | |||||
prismedia -f <FILE> --tags=STRING [options] | |||||
prismedia --hearthbeat | |||||
prismedia -h | --help | |||||
prismedia --version | |||||
Options: | |||||
-f, --file=STRING Path to the video file to upload. This is the only mandatory option. | |||||
--name=NAME Name of the video to upload. (default to video filename) | |||||
-d, --description=STRING Description of the video. (default: default description) | |||||
-t, --tags=STRING Tags for the video. comma separated. | |||||
WARN: tags with punctuation (!, ', ", ?, ...) | |||||
are not supported by Mastodon to be published from Peertube | |||||
-c, --category=STRING Category for the videos, see below. (default: Films) | |||||
--cca License should be CreativeCommon Attribution (affects Youtube upload only) | |||||
-p, --privacy=STRING Choose between public, unlisted or private. (default: private) | |||||
--disable-comments Disable comments (Peertube only as YT API does not support) (default: comments are enabled) | |||||
--nsfw Set the video as No Safe For Work (Peertube only as YT API does not support) (default: video is safe) | |||||
--nfo=STRING Configure a specific nfo file to set options for the video. | |||||
By default Prismedia search a .txt based on the video name and will | |||||
decode the file as UTF-8 (so make sure your nfo file is UTF-8 encoded) | |||||
See nfo_example.txt for more details | |||||
--platform=STRING List of platform(s) to upload to, comma separated. | |||||
Supported platforms are youtube and peertube (default is both) | |||||
--language=STRING Specify the default language for video. See below for supported language. (default is English) | |||||
--publishAt=DATE Publish the video at the given DATE using local server timezone. | |||||
DATE should be on the form YYYY-MM-DDThh:mm:ss eg: 2018-03-12T19:00:00 | |||||
DATE should be in the future | |||||
--peertubeAt=DATE | |||||
--youtubeAt=DATE Override publishAt for the corresponding platform. Allow to create preview on specific platform | |||||
--originalDate=DATE Configure the video as initially recorded at DATE | |||||
DATE should be on the form YYYY-MM-DDThh:mm:ss eg: 2018-03-12T19:00:00 | |||||
DATE should be in the past | |||||
--auto-originalDate Automatically use the file modification time as original date | |||||
--thumbnail=STRING Path to a file to use as a thumbnail for the video. | |||||
By default, prismedia search for an image based on video name followed by .jpg, .jpeg or .png | |||||
--channel=STRING Set the channel to use for the video (Peertube only) | |||||
If the channel is not found, spawn an error except if --channelCreate is set. | |||||
--channelCreate Create the channel if not exists. (Peertube only, default do not create) | |||||
Only relevant if --channel is set. | |||||
--playlist=STRING Set the playlist to use for the video. | |||||
If the playlist is not found, spawn an error except if --playlistCreate is set. | |||||
--playlistCreate Create the playlist if not exists. (default do not create) | |||||
Only relevant if --playlist is set. | |||||
--progress=STRING Set the progress bar view, one of percentage, bigFile (MB), accurate (KB). | |||||
--hearthbeat Use some credits to show some activity for you apikey so the platform know it is used and would not put your quota to 0 (only Youtube currently) | |||||
-h --help Show this help. | |||||
--version Show version. | |||||
Logging options | |||||
-q --quiet Suppress any log except Critical (alias for --log=critical). | |||||
--log=STRING Log level, between debug, info, warning, error, critical. Ignored if --quiet is set (default to info) | |||||
-u --url-only Display generated URL after upload directly on stdout, implies --quiet | |||||
--batch Display generated URL after upload with platform information for easier parsing. Implies --quiet | |||||
Be careful --batch and --url-only are mutually exclusives. | |||||
Strict options: | |||||
Strict options allow you to force some option to be present when uploading a video. It's useful to be sure you do not | |||||
forget something when uploading a video, for example if you use multiples NFO. You may force the presence of description, | |||||
tags, thumbnail, ... | |||||
All strict option are optionals and are provided only to avoid errors when uploading :-) | |||||
All strict options can be specified in NFO directly, the only strict option mandatory on cli is --withNFO | |||||
All strict options are off by default | |||||
--withNFO Prevent the upload without a NFO, either specified via cli or found in the directory | |||||
--withThumbnail Prevent the upload without a thumbnail | |||||
--withName Prevent the upload if no name are found | |||||
--withDescription Prevent the upload without description | |||||
--withTags Prevent the upload without tags | |||||
--withPlaylist Prevent the upload if no playlist | |||||
--withPublishAt Prevent the upload if no schedule | |||||
--withOriginalDate Prevent the upload if no original date configured | |||||
--withPlatform Prevent the upload if at least one platform is not specified | |||||
--withCategory Prevent the upload if no category | |||||
--withLanguage Prevent upload if no language | |||||
--withChannel Prevent upload if no channel | |||||
Categories: | |||||
Category is the type of video you upload. Default is films. | |||||
Here are available categories from Peertube and Youtube: | |||||
music, films, vehicles, | |||||
sports, travels, gaming, people, | |||||
comedy, entertainment, news, | |||||
how to, education, activism, science & technology, | |||||
science, technology, animals | |||||
Languages: | |||||
Language of the video (audio track), choose one. Default is English | |||||
Here are available languages from Peertube and Youtube: | |||||
Arabic, English, French, German, Hindi, Italian, | |||||
Japanese, Korean, Mandarin, Portuguese, Punjabi, Russian, Spanish | |||||
""" | |||||
import sys | |||||
if sys.version_info[0] < 3: | |||||
raise Exception("Python 3 or a more recent version is required.") | |||||
import os | |||||
import datetime | |||||
import logging | |||||
logger = logging.getLogger('Prismedia') | |||||
from docopt import docopt | |||||
from . import yt_upload | |||||
from . import pt_upload | |||||
from . import utils | |||||
try: | |||||
# noinspection PyUnresolvedReferences | |||||
from schema import Schema, And, Or, Optional, SchemaError, Hook, Use | |||||
except ImportError: | |||||
logger.critical('This program requires that the `schema` data-validation library' | |||||
' is installed: \n' | |||||
'see https://github.com/halst/schema\n') | |||||
exit(1) | |||||
VERSION = "prismedia v0.12.2" | |||||
VALID_PRIVACY_STATUSES = ('public', 'private', 'unlisted') | |||||
VALID_CATEGORIES = ( | |||||
"music", "films", "vehicles", | |||||
"sports", "travels", "gaming", "people", | |||||
"comedy", "entertainment", "news", | |||||
"how to", "education", "activism", "science & technology", | |||||
"science", "technology", "animals" | |||||
) | |||||
VALID_PLATFORM = ('youtube', 'peertube', 'none') | |||||
VALID_LANGUAGES = ('arabic', 'english', 'french', | |||||
'german', 'hindi', 'italian', | |||||
'japanese', 'korean', 'mandarin', | |||||
'portuguese', 'punjabi', 'russian', 'spanish') | |||||
VALID_PROGRESS = ('percentage', 'bigfile', 'accurate') | |||||
def validateCategory(category): | |||||
if category.lower() in VALID_CATEGORIES: | |||||
return True | |||||
else: | |||||
return False | |||||
def validatePrivacy(privacy): | |||||
if privacy.lower() in VALID_PRIVACY_STATUSES: | |||||
return True | |||||
else: | |||||
return False | |||||
def validatePlatform(platform): | |||||
for plfrm in platform.split(','): | |||||
if plfrm.lower().replace(" ", "") not in VALID_PLATFORM: | |||||
return False | |||||
return True | |||||
def validateLanguage(language): | |||||
if language.lower() in VALID_LANGUAGES: | |||||
return True | |||||
else: | |||||
return False | |||||
def validatePublishDate(publishDate): | |||||
# Check date format and if date is future | |||||
try: | |||||
now = datetime.datetime.now() | |||||
publishAt = datetime.datetime.strptime(publishDate, '%Y-%m-%dT%H:%M:%S') | |||||
if now >= publishAt: | |||||
return False | |||||
except ValueError: | |||||
return False | |||||
return True | |||||
def validateOriginalDate(originalDate): | |||||
# Check date format and if date is past | |||||
try: | |||||
now = datetime.datetime.now() | |||||
originalDate = datetime.datetime.strptime(originalDate, '%Y-%m-%dT%H:%M:%S') | |||||
if now <= originalDate: | |||||
return False | |||||
except ValueError: | |||||
return False | |||||
return True | |||||
def validateLogLevel(loglevel): | |||||
numeric_level = getattr(logging, loglevel, None) | |||||
if not isinstance(numeric_level, int): | |||||
return False | |||||
return True | |||||
def validateProgress(progress): | |||||
for prgs in progress.split(','): | |||||
if prgs.lower().replace(" ", "") not in VALID_PROGRESS: | |||||
return False | |||||
return True | |||||
def _optionnalOrStrict(key, scope, error): | |||||
option = key.replace('-', '') | |||||
option = option[0].upper() + option[1:] | |||||
if scope["--with" + option] is True and scope[key] is None: | |||||
logger.critical("Prismedia: you have required the strict presence of " + key + " but none is found") | |||||
exit(1) | |||||
return True | |||||
def configureLogs(options): | |||||
if options.get('--batch') and options.get('--url-only'): | |||||
logger.critical("Prismedia: Please use either --batch OR --url-only, not both.") | |||||
exit(1) | |||||
# batch and url-only implies quiet | |||||
if options.get('--batch') or options.get('--url-only'): | |||||
options['--quiet'] = True | |||||
for handler in logger.handlers or logger.parent.handlers: | |||||
if options.get('--quiet'): | |||||
# We need to set both log level in the same time | |||||
logger.setLevel(50) | |||||
handler.setLevel(50) | |||||
elif options.get('--log'): | |||||
numeric_level = getattr(logging, options["--log"], None) | |||||
# We need to set both log level in the same time | |||||
logger.setLevel(numeric_level) | |||||
handler.setLevel(numeric_level) | |||||
elif options.get('--debug'): | |||||
# Deprecated, | |||||
logger.setLevel(10) | |||||
handler.setLevel(10) | |||||
def configureStdoutLogs(): | |||||
logger_stdout = logging.getLogger('stdoutlogs') | |||||
logger_stdout.setLevel(logging.INFO) | |||||
ch_stdout = logging.StreamHandler(stream=sys.stdout) | |||||
ch_stdout.setLevel(logging.INFO) | |||||
# Default stdout logs is url only | |||||
formatter_stdout = logging.Formatter('%(message)s') | |||||
ch_stdout.setFormatter(formatter_stdout) | |||||
logger_stdout.addHandler(ch_stdout) | |||||
def main(): | |||||
options = docopt(__doc__, version=VERSION) | |||||
earlyoptionSchema = Schema({ | |||||
Optional('--log'): Or(None, And( | |||||
str, | |||||
Use(str.upper), | |||||
validateLogLevel, | |||||
error="Log level not recognized") | |||||
), | |||||
Optional('--quiet', default=False): bool, | |||||
Optional('--debug'): bool, | |||||
Optional('--url-only', default=False): bool, | |||||
Optional('--batch', default=False): bool, | |||||
Optional('--withNFO', default=False): bool, | |||||
Optional('--withThumbnail', default=False): bool, | |||||
Optional('--withName', default=False): bool, | |||||
Optional('--withDescription', default=False): bool, | |||||
Optional('--withTags', default=False): bool, | |||||
Optional('--withPlaylist', default=False): bool, | |||||
Optional('--withPublishAt', default=False): bool, | |||||
Optional('--withOriginalDate', default=False): bool, | |||||
Optional('--withPlatform', default=False): bool, | |||||
Optional('--withCategory', default=False): bool, | |||||
Optional('--withLanguage', default=False): bool, | |||||
Optional('--withChannel', default=False): bool, | |||||
# This allow to return all other options for further use: https://github.com/keleshev/schema#extra-keys | |||||
object: object | |||||
}) | |||||
schema = Schema({ | |||||
'--file': And(str, os.path.exists, error='file does not exists, please check path'), | |||||
# Strict option checks - at the moment Schema needs to check Hook and Optional separately # | |||||
Hook('--name', handler=_optionnalOrStrict): object, | |||||
Hook('--description', handler=_optionnalOrStrict): object, | |||||
Hook('--tags', handler=_optionnalOrStrict): object, | |||||
Hook('--category', handler=_optionnalOrStrict): object, | |||||
Hook('--language', handler=_optionnalOrStrict): object, | |||||
Hook('--platform', handler=_optionnalOrStrict): object, | |||||
Hook('--publishAt', handler=_optionnalOrStrict): object, | |||||
Hook('--originalDate', handler=_optionnalOrStrict): object, | |||||
Hook('--thumbnail', handler=_optionnalOrStrict): object, | |||||
Hook('--channel', handler=_optionnalOrStrict): object, | |||||
Hook('--playlist', handler=_optionnalOrStrict): object, | |||||
# Validate checks # | |||||
Optional('--name'): Or(None, And( | |||||
str, | |||||
lambda x: not x.isdigit(), | |||||
error="The video name should be a string") | |||||
), | |||||
Optional('--description'): Or(None, And( | |||||
str, | |||||
lambda x: not x.isdigit(), | |||||
error="The video description should be a string") | |||||
), | |||||
Optional('--tags'): Or(None, And( | |||||
str, | |||||
lambda x: not x.isdigit(), | |||||
error="Tags should be a string") | |||||
), | |||||
Optional('--category'): Or(None, And( | |||||
str, | |||||
validateCategory, | |||||
error="Category not recognized, please see --help") | |||||
), | |||||
Optional('--language'): Or(None, And( | |||||
str, | |||||
validateLanguage, | |||||
error="Language not recognized, please see --help") | |||||
), | |||||
Optional('--privacy'): Or(None, And( | |||||
str, | |||||
validatePrivacy, | |||||
error="Please use recognized privacy between public, unlisted or private") | |||||
), | |||||
Optional('--nfo'): Or(None, str), | |||||
Optional('--platform'): Or(None, And(str, validatePlatform, error="Sorry, upload platform not supported")), | |||||
Optional('--publishAt'): Or(None, And( | |||||
str, | |||||
validatePublishDate, | |||||
error="Publish Date should be the form YYYY-MM-DDThh:mm:ss and has to be in the future") | |||||
), | |||||
Optional('--peertubeAt'): Or(None, And( | |||||
str, | |||||
validatePublishDate, | |||||
error="Publish Date should be the form YYYY-MM-DDThh:mm:ss and has to be in the future") | |||||
), | |||||
Optional('--youtubeAt'): Or(None, And( | |||||
str, | |||||
validatePublishDate, | |||||
error="Publish Date should be the form YYYY-MM-DDThh:mm:ss and has to be in the future") | |||||
), | |||||
Optional('--originalDate'): Or(None, And( | |||||
str, | |||||
validateOriginalDate, | |||||
error="Original date should be the form YYYY-MM-DDThh:mm:ss and has to be in the past") | |||||
), | |||||
Optional('--auto-originalDate'): bool, | |||||
Optional('--cca'): bool, | |||||
Optional('--disable-comments'): bool, | |||||
Optional('--nsfw'): bool, | |||||
Optional('--thumbnail'): Or(None, And( | |||||
str, os.path.exists, error='Thumbnail does not exists, please check the path.'), | |||||
), | |||||
Optional('--channel'): Or(None, str), | |||||
Optional('--channelCreate'): bool, | |||||
Optional('--playlist'): Or(None, str), | |||||
Optional('--playlistCreate'): bool, | |||||
Optional('--progress'): Or(None, And(str, validateProgress, error="Sorry, progress visualisation not supported")), | |||||
'--hearthbeat': bool, | |||||
'--help': bool, | |||||
'--version': bool, | |||||
# This allow to return all other options for further use: https://github.com/keleshev/schema#extra-keys | |||||
object: object | |||||
}) | |||||
if options.get('--hearthbeat'): | |||||
yt_upload.hearthbeat() | |||||
exit(0) | |||||
# We need to validate early options first as withNFO and logs options should be prioritized | |||||
try: | |||||
options = earlyoptionSchema.validate(options) | |||||
configureLogs(options) | |||||
except SchemaError as e: | |||||
logger.critical(e) | |||||
exit(1) | |||||
if options.get('--url-only') or options.get('--batch'): | |||||
configureStdoutLogs() | |||||
options = utils.parseNFO(options) | |||||
# If after loading NFO we still has no original date and --auto-originalDate is enabled, | |||||
# then we need to search from the file | |||||
# We need to do that before the strict validation in case --withOriginalDate is enabled | |||||
if not options.get('--originalDate') and options.get('--auto-originalDate'): | |||||
options['--originalDate'] = utils.searchOriginalDate(options) | |||||
# Once NFO are loaded, we need to revalidate strict options in case some were in NFO | |||||
try: | |||||
options = earlyoptionSchema.validate(options) | |||||
except SchemaError as e: | |||||
logger.critical(e) | |||||
exit(1) | |||||
if not options.get('--thumbnail'): | |||||
options = utils.searchThumbnail(options) | |||||
try: | |||||
options = schema.validate(options) | |||||
except SchemaError as e: | |||||
logger.critical(e) | |||||
exit(1) | |||||
logger.debug("Python " + sys.version) | |||||
logger.debug(options) | |||||
if options.get('--platform') is None or "peertube" in options.get('--platform'): | |||||
pt_upload.run(options) | |||||
if options.get('--platform') is None or "youtube" in options.get('--platform'): | |||||
yt_upload.run(options) | |||||
if __name__ == '__main__': | |||||
logger.warning("DEPRECATION: use 'python -m prismedia', not 'python -m prismedia.upload'") | |||||
main() |
@ -0,0 +1,240 @@ | |||||
#!/usr/bin/python | |||||
# coding: utf-8 | |||||
from configparser import RawConfigParser, NoOptionError, NoSectionError | |||||
from os.path import dirname, splitext, basename, isfile, getmtime | |||||
import re | |||||
import unidecode | |||||
import logging | |||||
import datetime | |||||
logger = logging.getLogger('Prismedia') | |||||
### CATEGORIES ### | |||||
YOUTUBE_CATEGORY = { | |||||
"music": 10, | |||||
"films": 1, | |||||
"vehicles": 2, | |||||
"sport": 17, | |||||
"travels": 19, | |||||
"gaming": 20, | |||||
"people": 22, | |||||
"comedy": 23, | |||||
"entertainment": 24, | |||||
"news": 25, | |||||
"how to": 26, | |||||
"education": 27, | |||||
"activism": 29, | |||||
"science & technology": 28, | |||||
"science": 28, | |||||
"technology": 28, | |||||
"animals": 15 | |||||
} | |||||
PEERTUBE_CATEGORY = { | |||||
"music": 1, | |||||
"films": 2, | |||||
"vehicles": 3, | |||||
"sport": 5, | |||||
"travels": 6, | |||||
"gaming": 7, | |||||
"people": 8, | |||||
"comedy": 9, | |||||
"entertainment": 10, | |||||
"news": 11, | |||||
"how to": 12, | |||||
"education": 13, | |||||
"activism": 14, | |||||
"science & technology": 15, | |||||
"science": 15, | |||||
"technology": 15, | |||||
"animals": 16 | |||||
} | |||||
### LANGUAGES ### | |||||
YOUTUBE_LANGUAGE = { | |||||
"arabic": 'ar', | |||||
"english": 'en', | |||||
"french": 'fr', | |||||
"german": 'de', | |||||
"hindi": 'hi', | |||||
"italian": 'it', | |||||
"japanese": 'ja', | |||||
"korean": 'ko', | |||||
"mandarin": 'zh-CN', | |||||
"portuguese": 'pt-PT', | |||||
"punjabi": 'pa', | |||||
"russian": 'ru', | |||||
"spanish": 'es' | |||||
} | |||||
PEERTUBE_LANGUAGE = { | |||||
"arabic": "ar", | |||||
"english": "en", | |||||
"french": "fr", | |||||
"german": "de", | |||||
"hindi": "hi", | |||||
"italian": "it", | |||||
"japanese": "ja", | |||||
"korean": "ko", | |||||
"mandarin": "zh", | |||||
"portuguese": "pt", | |||||
"punjabi": "pa", | |||||
"russian": "ru", | |||||
"spanish": "es" | |||||
} | |||||
###################### | |||||
def getCategory(category, platform): | |||||
if platform == "youtube": | |||||
return YOUTUBE_CATEGORY[category.lower()] | |||||
else: | |||||
return PEERTUBE_CATEGORY[category.lower()] | |||||
def getLanguage(language, platform): | |||||
if platform == "youtube": | |||||
return YOUTUBE_LANGUAGE[language.lower()] | |||||
else: | |||||
return PEERTUBE_LANGUAGE[language.lower()] | |||||
def ask_overwrite(question): | |||||
while True: | |||||
reply = str(input(question + ' (Yes/[No]): ') or "No").lower().strip() | |||||
if reply[:1] == 'y': | |||||
return True | |||||
if reply[:1] == 'n': | |||||
return False | |||||
def remove_empty_kwargs(**kwargs): | |||||
good_kwargs = {} | |||||
if kwargs is not None: | |||||
for key, value in kwargs.items(): | |||||
if value: | |||||
good_kwargs[key] = value | |||||
return good_kwargs | |||||
def searchThumbnail(options): | |||||
video_directory = dirname(options.get('--file')) + "/" | |||||
# First, check for thumbnail based on videoname | |||||
if options.get('--name'): | |||||
if isfile(video_directory + options.get('--name') + ".jpg"): | |||||
options['--thumbnail'] = video_directory + options.get('--name') + ".jpg" | |||||
elif isfile(video_directory + options.get('--name') + ".jpeg"): | |||||
options['--thumbnail'] = video_directory + options.get('--name') + ".jpeg" | |||||
elif isfile(video_directory + options.get('--name') + ".png"): | |||||
options['--thumbnail'] = video_directory + options.get('--name') + ".png" | |||||
# Then, if we still not have thumbnail, check for thumbnail based on videofile name | |||||
if not options.get('--thumbnail'): | |||||
video_file = splitext(basename(options.get('--file')))[0] | |||||
if isfile(video_directory + video_file + ".jpg"): | |||||
options['--thumbnail'] = video_directory + video_file + ".jpg" | |||||
elif isfile(video_directory + video_file + ".jpeg"): | |||||
options['--thumbnail'] = video_directory + video_file + ".jpeg" | |||||
elif isfile(video_directory + video_file + ".png"): | |||||
options['--thumbnail'] = video_directory + video_file + ".png" | |||||
# Display some info after research | |||||
if not options.get('--thumbnail'): | |||||
logger.debug("No thumbnail has been found, continuing") | |||||
else: | |||||
logger.info("Using " + options.get('--thumbnail') + " as thumbnail") | |||||
return options | |||||
def searchOriginalDate(options): | |||||
fileModificationDate = str(getmtime(options.get('--file'))).split('.') | |||||
return datetime.datetime.fromtimestamp(int(fileModificationDate[0])).isoformat() | |||||
# return the nfo as a RawConfigParser object | |||||
def loadNFO(filename): | |||||
try: | |||||
logger.info("Loading " + filename + " as NFO") | |||||
nfo = RawConfigParser() | |||||
nfo.read(filename, encoding='utf-8') | |||||
return nfo | |||||
except Exception as e: | |||||
logger.critical("Problem loading NFO file " + filename + ": " + str(e)) | |||||
exit(1) | |||||
return False | |||||
def parseNFO(options): | |||||
video_directory = dirname(options.get('--file')) | |||||
directory_name = basename(video_directory) | |||||
nfo_txt = False | |||||
nfo_directory = False | |||||
nfo_videoname = False | |||||
nfo_file = False | |||||
nfo_cli = False | |||||
if isfile(video_directory + "/" + "nfo.txt"): | |||||
nfo_txt = loadNFO(video_directory + "/" + "nfo.txt") | |||||
elif isfile(video_directory + "/" + "NFO.txt"): | |||||
nfo_txt = loadNFO(video_directory + "/" + "NFO.txt") | |||||
if isfile(video_directory + "/" + directory_name + ".txt"): | |||||
nfo_directory = loadNFO(video_directory + "/" + directory_name + ".txt") | |||||
if options.get('--name'): | |||||
if isfile(video_directory + "/" + options.get('--name')): | |||||
nfo_videoname = loadNFO(video_directory + "/" + options.get('--name') + ".txt") | |||||
video_file = splitext(basename(options.get('--file')))[0] | |||||
if isfile(video_directory + "/" + video_file + ".txt"): | |||||
nfo_file = loadNFO(video_directory + "/" + video_file + ".txt") | |||||
if options.get('--nfo'): | |||||
if isfile(options.get('--nfo')): | |||||
nfo_cli = loadNFO(options.get('--nfo')) | |||||
else: | |||||
logger.critical("Given NFO file does not exist, please check your path.") | |||||
exit(1) | |||||
# If there is no NFO and strict option is enabled, then stop there | |||||
if options.get('--withNFO'): | |||||
if not isinstance(nfo_cli, RawConfigParser) and \ | |||||
not isinstance(nfo_file, RawConfigParser) and \ | |||||
not isinstance(nfo_videoname, RawConfigParser) and \ | |||||
not isinstance(nfo_directory, RawConfigParser) and \ | |||||
not isinstance(nfo_txt, RawConfigParser): | |||||
logger.critical("You have required the strict presence of NFO but none is found, please use a NFO.") | |||||
exit(1) | |||||
# We need to load NFO in this exact order to keep the priorities | |||||
# options in cli > nfo_cli > nfo_file > nfo_videoname > nfo_directory > nfo_txt | |||||
for nfo in [nfo_cli, nfo_file, nfo_videoname, nfo_directory, nfo_txt]: | |||||
if nfo: | |||||
# We need to check all options and replace it with the nfo value if not defined (None or False) | |||||
for key, value in options.items(): | |||||
key = key.replace("--", "") | |||||
try: | |||||
# get string options | |||||
if value is None and nfo.get('video', key): | |||||
options['--' + key] = nfo.get('video', key) | |||||
# get boolean options | |||||
elif value is False and nfo.getboolean('video', key): | |||||
options['--' + key] = nfo.getboolean('video', key) | |||||
except NoOptionError: | |||||
continue | |||||
except NoSectionError: | |||||
logger.critical(nfo + " misses section [video], please check syntax of your NFO.") | |||||
exit(1) | |||||
return options | |||||
def upcaseFirstLetter(s): | |||||
return s[0].upper() + s[1:] | |||||
def cleanString(toclean): | |||||
toclean = unidecode.unidecode(toclean) | |||||
cleaned = re.sub('[^A-Za-z0-9]+', '', toclean) | |||||
return cleaned |
@ -1,237 +0,0 @@ | |||||
#!/usr/bin/env python2 | |||||
# coding: utf-8 | |||||
""" | |||||
prismedia_upload - tool to upload videos to Peertube and Youtube | |||||
Usage: | |||||
prismedia_upload.py --file=<FILE> [options] | |||||
prismedia_upload.py -f <FILE> --tags=STRING [options] | |||||
prismedia_upload.py -h | --help | |||||
prismedia_upload.py --version | |||||
Options: | |||||
-f, --file=STRING Path to the video file to upload in mp4 | |||||
--name=NAME Name of the video to upload. (default to video filename) | |||||
-d, --description=STRING Description of the video. (default: default description) | |||||
-t, --tags=STRING Tags for the video. comma separated. | |||||
WARN: tags with space and special characters (!, ', ", ?, ...) | |||||
are not supported by Mastodon to be published from Peertube | |||||
use mastodon compatibility below | |||||
-c, --category=STRING Category for the videos, see below. (default: Films) | |||||
--cca License should be CreativeCommon Attribution (affects Youtube upload only) | |||||
-p, --privacy=STRING Choose between public, unlisted or private. (default: private) | |||||
--disable-comments Disable comments (Peertube only as YT API does not support) (default: comments are enabled) | |||||
--nsfw Set the video as No Safe For Work (Peertube only as YT API does not support) (default: video is safe) | |||||
--nfo=STRING Configure a specific nfo file to set options for the video. | |||||
By default Prismedia search a .txt based on the video name and will | |||||
decode the file as UTF-8 (so make sure your nfo file is UTF-8 encoded) | |||||
See nfo_example.txt for more details | |||||
--platform=STRING List of platform(s) to upload to, comma separated. | |||||
Supported platforms are youtube and peertube (default is both) | |||||
--language=STRING Specify the default language for video. See below for supported language. (default is English) | |||||
--publishAt=DATE Publish the video at the given DATE using local server timezone. | |||||
DATE should be on the form YYYY-MM-DDThh:mm:ss eg: 2018-03-12T19:00:00 | |||||
DATE should be in the future | |||||
For Peertube, requires the "atd" and "curl utilities installed on the system | |||||
--thumbnail=STRING Path to a file to use as a thumbnail for the video. | |||||
Supported types are jpg and jpeg. | |||||
By default, prismedia search for an image based on video name followed by .jpg or .jpeg | |||||
--channel=STRING Set the channel to use for the video (Peertube only) | |||||
If the channel is not found, spawn an error except if --channelCreate is set. | |||||
--channelCreate Create the channel if not exists. (Peertube only, default do not create) | |||||
Only relevant if --channel is set. | |||||
--playlist=STRING Set the playlist to use for the video. Also known as Channel for Peertube. | |||||
If the playlist is not found, spawn an error except if --playlistCreate is set. | |||||
--playlistCreate Create the playlist if not exists. (default do not create) | |||||
Only relevant if --playlist is set. | |||||
-h --help Show this help. | |||||
--version Show version. | |||||
Categories: | |||||
Category is the type of video you upload. Default is films. | |||||
Here are available categories from Peertube and Youtube: | |||||
music, films, vehicles, | |||||
sports, travels, gaming, people, | |||||
comedy, entertainment, news, | |||||
how to, education, activism, science & technology, | |||||
science, technology, animals | |||||
Languages: | |||||
Language of the video (audio track), choose one. Default is English | |||||
Here are available languages from Peertube and Youtube: | |||||
Arabic, English, French, German, Hindi, Italian, | |||||
Japanese, Korean, Mandarin, Portuguese, Punjabi, Russian, Spanish | |||||
""" | |||||
from os.path import dirname, realpath | |||||
import sys | |||||
import datetime | |||||
import locale | |||||
import logging | |||||
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO) | |||||
from docopt import docopt | |||||
# Allows a relative import from the parent folder | |||||
sys.path.insert(0, dirname(realpath(__file__)) + "/lib") | |||||
import yt_upload | |||||
import pt_upload | |||||
import utils | |||||
try: | |||||
# noinspection PyUnresolvedReferences | |||||
from schema import Schema, And, Or, Optional, SchemaError | |||||
except ImportError: | |||||
logging.error('This program requires that the `schema` data-validation library' | |||||
' is installed: \n' | |||||
'see https://github.com/halst/schema\n') | |||||
exit(1) | |||||
try: | |||||
# noinspection PyUnresolvedReferences | |||||
import magic | |||||
except ImportError: | |||||
logging.error('This program requires that the `python-magic` library' | |||||
' is installed, NOT the Python bindings to libmagic API \n' | |||||
'see https://github.com/ahupp/python-magic\n') | |||||
exit(1) | |||||
VERSION = "prismedia v0.7.1" | |||||
VALID_PRIVACY_STATUSES = ('public', 'private', 'unlisted') | |||||
VALID_CATEGORIES = ( | |||||
"music", "films", "vehicles", | |||||
"sports", "travels", "gaming", "people", | |||||
"comedy", "entertainment", "news", | |||||
"how to", "education", "activism", "science & technology", | |||||
"science", "technology", "animals" | |||||
) | |||||
VALID_PLATFORM = ('youtube', 'peertube') | |||||
VALID_LANGUAGES = ('arabic', 'english', 'french', | |||||
'german', 'hindi', 'italian', | |||||
'japanese', 'korean', 'mandarin', | |||||
'portuguese', 'punjabi', 'russian', 'spanish') | |||||
def validateVideo(path): | |||||
supported_types = ['video/mp4'] | |||||
if magic.from_file(path, mime=True) in supported_types: | |||||
return path | |||||
else: | |||||
return False | |||||
def validateCategory(category): | |||||
if category.lower() in VALID_CATEGORIES: | |||||
return True | |||||
else: | |||||
return False | |||||
def validatePrivacy(privacy): | |||||
if privacy.lower() in VALID_PRIVACY_STATUSES: | |||||
return True | |||||
else: | |||||
return False | |||||
def validatePlatform(platform): | |||||
for plfrm in platform.split(','): | |||||
if plfrm.lower().replace(" ", "") not in VALID_PLATFORM: | |||||
return False | |||||
return True | |||||
def validateLanguage(language): | |||||
if language.lower() in VALID_LANGUAGES: | |||||
return True | |||||
else: | |||||
return False | |||||
def validatePublish(publish): | |||||
# Check date format and if date is future | |||||
try: | |||||
now = datetime.datetime.now() | |||||
publishAt = datetime.datetime.strptime(publish, '%Y-%m-%dT%H:%M:%S') | |||||
if now >= publishAt: | |||||
return False | |||||
except ValueError: | |||||
return False | |||||
return True | |||||
def validateThumbnail(thumbnail): | |||||
supported_types = ['image/jpg', 'image/jpeg'] | |||||
if magic.from_file(thumbnail, mime=True) in supported_types: | |||||
return thumbnail | |||||
else: | |||||
return False | |||||
if __name__ == '__main__': | |||||
options = docopt(__doc__, version=VERSION) | |||||
schema = Schema({ | |||||
'--file': And(str, validateVideo, error='file is not supported, please use mp4'), | |||||
Optional('--name'): Or(None, And( | |||||
basestring, | |||||
lambda x: not x.isdigit(), | |||||
error="The video name should be a string") | |||||
), | |||||
Optional('--description'): Or(None, And( | |||||
basestring, | |||||
lambda x: not x.isdigit(), | |||||
error="The video description should be a string") | |||||
), | |||||
Optional('--tags'): Or(None, And( | |||||
basestring, | |||||
lambda x: not x.isdigit(), | |||||
error="Tags should be a string") | |||||
), | |||||
Optional('--category'): Or(None, And( | |||||
str, | |||||
validateCategory, | |||||
error="Category not recognized, please see --help") | |||||
), | |||||
Optional('--language'): Or(None, And( | |||||
str, | |||||
validateLanguage, | |||||
error="Language not recognized, please see --help") | |||||
), | |||||
Optional('--privacy'): Or(None, And( | |||||
str, | |||||
validatePrivacy, | |||||
error="Please use recognized privacy between public, unlisted or private") | |||||
), | |||||
Optional('--nfo'): Or(None, str), | |||||
Optional('--platform'): Or(None, And(str, validatePlatform, error="Sorry, upload platform not supported")), | |||||
Optional('--publishAt'): Or(None, And( | |||||
str, | |||||
validatePublish, | |||||
error="DATE should be the form YYYY-MM-DDThh:mm:ss and has to be in the future") | |||||
), | |||||
Optional('--cca'): bool, | |||||
Optional('--disable-comments'): bool, | |||||
Optional('--nsfw'): bool, | |||||
Optional('--thumbnail'): Or(None, And( | |||||
str, validateThumbnail, error='thumbnail is not supported, please use jpg/jpeg'), | |||||
), | |||||
Optional('--channel'): Or(None, str), | |||||
Optional('--channelCreate'): bool, | |||||
Optional('--playlist'): Or(None, str), | |||||
Optional('--playlistCreate'): bool, | |||||
'--help': bool, | |||||
'--version': bool | |||||
}) | |||||
utils.decodeArgumentStrings(options, locale.getpreferredencoding()) | |||||
options = utils.parseNFO(options) | |||||
if not options.get('--thumbnail'): | |||||
options = utils.searchThumbnail(options) | |||||
try: | |||||
options = schema.validate(options) | |||||
except SchemaError as e: | |||||
exit(e) | |||||
if options.get('--platform') is None or "youtube" in options.get('--platform'): | |||||
yt_upload.run(options) | |||||
if options.get('--platform') is None or "peertube" in options.get('--platform'): | |||||
pt_upload.run(options) |
@ -0,0 +1,49 @@ | |||||
[tool.poetry] | |||||
name = "prismedia" | |||||
version = "0.12.2" | |||||
description = "scripting your way to upload videos on peertube and youtube" | |||||
authors = [ | |||||
"LecygneNoir <git@lecygnenoir.info>", | |||||
"Rigel Kent <sendmemail@rigelk.eu>", | |||||
"Zykino" | |||||
] | |||||
license = "AGPL-3.0-only" | |||||
readme = 'README.md' | |||||
repository = "https://git.lecygnenoir.info/LecygneNoir/prismedia" | |||||
homepage = "https://git.lecygnenoir.info/LecygneNoir/prismedia" | |||||
keywords = ['peertube', 'youtube', 'prismedia'] | |||||
[tool.poetry.dependencies] | |||||
python = ">=3.6" | |||||
clint = ">=0.5.1" | |||||
configparser = ">=3.7.1" | |||||
docopt = ">=0.6.2" | |||||
future = ">=0.17.1" | |||||
google-api-python-client = ">=1.7.6" | |||||
google-auth = ">=1.6.1" | |||||
google-auth-httplib2 = ">=0.0.3" | |||||
google-auth-oauthlib = ">=0.2.0" | |||||
httplib2 = ">=0.12.1" | |||||
oauthlib = "=2.1.0" | |||||
requests = ">=2.18.4" | |||||
requests-oauthlib = "=1.1.0" | |||||
requests-toolbelt = ">=0.9.1" | |||||
pytz = "=2022.1" | |||||
schema = ">=0.7.1" | |||||
tzlocal = ">=1.5.1" | |||||
Unidecode = ">=1.0.23" | |||||
uritemplate = ">=3.0.0" | |||||
urllib3 = ">=1.22" | |||||
[tool.poetry.dev-dependencies] | |||||
[tool.poetry.scripts] | |||||
prismedia = 'prismedia.upload:main' | |||||
prismedia-init = 'prismedia.genconfig:genconfig' | |||||
[build-system] | |||||
requires = ["poetry>=0.12"] | |||||
build-backend = "poetry.masonry.api" |
@ -0,0 +1,139 @@ | |||||
args==0.1.0 \ | |||||
--hash=sha256:a785b8d837625e9b61c39108532d95b85274acd679693b71ebb5156848fcf814 | |||||
cachetools==3.1.1 \ | |||||
--hash=sha256:428266a1c0d36dc5aca63a2d7c5942e88c2c898d72139fca0e97fdd2380517ae \ | |||||
--hash=sha256:8ea2d3ce97850f31e4a08b0e2b5e6c34997d7216a9d2c98e0f3978630d4da69a | |||||
certifi==2020.12.5 \ | |||||
--hash=sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830 \ | |||||
--hash=sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c | |||||
chardet==4.0.0 \ | |||||
--hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 \ | |||||
--hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa | |||||
clint==0.5.1 \ | |||||
--hash=sha256:05224c32b1075563d0b16d0015faaf9da43aa214e4a2140e51f08789e7a4c5aa | |||||
configparser==5.0.2 \ | |||||
--hash=sha256:af59f2cdd7efbdd5d111c1976ecd0b82db9066653362f0962d7bf1d3ab89a1fa \ | |||||
--hash=sha256:85d5de102cfe6d14a5172676f09d19c465ce63d6019cf0a4ef13385fc535e828 | |||||
contextlib2==0.6.0.post1 \ | |||||
--hash=sha256:3355078a159fbb44ee60ea80abd0d87b80b78c248643b49aa6d94673b413609b \ | |||||
--hash=sha256:01f490098c18b19d2bd5bb5dc445b2054d2fa97f09a4280ba2c5f3c394c8162e | |||||
docopt==0.6.2 \ | |||||
--hash=sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491 | |||||
future==0.18.2 \ | |||||
--hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d | |||||
google-api-core==1.26.3 \ | |||||
--hash=sha256:b914345c7ea23861162693a27703bab804a55504f7e6e9abcaff174d80df32ac \ | |||||
--hash=sha256:099762d4b4018cd536bcf85136bf337957da438807572db52f21dc61251be089 | |||||
google-api-python-client==2.1.0 \ | |||||
--hash=sha256:f9ac377efe69571aea1acc9e15760d4204aca23c4464eb63f963ae4defc95d97 \ | |||||
--hash=sha256:921fe10cdff22d1f5b8af7473f9a298efb991fb6ea67dadd6c37fbecfb0575f4 | |||||
google-auth==1.28.1 \ | |||||
--hash=sha256:70b39558712826e41f65e5f05a8d879361deaf84df8883e5dd0ec3d0da6ab66e \ | |||||
--hash=sha256:186fe2564634d67fbbb64f3daf8bc8c9cecbb2a7f535ed1a8a71795e50db8d87 | |||||
google-auth-httplib2==0.1.0 \ | |||||
--hash=sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac \ | |||||
--hash=sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10 | |||||
google-auth-oauthlib==0.4.4 \ | |||||
--hash=sha256:09832c6e75032f93818edf1affe4746121d640c625a5bef9b5c96af676e98eee \ | |||||
--hash=sha256:0e92aacacfb94978de3b7972cf4b0f204c3cd206f74ddd0dc0b31e91164e6317 | |||||
googleapis-common-protos==1.53.0 \ | |||||
--hash=sha256:a88ee8903aa0a81f6c3cec2d5cf62d3c8aa67c06439b0496b49048fb1854ebf4 \ | |||||
--hash=sha256:f6d561ab8fb16b30020b940e2dd01cd80082f4762fa9f3ee670f4419b4b8dbd0 | |||||
httplib2==0.19.1 \ | |||||
--hash=sha256:2ad195faf9faf079723f6714926e9a9061f694d07724b846658ce08d40f522b4 \ | |||||
--hash=sha256:0b12617eeca7433d4c396a100eaecfa4b08ee99aa881e6df6e257a7aad5d533d | |||||
idna==2.10 \ | |||||
--hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 \ | |||||
--hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 | |||||
oauthlib==3.1.0 \ | |||||
--hash=sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea \ | |||||
--hash=sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889 | |||||
packaging==20.9 \ | |||||
--hash=sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a \ | |||||
--hash=sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5 | |||||
protobuf==3.15.8 \ | |||||
--hash=sha256:fad4f971ec38d8df7f4b632c819bf9bbf4f57cfd7312cf526c69ce17ef32436a \ | |||||
--hash=sha256:f17b352d7ce33c81773cf81d536ca70849de6f73c96413f17309f4b43ae7040b \ | |||||
--hash=sha256:4a054b0b5900b7ea7014099e783fb8c4618e4209fffcd6050857517b3f156e18 \ | |||||
--hash=sha256:efa4c4d4fc9ba734e5e85eaced70e1b63fb3c8d08482d839eb838566346f1737 \ | |||||
--hash=sha256:07eec4e2ccbc74e95bb9b3afe7da67957947ee95bdac2b2e91b038b832dd71f0 \ | |||||
--hash=sha256:f9cadaaa4065d5dd4d15245c3b68b967b3652a3108e77f292b58b8c35114b56c \ | |||||
--hash=sha256:2dc0e8a9e4962207bdc46a365b63a3f1aca6f9681a5082a326c5837ef8f4b745 \ | |||||
--hash=sha256:f80afc0a0ba13339bbab25ca0409e9e2836b12bb012364c06e97c2df250c3343 \ | |||||
--hash=sha256:c5566f956a26cda3abdfacc0ca2e21db6c9f3d18f47d8d4751f2209d6c1a5297 \ | |||||
--hash=sha256:dab75b56a12b1ceb3e40808b5bd9dfdaef3a1330251956e6744e5b6ed8f8830b \ | |||||
--hash=sha256:3053f13207e7f13dc7be5e9071b59b02020172f09f648e85dc77e3fcb50d1044 \ | |||||
--hash=sha256:1f0b5d156c3df08cc54bc2c8b8b875648ea4cd7ebb2a9a130669f7547ec3488c \ | |||||
--hash=sha256:90270fe5732c1f1ff664a3bd7123a16456d69b4e66a09a139a00443a32f210b8 \ | |||||
--hash=sha256:f42c2f5fb67da5905bfc03733a311f72fa309252bcd77c32d1462a1ad519521e \ | |||||
--hash=sha256:f6077db37bfa16494dca58a4a02bfdacd87662247ad6bc1f7f8d13ff3f0013e1 \ | |||||
--hash=sha256:510e66491f1a5ac5953c908aa8300ec47f793130097e4557482803b187a8ee05 \ | |||||
--hash=sha256:5ff9fa0e67fcab442af9bc8d4ec3f82cb2ff3be0af62dba047ed4187f0088b7d \ | |||||
--hash=sha256:1c0e9e56202b9dccbc094353285a252e2b7940b74fdf75f1b4e1b137833fabd7 \ | |||||
--hash=sha256:a0a08c6b2e6d6c74a6eb5bf6184968eefb1569279e78714e239d33126e753403 \ | |||||
--hash=sha256:0277f62b1e42210cafe79a71628c1d553348da81cbd553402a7f7549c50b11d0 | |||||
pyasn1==0.4.8 \ | |||||
--hash=sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3 \ | |||||
--hash=sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf \ | |||||
--hash=sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00 \ | |||||
--hash=sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8 \ | |||||
--hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \ | |||||
--hash=sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86 \ | |||||
--hash=sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7 \ | |||||
--hash=sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576 \ | |||||
--hash=sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12 \ | |||||
--hash=sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2 \ | |||||
--hash=sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359 \ | |||||
--hash=sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776 \ | |||||
--hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba | |||||
pyasn1-modules==0.2.8 \ | |||||
--hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \ | |||||
--hash=sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199 \ | |||||
--hash=sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405 \ | |||||
--hash=sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb \ | |||||
--hash=sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8 \ | |||||
--hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 \ | |||||
--hash=sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d \ | |||||
--hash=sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45 \ | |||||
--hash=sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4 \ | |||||
--hash=sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811 \ | |||||
--hash=sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed \ | |||||
--hash=sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0 \ | |||||
--hash=sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd | |||||
pyparsing==2.4.7 \ | |||||
--hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b \ | |||||
--hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 | |||||
pytz==2021.1 \ | |||||
--hash=sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798 \ | |||||
--hash=sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da | |||||
requests==2.25.1 \ | |||||
--hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e \ | |||||
--hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 | |||||
requests-oauthlib==1.3.0 \ | |||||
--hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a \ | |||||
--hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \ | |||||
--hash=sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc | |||||
requests-toolbelt==0.9.1 \ | |||||
--hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 \ | |||||
--hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f | |||||
rsa==4.4; python_version >= "3.6" \ | |||||
--hash=sha256:4afbaaecc3e9550c7351fdf0ab3fea1857ff616b85bab59215f00fb42e0e9582 \ | |||||
--hash=sha256:5d95293bbd0fbee1dd9cb4b72d27b723942eb50584abc8c4f5f00e4bcfa55307 | |||||
schema==0.7.4 \ | |||||
--hash=sha256:cf97e4cd27e203ab6bb35968532de1ed8991bce542a646f0ff1d643629a4945d \ | |||||
--hash=sha256:fbb6a52eb2d9facf292f233adcc6008cffd94343c63ccac9a1cb1f3e6de1db17 | |||||
six==1.15.0 \ | |||||
--hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced \ | |||||
--hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 | |||||
tzlocal==2.1 \ | |||||
--hash=sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4 \ | |||||
--hash=sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44 | |||||
unidecode==1.2.0 \ | |||||
--hash=sha256:12435ef2fc4cdfd9cf1035a1db7e98b6b047fe591892e81f34e94959591fad00 \ | |||||
--hash=sha256:8d73a97d387a956922344f6b74243c2c6771594659778744b2dbdaad8f6b727d | |||||
uritemplate==3.0.1 \ | |||||
--hash=sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f \ | |||||
--hash=sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae | |||||
urllib3==1.22 \ | |||||
--hash=sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b \ | |||||
--hash=sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f |