Browse Source

Merge branch 'release/v0.10.0'

master v0.10.0
LecygneNoir 3 years ago
parent
commit
e7a4d1656a
8 changed files with 422 additions and 162 deletions
  1. +11
    -0
      CHANGELOG.md
  2. +62
    -9
      README.md
  3. +91
    -71
      poetry.lock
  4. +39
    -27
      prismedia/pt_upload.py
  5. +155
    -17
      prismedia/upload.py
  6. +23
    -4
      prismedia/utils.py
  7. +35
    -28
      prismedia/yt_upload.py
  8. +6
    -6
      pyproject.toml

+ 11
- 0
CHANGELOG.md View File

@ -1,5 +1,16 @@
# Changelog # Changelog
## v0.10.0
## 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 ## v0.9.1
### Features ### Features

+ 62
- 9
README.md View File

@ -5,7 +5,7 @@ Scripting your way to upload videos to peertube and youtube. Works with Python 3
[TOC]: # [TOC]: #
## Table of Contents ## Table of Contents
- [Installation](#installation)
- [Installation](#installation-and-upgrade)
- [From pip](#from-pip) - [From pip](#from-pip)
- [From source](#from-source) - [From source](#from-source)
- [Configuration](#configuration) - [Configuration](#configuration)
@ -13,12 +13,13 @@ Scripting your way to upload videos to peertube and youtube. Works with Python 3
- [Youtube](#youtube) - [Youtube](#youtube)
- [Usage](#usage) - [Usage](#usage)
- [Enhanced use of NFO](#enhanced-use-of-nfo) - [Enhanced use of NFO](#enhanced-use-of-nfo)
- [Strict check options](#strict-check-options)
- [Features](#features) - [Features](#features)
- [Compatibility](#compatibility) - [Compatibility](#compatibility)
- [Sources](#sources)
- [Inspirations](#inspirations)
- [Contributors](#contributors) - [Contributors](#contributors)
## Installation an upgrade
## Installation and upgrade
### From pip ### From pip
@ -121,9 +122,8 @@ Use --help to get all available options:
``` ```
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) --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) -d, --description=STRING Description of the video. (default: default description)
-t, --tags=STRING Tags for the video. comma separated. -t, --tags=STRING Tags for the video. comma separated.
WARN: tags with punctuation (!, ', ", ?, ...) WARN: tags with punctuation (!, ', ", ?, ...)
@ -159,6 +159,34 @@ Options:
-h --help Show this help. -h --help Show this help.
--version Show version. --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: Categories:
Category is the type of video you upload. Default is films. Category is the type of video you upload. Default is films.
Here are available categories from Peertube and Youtube: Here are available categories from Peertube and Youtube:
@ -173,6 +201,7 @@ Languages:
Here are available languages from Peertube and Youtube: Here are available languages from Peertube and Youtube:
Arabic, English, French, German, Hindi, Italian, Arabic, English, French, German, Hindi, Italian,
Japanese, Korean, Mandarin, Portuguese, Punjabi, Russian, Spanish Japanese, Korean, Mandarin, Portuguese, Punjabi, Russian, Spanish
``` ```
## Enhanced use of NFO ## Enhanced use of NFO
@ -211,10 +240,32 @@ Prismedia will:
- erase any previous option regarding CCA as it's specified in cli with `--cca` - 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 - 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. 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 ## Features
- [x] Youtube upload - [x] Youtube upload
@ -235,9 +286,11 @@ It allows to specify more easily default options for an entire set of video, dir
- [x] schedule your video with publishAt - [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] 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] 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] Usable on Desktop (Linux and/or Windows and/or MacOS)
- [x] Different schedules on platforms to prepare preview - [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: [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 - [ ] A usable graphical interface
## Compatibility ## Compatibility
@ -245,8 +298,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 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 - 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 ## Contributors
Thanks to: @Zykino, @meewan, @rigelk 😘 Thanks to: @Zykino, @meewan, @rigelk 😘

+ 91
- 71
poetry.lock View File

@ -12,7 +12,7 @@ description = "Python package for providing Mozilla's CA Bundle."
name = "certifi" name = "certifi"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "2020.4.5.1"
version = "2020.6.20"
[[package]] [[package]]
category = "main" category = "main"
@ -34,6 +34,14 @@ version = "3.8.1"
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] 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"] 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]] [[package]]
category = "main" category = "main"
description = "Pythonic argument parser, that will make you smile" description = "Pythonic argument parser, that will make you smile"
@ -56,19 +64,19 @@ description = "Google API client core library"
name = "google-api-core" name = "google-api-core"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
version = "1.17.0"
version = "1.22.2"
[package.dependencies] [package.dependencies]
google-auth = ">=1.14.0,<2.0dev"
google-auth = ">=1.21.1,<2.0dev"
googleapis-common-protos = ">=1.6.0,<2.0dev" googleapis-common-protos = ">=1.6.0,<2.0dev"
protobuf = ">=3.4.0"
protobuf = ">=3.12.0"
pytz = "*" pytz = "*"
requests = ">=2.18.0,<3.0.0dev" requests = ">=2.18.0,<3.0.0dev"
setuptools = ">=34.0.0" setuptools = ">=34.0.0"
six = ">=1.10.0" six = ">=1.10.0"
[package.extras] [package.extras]
grpc = ["grpcio (>=1.8.2,<2.0dev)"]
grpc = ["grpcio (>=1.29.0,<2.0dev)"]
grpcgcp = ["grpcio-gcp (>=0.2.2)"] grpcgcp = ["grpcio-gcp (>=0.2.2)"]
grpcio-gcp = ["grpcio-gcp (>=0.2.2)"] grpcio-gcp = ["grpcio-gcp (>=0.2.2)"]
@ -78,14 +86,14 @@ description = "Google API Client Library for Python"
name = "google-api-python-client" name = "google-api-python-client"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
version = "1.8.2"
version = "1.12.1"
[package.dependencies] [package.dependencies]
google-api-core = ">=1.13.0,<2dev"
google-auth = ">=1.4.1"
google-api-core = ">=1.21.0,<2dev"
google-auth = ">=1.16.0"
google-auth-httplib2 = ">=0.0.3" google-auth-httplib2 = ">=0.0.3"
httplib2 = ">=0.9.2,<1dev" httplib2 = ">=0.9.2,<1dev"
six = ">=1.6.1,<2dev"
six = ">=1.13.0,<2dev"
uritemplate = ">=3.0.0,<4dev" uritemplate = ">=3.0.0,<4dev"
[[package]] [[package]]
@ -94,26 +102,30 @@ description = "Google Authentication Library"
name = "google-auth" name = "google-auth"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
version = "1.14.1"
version = "1.21.1"
[package.dependencies] [package.dependencies]
cachetools = ">=2.0.0,<5.0" cachetools = ">=2.0.0,<5.0"
pyasn1-modules = ">=0.2.1" pyasn1-modules = ">=0.2.1"
rsa = ">=3.1.4,<4.1"
setuptools = ">=40.3.0" setuptools = ">=40.3.0"
six = ">=1.9.0" six = ">=1.9.0"
[package.dependencies.rsa]
python = ">=3.5"
version = ">=3.1.4,<5"
[[package]] [[package]]
category = "main" category = "main"
description = "Google Authentication Library: httplib2 transport" description = "Google Authentication Library: httplib2 transport"
name = "google-auth-httplib2" name = "google-auth-httplib2"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "0.0.3"
version = "0.0.4"
[package.dependencies] [package.dependencies]
google-auth = "*" google-auth = "*"
httplib2 = ">=0.9.1" httplib2 = ">=0.9.1"
six = "*"
[[package]] [[package]]
category = "main" category = "main"
@ -121,7 +133,7 @@ description = "Google Authentication Library"
name = "google-auth-oauthlib" name = "google-auth-oauthlib"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "0.2.0"
version = "0.4.1"
[package.dependencies] [package.dependencies]
google-auth = "*" google-auth = "*"
@ -135,8 +147,8 @@ category = "main"
description = "Common protobufs used in Google APIs" description = "Common protobufs used in Google APIs"
name = "googleapis-common-protos" name = "googleapis-common-protos"
optional = false 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] [package.dependencies]
protobuf = ">=3.6.0" protobuf = ">=3.6.0"
@ -158,7 +170,7 @@ description = "Internationalized Domain Names in Applications (IDNA)"
name = "idna" name = "idna"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.9"
version = "2.10"
[[package]] [[package]]
category = "main" category = "main"
@ -180,7 +192,7 @@ description = "Protocol Buffers"
name = "protobuf" name = "protobuf"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "3.11.3"
version = "3.13.0"
[package.dependencies] [package.dependencies]
setuptools = "*" setuptools = "*"
@ -210,8 +222,8 @@ category = "main"
description = "File type identification using libmagic" description = "File type identification using libmagic"
name = "python-magic" name = "python-magic"
optional = false 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]] [[package]]
category = "main" category = "main"
@ -228,7 +240,7 @@ description = "World timezone definitions, modern and historical"
name = "pytz" name = "pytz"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "2019.3"
version = "2020.1"
[[package]] [[package]]
category = "main" category = "main"
@ -236,7 +248,7 @@ description = "Python HTTP for Humans."
name = "requests" name = "requests"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.23.0"
version = "2.24.0"
[package.dependencies] [package.dependencies]
certifi = ">=2017.4.17" certifi = ">=2017.4.17"
@ -277,10 +289,11 @@ requests = ">=2.0.1,<3.0.0"
[[package]] [[package]]
category = "main" category = "main"
description = "Pure-Python RSA implementation" description = "Pure-Python RSA implementation"
marker = "python_version >= \"3.5\""
name = "rsa" name = "rsa"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "4.0"
version = "4.4"
[package.dependencies] [package.dependencies]
pyasn1 = ">=0.1.3" pyasn1 = ">=0.1.3"
@ -291,7 +304,10 @@ description = "Simple data validation library"
name = "schema" name = "schema"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "0.6.8"
version = "0.7.3"
[package.dependencies]
contextlib2 = ">=0.5.5"
[[package]] [[package]]
category = "main" category = "main"
@ -299,7 +315,7 @@ description = "Python 2 and 3 compatibility utilities"
name = "six" name = "six"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
version = "1.14.0"
version = "1.15.0"
[[package]] [[package]]
category = "main" category = "main"
@ -341,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)"] socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
[metadata] [metadata]
content-hash = "b3063876dbcd6443d0459a9ef376ccdba2a21adc2e7a49d75c9450904b40615f"
content-hash = "5f912013e1ff1f79bdecd9626157960bd0ccc41f441370419888238adc32b385"
python-versions = ">=3.5" python-versions = ">=3.5"
[metadata.files] [metadata.files]
@ -350,8 +366,8 @@ cachetools = [
{file = "cachetools-3.1.1.tar.gz", hash = "sha256:8ea2d3ce97850f31e4a08b0e2b5e6c34997d7216a9d2c98e0f3978630d4da69a"}, {file = "cachetools-3.1.1.tar.gz", hash = "sha256:8ea2d3ce97850f31e4a08b0e2b5e6c34997d7216a9d2c98e0f3978630d4da69a"},
] ]
certifi = [ 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 = [ chardet = [
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
@ -361,6 +377,10 @@ configparser = [
{file = "configparser-3.8.1-py2.py3-none-any.whl", hash = "sha256:45d1272aad6cfd7a8a06cf5c73f2ceb6a190f6acc1fa707e7f82a4c053b28b18"}, {file = "configparser-3.8.1-py2.py3-none-any.whl", hash = "sha256:45d1272aad6cfd7a8a06cf5c73f2ceb6a190f6acc1fa707e7f82a4c053b28b18"},
{file = "configparser-3.8.1.tar.gz", hash = "sha256:bc37850f0cc42a1725a796ef7d92690651bf1af37d744cc63161dac62cabee17"}, {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 = [ docopt = [
{file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"},
] ]
@ -368,60 +388,60 @@ future = [
{file = "future-0.17.1.tar.gz", hash = "sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8"}, {file = "future-0.17.1.tar.gz", hash = "sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8"},
] ]
google-api-core = [ 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.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 = [ 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.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 = [ 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.21.1.tar.gz", hash = "sha256:bcbd9f970e7144fe933908aa286d7a12c44b7deb6d78a76871f0377a29d09789"},
{file = "google_auth-1.21.1-py2.py3-none-any.whl", hash = "sha256:f4d5093f13b1b1c0a434ab1dc851cd26a983f86a4d75c95239974e33ed406a87"},
] ]
google-auth-httplib2 = [ 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 = [ 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 = [ 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 = [ httplib2 = [
{file = "httplib2-0.12.3-py3-none-any.whl", hash = "sha256:23914b5487dfe8ef09db6656d6d63afb0cf3054ad9ebc50868ddc8e166b5f8e8"}, {file = "httplib2-0.12.3-py3-none-any.whl", hash = "sha256:23914b5487dfe8ef09db6656d6d63afb0cf3054ad9ebc50868ddc8e166b5f8e8"},
{file = "httplib2-0.12.3.tar.gz", hash = "sha256:a18121c7c72a56689efbf1aef990139ad940fee1e64c6f2458831736cd593600"}, {file = "httplib2-0.12.3.tar.gz", hash = "sha256:a18121c7c72a56689efbf1aef990139ad940fee1e64c6f2458831736cd593600"},
] ]
idna = [ 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 = [ oauthlib = [
{file = "oauthlib-2.1.0-py2.py3-none-any.whl", hash = "sha256:d883b36b21a6ad813953803edfa563b1b579d79ca758fe950d1bc9e8b326025b"}, {file = "oauthlib-2.1.0-py2.py3-none-any.whl", hash = "sha256:d883b36b21a6ad813953803edfa563b1b579d79ca758fe950d1bc9e8b326025b"},
{file = "oauthlib-2.1.0.tar.gz", hash = "sha256:ac35665a61c1685c56336bda97d5eefa246f1202618a1d6f34fccb1bdd404162"}, {file = "oauthlib-2.1.0.tar.gz", hash = "sha256:ac35665a61c1685c56336bda97d5eefa246f1202618a1d6f34fccb1bdd404162"},
] ]
protobuf = [ 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.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 = [ pyasn1 = [
{file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"},
@ -454,8 +474,8 @@ pyasn1-modules = [
{file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"}, {file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"},
] ]
python-magic = [ 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 = [ python-magic-bin = [
{file = "python_magic_bin-0.4.14-py2.py3-none-macosx_10_6_intel.whl", hash = "sha256:7b1743b3dbf16601d6eedf4e7c2c9a637901b0faaf24ad4df4d4527e7d8f66a4"}, {file = "python_magic_bin-0.4.14-py2.py3-none-macosx_10_6_intel.whl", hash = "sha256:7b1743b3dbf16601d6eedf4e7c2c9a637901b0faaf24ad4df4d4527e7d8f66a4"},
@ -463,12 +483,12 @@ python-magic-bin = [
{file = "python_magic_bin-0.4.14-py2.py3-none-win_amd64.whl", hash = "sha256:90be6206ad31071a36065a2fc169c5afb5e0355cbe6030e87641c6c62edc2b69"}, {file = "python_magic_bin-0.4.14-py2.py3-none-win_amd64.whl", hash = "sha256:90be6206ad31071a36065a2fc169c5afb5e0355cbe6030e87641c6c62edc2b69"},
] ]
pytz = [ 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 = [ 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 = [ requests-oauthlib = [
{file = "requests-oauthlib-0.8.0.tar.gz", hash = "sha256:883ac416757eada6d3d07054ec7092ac21c7f35cb1d2cf82faf205637081f468"}, {file = "requests-oauthlib-0.8.0.tar.gz", hash = "sha256:883ac416757eada6d3d07054ec7092ac21c7f35cb1d2cf82faf205637081f468"},
@ -479,16 +499,16 @@ requests-toolbelt = [
{file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"},
] ]
rsa = [ 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 = [ 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.3-py2.py3-none-any.whl", hash = "sha256:c331438b60f634cab5664ab720d3083cc444f924d55269530c36b33e3354276f"},
{file = "schema-0.7.3.tar.gz", hash = "sha256:4cf529318cfd1e844ecbe02f41f7e5aa027463e7403666a52746f31f04f47a5e"},
] ]
six = [ 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 = [ tzlocal = [
{file = "tzlocal-1.5.1.tar.gz", hash = "sha256:4ebeb848845ac898da6519b9b31879cf13b6626f7184c496037b818e238f2c4e"}, {file = "tzlocal-1.5.1.tar.gz", hash = "sha256:4ebeb848845ac898da6519b9b31879cf13b6626f7184c496037b818e238f2c4e"},

+ 39
- 27
prismedia/pt_upload.py View File

@ -5,6 +5,7 @@ import os
import mimetypes import mimetypes
import json import json
import logging import logging
import sys
import datetime import datetime
import pytz import pytz
from os.path import splitext, basename, abspath from os.path import splitext, basename, abspath
@ -16,6 +17,7 @@ from oauthlib.oauth2 import LegacyApplicationClient
from requests_toolbelt.multipart.encoder import MultipartEncoder from requests_toolbelt.multipart.encoder import MultipartEncoder
from . import utils from . import utils
logger = logging.getLogger('Prismedia')
PEERTUBE_SECRETS_FILE = 'peertube_secret' PEERTUBE_SECRETS_FILE = 'peertube_secret'
PEERTUBE_PRIVACY = { PEERTUBE_PRIVACY = {
@ -43,10 +45,10 @@ def get_authenticated_service(secret):
) )
except Exception as e: except Exception as e:
if hasattr(e, 'message'): if hasattr(e, 'message'):
logging.error("Peertube: Error: " + str(e.message))
logger.critical("Peertube: " + str(e.message))
exit(1) exit(1)
else: else:
logging.error("Peertube: Error: " + str(e))
logger.critical("Peertube: " + str(e))
exit(1) exit(1)
return oauth return oauth
@ -63,7 +65,7 @@ def get_channel_by_name(user_info, options):
def create_channel(oauth, url, options): def create_channel(oauth, url, options):
template = ('Peertube: Channel %s does not exist, creating it.') 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'))) channel_name = utils.cleanString(str(options.get('--channel')))
# Peertube allows 20 chars max for channel name # Peertube allows 20 chars max for channel name
channel_name = channel_name[:19] channel_name = channel_name[:19]
@ -81,23 +83,23 @@ def create_channel(oauth, url, options):
headers=headers) headers=headers)
except Exception as e: except Exception as e:
if hasattr(e, 'message'): if hasattr(e, 'message'):
logging.error("Error: " + str(e.message))
logger.error("Peertube: " + str(e.message))
else: else:
logging.error("Error: " + str(e))
logger.error("Peertube: " + str(e))
if response is not None: if response is not None:
if response.status_code == 200: if response.status_code == 200:
jresponse = response.json() jresponse = response.json()
jresponse = jresponse['videoChannel'] jresponse = jresponse['videoChannel']
return jresponse['id'] return jresponse['id']
if response.status_code == 409: 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 + '.' + channel_name + '.'
' Please beware Peertube internal name is compiled from 20 firsts characters of 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)' ' Also note that channel name are not case sensitive (no uppercase nor accent)'
' Please check your channel name and retry.') ' Please check your channel name and retry.')
exit(1) exit(1)
else: else:
logging.error(('Peertube: Creating channel failed with an unexpected response: '
logger.critical(('Peertube: Creating channel failed with an unexpected response: '
'%s') % response) '%s') % response)
exit(1) exit(1)
@ -114,7 +116,7 @@ def get_playlist_by_name(user_playlists, options):
def create_playlist(oauth, url, options, channel): def create_playlist(oauth, url, options, channel):
template = ('Peertube: Playlist %s does not exist, creating it.') 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 # We use files for form-data Content
# see https://requests.readthedocs.io/en/latest/user/quickstart/#post-a-multipart-encoded-file # see https://requests.readthedocs.io/en/latest/user/quickstart/#post-a-multipart-encoded-file
# None is used to mute "filename" field # None is used to mute "filename" field
@ -128,22 +130,22 @@ def create_playlist(oauth, url, options, channel):
files=files) files=files)
except Exception as e: except Exception as e:
if hasattr(e, 'message'): if hasattr(e, 'message'):
logging.error("Error: " + str(e.message))
logger.error("Peertube: " + str(e.message))
else: else:
logging.error("Error: " + str(e))
logger.error("Peertube: " + str(e))
if response is not None: if response is not None:
if response.status_code == 200: if response.status_code == 200:
jresponse = response.json() jresponse = response.json()
jresponse = jresponse['videoPlaylist'] jresponse = jresponse['videoPlaylist']
return jresponse['id'] return jresponse['id']
else: 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) '%s') % response)
exit(1) exit(1)
def set_playlist(oauth, url, video_id, playlist_id): 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) + '"}' data = '{"videoId":"' + str(video_id) + '"}'
headers = { headers = {
@ -155,14 +157,14 @@ def set_playlist(oauth, url, video_id, playlist_id):
headers=headers) headers=headers)
except Exception as e: except Exception as e:
if hasattr(e, 'message'): if hasattr(e, 'message'):
logging.error("Error: " + str(e.message))
logger.error("Peertube: " + str(e.message))
else: else:
logging.error("Error: " + str(e))
logger.error("Peertube: " + str(e))
if response is not None: if response is not None:
if response.status_code == 200: 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: 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) '%s') % response)
exit(1) exit(1)
@ -205,8 +207,9 @@ def upload_video(oauth, secret, options):
continue continue
# Tag more than 30 chars crashes Peertube, so exit and check tags # Tag more than 30 chars crashes Peertube, so exit and check tags
if len(strtag) >= 30: 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)) fields.append(("tags[]", strtag))
if options.get('--category'): if options.get('--category'):
@ -256,7 +259,7 @@ def upload_video(oauth, secret, options):
if not channel_id and options.get('--channelCreate'): if not channel_id and options.get('--channelCreate'):
channel_id = create_channel(oauth, url, options) channel_id = create_channel(oauth, url, options)
elif not channel_id: 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) channel_id = get_default_channel(user_info)
else: else:
channel_id = get_default_channel(user_info) channel_id = get_default_channel(user_info)
@ -268,10 +271,14 @@ def upload_video(oauth, secret, options):
if not playlist_id and options.get('--playlistCreate'): if not playlist_id and options.get('--playlistCreate'):
playlist_id = create_playlist(oauth, url, options, channel_id) playlist_id = create_playlist(oauth, url, options, channel_id)
elif not playlist_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") " if you want to create it")
exit(1) exit(1)
logger_stdout = None
if options.get('--url-only') or options.get('--batch'):
logger_stdout = logging.getLogger('stdoutlogs')
multipart_data = MultipartEncoder(fields) multipart_data = MultipartEncoder(fields)
headers = { headers = {
@ -286,14 +293,19 @@ def upload_video(oauth, secret, options):
jresponse = jresponse['video'] jresponse = jresponse['video']
uuid = jresponse['uuid'] uuid = jresponse['uuid']
video_id = str(jresponse['id']) 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.' template = 'Peertube: Watch it at %s/videos/watch/%s.'
logging.info(template % (url, uuid))
logger.info(template % (url, uuid))
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 # Upload is successful we may set playlist
if options.get('--playlist'): if options.get('--playlist'):
set_playlist(oauth, url, video_id, playlist_id) set_playlist(oauth, url, video_id, playlist_id)
else: else:
logging.error(('Peertube: The upload failed with an unexpected response: '
logger.critical(('Peertube: The upload failed with an unexpected response: '
'%s') % response) '%s') % response)
exit(1) exit(1)
@ -303,16 +315,16 @@ def run(options):
try: try:
secret.read(PEERTUBE_SECRETS_FILE) secret.read(PEERTUBE_SECRETS_FILE)
except Exception as e: 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) exit(1)
insecure_transport = secret.get('peertube', 'OAUTHLIB_INSECURE_TRANSPORT') insecure_transport = secret.get('peertube', 'OAUTHLIB_INSECURE_TRANSPORT')
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = insecure_transport os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = insecure_transport
oauth = get_authenticated_service(secret) oauth = get_authenticated_service(secret)
try: try:
logging.info('Peertube: Uploading video...')
logger.info('Peertube: Uploading video...')
upload_video(oauth, secret, options) upload_video(oauth, secret, options)
except Exception as e: except Exception as e:
if hasattr(e, 'message'): if hasattr(e, 'message'):
logging.error("Peertube: Error: " + str(e.message))
logger.error("Peertube: " + str(e.message))
else: else:
logging.error("Peertube: Error: " + str(e))
logger.error("Peertube: " + str(e))

+ 155
- 17
prismedia/upload.py View File

@ -11,9 +11,8 @@ Usage:
prismedia --version prismedia --version
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) --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) -d, --description=STRING Description of the video. (default: default description)
-t, --tags=STRING Tags for the video. comma separated. -t, --tags=STRING Tags for the video. comma separated.
WARN: tags with punctuation (!, ', ", ?, ...) WARN: tags with punctuation (!, ', ", ?, ...)
@ -49,6 +48,34 @@ Options:
-h --help Show this help. -h --help Show this help.
--version Show version. --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: Categories:
Category is the type of video you upload. Default is films. Category is the type of video you upload. Default is films.
Here are available categories from Peertube and Youtube: Here are available categories from Peertube and Youtube:
@ -69,9 +96,16 @@ import sys
if sys.version_info[0] < 3: if sys.version_info[0] < 3:
raise Exception("Python 3 or a more recent version is required.") raise Exception("Python 3 or a more recent version is required.")
import os
import datetime import datetime
import logging 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 from docopt import docopt
@ -81,9 +115,9 @@ from . import utils
try: try:
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
from schema import Schema, And, Or, Optional, SchemaError
from schema import Schema, And, Or, Optional, SchemaError, Hook, Use
except ImportError: 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' ' is installed: \n'
'see https://github.com/halst/schema\n') 'see https://github.com/halst/schema\n')
exit(1) exit(1)
@ -91,12 +125,12 @@ try:
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
import magic import magic
except ImportError: 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' ' is installed, NOT the Python bindings to libmagic API \n'
'see https://github.com/ahupp/python-magic\n') 'see https://github.com/ahupp/python-magic\n')
exit(1) exit(1)
VERSION = "prismedia v0.9.1"
VERSION = "prismedia v0.10.0"
VALID_PRIVACY_STATUSES = ('public', 'private', 'unlisted') VALID_PRIVACY_STATUSES = ('public', 'private', 'unlisted')
VALID_CATEGORIES = ( VALID_CATEGORIES = (
@ -170,17 +204,104 @@ def validatePublish(publish):
def validateThumbnail(thumbnail): def validateThumbnail(thumbnail):
supported_types = ['image/jpg', 'image/jpeg'] 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 return thumbnail
else: else:
return False 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:
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
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)
# Default stdout logs is url only
formatter_stdout = logging.Formatter('%(message)s')
ch_stdout.setFormatter(formatter_stdout)
logger_stdout.addHandler(ch_stdout)
def main(): def main():
options = docopt(__doc__, version=VERSION) 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('--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({ 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( Optional('--name'): Or(None, And(
str, str,
lambda x: not x.isdigit(), lambda x: not x.isdigit(),
@ -228,7 +349,6 @@ def main():
validatePublish, validatePublish,
error="DATE should be the form YYYY-MM-DDThh:mm:ss and has to be in the future") 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('--cca'): bool,
Optional('--disable-comments'): bool, Optional('--disable-comments'): bool,
Optional('--nsfw'): bool, Optional('--nsfw'): bool,
@ -240,22 +360,41 @@ def main():
Optional('--playlist'): Or(None, str), Optional('--playlist'): Or(None, str),
Optional('--playlistCreate'): bool, Optional('--playlistCreate'): bool,
'--help': bool, '--help': bool,
'--version': bool
'--version': bool,
# This allow to return all other options for further use: https://github.com/keleshev/schema#extra-keys
object: object
}) })
# 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) options = utils.parseNFO(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'): if not options.get('--thumbnail'):
options = utils.searchThumbnail(options) options = utils.searchThumbnail(options)
try: try:
options = schema.validate(options) options = schema.validate(options)
except SchemaError as e: except SchemaError as e:
exit(e)
logger.critical(e)
exit(1)
if options.get('--debug'):
print(sys.version)
print(options)
logger.debug("Python " + sys.version)
logger.debug(options)
if options.get('--platform') is None or "peertube" in options.get('--platform'): if options.get('--platform') is None or "peertube" in options.get('--platform'):
pt_upload.run(options) pt_upload.run(options)
@ -264,6 +403,5 @@ def main():
if __name__ == '__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() main()

+ 23
- 4
prismedia/utils.py View File

@ -9,6 +9,8 @@ from subprocess import check_call, CalledProcessError, STDOUT
import unidecode import unidecode
import logging import logging
logger = logging.getLogger('Prismedia')
### CATEGORIES ### ### CATEGORIES ###
YOUTUBE_CATEGORY = { YOUTUBE_CATEGORY = {
"music": 10, "music": 10,
@ -123,18 +125,25 @@ def searchThumbnail(options):
options['--thumbnail'] = video_directory + video_file + ".jpg" options['--thumbnail'] = video_directory + video_file + ".jpg"
elif isfile(video_directory + video_file + ".jpeg"): elif isfile(video_directory + video_file + ".jpeg"):
options['--thumbnail'] = 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 options
# return the nfo as a RawConfigParser object # return the nfo as a RawConfigParser object
def loadNFO(filename): def loadNFO(filename):
try: try:
logging.info("Loading " + filename + " as NFO")
logger.info("Loading " + filename + " as NFO")
nfo = RawConfigParser() nfo = RawConfigParser()
nfo.read(filename, encoding='utf-8') nfo.read(filename, encoding='utf-8')
return nfo return nfo
except Exception as e: except Exception as e:
logging.error("Problem loading NFO file " + filename + ": " + str(e))
logger.critical("Problem loading NFO file " + filename + ": " + str(e))
exit(1) exit(1)
return False return False
@ -168,7 +177,17 @@ def parseNFO(options):
if isfile(options.get('--nfo')): if isfile(options.get('--nfo')):
nfo_cli = loadNFO(options.get('--nfo')) nfo_cli = loadNFO(options.get('--nfo'))
else: 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
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) exit(1)
# We need to load NFO in this exact order to keep the priorities # We need to load NFO in this exact order to keep the priorities
@ -188,7 +207,7 @@ def parseNFO(options):
except NoOptionError: except NoOptionError:
continue continue
except NoSectionError: except NoSectionError:
logging.error(nfo + " misses section [video], please check syntax of your NFO.")
logger.critical(nfo + " misses section [video], please check syntax of your NFO.")
exit(1) exit(1)
return options return options

+ 35
- 28
prismedia/yt_upload.py View File

@ -23,9 +23,7 @@ from google_auth_oauthlib.flow import InstalledAppFlow
from . import utils 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 # Explicitly tell the underlying HTTP transport library not to retry, since
# we are handling retry logic ourselves. # we are handling retry logic ourselves.
@ -87,7 +85,7 @@ def check_authenticated_scopes():
credential_params = json.load(f) credential_params = json.load(f)
# Check if all scopes are present # Check if all scopes are present
if credential_params["_scopes"] != SCOPES: 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) os.remove(CREDENTIALS_PATH)
@ -144,8 +142,8 @@ def initialize_upload(youtube, options):
if not playlist_id and options.get('--playlistCreate'): if not playlist_id and options.get('--playlistCreate'):
playlist_id = create_playlist(youtube, options.get('--playlist')) playlist_id = create_playlist(youtube, options.get('--playlist'))
elif not playlist_id: 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 = "" playlist_id = ""
else: else:
playlist_id = "" playlist_id = ""
@ -156,7 +154,7 @@ def initialize_upload(youtube, options):
body=body, body=body,
media_body=MediaFileUpload(path, chunksize=-1, resumable=True) media_body=MediaFileUpload(path, chunksize=-1, resumable=True)
) )
video_id = resumable_upload(insert_request, 'video', 'insert')
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 we get a video_id, upload is successful and we are able to set thumbnail
if video_id and options.get('--thumbnail'): if video_id and options.get('--thumbnail'):
@ -179,8 +177,8 @@ def get_playlist_by_name(youtube, playlist_name):
def create_playlist(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, resources = build_resource({'snippet.title': playlist_name,
'snippet.description': '', 'snippet.description': '',
'status.privacyStatus': 'public'}) 'status.privacyStatus': 'public'})
@ -244,7 +242,7 @@ def set_thumbnail(youtube, media_file, **kwargs):
def set_playlist(youtube, playlist_id, video_id): 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, resource = build_resource({'snippet.playlistId': playlist_id,
'snippet.resourceId.kind': 'youtube#video', 'snippet.resourceId.kind': 'youtube#video',
'snippet.resourceId.videoId': video_id, 'snippet.resourceId.videoId': video_id,
@ -256,38 +254,48 @@ def set_playlist(youtube, playlist_id, video_id):
part='snippet' part='snippet'
).execute() ).execute()
except Exception as e: except Exception as e:
if hasattr(e, 'message'):
logging.error("Youtube: Error: " + str(e.message))
exit(1)
else:
logging.error("Youtube: Error: " + str(e))
exit(1)
logging.info('Youtube: Video is correctly added to the playlist.')
# 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.')
# This method implements an exponential backoff strategy to resume a # This method implements an exponential backoff strategy to resume a
# failed upload. # failed upload.
def resumable_upload(request, resource, method):
def resumable_upload(request, resource, method, options):
response = None response = None
error = None error = None
retry = 0 retry = 0
logger_stdout = None
if options.get('--url-only') or options.get('--batch'):
logger_stdout = logging.getLogger('stdoutlogs')
while response is None: while response is None:
try: try:
template = 'Youtube: Uploading %s...' template = 'Youtube: Uploading %s...'
logging.info(template % resource)
logger.info(template % resource)
status, response = request.next_chunk() status, response = request.next_chunk()
if response is not None: if response is not None:
if method == 'insert' and 'id' in response: 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)' 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'])
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'] return response['id']
elif method != 'insert' or "id" not in response: elif method != 'insert' or "id" not in response:
logging.info('Youtube: Thumbnail was successfully set.')
logger.info('Youtube: Thumbnail was successfully set.')
else: else:
template = ('Youtube : The upload failed with an ' template = ('Youtube : The upload failed with an '
'unexpected response: %s') 'unexpected response: %s')
logging.error(template % response)
logger.critical(template % response)
exit(1) exit(1)
except HttpError as e: except HttpError as e:
if e.resp.status in RETRIABLE_STATUS_CODES: if e.resp.status in RETRIABLE_STATUS_CODES:
@ -299,15 +307,14 @@ def resumable_upload(request, resource, method):
error = 'Youtube : A retriable error occurred: %s' % e error = 'Youtube : A retriable error occurred: %s' % e
if error is not None: if error is not None:
logging.warning(error)
logger.warning(error)
retry += 1 retry += 1
if retry > MAX_RETRIES: 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 max_sleep = 2 ** retry
sleep_seconds = random.random() * max_sleep 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) % sleep_seconds)
time.sleep(sleep_seconds) time.sleep(sleep_seconds)
@ -317,5 +324,5 @@ def run(options):
try: try:
initialize_upload(youtube, options) initialize_upload(youtube, options)
except HttpError as e: 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)) e.content))

