From 003830696f5eb1ece7bfb8083c8d515d65ecbf26 Mon Sep 17 00:00:00 2001 From: LecygneNoir Date: Tue, 14 Jul 2020 13:18:53 +0200 Subject: [PATCH 1/8] Add strict options to force and check the existence of specific option before uploading, following issue #36. Need upgrade of Schema to have Hooks. --- poetry.lock | 147 +++++++++++++++++++++++++------------------- prismedia/upload.py | 81 ++++++++++++++++++++++-- prismedia/utils.py | 12 +++- pyproject.toml | 2 +- 4 files changed, 171 insertions(+), 71 deletions(-) diff --git a/poetry.lock b/poetry.lock index d69caf7..1f1ffc6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -12,7 +12,7 @@ description = "Python package for providing Mozilla's CA Bundle." name = "certifi" optional = false python-versions = "*" -version = "2020.4.5.1" +version = "2020.6.20" [[package]] category = "main" @@ -34,6 +34,14 @@ version = "3.8.1" docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2)", "pytest-flake8"] +[[package]] +category = "main" +description = "Backports and enhancements for the contextlib module" +name = "contextlib2" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.6.0.post1" + [[package]] category = "main" description = "Pythonic argument parser, that will make you smile" @@ -56,19 +64,19 @@ description = "Google API client core library" name = "google-api-core" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "1.17.0" +version = "1.21.0" [package.dependencies] -google-auth = ">=1.14.0,<2.0dev" +google-auth = ">=1.18.0,<2.0dev" googleapis-common-protos = ">=1.6.0,<2.0dev" -protobuf = ">=3.4.0" +protobuf = ">=3.12.0" pytz = "*" requests = ">=2.18.0,<3.0.0dev" setuptools = ">=34.0.0" six = ">=1.10.0" [package.extras] -grpc = ["grpcio (>=1.8.2,<2.0dev)"] +grpc = ["grpcio (>=1.29.0,<2.0dev)"] grpcgcp = ["grpcio-gcp (>=0.2.2)"] grpcio-gcp = ["grpcio-gcp (>=0.2.2)"] @@ -78,11 +86,11 @@ description = "Google API Client Library for Python" name = "google-api-python-client" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "1.8.2" +version = "1.9.3" [package.dependencies] -google-api-core = ">=1.13.0,<2dev" -google-auth = ">=1.4.1" +google-api-core = ">=1.18.0,<2dev" +google-auth = ">=1.16.0" google-auth-httplib2 = ">=0.0.3" httplib2 = ">=0.9.2,<1dev" six = ">=1.6.1,<2dev" @@ -94,15 +102,18 @@ description = "Google Authentication Library" name = "google-auth" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "1.14.1" +version = "1.18.0" [package.dependencies] cachetools = ">=2.0.0,<5.0" pyasn1-modules = ">=0.2.1" -rsa = ">=3.1.4,<4.1" setuptools = ">=40.3.0" six = ">=1.9.0" +[package.dependencies.rsa] +python = ">=3" +version = ">=3.1.4,<5" + [[package]] category = "main" description = "Google Authentication Library: httplib2 transport" @@ -135,8 +146,8 @@ category = "main" description = "Common protobufs used in Google APIs" name = "googleapis-common-protos" optional = false -python-versions = "*" -version = "1.51.0" +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "1.52.0" [package.dependencies] protobuf = ">=3.6.0" @@ -158,7 +169,7 @@ description = "Internationalized Domain Names in Applications (IDNA)" name = "idna" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.9" +version = "2.10" [[package]] category = "main" @@ -180,7 +191,7 @@ description = "Protocol Buffers" name = "protobuf" optional = false python-versions = "*" -version = "3.11.3" +version = "3.12.2" [package.dependencies] setuptools = "*" @@ -210,8 +221,8 @@ category = "main" description = "File type identification using libmagic" name = "python-magic" optional = false -python-versions = "*" -version = "0.4.15" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.18" [[package]] category = "main" @@ -228,7 +239,7 @@ description = "World timezone definitions, modern and historical" name = "pytz" optional = false python-versions = "*" -version = "2019.3" +version = "2020.1" [[package]] category = "main" @@ -236,7 +247,7 @@ description = "Python HTTP for Humans." name = "requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.23.0" +version = "2.24.0" [package.dependencies] certifi = ">=2017.4.17" @@ -277,10 +288,11 @@ requests = ">=2.0.1,<3.0.0" [[package]] category = "main" description = "Pure-Python RSA implementation" +marker = "python_version >= \"3\"" name = "rsa" optional = false python-versions = "*" -version = "4.0" +version = "4.4" [package.dependencies] pyasn1 = ">=0.1.3" @@ -291,7 +303,10 @@ description = "Simple data validation library" name = "schema" optional = false python-versions = "*" -version = "0.6.8" +version = "0.7.2" + +[package.dependencies] +contextlib2 = ">=0.5.5" [[package]] category = "main" @@ -299,7 +314,7 @@ description = "Python 2 and 3 compatibility utilities" name = "six" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.14.0" +version = "1.15.0" [[package]] category = "main" @@ -341,7 +356,7 @@ secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "cer socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] [metadata] -content-hash = "b3063876dbcd6443d0459a9ef376ccdba2a21adc2e7a49d75c9450904b40615f" +content-hash = "f8eb99e94228881cc7a2c1f7fe0524732f7050ecc629321b275d7fe5c8e0e12c" python-versions = ">=3.5" [metadata.files] @@ -350,8 +365,8 @@ cachetools = [ {file = "cachetools-3.1.1.tar.gz", hash = "sha256:8ea2d3ce97850f31e4a08b0e2b5e6c34997d7216a9d2c98e0f3978630d4da69a"}, ] certifi = [ - {file = "certifi-2020.4.5.1-py2.py3-none-any.whl", hash = "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304"}, - {file = "certifi-2020.4.5.1.tar.gz", hash = "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"}, + {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, + {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, ] chardet = [ {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, @@ -361,6 +376,10 @@ configparser = [ {file = "configparser-3.8.1-py2.py3-none-any.whl", hash = "sha256:45d1272aad6cfd7a8a06cf5c73f2ceb6a190f6acc1fa707e7f82a4c053b28b18"}, {file = "configparser-3.8.1.tar.gz", hash = "sha256:bc37850f0cc42a1725a796ef7d92690651bf1af37d744cc63161dac62cabee17"}, ] +contextlib2 = [ + {file = "contextlib2-0.6.0.post1-py2.py3-none-any.whl", hash = "sha256:3355078a159fbb44ee60ea80abd0d87b80b78c248643b49aa6d94673b413609b"}, + {file = "contextlib2-0.6.0.post1.tar.gz", hash = "sha256:01f490098c18b19d2bd5bb5dc445b2054d2fa97f09a4280ba2c5f3c394c8162e"}, +] docopt = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] @@ -368,16 +387,16 @@ future = [ {file = "future-0.17.1.tar.gz", hash = "sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8"}, ] google-api-core = [ - {file = "google-api-core-1.17.0.tar.gz", hash = "sha256:e4082a0b479dc2dee2f8d7b80ea8b5d0184885b773caab15ab1836277a01d689"}, - {file = "google_api_core-1.17.0-py2.py3-none-any.whl", hash = "sha256:c0e430658ed6be902d7ba7095fb0a9cac810270d71bf7ac4484e76c300407aae"}, + {file = "google-api-core-1.21.0.tar.gz", hash = "sha256:fea9a434068406ddabe2704988d24d6c5bde3ecfc40823a34f43892d017b14f6"}, + {file = "google_api_core-1.21.0-py2.py3-none-any.whl", hash = "sha256:7b65e8e5ee59bd7517eab2bf9b3008e7b50fd9fb591d4efd780ead6859cd904b"}, ] google-api-python-client = [ - {file = "google-api-python-client-1.8.2.tar.gz", hash = "sha256:bf482c13fb41a6d01770f9d62be6b33fdcd41d68c97f2beb9be02297bdd9e725"}, - {file = "google_api_python_client-1.8.2-py3-none-any.whl", hash = "sha256:8dd35a3704650c2db44e6cf52abdaf9de71f409c93c56bbe48a321ab5e14ebad"}, + {file = "google-api-python-client-1.9.3.tar.gz", hash = "sha256:220349ce189a85229fc46875d467101318495a4a735c0ff2f165b9bdbc7511a0"}, + {file = "google_api_python_client-1.9.3-py3-none-any.whl", hash = "sha256:f8e73dd6433f8218922c952e09adc4fc0dbc360f9959cf427565a16e8d4c5d25"}, ] google-auth = [ - {file = "google-auth-1.14.1.tar.gz", hash = "sha256:e63b2210e03c4ed829063b72c4af0c4b867c2788efb3210b6b9439b488bd3afd"}, - {file = "google_auth-1.14.1-py2.py3-none-any.whl", hash = "sha256:0c41a453b9a8e77975bfa436b8daedac00aed1c545d84410daff8272fff40fbb"}, + {file = "google-auth-1.18.0.tar.gz", hash = "sha256:d6b390d3bb0969061ffec7e5766c45c1b39e13c302691e35029f1ad1ccd8ca3b"}, + {file = "google_auth-1.18.0-py2.py3-none-any.whl", hash = "sha256:5e3f540b7b0b892000d542cea6b818b837c230e9a4db9337bb2973bcae0fc078"}, ] google-auth-httplib2 = [ {file = "google-auth-httplib2-0.0.3.tar.gz", hash = "sha256:098fade613c25b4527b2c08fa42d11f3c2037dda8995d86de0745228e965d445"}, @@ -388,40 +407,40 @@ google-auth-oauthlib = [ {file = "google_auth_oauthlib-0.2.0-py2.py3-none-any.whl", hash = "sha256:81ba22acada4d13b1d83f9371ab19fd61f1250a542d21cf49e4dcf0637a7344a"}, ] googleapis-common-protos = [ - {file = "googleapis-common-protos-1.51.0.tar.gz", hash = "sha256:013c91704279119150e44ef770086fdbba158c1f978a6402167d47d5409e226e"}, + {file = "googleapis-common-protos-1.52.0.tar.gz", hash = "sha256:560716c807117394da12cecb0a54da5a451b5cf9866f1d37e9a5e2329a665351"}, + {file = "googleapis_common_protos-1.52.0-py2.py3-none-any.whl", hash = "sha256:c8961760f5aad9a711d37b675be103e0cc4e9a39327e0d6d857872f698403e24"}, ] httplib2 = [ {file = "httplib2-0.12.3-py3-none-any.whl", hash = "sha256:23914b5487dfe8ef09db6656d6d63afb0cf3054ad9ebc50868ddc8e166b5f8e8"}, {file = "httplib2-0.12.3.tar.gz", hash = "sha256:a18121c7c72a56689efbf1aef990139ad940fee1e64c6f2458831736cd593600"}, ] idna = [ - {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, - {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] 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.11.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ef2c2e56aaf9ee914d3dccc3408d42661aaf7d9bb78eaa8f17b2e6282f214481"}, - {file = "protobuf-3.11.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:dd9aa4401c36785ea1b6fff0552c674bdd1b641319cb07ed1fe2392388e9b0d7"}, - {file = "protobuf-3.11.3-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:310a7aca6e7f257510d0c750364774034272538d51796ca31d42c3925d12a52a"}, - {file = "protobuf-3.11.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:e512b7f3a4dd780f59f1bf22c302740e27b10b5c97e858a6061772668cd6f961"}, - {file = "protobuf-3.11.3-cp35-cp35m-win32.whl", hash = "sha256:fdfb6ad138dbbf92b5dbea3576d7c8ba7463173f7d2cb0ca1bd336ec88ddbd80"}, - {file = "protobuf-3.11.3-cp35-cp35m-win_amd64.whl", hash = "sha256:e2f8a75261c26b2f5f3442b0525d50fd79a71aeca04b5ec270fc123536188306"}, - {file = "protobuf-3.11.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c40973a0aee65422d8cb4e7d7cbded95dfeee0199caab54d5ab25b63bce8135a"}, - {file = "protobuf-3.11.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:adf0e4d57b33881d0c63bb11e7f9038f98ee0c3e334c221f0858f826e8fb0151"}, - {file = "protobuf-3.11.3-cp36-cp36m-win32.whl", hash = "sha256:0bae429443cc4748be2aadfdaf9633297cfaeb24a9a02d0ab15849175ce90fab"}, - {file = "protobuf-3.11.3-cp36-cp36m-win_amd64.whl", hash = "sha256:e11df1ac6905e81b815ab6fd518e79be0a58b5dc427a2cf7208980f30694b956"}, - {file = "protobuf-3.11.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7774bbbaac81d3ba86de646c39f154afc8156717972bf0450c9dbfa1dc8dbea2"}, - {file = "protobuf-3.11.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8eb9c93798b904f141d9de36a0ba9f9b73cc382869e67c9e642c0aba53b0fc07"}, - {file = "protobuf-3.11.3-cp37-cp37m-win32.whl", hash = "sha256:fac513a9dc2a74b99abd2e17109b53945e364649ca03d9f7a0b96aa8d1807d0a"}, - {file = "protobuf-3.11.3-cp37-cp37m-win_amd64.whl", hash = "sha256:82d7ac987715d8d1eb4068bf997f3053468e0ce0287e2729c30601feb6602fee"}, - {file = "protobuf-3.11.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:73152776dc75f335c476d11d52ec6f0f6925774802cd48d6189f4d5d7fe753f4"}, - {file = "protobuf-3.11.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:52e586072612c1eec18e1174f8e3bb19d08f075fc2e3f91d3b16c919078469d0"}, - {file = "protobuf-3.11.3-py2.7.egg", hash = "sha256:2affcaba328c4662f3bc3c0e9576ea107906b2c2b6422344cdad961734ff6b93"}, - {file = "protobuf-3.11.3-py2.py3-none-any.whl", hash = "sha256:24e3b6ad259544d717902777b33966a1a069208c885576254c112663e6a5bb0f"}, - {file = "protobuf-3.11.3.tar.gz", hash = "sha256:c77c974d1dadf246d789f6dad1c24426137c9091e930dbf50e0a29c1fcf00b1f"}, + {file = "protobuf-3.12.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e1464a4a2cf12f58f662c8e6421772c07947266293fb701cb39cd9c1e183f63c"}, + {file = "protobuf-3.12.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:6f349adabf1c004aba53f7b4633459f8ca8a09654bf7e69b509c95a454755776"}, + {file = "protobuf-3.12.2-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:be04fe14ceed7f8641e30f36077c1a654ff6f17d0c7a5283b699d057d150d82a"}, + {file = "protobuf-3.12.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f4b73736108a416c76c17a8a09bc73af3d91edaa26c682aaa460ef91a47168d3"}, + {file = "protobuf-3.12.2-cp35-cp35m-win32.whl", hash = "sha256:5524c7020eb1fb7319472cb75c4c3206ef18b34d6034d2ee420a60f99cddeb07"}, + {file = "protobuf-3.12.2-cp35-cp35m-win_amd64.whl", hash = "sha256:bff02030bab8b969f4de597543e55bd05e968567acb25c0a87495a31eb09e925"}, + {file = "protobuf-3.12.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c9ca9f76805e5a637605f171f6c4772fc4a81eced4e2f708f79c75166a2c99ea"}, + {file = "protobuf-3.12.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:304e08440c4a41a0f3592d2a38934aad6919d692bb0edfb355548786728f9a5e"}, + {file = "protobuf-3.12.2-cp36-cp36m-win32.whl", hash = "sha256:b5a114ea9b7fc90c2cc4867a866512672a47f66b154c6d7ee7e48ddb68b68122"}, + {file = "protobuf-3.12.2-cp36-cp36m-win_amd64.whl", hash = "sha256:85b94d2653b0fdf6d879e39d51018bf5ccd86c81c04e18a98e9888694b98226f"}, + {file = "protobuf-3.12.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a7ab28a8f1f043c58d157bceb64f80e4d2f7f1b934bc7ff5e7f7a55a337ea8b0"}, + {file = "protobuf-3.12.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:eafe9fa19fcefef424ee089fb01ac7177ff3691af7cc2ae8791ae523eb6ca907"}, + {file = "protobuf-3.12.2-cp37-cp37m-win32.whl", hash = "sha256:612bc97e42b22af10ba25e4140963fbaa4c5181487d163f4eb55b0b15b3dfcd2"}, + {file = "protobuf-3.12.2-cp37-cp37m-win_amd64.whl", hash = "sha256:e72736dd822748b0721f41f9aaaf6a5b6d5cfc78f6c8690263aef8bba4457f0e"}, + {file = "protobuf-3.12.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:87535dc2d2ef007b9d44e309d2b8ea27a03d2fa09556a72364d706fcb7090828"}, + {file = "protobuf-3.12.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:50b5fee674878b14baea73b4568dc478c46a31dd50157a5b5d2f71138243b1a9"}, + {file = "protobuf-3.12.2-py2.py3-none-any.whl", hash = "sha256:a96f8fc625e9ff568838e556f6f6ae8eca8b4837cdfb3f90efcb7c00e342a2eb"}, + {file = "protobuf-3.12.2.tar.gz", hash = "sha256:49ef8ab4c27812a89a76fa894fe7a08f42f2147078392c0dee51d4a444ef6df5"}, ] pyasn1 = [ {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, @@ -454,8 +473,8 @@ pyasn1-modules = [ {file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"}, ] python-magic = [ - {file = "python-magic-0.4.15.tar.gz", hash = "sha256:f3765c0f582d2dfc72c15f3b5a82aecfae9498bd29ca840d72f37d7bd38bfcd5"}, - {file = "python_magic-0.4.15-py2.py3-none-any.whl", hash = "sha256:f2674dcfad52ae6c49d4803fa027809540b130db1dec928cfbb9240316831375"}, + {file = "python-magic-0.4.18.tar.gz", hash = "sha256:b757db2a5289ea3f1ced9e60f072965243ea43a2221430048fd8cacab17be0ce"}, + {file = "python_magic-0.4.18-py2.py3-none-any.whl", hash = "sha256:356efa93c8899047d1eb7d3eb91e871ba2f5b1376edbaf4cc305e3c872207355"}, ] python-magic-bin = [ {file = "python_magic_bin-0.4.14-py2.py3-none-macosx_10_6_intel.whl", hash = "sha256:7b1743b3dbf16601d6eedf4e7c2c9a637901b0faaf24ad4df4d4527e7d8f66a4"}, @@ -463,12 +482,12 @@ python-magic-bin = [ {file = "python_magic_bin-0.4.14-py2.py3-none-win_amd64.whl", hash = "sha256:90be6206ad31071a36065a2fc169c5afb5e0355cbe6030e87641c6c62edc2b69"}, ] pytz = [ - {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"}, - {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, + {file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"}, + {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"}, ] requests = [ - {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"}, - {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"}, + {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, + {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, ] requests-oauthlib = [ {file = "requests-oauthlib-0.8.0.tar.gz", hash = "sha256:883ac416757eada6d3d07054ec7092ac21c7f35cb1d2cf82faf205637081f468"}, @@ -479,16 +498,16 @@ requests-toolbelt = [ {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, ] rsa = [ - {file = "rsa-4.0-py2.py3-none-any.whl", hash = "sha256:14ba45700ff1ec9eeb206a2ce76b32814958a98e372006c8fb76ba820211be66"}, - {file = "rsa-4.0.tar.gz", hash = "sha256:1a836406405730121ae9823e19c6e806c62bbad73f890574fff50efa4122c487"}, + {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.6.8-py2.py3-none-any.whl", hash = "sha256:d994b0dc4966000037b26898df638e3e2a694cc73636cb2050e652614a350687"}, - {file = "schema-0.6.8.tar.gz", hash = "sha256:fa1a53fe5f3b6929725a4e81688c250f46838e25d8c1885a10a590c8c01a7b74"}, + {file = "schema-0.7.2-py2.py3-none-any.whl", hash = "sha256:3a03c2e2b22e6a331ae73750ab1da46916da6ca861b16e6f073ac1d1eba43b71"}, + {file = "schema-0.7.2.tar.gz", hash = "sha256:b536f2375b49fdf56f36279addae98bd86a8afbd58b3c32ce363c464bed5fc1c"}, ] six = [ - {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, - {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, ] tzlocal = [ {file = "tzlocal-1.5.1.tar.gz", hash = "sha256:4ebeb848845ac898da6519b9b31879cf13b6626f7184c496037b818e238f2c4e"}, diff --git a/prismedia/upload.py b/prismedia/upload.py index ee328e5..2e83bd5 100755 --- a/prismedia/upload.py +++ b/prismedia/upload.py @@ -49,6 +49,26 @@ Options: -h --help Show this help. --version Show version. +Strict options: + Strict options allow you to force some option to be present when uploading a video. It's useful to be sue you do not + forget something when uploading a vidéo, 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 + --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: @@ -69,6 +89,7 @@ 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 logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO) @@ -81,7 +102,7 @@ from . import utils try: # noinspection PyUnresolvedReferences - from schema import Schema, And, Or, Optional, SchemaError + from schema import Schema, And, Or, Optional, SchemaError, Hook except ImportError: logging.error('This program requires that the `schema` data-validation library' ' is installed: \n' @@ -170,17 +191,54 @@ def validatePublish(publish): def validateThumbnail(thumbnail): supported_types = ['image/jpg', 'image/jpeg'] - if magic.from_file(thumbnail, mime=True) in supported_types: + if os.path.exists(thumbnail) and \ + magic.from_file(thumbnail, mime=True) in supported_types: return thumbnail else: return False +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: + logging.error("Prismedia: you have required the strict presence of " + key + " but none is found") + exit(1) + return True + + def main(): options = docopt(__doc__, version=VERSION) + strictoptionSchema = Schema({ + 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('--withPlatform', default=False): bool, + Optional('--withCategory', default=False): bool, + Optional('--withLanguage', default=False): bool, + Optional('--withChannel', default=False): bool, + object: object # This allow to return all other options for further use: https://github.com/keleshev/schema#extra-keys + }) + schema = Schema({ - '--file': And(str, validateVideo, error='file is not supported, please use mp4'), + '--file': And(str, os.path.exists, validateVideo, error='file is not supported, please use mp4'), + # 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('--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(), @@ -240,11 +298,24 @@ def main(): Optional('--playlist'): Or(None, str), Optional('--playlistCreate'): bool, '--help': bool, - '--version': bool + '--version': bool, + object: object # This allow to return all other options for further use: https://github.com/keleshev/schema#extra-keys }) + # We need to validate strict options first as withNFO should be validated before NFO parsing + try: + options = strictoptionSchema.validate(options) + except SchemaError as e: + exit(e) + options = utils.parseNFO(options) + # Once NFO are loaded, we need to revalidate strict options in case some were in NFO + try: + options = strictoptionSchema.validate(options) + except SchemaError as e: + exit(e) + if not options.get('--thumbnail'): options = utils.searchThumbnail(options) @@ -254,7 +325,7 @@ def main(): exit(e) if options.get('--debug'): - print(sys.version) + print("Python " + sys.version) print(options) if options.get('--platform') is None or "peertube" in options.get('--platform'): diff --git a/prismedia/utils.py b/prismedia/utils.py index b9e4c89..36b54fb 100644 --- a/prismedia/utils.py +++ b/prismedia/utils.py @@ -171,6 +171,16 @@ def parseNFO(options): logging.error("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): + logging.error("Prismedia: 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]: @@ -188,7 +198,7 @@ def parseNFO(options): except NoOptionError: continue except NoSectionError: - logging.error(nfo + " misses section [video], please check syntax of your NFO.") + logging.error("Prismedia: " + nfo + " misses section [video], please check syntax of your NFO.") exit(1) return options diff --git a/pyproject.toml b/pyproject.toml index 14e0022..b0210a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ python-magic-bin = { version = "^0.4.14", markers = "platform_system == 'Windows requests = "^2.18.4" requests-oauthlib = "^0.8.0" requests-toolbelt = "^0.9.1" -schema = "^0.6.8" +schema = ">=0.7.1" tzlocal = "^1.5.1" Unidecode = "^1.0.23" uritemplate = "^3.0.0" From 5022aface49e1aefa11cf3994d5def463c236d62 Mon Sep 17 00:00:00 2001 From: LecygneNoir Date: Tue, 14 Jul 2020 13:31:35 +0200 Subject: [PATCH 2/8] Prepare changelog and documentation for strict check option --- CHANGELOG.md | 5 +++++ README.md | 33 +++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f02feed..f0a236b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## v0.10.0 + +## Features + - Add the possibility to specify strict checks option to never forgot parameters when uploading (see #36) + ## v0.9.1 ### Features diff --git a/README.md b/README.md index 1450bc4..64bcaf6 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,10 @@ Scripting your way to upload videos to peertube and youtube. Works with Python 3 - [Youtube](#youtube) - [Usage](#usage) - [Enhanced use of NFO](#enhanced-use-of-nfo) +- [Strict check options](#strict-check-options) - [Features](#features) - [Compatibility](#compatibility) -- [Sources](#sources) +- [Inspirations](#inspirations) - [Contributors](#contributors) ## Installation an upgrade @@ -215,6 +216,28 @@ In other word, Prismedia will now use option given in cli, then look for option It allows to specify more easily default options for an entire set of video, directory, playlist and so on. +## Strict check options +Since prismedia v0.10.0, a bunch of special options have been added to force the presence of parameters before uploading. +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. + +Available strict options: + - --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 + - --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 + ## Features - [x] Youtube upload @@ -235,9 +258,11 @@ It allows to specify more easily default options for an entire set of video, dir - [x] schedule your video with publishAt - [x] combine channel and playlist (Peertube only as channel is Peertube feature). See [issue 40](https://git.lecygnenoir.info/LecygneNoir/prismedia/issues/40) for detailed usage. - [x] Use a config file (NFO) file to retrieve videos arguments -- [x] Allow to choose peertube or youtube upload (to resume failed upload for example) +- [x] Allow choosing peertube or youtube upload (to retry a failed upload for example) - [x] Usable on Desktop (Linux and/or Windows and/or MacOS) - [x] Different schedules on platforms to prepare preview +- [x] Possibility to force the presence of upload options +- [ ] Copy and forget, eg possibility to copy video in a directory, and prismedia uploads itself (Discussions in [issue 27](https://git.lecygnenoir.info/LecygneNoir/prismedia/issues/27)) - [ ] A usable graphical interface ## Compatibility @@ -245,8 +270,8 @@ It allows to specify more easily default options for an entire set of video, dir - If you still use python2, use the version 0.7.1 (no more updated) - If you use peertube before 1.0.0-beta4, use the version inside tag 1.0.0-beta3 -## Sources -inspired by [peeror](https://git.rigelk.eu/rigelk/peeror) and [youtube-upload](https://github.com/tokland/youtube-upload) +## Inspirations +Inspired by [peeror](https://git.rigelk.eu/rigelk/peeror) and [youtube-upload](https://github.com/tokland/youtube-upload) ## Contributors Thanks to: @Zykino, @meewan, @rigelk 😘 \ No newline at end of file From 542bb6f1f951473c4242020aa19674b6deb13a79 Mon Sep 17 00:00:00 2001 From: LecygneNoir Date: Tue, 14 Jul 2020 13:50:56 +0200 Subject: [PATCH 3/8] Correction of some typos --- README.md | 4 ++-- prismedia/upload.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 64bcaf6..23c9319 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Scripting your way to upload videos to peertube and youtube. Works with Python 3 [TOC]: # ## Table of Contents -- [Installation](#installation) +- [Installation](#installation-and-upgrade) - [From pip](#from-pip) - [From source](#from-source) - [Configuration](#configuration) @@ -19,7 +19,7 @@ Scripting your way to upload videos to peertube and youtube. Works with Python 3 - [Inspirations](#inspirations) - [Contributors](#contributors) -## Installation an upgrade +## Installation and upgrade ### From pip diff --git a/prismedia/upload.py b/prismedia/upload.py index 2e83bd5..1a5fcb3 100755 --- a/prismedia/upload.py +++ b/prismedia/upload.py @@ -50,8 +50,8 @@ Options: --version Show version. Strict options: - Strict options allow you to force some option to be present when uploading a video. It's useful to be sue you do not - forget something when uploading a vidéo, for example if you use multiples NFO. You may force the presence of description, + 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 From dc7dd5cb46271ec5d3b47e6280e8682c329a5978 Mon Sep 17 00:00:00 2001 From: LecygneNoir Date: Fri, 17 Jul 2020 15:05:00 +0200 Subject: [PATCH 4/8] Full rewrite of logging system to introduce --log as an option, and prepare the work for #29 --- prismedia/pt_upload.py | 56 ++++++++++++++++---------------- prismedia/upload.py | 72 ++++++++++++++++++++++++++++++------------ prismedia/utils.py | 19 ++++++++--- prismedia/yt_upload.py | 41 +++++++++++------------- 4 files changed, 114 insertions(+), 74 deletions(-) diff --git a/prismedia/pt_upload.py b/prismedia/pt_upload.py index 8e1aa1f..77584ae 100644 --- a/prismedia/pt_upload.py +++ b/prismedia/pt_upload.py @@ -16,6 +16,7 @@ from oauthlib.oauth2 import LegacyApplicationClient from requests_toolbelt.multipart.encoder import MultipartEncoder from . import utils +logger = logging.getLogger('Prismedia') PEERTUBE_SECRETS_FILE = 'peertube_secret' PEERTUBE_PRIVACY = { @@ -43,10 +44,10 @@ def get_authenticated_service(secret): ) except Exception as e: if hasattr(e, 'message'): - logging.error("Peertube: Error: " + str(e.message)) + logger.critical("Peertube: " + str(e.message)) exit(1) else: - logging.error("Peertube: Error: " + str(e)) + logger.critical("Peertube: " + str(e)) exit(1) return oauth @@ -63,7 +64,7 @@ def get_channel_by_name(user_info, options): def create_channel(oauth, url, options): template = ('Peertube: Channel %s does not exist, creating it.') - logging.info(template % (str(options.get('--channel')))) + logger.info(template % (str(options.get('--channel')))) channel_name = utils.cleanString(str(options.get('--channel'))) # Peertube allows 20 chars max for channel name channel_name = channel_name[:19] @@ -81,23 +82,23 @@ def create_channel(oauth, url, options): headers=headers) except Exception as e: if hasattr(e, 'message'): - logging.error("Error: " + str(e.message)) + logger.error("Peertube: " + str(e.message)) else: - logging.error("Error: " + str(e)) + logger.error("Peertube: " + str(e)) if response is not None: if response.status_code == 200: jresponse = response.json() jresponse = jresponse['videoChannel'] return jresponse['id'] if response.status_code == 409: - logging.error('Peertube: Error: It seems there is a conflict with an existing channel named ' + logger.critical('Peertube: It seems there is a conflict with an existing channel named ' + channel_name + '.' ' Please beware Peertube internal name is compiled from 20 firsts characters of channel name.' ' Also note that channel name are not case sensitive (no uppercase nor accent)' ' Please check your channel name and retry.') exit(1) else: - logging.error(('Peertube: Creating channel failed with an unexpected response: ' + logger.critical(('Peertube: Creating channel failed with an unexpected response: ' '%s') % response) exit(1) @@ -114,7 +115,7 @@ def get_playlist_by_name(user_playlists, options): def create_playlist(oauth, url, options, channel): template = ('Peertube: Playlist %s does not exist, creating it.') - logging.info(template % (str(options.get('--playlist')))) + logger.info(template % (str(options.get('--playlist')))) # We use files for form-data Content # see https://requests.readthedocs.io/en/latest/user/quickstart/#post-a-multipart-encoded-file # None is used to mute "filename" field @@ -128,22 +129,22 @@ def create_playlist(oauth, url, options, channel): files=files) except Exception as e: if hasattr(e, 'message'): - logging.error("Error: " + str(e.message)) + logger.error("Peertube: " + str(e.message)) else: - logging.error("Error: " + str(e)) + logger.error("Peertube: " + str(e)) if response is not None: if response.status_code == 200: jresponse = response.json() jresponse = jresponse['videoPlaylist'] return jresponse['id'] else: - logging.error(('Peertube: Creating the playlist failed with an unexpected response: ' + logger.critical(('Peertube: Creating the playlist failed with an unexpected response: ' '%s') % response) exit(1) def set_playlist(oauth, url, video_id, playlist_id): - logging.info('Peertube: add video to playlist.') + logger.info('Peertube: add video to playlist.') data = '{"videoId":"' + str(video_id) + '"}' headers = { @@ -155,14 +156,14 @@ def set_playlist(oauth, url, video_id, playlist_id): headers=headers) except Exception as e: if hasattr(e, 'message'): - logging.error("Error: " + str(e.message)) + logger.error("Peertube: " + str(e.message)) else: - logging.error("Error: " + str(e)) + logger.error("Peertube: " + str(e)) if response is not None: if response.status_code == 200: - logging.info('Peertube: Video is successfully added to the playlist.') + logger.info('Peertube: Video is successfully added to the playlist.') else: - logging.error(('Peertube: Configuring the playlist failed with an unexpected response: ' + logger.critical(('Peertube: Configuring the playlist failed with an unexpected response: ' '%s') % response) exit(1) @@ -205,8 +206,9 @@ def upload_video(oauth, secret, options): continue # Tag more than 30 chars crashes Peertube, so exit and check tags if len(strtag) >= 30: - logging.warning("Peertube: Sorry, Peertube does not support tag with more than 30 characters, please reduce tag: " + strtag) - exit(1) + logger.error("Peertube: Sorry, Peertube does not support tag with more than 30 characters, please reduce tag: " + strtag) + logger.error("Peertube: Meanwhile, this tag will be skipped") + continue fields.append(("tags[]", strtag)) if options.get('--category'): @@ -256,7 +258,7 @@ def upload_video(oauth, secret, options): if not channel_id and options.get('--channelCreate'): channel_id = create_channel(oauth, url, options) elif not channel_id: - logging.warning("Channel `" + options.get('--channel') + "` is unknown, using default channel.") + logger.warning("Peertube: Channel `" + options.get('--channel') + "` is unknown, using default channel.") channel_id = get_default_channel(user_info) else: channel_id = get_default_channel(user_info) @@ -268,7 +270,7 @@ def upload_video(oauth, secret, options): if not playlist_id and options.get('--playlistCreate'): playlist_id = create_playlist(oauth, url, options, channel_id) elif not playlist_id: - logging.warning("Playlist `" + options.get('--playlist') + "` does not exist, please set --playlistCreate" + logger.critical("Peertube: Playlist `" + options.get('--playlist') + "` does not exist, please set --playlistCreate" " if you want to create it") exit(1) @@ -286,14 +288,14 @@ def upload_video(oauth, secret, options): jresponse = jresponse['video'] uuid = jresponse['uuid'] video_id = str(jresponse['id']) - logging.info('Peertube : Video was successfully uploaded.') + logger.info('Peertube : Video was successfully uploaded.') template = 'Peertube: Watch it at %s/videos/watch/%s.' - logging.info(template % (url, uuid)) + logger.info(template % (url, uuid)) # Upload is successful we may set playlist if options.get('--playlist'): set_playlist(oauth, url, video_id, playlist_id) else: - logging.error(('Peertube: The upload failed with an unexpected response: ' + logger.critical(('Peertube: The upload failed with an unexpected response: ' '%s') % response) exit(1) @@ -303,16 +305,16 @@ def run(options): try: secret.read(PEERTUBE_SECRETS_FILE) except Exception as e: - logging.error("Peertube: Error loading " + str(PEERTUBE_SECRETS_FILE) + ": " + str(e)) + logger.critical("Peertube: Error loading " + str(PEERTUBE_SECRETS_FILE) + ": " + str(e)) exit(1) insecure_transport = secret.get('peertube', 'OAUTHLIB_INSECURE_TRANSPORT') os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = insecure_transport oauth = get_authenticated_service(secret) try: - logging.info('Peertube: Uploading video...') + logger.info('Peertube: Uploading video...') upload_video(oauth, secret, options) except Exception as e: if hasattr(e, 'message'): - logging.error("Peertube: Error: " + str(e.message)) + logger.error("Peertube: " + str(e.message)) else: - logging.error("Peertube: Error: " + str(e)) + logger.error("Peertube: " + str(e)) diff --git a/prismedia/upload.py b/prismedia/upload.py index 1a5fcb3..a03ba1d 100755 --- a/prismedia/upload.py +++ b/prismedia/upload.py @@ -13,7 +13,6 @@ Usage: 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) - --debug Trigger some debug information like options used (default: no) -d, --description=STRING Description of the video. (default: default description) -t, --tags=STRING Tags for the video. comma separated. WARN: tags with punctuation (!, ', ", ?, ...) @@ -46,6 +45,8 @@ Options: 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. + --log=STRING Log level, between debug, info, warning, error, critical (default to info) + --debug (Deprecated) Alias for --log=DEBUG. Ignored if --log is set -h --help Show this help. --version Show version. @@ -92,7 +93,13 @@ if sys.version_info[0] < 3: import os import datetime import logging -logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO) +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 docopt import docopt @@ -102,9 +109,9 @@ from . import utils try: # noinspection PyUnresolvedReferences - from schema import Schema, And, Or, Optional, SchemaError, Hook + from schema import Schema, And, Or, Optional, SchemaError, Hook, Use except ImportError: - logging.error('This program requires that the `schema` data-validation library' + logger.critical('This program requires that the `schema` data-validation library' ' is installed: \n' 'see https://github.com/halst/schema\n') exit(1) @@ -112,7 +119,7 @@ try: # noinspection PyUnresolvedReferences import magic except ImportError: - logging.error('This program requires that the `python-magic` library' + logger.critical('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) @@ -198,11 +205,17 @@ def validateThumbnail(thumbnail): return False +def validateLogLevel(loglevel): + numeric_level = getattr(logging, loglevel, None) + if not isinstance(numeric_level, int): + 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: - logging.error("Prismedia: you have required the strict presence of " + key + " but none is found") + logger.critical("Prismedia: you have required the strict presence of " + key + " but none is found") exit(1) return True @@ -210,7 +223,13 @@ def _optionnalOrStrict(key, scope, error): def main(): options = docopt(__doc__, version=VERSION) - strictoptionSchema = Schema({ + earlyoptionSchema = Schema({ + Optional('--log'): Or(None, And( + str, + Use(str.upper), + validateLogLevel, + error="Log level not recognized") + ), Optional('--withNFO', default=False): bool, Optional('--withThumbnail', default=False): bool, Optional('--withName', default=False): bool, @@ -222,7 +241,8 @@ def main(): Optional('--withCategory', default=False): bool, Optional('--withLanguage', default=False): bool, Optional('--withChannel', default=False): bool, - object: object # This allow to return all other options for further use: https://github.com/keleshev/schema#extra-keys + # This allow to return all other options for further use: https://github.com/keleshev/schema#extra-keys + object: object }) schema = Schema({ @@ -299,22 +319,34 @@ def main(): Optional('--playlistCreate'): bool, '--help': bool, '--version': bool, - object: object # This allow to return all other options for further use: https://github.com/keleshev/schema#extra-keys + # This allow to return all other options for further use: https://github.com/keleshev/schema#extra-keys + object: object }) - - # We need to validate strict options first as withNFO should be validated before NFO parsing + # We need to validate early options first as withNFO and logs options should be prioritized try: - options = strictoptionSchema.validate(options) + options = earlyoptionSchema.validate(options) except SchemaError as e: - exit(e) + logger.critical(e) + exit(1) + + if 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) + ch.setLevel(numeric_level) + elif options.get('--debug'): + logger.warning("DEPRECATION: --debug is deprecated, please use --log=debug instead") + logger.setLevel(10) + ch.setLevel(10) options = utils.parseNFO(options) # Once NFO are loaded, we need to revalidate strict options in case some were in NFO try: - options = strictoptionSchema.validate(options) + options = earlyoptionSchema.validate(options) except SchemaError as e: - exit(e) + logger.critical(e) + exit(1) if not options.get('--thumbnail'): options = utils.searchThumbnail(options) @@ -322,11 +354,11 @@ def main(): try: options = schema.validate(options) except SchemaError as e: - exit(e) + logger.critical(e) + exit(1) - if options.get('--debug'): - print("Python " + sys.version) - print(options) + logger.debug("Python " + sys.version) + logger.debug(options) if options.get('--platform') is None or "peertube" in options.get('--platform'): pt_upload.run(options) @@ -336,5 +368,5 @@ def main(): if __name__ == '__main__': import warnings - warnings.warn("use 'python -m prismedia', not 'python -m prismedia.upload'", DeprecationWarning) + logger.warning("DEPRECATION: use 'python -m prismedia', not 'python -m prismedia.upload'") main() diff --git a/prismedia/utils.py b/prismedia/utils.py index 36b54fb..c2239c6 100644 --- a/prismedia/utils.py +++ b/prismedia/utils.py @@ -9,6 +9,8 @@ from subprocess import check_call, CalledProcessError, STDOUT import unidecode import logging +logger = logging.getLogger('Prismedia') + ### CATEGORIES ### YOUTUBE_CATEGORY = { "music": 10, @@ -123,18 +125,25 @@ def searchThumbnail(options): options['--thumbnail'] = video_directory + video_file + ".jpg" elif isfile(video_directory + video_file + ".jpeg"): options['--thumbnail'] = video_directory + video_file + ".jpeg" + + # 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 # return the nfo as a RawConfigParser object def loadNFO(filename): try: - logging.info("Loading " + filename + " as NFO") + logger.info("Loading " + filename + " as NFO") nfo = RawConfigParser() nfo.read(filename, encoding='utf-8') return nfo except Exception as e: - logging.error("Problem loading NFO file " + filename + ": " + str(e)) + logger.critical("Problem loading NFO file " + filename + ": " + str(e)) exit(1) return False @@ -168,7 +177,7 @@ def parseNFO(options): if isfile(options.get('--nfo')): nfo_cli = loadNFO(options.get('--nfo')) else: - logging.error("Given NFO file does not exist, please check your path.") + 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 @@ -178,7 +187,7 @@ def parseNFO(options): not isinstance(nfo_videoname, RawConfigParser) and \ not isinstance(nfo_directory, RawConfigParser) and \ not isinstance(nfo_txt, RawConfigParser): - logging.error("Prismedia: you have required the strict presence of NFO but none is found, please use a NFO.") + 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 @@ -198,7 +207,7 @@ def parseNFO(options): except NoOptionError: continue except NoSectionError: - logging.error("Prismedia: " + nfo + " misses section [video], please check syntax of your NFO.") + logger.critical(nfo + " misses section [video], please check syntax of your NFO.") exit(1) return options diff --git a/prismedia/yt_upload.py b/prismedia/yt_upload.py index da21ccc..f1f8976 100644 --- a/prismedia/yt_upload.py +++ b/prismedia/yt_upload.py @@ -23,9 +23,7 @@ from google_auth_oauthlib.flow import InstalledAppFlow from . import utils - -logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO) - +logger = logging.getLogger('Prismedia') # Explicitly tell the underlying HTTP transport library not to retry, since # we are handling retry logic ourselves. @@ -87,7 +85,7 @@ def check_authenticated_scopes(): credential_params = json.load(f) # Check if all scopes are present if credential_params["_scopes"] != SCOPES: - logging.warning("Youtube: Credentials are obsolete, need to re-authenticate.") + logger.warning("Youtube: Credentials are obsolete, need to re-authenticate.") os.remove(CREDENTIALS_PATH) @@ -144,8 +142,8 @@ def initialize_upload(youtube, options): if not playlist_id and options.get('--playlistCreate'): playlist_id = create_playlist(youtube, options.get('--playlist')) elif not playlist_id: - logging.warning("Youtube: Playlist `" + options.get('--playlist') + "` is unknown.") - logging.warning("If you want to create it, set the --playlistCreate option.") + logger.warning("Youtube: Playlist `" + options.get('--playlist') + "` is unknown.") + logger.warning("Youtube: If you want to create it, set the --playlistCreate option.") playlist_id = "" else: playlist_id = "" @@ -179,8 +177,8 @@ def get_playlist_by_name(youtube, playlist_name): def create_playlist(youtube, playlist_name): - template = ('Youtube: Playlist %s does not exist, creating it.') - logging.info(template % (str(playlist_name))) + template = 'Youtube: Playlist %s does not exist, creating it.' + logger.info(template % (str(playlist_name))) resources = build_resource({'snippet.title': playlist_name, 'snippet.description': '', 'status.privacyStatus': 'public'}) @@ -244,7 +242,7 @@ def set_thumbnail(youtube, media_file, **kwargs): def set_playlist(youtube, playlist_id, video_id): - logging.info('Youtube: Configuring playlist...') + logger.info('Youtube: Configuring playlist...') resource = build_resource({'snippet.playlistId': playlist_id, 'snippet.resourceId.kind': 'youtube#video', 'snippet.resourceId.videoId': video_id, @@ -257,12 +255,12 @@ def set_playlist(youtube, playlist_id, video_id): ).execute() except Exception as e: if hasattr(e, 'message'): - logging.error("Youtube: Error: " + str(e.message)) + logger.critical("Youtube: " + str(e.message)) exit(1) else: - logging.error("Youtube: Error: " + str(e)) + logger.critical("Youtube: " + str(e)) exit(1) - logging.info('Youtube: Video is correctly added to the playlist.') + logger.info('Youtube: Video is correctly added to the playlist.') # This method implements an exponential backoff strategy to resume a @@ -274,20 +272,20 @@ def resumable_upload(request, resource, method): while response is None: try: template = 'Youtube: Uploading %s...' - logging.info(template % resource) + logger.info(template % resource) status, response = request.next_chunk() if response is not None: if method == 'insert' and 'id' in response: - logging.info('Youtube : Video was successfully uploaded.') + logger.info('Youtube : Video was successfully uploaded.') template = 'Youtube: Watch it at https://youtu.be/%s (post-encoding could take some time)' - logging.info(template % response['id']) + logger.info(template % response['id']) return response['id'] elif method != 'insert' or "id" not in response: - logging.info('Youtube: Thumbnail was successfully set.') + logger.info('Youtube: Thumbnail was successfully set.') else: template = ('Youtube : The upload failed with an ' 'unexpected response: %s') - logging.error(template % response) + logger.critical(template % response) exit(1) except HttpError as e: if e.resp.status in RETRIABLE_STATUS_CODES: @@ -299,15 +297,14 @@ def resumable_upload(request, resource, method): error = 'Youtube : A retriable error occurred: %s' % e if error is not None: - logging.warning(error) + logger.warning(error) retry += 1 if retry > MAX_RETRIES: - logging.error('Youtube : No longer attempting to retry.') - exit(1) + logger.error('Youtube : No longer attempting to retry.') max_sleep = 2 ** retry sleep_seconds = random.random() * max_sleep - logging.warning('Youtube : Sleeping %f seconds and then retrying...' + logger.warning('Youtube : Sleeping %f seconds and then retrying...' % sleep_seconds) time.sleep(sleep_seconds) @@ -317,5 +314,5 @@ def run(options): try: initialize_upload(youtube, options) except HttpError as e: - logging.error('Youtube : An HTTP error %d occurred:\n%s' % (e.resp.status, + logger.error('Youtube : An HTTP error %d occurred:\n%s' % (e.resp.status, e.content)) From 1c441bf67a2f33dd993baebbcdd6fb30abb6f468 Mon Sep 17 00:00:00 2001 From: LecygneNoir Date: Sat, 18 Jul 2020 13:39:19 +0200 Subject: [PATCH 5/8] Add new options --quiet and --print-url for an easier scripting use of prismedia, cf #29 --- prismedia/pt_upload.py | 7 ++++++ prismedia/upload.py | 55 +++++++++++++++++++++++++++++++----------- prismedia/yt_upload.py | 11 +++++++-- 3 files changed, 57 insertions(+), 16 deletions(-) diff --git a/prismedia/pt_upload.py b/prismedia/pt_upload.py index 77584ae..3e0eec7 100644 --- a/prismedia/pt_upload.py +++ b/prismedia/pt_upload.py @@ -274,6 +274,10 @@ def upload_video(oauth, secret, options): " if you want to create it") exit(1) + logger_stdout = None + if options.get('--print-url'): + logger_stdout = logging.getLogger('stdoutlogs') + multipart_data = MultipartEncoder(fields) headers = { @@ -291,6 +295,9 @@ def upload_video(oauth, secret, options): logger.info('Peertube : Video was successfully uploaded.') template = 'Peertube: Watch it at %s/videos/watch/%s.' logger.info(template % (url, uuid)) + if logger_stdout: + template_stdout = '%s/videos/watch/%s' + logger_stdout.info(template_stdout % (url, uuid)) # Upload is successful we may set playlist if options.get('--playlist'): set_playlist(oauth, url, video_id, playlist_id) diff --git a/prismedia/upload.py b/prismedia/upload.py index a03ba1d..82d3aff 100755 --- a/prismedia/upload.py +++ b/prismedia/upload.py @@ -11,7 +11,7 @@ Usage: prismedia --version Options: - -f, --file=STRING Path to the video file to upload in mp4 + -f, --file=STRING Path to the video file to upload in mp4. 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. @@ -45,11 +45,16 @@ Options: 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. - --log=STRING Log level, between debug, info, warning, error, critical (default to info) - --debug (Deprecated) Alias for --log=DEBUG. Ignored if --log is set -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 --print-url Display generated URL after upload directly on stdout in addition to the usual logging. + May be used in conjunction with --quiet for batch scripting + --debug (Deprecated) Alias for --log=debug. Ignored if --log is set + 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, @@ -220,6 +225,32 @@ def _optionnalOrStrict(key, scope, error): return True +def configureLogs(options): + if options.get('--quiet'): + # We need to set both log level in the same time + logger.setLevel(50) + ch.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) + ch.setLevel(numeric_level) + elif options.get('--debug'): + logger.warning("DEPRECATION: --debug is deprecated, please use --log=debug instead") + logger.setLevel(10) + ch.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) + formatter_stdout = logging.Formatter('%(message)s') + ch_stdout.setFormatter(formatter_stdout) + logger_stdout.addHandler(ch_stdout) + + def main(): options = docopt(__doc__, version=VERSION) @@ -230,6 +261,8 @@ def main(): validateLogLevel, error="Log level not recognized") ), + Optional('--quiet', default=False): bool, + Optional('--debug'): bool, Optional('--withNFO', default=False): bool, Optional('--withThumbnail', default=False): bool, Optional('--withName', default=False): bool, @@ -306,7 +339,6 @@ def main(): validatePublish, error="DATE should be the form YYYY-MM-DDThh:mm:ss and has to be in the future") ), - Optional('--debug'): bool, Optional('--cca'): bool, Optional('--disable-comments'): bool, Optional('--nsfw'): bool, @@ -317,6 +349,7 @@ def main(): Optional('--channelCreate'): bool, Optional('--playlist'): Or(None, str), Optional('--playlistCreate'): bool, + Optional('--print-url', default=False): bool, '--help': bool, '--version': bool, # This allow to return all other options for further use: https://github.com/keleshev/schema#extra-keys @@ -325,20 +358,11 @@ def main(): # 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('--log'): - numeric_level = getattr(logging, options["--log"], None) - # We need to set both log level in the same time - logger.setLevel(numeric_level) - ch.setLevel(numeric_level) - elif options.get('--debug'): - logger.warning("DEPRECATION: --debug is deprecated, please use --log=debug instead") - logger.setLevel(10) - ch.setLevel(10) - options = utils.parseNFO(options) # Once NFO are loaded, we need to revalidate strict options in case some were in NFO @@ -357,6 +381,9 @@ def main(): logger.critical(e) exit(1) + if options.get('--print-url'): + configureStdoutLogs() + logger.debug("Python " + sys.version) logger.debug(options) diff --git a/prismedia/yt_upload.py b/prismedia/yt_upload.py index f1f8976..1c70b62 100644 --- a/prismedia/yt_upload.py +++ b/prismedia/yt_upload.py @@ -148,13 +148,17 @@ def initialize_upload(youtube, options): else: playlist_id = "" + logger_stdout = None + if options.get('--print-url'): + logger_stdout = logging.getLogger('stdoutlogs') + # Call the API's videos.insert method to create and upload the video. insert_request = youtube.videos().insert( part=','.join(list(body.keys())), body=body, media_body=MediaFileUpload(path, chunksize=-1, resumable=True) ) - video_id = resumable_upload(insert_request, 'video', 'insert') + video_id = resumable_upload(insert_request, 'video', 'insert', logger_stdout) # If we get a video_id, upload is successful and we are able to set thumbnail if video_id and options.get('--thumbnail'): @@ -265,7 +269,7 @@ def set_playlist(youtube, playlist_id, video_id): # This method implements an exponential backoff strategy to resume a # failed upload. -def resumable_upload(request, resource, method): +def resumable_upload(request, resource, method, logger_stdout=None): response = None error = None retry = 0 @@ -279,6 +283,9 @@ def resumable_upload(request, resource, method): logger.info('Youtube : Video was successfully uploaded.') template = 'Youtube: Watch it at https://youtu.be/%s (post-encoding could take some time)' logger.info(template % response['id']) + if logger_stdout: + template_stdout = 'https://youtu.be/%s' + logger_stdout.info(template_stdout % response['id']) return response['id'] elif method != 'insert' or "id" not in response: logger.info('Youtube: Thumbnail was successfully set.') From 379aef1dd8be0db58ab23329603a830edf30f417 Mon Sep 17 00:00:00 2001 From: LecygneNoir Date: Mon, 14 Sep 2020 14:19:59 +0200 Subject: [PATCH 6/8] Following discussion in #29, rename print-url option and add a batch options --- README.md | 32 ++++++++++++++++++++++++++++++-- prismedia/pt_upload.py | 9 ++++++--- prismedia/upload.py | 24 ++++++++++++++++-------- prismedia/yt_upload.py | 17 +++++++++-------- 4 files changed, 61 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 23c9319..f20ea67 100644 --- a/README.md +++ b/README.md @@ -122,9 +122,8 @@ Use --help to get all available options: ``` Options: - -f, --file=STRING Path to the video file to upload in mp4 + -f, --file=STRING Path to the video file to upload in mp4. This is the only mandatory option. --name=NAME Name of the video to upload. (default to video filename) - --debug Trigger some debug information like options used (default: no) -d, --description=STRING Description of the video. (default: default description) -t, --tags=STRING Tags for the video. comma separated. WARN: tags with punctuation (!, ', ", ?, ...) @@ -160,6 +159,34 @@ Options: -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. + --debug (Deprecated) Alias for --log=debug. Ignored if --log is set + +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 + --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: @@ -174,6 +201,7 @@ Languages: Here are available languages from Peertube and Youtube: Arabic, English, French, German, Hindi, Italian, Japanese, Korean, Mandarin, Portuguese, Punjabi, Russian, Spanish + ``` ## Enhanced use of NFO diff --git a/prismedia/pt_upload.py b/prismedia/pt_upload.py index 3e0eec7..b95e550 100644 --- a/prismedia/pt_upload.py +++ b/prismedia/pt_upload.py @@ -5,6 +5,7 @@ import os import mimetypes import json import logging +import sys import datetime import pytz from os.path import splitext, basename, abspath @@ -275,7 +276,7 @@ def upload_video(oauth, secret, options): exit(1) logger_stdout = None - if options.get('--print-url'): + if options.get('--url-only') or options.get('--batch'): logger_stdout = logging.getLogger('stdoutlogs') multipart_data = MultipartEncoder(fields) @@ -295,9 +296,11 @@ def upload_video(oauth, secret, options): logger.info('Peertube : Video was successfully uploaded.') template = 'Peertube: Watch it at %s/videos/watch/%s.' logger.info(template % (url, uuid)) - if logger_stdout: - template_stdout = '%s/videos/watch/%s' + template_stdout = '%s/videos/watch/%s' + if options.get('--url-only'): logger_stdout.info(template_stdout % (url, uuid)) + elif options.get('--batch'): + logger_stdout.info("Peertube: " + template_stdout % (url, uuid)) # Upload is successful we may set playlist if options.get('--playlist'): set_playlist(oauth, url, video_id, playlist_id) diff --git a/prismedia/upload.py b/prismedia/upload.py index 82d3aff..d91cafa 100755 --- a/prismedia/upload.py +++ b/prismedia/upload.py @@ -51,8 +51,9 @@ Options: 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 --print-url Display generated URL after upload directly on stdout in addition to the usual logging. - May be used in conjunction with --quiet for batch scripting + -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. --debug (Deprecated) Alias for --log=debug. Ignored if --log is set Strict options: @@ -226,6 +227,13 @@ def _optionnalOrStrict(key, scope, error): 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 + if options.get('--quiet'): # We need to set both log level in the same time logger.setLevel(50) @@ -246,11 +254,11 @@ def configureStdoutLogs(): 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) @@ -263,6 +271,8 @@ def main(): ), 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, @@ -349,7 +359,6 @@ def main(): Optional('--channelCreate'): bool, Optional('--playlist'): Or(None, str), Optional('--playlistCreate'): bool, - Optional('--print-url', default=False): bool, '--help': bool, '--version': bool, # This allow to return all other options for further use: https://github.com/keleshev/schema#extra-keys @@ -363,6 +372,9 @@ def main(): logger.critical(e) exit(1) + if options.get('--url-only') or options.get('--batch'): + configureStdoutLogs() + options = utils.parseNFO(options) # Once NFO are loaded, we need to revalidate strict options in case some were in NFO @@ -381,9 +393,6 @@ def main(): logger.critical(e) exit(1) - if options.get('--print-url'): - configureStdoutLogs() - logger.debug("Python " + sys.version) logger.debug(options) @@ -394,6 +403,5 @@ def main(): if __name__ == '__main__': - import warnings logger.warning("DEPRECATION: use 'python -m prismedia', not 'python -m prismedia.upload'") main() diff --git a/prismedia/yt_upload.py b/prismedia/yt_upload.py index 1c70b62..5ed432d 100644 --- a/prismedia/yt_upload.py +++ b/prismedia/yt_upload.py @@ -148,17 +148,13 @@ def initialize_upload(youtube, options): else: playlist_id = "" - logger_stdout = None - if options.get('--print-url'): - logger_stdout = logging.getLogger('stdoutlogs') - # Call the API's videos.insert method to create and upload the video. insert_request = youtube.videos().insert( part=','.join(list(body.keys())), body=body, media_body=MediaFileUpload(path, chunksize=-1, resumable=True) ) - video_id = resumable_upload(insert_request, 'video', 'insert', logger_stdout) + video_id = resumable_upload(insert_request, 'video', 'insert', options) # If we get a video_id, upload is successful and we are able to set thumbnail if video_id and options.get('--thumbnail'): @@ -269,10 +265,13 @@ def set_playlist(youtube, playlist_id, video_id): # This method implements an exponential backoff strategy to resume a # failed upload. -def resumable_upload(request, resource, method, logger_stdout=None): +def resumable_upload(request, resource, method, options): response = None error = None retry = 0 + logger_stdout = None + if options.get('--url-only') or options.get('--batch'): + logger_stdout = logging.getLogger('stdoutlogs') while response is None: try: template = 'Youtube: Uploading %s...' @@ -283,9 +282,11 @@ def resumable_upload(request, resource, method, logger_stdout=None): logger.info('Youtube : Video was successfully uploaded.') template = 'Youtube: Watch it at https://youtu.be/%s (post-encoding could take some time)' logger.info(template % response['id']) - if logger_stdout: - template_stdout = 'https://youtu.be/%s' + template_stdout = 'https://youtu.be/%s' + if options.get('--url-only'): logger_stdout.info(template_stdout % response['id']) + elif options.get('--batch'): + logger_stdout.info("Youtube: " + template_stdout % response['id']) return response['id'] elif method != 'insert' or "id" not in response: logger.info('Youtube: Thumbnail was successfully set.') From ee578e8e82d459075bf3aaecb6e979388892916e Mon Sep 17 00:00:00 2001 From: LecygneNoir Date: Tue, 15 Sep 2020 09:30:03 +0200 Subject: [PATCH 7/8] Add a workaround Youtube API bug regarding playlist to ignore incorrect 404 return, see #47 for more details --- prismedia/yt_upload.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/prismedia/yt_upload.py b/prismedia/yt_upload.py index 5ed432d..b96df94 100644 --- a/prismedia/yt_upload.py +++ b/prismedia/yt_upload.py @@ -254,12 +254,14 @@ def set_playlist(youtube, playlist_id, video_id): part='snippet' ).execute() except Exception as e: - if hasattr(e, 'message'): - logger.critical("Youtube: " + str(e.message)) - exit(1) - else: - logger.critical("Youtube: " + str(e)) - exit(1) + # Workaround while youtube API is broken, see issue #47 for details + if e.resp.status != 404 and "Video not found" not in str(e): + if hasattr(e, 'message'): + logger.critical("Youtube: " + str(e.message)) + exit(1) + else: + logger.critical("Youtube: " + str(e)) + exit(1) logger.info('Youtube: Video is correctly added to the playlist.') From 1e9b719e0cf18288a40b064ba17a55cd1fd348b4 Mon Sep 17 00:00:00 2001 From: LecygneNoir Date: Tue, 15 Sep 2020 09:46:46 +0200 Subject: [PATCH 8/8] Bump version to 0.10.0 and edit Changelog en Readme for the 0.10.0 --- CHANGELOG.md | 6 ++++ README.md | 4 +-- poetry.lock | 87 +++++++++++++++++++++++---------------------- prismedia/upload.py | 2 +- pyproject.toml | 10 +++--- 5 files changed, 58 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0a236b..6f6de9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ ## Features - Add the possibility to specify strict checks option to never forgot parameters when uploading (see #36) + - Improve logging system, add options for batch upload and print url-only in the stdout (see #29) + - --debug option is now deprecated in favor of --log=debug + +## Fixes + - Workaround against the Youtube API breakdown while adding video in playlist. See #47 for details. Should be removed once Google fix their bugs. + ## v0.9.1 diff --git a/README.md b/README.md index f20ea67..02269d0 100644 --- a/README.md +++ b/README.md @@ -240,7 +240,7 @@ Prismedia will: - erase any previous option regarding CCA as it's specified in cli with `--cca` - take `yourvideo1.jpg` as thumbnail if no other files has been specified in previous NFO -In other word, Prismedia will now use option given in cli, then look for option in cli_nfo.txt, then complete with video_name.txt, then directory_name.txt, and finally complete with nfo.txt +In other word, Prismedia will use option given in cli, then look for option in cli_nfo.txt, then complete with video_name.txt, then directory_name.txt, and finally complete with nfo.txt It allows to specify more easily default options for an entire set of video, directory, playlist and so on. @@ -290,7 +290,7 @@ Available strict options: - [x] Usable on Desktop (Linux and/or Windows and/or MacOS) - [x] Different schedules on platforms to prepare preview - [x] Possibility to force the presence of upload options -- [ ] Copy and forget, eg possibility to copy video in a directory, and prismedia uploads itself (Discussions in [issue 27](https://git.lecygnenoir.info/LecygneNoir/prismedia/issues/27)) +- [ ] Copy and forget, eg possibility to copy video in a directory, and prismedia uploads itself: [Work in progress](https://git.lecygnenoir.info/Zykino/prismedia-autoupload) thanks to @Zykino 🎉 (Discussions in [issue 27](https://git.lecygnenoir.info/LecygneNoir/prismedia/issues/27)) - [ ] A usable graphical interface ## Compatibility diff --git a/poetry.lock b/poetry.lock index 1f1ffc6..ebaf6e1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -64,10 +64,10 @@ description = "Google API client core library" name = "google-api-core" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "1.21.0" +version = "1.22.2" [package.dependencies] -google-auth = ">=1.18.0,<2.0dev" +google-auth = ">=1.21.1,<2.0dev" googleapis-common-protos = ">=1.6.0,<2.0dev" protobuf = ">=3.12.0" pytz = "*" @@ -86,14 +86,14 @@ description = "Google API Client Library for Python" name = "google-api-python-client" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "1.9.3" +version = "1.12.1" [package.dependencies] -google-api-core = ">=1.18.0,<2dev" +google-api-core = ">=1.21.0,<2dev" google-auth = ">=1.16.0" google-auth-httplib2 = ">=0.0.3" httplib2 = ">=0.9.2,<1dev" -six = ">=1.6.1,<2dev" +six = ">=1.13.0,<2dev" uritemplate = ">=3.0.0,<4dev" [[package]] @@ -102,7 +102,7 @@ description = "Google Authentication Library" name = "google-auth" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "1.18.0" +version = "1.21.1" [package.dependencies] cachetools = ">=2.0.0,<5.0" @@ -111,7 +111,7 @@ setuptools = ">=40.3.0" six = ">=1.9.0" [package.dependencies.rsa] -python = ">=3" +python = ">=3.5" version = ">=3.1.4,<5" [[package]] @@ -120,11 +120,12 @@ description = "Google Authentication Library: httplib2 transport" name = "google-auth-httplib2" optional = false python-versions = "*" -version = "0.0.3" +version = "0.0.4" [package.dependencies] google-auth = "*" httplib2 = ">=0.9.1" +six = "*" [[package]] category = "main" @@ -132,7 +133,7 @@ description = "Google Authentication Library" name = "google-auth-oauthlib" optional = false python-versions = "*" -version = "0.2.0" +version = "0.4.1" [package.dependencies] google-auth = "*" @@ -191,7 +192,7 @@ description = "Protocol Buffers" name = "protobuf" optional = false python-versions = "*" -version = "3.12.2" +version = "3.13.0" [package.dependencies] setuptools = "*" @@ -288,7 +289,7 @@ requests = ">=2.0.1,<3.0.0" [[package]] category = "main" description = "Pure-Python RSA implementation" -marker = "python_version >= \"3\"" +marker = "python_version >= \"3.5\"" name = "rsa" optional = false python-versions = "*" @@ -303,7 +304,7 @@ description = "Simple data validation library" name = "schema" optional = false python-versions = "*" -version = "0.7.2" +version = "0.7.3" [package.dependencies] contextlib2 = ">=0.5.5" @@ -356,7 +357,7 @@ secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "cer socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] [metadata] -content-hash = "f8eb99e94228881cc7a2c1f7fe0524732f7050ecc629321b275d7fe5c8e0e12c" +content-hash = "5f912013e1ff1f79bdecd9626157960bd0ccc41f441370419888238adc32b385" python-versions = ">=3.5" [metadata.files] @@ -387,24 +388,24 @@ future = [ {file = "future-0.17.1.tar.gz", hash = "sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8"}, ] google-api-core = [ - {file = "google-api-core-1.21.0.tar.gz", hash = "sha256:fea9a434068406ddabe2704988d24d6c5bde3ecfc40823a34f43892d017b14f6"}, - {file = "google_api_core-1.21.0-py2.py3-none-any.whl", hash = "sha256:7b65e8e5ee59bd7517eab2bf9b3008e7b50fd9fb591d4efd780ead6859cd904b"}, + {file = "google-api-core-1.22.2.tar.gz", hash = "sha256:779107f17e0fef8169c5239d56a8fbff03f9f72a3893c0c9e5842ec29dfedd54"}, + {file = "google_api_core-1.22.2-py2.py3-none-any.whl", hash = "sha256:67e33a852dcca7cb7eff49abc35c8cc2c0bb8ab11397dc8306d911505cae2990"}, ] google-api-python-client = [ - {file = "google-api-python-client-1.9.3.tar.gz", hash = "sha256:220349ce189a85229fc46875d467101318495a4a735c0ff2f165b9bdbc7511a0"}, - {file = "google_api_python_client-1.9.3-py3-none-any.whl", hash = "sha256:f8e73dd6433f8218922c952e09adc4fc0dbc360f9959cf427565a16e8d4c5d25"}, + {file = "google-api-python-client-1.12.1.tar.gz", hash = "sha256:ddadc243ce627512c2a27e11d369f5ddf658ef80dbffb247787499486ef1ea98"}, + {file = "google_api_python_client-1.12.1-py2.py3-none-any.whl", hash = "sha256:750316d670119bf680c24ff73825a05b1b4f48b9157bd48c6e3f2bea15ceb586"}, ] google-auth = [ - {file = "google-auth-1.18.0.tar.gz", hash = "sha256:d6b390d3bb0969061ffec7e5766c45c1b39e13c302691e35029f1ad1ccd8ca3b"}, - {file = "google_auth-1.18.0-py2.py3-none-any.whl", hash = "sha256:5e3f540b7b0b892000d542cea6b818b837c230e9a4db9337bb2973bcae0fc078"}, + {file = "google-auth-1.21.1.tar.gz", hash = "sha256:bcbd9f970e7144fe933908aa286d7a12c44b7deb6d78a76871f0377a29d09789"}, + {file = "google_auth-1.21.1-py2.py3-none-any.whl", hash = "sha256:f4d5093f13b1b1c0a434ab1dc851cd26a983f86a4d75c95239974e33ed406a87"}, ] google-auth-httplib2 = [ - {file = "google-auth-httplib2-0.0.3.tar.gz", hash = "sha256:098fade613c25b4527b2c08fa42d11f3c2037dda8995d86de0745228e965d445"}, - {file = "google_auth_httplib2-0.0.3-py2.py3-none-any.whl", hash = "sha256:f1c437842155680cf9918df9bc51c1182fda41feef88c34004bd1978c8157e08"}, + {file = "google-auth-httplib2-0.0.4.tar.gz", hash = "sha256:8d092cc60fb16517b12057ec0bba9185a96e3b7169d86ae12eae98e645b7bc39"}, + {file = "google_auth_httplib2-0.0.4-py2.py3-none-any.whl", hash = "sha256:aeaff501738b289717fac1980db9711d77908a6c227f60e4aa1923410b43e2ee"}, ] google-auth-oauthlib = [ - {file = "google-auth-oauthlib-0.2.0.tar.gz", hash = "sha256:226d1d0960f86ba5d9efd426a70b291eaba96f47d071657e0254ea969025728a"}, - {file = "google_auth_oauthlib-0.2.0-py2.py3-none-any.whl", hash = "sha256:81ba22acada4d13b1d83f9371ab19fd61f1250a542d21cf49e4dcf0637a7344a"}, + {file = "google-auth-oauthlib-0.4.1.tar.gz", hash = "sha256:88d2cd115e3391eb85e1243ac6902e76e77c5fe438b7276b297fbe68015458dd"}, + {file = "google_auth_oauthlib-0.4.1-py2.py3-none-any.whl", hash = "sha256:a92a0f6f41a0fb6138454fbc02674e64f89d82a244ea32f98471733c8ef0e0e1"}, ] googleapis-common-protos = [ {file = "googleapis-common-protos-1.52.0.tar.gz", hash = "sha256:560716c807117394da12cecb0a54da5a451b5cf9866f1d37e9a5e2329a665351"}, @@ -423,24 +424,24 @@ oauthlib = [ {file = "oauthlib-2.1.0.tar.gz", hash = "sha256:ac35665a61c1685c56336bda97d5eefa246f1202618a1d6f34fccb1bdd404162"}, ] protobuf = [ - {file = "protobuf-3.12.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e1464a4a2cf12f58f662c8e6421772c07947266293fb701cb39cd9c1e183f63c"}, - {file = "protobuf-3.12.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:6f349adabf1c004aba53f7b4633459f8ca8a09654bf7e69b509c95a454755776"}, - {file = "protobuf-3.12.2-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:be04fe14ceed7f8641e30f36077c1a654ff6f17d0c7a5283b699d057d150d82a"}, - {file = "protobuf-3.12.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f4b73736108a416c76c17a8a09bc73af3d91edaa26c682aaa460ef91a47168d3"}, - {file = "protobuf-3.12.2-cp35-cp35m-win32.whl", hash = "sha256:5524c7020eb1fb7319472cb75c4c3206ef18b34d6034d2ee420a60f99cddeb07"}, - {file = "protobuf-3.12.2-cp35-cp35m-win_amd64.whl", hash = "sha256:bff02030bab8b969f4de597543e55bd05e968567acb25c0a87495a31eb09e925"}, - {file = "protobuf-3.12.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c9ca9f76805e5a637605f171f6c4772fc4a81eced4e2f708f79c75166a2c99ea"}, - {file = "protobuf-3.12.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:304e08440c4a41a0f3592d2a38934aad6919d692bb0edfb355548786728f9a5e"}, - {file = "protobuf-3.12.2-cp36-cp36m-win32.whl", hash = "sha256:b5a114ea9b7fc90c2cc4867a866512672a47f66b154c6d7ee7e48ddb68b68122"}, - {file = "protobuf-3.12.2-cp36-cp36m-win_amd64.whl", hash = "sha256:85b94d2653b0fdf6d879e39d51018bf5ccd86c81c04e18a98e9888694b98226f"}, - {file = "protobuf-3.12.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a7ab28a8f1f043c58d157bceb64f80e4d2f7f1b934bc7ff5e7f7a55a337ea8b0"}, - {file = "protobuf-3.12.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:eafe9fa19fcefef424ee089fb01ac7177ff3691af7cc2ae8791ae523eb6ca907"}, - {file = "protobuf-3.12.2-cp37-cp37m-win32.whl", hash = "sha256:612bc97e42b22af10ba25e4140963fbaa4c5181487d163f4eb55b0b15b3dfcd2"}, - {file = "protobuf-3.12.2-cp37-cp37m-win_amd64.whl", hash = "sha256:e72736dd822748b0721f41f9aaaf6a5b6d5cfc78f6c8690263aef8bba4457f0e"}, - {file = "protobuf-3.12.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:87535dc2d2ef007b9d44e309d2b8ea27a03d2fa09556a72364d706fcb7090828"}, - {file = "protobuf-3.12.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:50b5fee674878b14baea73b4568dc478c46a31dd50157a5b5d2f71138243b1a9"}, - {file = "protobuf-3.12.2-py2.py3-none-any.whl", hash = "sha256:a96f8fc625e9ff568838e556f6f6ae8eca8b4837cdfb3f90efcb7c00e342a2eb"}, - {file = "protobuf-3.12.2.tar.gz", hash = "sha256:49ef8ab4c27812a89a76fa894fe7a08f42f2147078392c0dee51d4a444ef6df5"}, + {file = "protobuf-3.13.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9c2e63c1743cba12737169c447374fab3dfeb18111a460a8c1a000e35836b18c"}, + {file = "protobuf-3.13.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1e834076dfef9e585815757a2c7e4560c7ccc5962b9d09f831214c693a91b463"}, + {file = "protobuf-3.13.0-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:df3932e1834a64b46ebc262e951cd82c3cf0fa936a154f0a42231140d8237060"}, + {file = "protobuf-3.13.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:8c35bcbed1c0d29b127c886790e9d37e845ffc2725cc1db4bd06d70f4e8359f4"}, + {file = "protobuf-3.13.0-cp35-cp35m-win32.whl", hash = "sha256:339c3a003e3c797bc84499fa32e0aac83c768e67b3de4a5d7a5a9aa3b0da634c"}, + {file = "protobuf-3.13.0-cp35-cp35m-win_amd64.whl", hash = "sha256:361acd76f0ad38c6e38f14d08775514fbd241316cce08deb2ce914c7dfa1184a"}, + {file = "protobuf-3.13.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9edfdc679a3669988ec55a989ff62449f670dfa7018df6ad7f04e8dbacb10630"}, + {file = "protobuf-3.13.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5db9d3e12b6ede5e601b8d8684a7f9d90581882925c96acf8495957b4f1b204b"}, + {file = "protobuf-3.13.0-cp36-cp36m-win32.whl", hash = "sha256:c8abd7605185836f6f11f97b21200f8a864f9cb078a193fe3c9e235711d3ff1e"}, + {file = "protobuf-3.13.0-cp36-cp36m-win_amd64.whl", hash = "sha256:4d1174c9ed303070ad59553f435846a2f877598f59f9afc1b89757bdf846f2a7"}, + {file = "protobuf-3.13.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0bba42f439bf45c0f600c3c5993666fcb88e8441d011fad80a11df6f324eef33"}, + {file = "protobuf-3.13.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c0c5ab9c4b1eac0a9b838f1e46038c3175a95b0f2d944385884af72876bd6bc7"}, + {file = "protobuf-3.13.0-cp37-cp37m-win32.whl", hash = "sha256:f68eb9d03c7d84bd01c790948320b768de8559761897763731294e3bc316decb"}, + {file = "protobuf-3.13.0-cp37-cp37m-win_amd64.whl", hash = "sha256:91c2d897da84c62816e2f473ece60ebfeab024a16c1751aaf31100127ccd93ec"}, + {file = "protobuf-3.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3dee442884a18c16d023e52e32dd34a8930a889e511af493f6dc7d4d9bf12e4f"}, + {file = "protobuf-3.13.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:e7662437ca1e0c51b93cadb988f9b353fa6b8013c0385d63a70c8a77d84da5f9"}, + {file = "protobuf-3.13.0-py2.py3-none-any.whl", hash = "sha256:d69697acac76d9f250ab745b46c725edf3e98ac24763990b24d58c16c642947a"}, + {file = "protobuf-3.13.0.tar.gz", hash = "sha256:6a82e0c8bb2bf58f606040cc5814e07715b2094caeba281e2e7d0b0e2e397db5"}, ] pyasn1 = [ {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, @@ -502,8 +503,8 @@ rsa = [ {file = "rsa-4.4.tar.gz", hash = "sha256:5d95293bbd0fbee1dd9cb4b72d27b723942eb50584abc8c4f5f00e4bcfa55307"}, ] schema = [ - {file = "schema-0.7.2-py2.py3-none-any.whl", hash = "sha256:3a03c2e2b22e6a331ae73750ab1da46916da6ca861b16e6f073ac1d1eba43b71"}, - {file = "schema-0.7.2.tar.gz", hash = "sha256:b536f2375b49fdf56f36279addae98bd86a8afbd58b3c32ce363c464bed5fc1c"}, + {file = "schema-0.7.3-py2.py3-none-any.whl", hash = "sha256:c331438b60f634cab5664ab720d3083cc444f924d55269530c36b33e3354276f"}, + {file = "schema-0.7.3.tar.gz", hash = "sha256:4cf529318cfd1e844ecbe02f41f7e5aa027463e7403666a52746f31f04f47a5e"}, ] six = [ {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, diff --git a/prismedia/upload.py b/prismedia/upload.py index d91cafa..9e59637 100755 --- a/prismedia/upload.py +++ b/prismedia/upload.py @@ -130,7 +130,7 @@ except ImportError: 'see https://github.com/ahupp/python-magic\n') exit(1) -VERSION = "prismedia v0.9.1" +VERSION = "prismedia v0.10.0" VALID_PRIVACY_STATUSES = ('public', 'private', 'unlisted') VALID_CATEGORIES = ( diff --git a/pyproject.toml b/pyproject.toml index b0210a5..529da70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "prismedia" -version = "0.9.1" +version = "0.10.0" description = "scripting your way to upload videos on peertube and youtube" authors = [ "LecygneNoir ", @@ -21,10 +21,10 @@ python = ">=3.5" 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" +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" python-magic = "^0.4.15"