+ 6
- 6
pyproject.toml View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "prismedia" name = "prismedia"
version = "0.9.1"
version = "0.10.0"
description = "scripting your way to upload videos on peertube and youtube" description = "scripting your way to upload videos on peertube and youtube"
authors = [ authors = [
"LecygneNoir <git@lecygnenoir.info>", "LecygneNoir <git@lecygnenoir.info>",
@ -21,10 +21,10 @@ python = ">=3.5"
configparser = "^3.7.1" configparser = "^3.7.1"
docopt = "^0.6.2" docopt = "^0.6.2"
future = "^0.17.1" 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" httplib2 = "^0.12.1"
oauthlib = "^2.1.0" oauthlib = "^2.1.0"
python-magic = "^0.4.15" python-magic = "^0.4.15"
@ -32,7 +32,7 @@ python-magic-bin = { version = "^0.4.14", markers = "platform_system == 'Windows
requests = "^2.18.4" requests = "^2.18.4"
requests-oauthlib = "^0.8.0" requests-oauthlib = "^0.8.0"
requests-toolbelt = "^0.9.1" requests-toolbelt = "^0.9.1"
schema = "^0.6.8"
schema = ">=0.7.1"
tzlocal = "^1.5.1" tzlocal = "^1.5.1"
Unidecode = "^1.0.23" Unidecode = "^1.0.23"
uritemplate = "^3.0.0" uritemplate = "^3.0.0"

Loading…
Cancel
Save