From fb889cd078312029aba9a148004b8bba8aa398dd Mon Sep 17 00:00:00 2001 From: Christopher Usher Date: Thu, 29 Aug 2019 19:36:50 +0100 Subject: [PATCH 01/26] database changes to keep track of editors and edit times --- DATABASE.md | 3 +++ common/common/database.py | 11 ++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/DATABASE.md b/DATABASE.md index 5a568d7..ac109cd 100644 --- a/DATABASE.md +++ b/DATABASE.md @@ -145,3 +145,6 @@ columns | type | role | `error` | `TEXT` | state | A human-readable error message, set if a non-retryable error occurs. Its presence indicates operator intervention is required. Cleared on a re-edit if set. `video_id` | `TEXT` | state | An id that can be used to refer to the video to check if transcoding is complete. Often the video_link can be generated from this, but not nessecarily. `video_link` | `TEXT` | output | A link to the uploaded video. Only set when state is `TRANSCODING` or `DONE`. +`editor` | `TEXT` | state | Email address of the last editor; corresponds to an entry in the `editors` table. Only set when state is not `UNEDITED`. +`edit_time` | `TIMESTAMP` | state | Time of the last edit. Only set when state is not `UNEDITED`. +`upload_time` | `TIMESTAMP` | state | Time when video state is set to `DONE`. Only set when state is `DONE`. diff --git a/common/common/database.py b/common/common/database.py index 8625f52..8627245 100644 --- a/common/common/database.py +++ b/common/common/database.py @@ -54,7 +54,11 @@ CREATE TABLE IF NOT EXISTS events ( uploader TEXT CHECK (state IN ('UNEDITED', 'EDITED', 'DONE') OR uploader IS NOT NULL), error TEXT, video_id TEXT, - video_link TEXT CHECK (state != 'DONE' OR video_link IS NOT NULL) + video_link TEXT CHECK (state != 'DONE' OR video_link IS NOT NULL), + editor TEXT CHECK (state = 'UNEDITED' OR editor IS NOT NULL), + edit_time TIMESTAMP CHECK (state = 'UNEDITED' OR editor IS NOT NULL), + upload_time TIMESTAMP CHECK (state != 'DONE' OR upload_time IS NOT NULL), + ); -- Index on state, since that's almost always what we're querying on besides id @@ -66,6 +70,11 @@ CREATE TABLE IF NOT EXISTS nodes ( backfill_from BOOLEAN NOT NULL DEFAULT TRUE ); +CREATE TABLE IF NOT EXISTS editors ( + email TEXT PRIMARY KEY, + name TEXT NOT NULL +); + """ From ea42cab8fd40d915eb8ed1ba59cdc4bd17784f8f Mon Sep 17 00:00:00 2001 From: Christopher Usher Date: Fri, 30 Aug 2019 02:27:33 +0100 Subject: [PATCH 02/26] Added editor, edit_time and upload_time to thrimshim and cutter updates of the database --- common/common/database.py | 2 +- cutter/cutter/main.py | 5 +++-- thrimshim/thrimshim/main.py | 13 +++++++++---- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/common/common/database.py b/common/common/database.py index 8627245..fe65077 100644 --- a/common/common/database.py +++ b/common/common/database.py @@ -57,7 +57,7 @@ CREATE TABLE IF NOT EXISTS events ( video_link TEXT CHECK (state != 'DONE' OR video_link IS NOT NULL), editor TEXT CHECK (state = 'UNEDITED' OR editor IS NOT NULL), edit_time TIMESTAMP CHECK (state = 'UNEDITED' OR editor IS NOT NULL), - upload_time TIMESTAMP CHECK (state != 'DONE' OR upload_time IS NOT NULL), + upload_time TIMESTAMP CHECK (state != 'DONE' OR upload_time IS NOT NULL) ); diff --git a/cutter/cutter/main.py b/cutter/cutter/main.py index 60e8735..e116f6b 100644 --- a/cutter/cutter/main.py +++ b/cutter/cutter/main.py @@ -1,4 +1,5 @@ +import datetime import json import logging import os @@ -461,9 +462,9 @@ class TranscodeChecker(object): def mark_done(self, ids): result = query(self.conn, """ UPDATE events - SET state = 'DONE' + SET state = 'DONE', upload_time = %s WHERE id = ANY (%s::uuid[]) AND state = 'TRANSCODING' - """, ids.keys()) + """, datetime.datetime.utcnow(), ids.keys()) return result.rowcount diff --git a/thrimshim/thrimshim/main.py b/thrimshim/thrimshim/main.py index 39543f9..d59b5a7 100644 --- a/thrimshim/thrimshim/main.py +++ b/thrimshim/thrimshim/main.py @@ -151,6 +151,9 @@ def update_row(ident, new_row): return 'Invalid state {}'.format(new_row['state']), 400 new_row['uploader'] = None new_row['error'] = None + editor = '' # TODO replace with email form authentication + new_row['editor'] = editor + new_row['edit_time'] = datetime.datetime.utcnow() # actually update database build_query = sql.SQL(""" @@ -186,12 +189,14 @@ def manual_link(ident): return 'Row {} not found'.format(ident), 404 if old_row.state != 'UNEDITED' and not (old_row.state == 'DONE' and old_row.upload_location == 'manual'): return 'Invalid state {} for manual video link'.format(old_row.state), 403 - + editor = '' # TODO replace with email form authentication + now = datetime.datetime.utcnow() results = database.query(conn, """ UPDATE events - SET state='DONE', upload_location = 'manual', video_link = %s + SET state='DONE', upload_location = 'manual', video_link = %s, + editor = %s, edit_time = %s, upload_time = %s WHERE id = %s AND (state = 'UNEDITED' OR (state = 'DONE' AND - upload_location = 'manual'))""", link, ident) + upload_location = 'manual'))""", link, editor, now, now, ident) logging.info("Row {} video_link set to {}".format(ident, link)) return '' @@ -204,7 +209,7 @@ def reset_row(ident): results = database.query(conn, """ UPDATE events SET state='UNEDITED', error = NULL, video_id = NULL, video_link = NULL, - uploader = NULL + uploader = NULL, editor = NULL, edit_time = NULL, upload_time = NULL WHERE id = %s""", ident) if results.rowcount != 1: return 'Row id = {} not found'.format(ident), 404 From 365d7b78529e1b00aac31b93d107a13e59c47294 Mon Sep 17 00:00:00 2001 From: Christopher Usher Date: Fri, 13 Sep 2019 00:48:53 +0100 Subject: [PATCH 03/26] Placeholder text --- thrimshim/thrimshim/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/thrimshim/thrimshim/main.py b/thrimshim/thrimshim/main.py index d59b5a7..623f29c 100644 --- a/thrimshim/thrimshim/main.py +++ b/thrimshim/thrimshim/main.py @@ -151,7 +151,7 @@ def update_row(ident, new_row): return 'Invalid state {}'.format(new_row['state']), 400 new_row['uploader'] = None new_row['error'] = None - editor = '' # TODO replace with email form authentication + editor = 'PLACEHOLDER' # TODO replace with email form authentication new_row['editor'] = editor new_row['edit_time'] = datetime.datetime.utcnow() @@ -189,7 +189,7 @@ def manual_link(ident): return 'Row {} not found'.format(ident), 404 if old_row.state != 'UNEDITED' and not (old_row.state == 'DONE' and old_row.upload_location == 'manual'): return 'Invalid state {} for manual video link'.format(old_row.state), 403 - editor = '' # TODO replace with email form authentication + editor = 'PLACEHOLDER' # TODO replace with email form authentication now = datetime.datetime.utcnow() results = database.query(conn, """ UPDATE events From d578da90d065e6675ca8710d5dd1ac558e86bdae Mon Sep 17 00:00:00 2001 From: Christopher Usher Date: Fri, 13 Sep 2019 01:13:18 +0100 Subject: [PATCH 04/26] Setting up Auth Test function --- thrimshim/setup.py | 1 + thrimshim/thrimshim/main.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/thrimshim/setup.py b/thrimshim/setup.py index 947530a..a3f14c4 100644 --- a/thrimshim/setup.py +++ b/thrimshim/setup.py @@ -10,6 +10,7 @@ setup( "gevent", "psycogreen", "psycopg2", + "google-auth" "wubloader-common", ], ) diff --git a/thrimshim/thrimshim/main.py b/thrimshim/thrimshim/main.py index 623f29c..bd5857a 100644 --- a/thrimshim/thrimshim/main.py +++ b/thrimshim/thrimshim/main.py @@ -15,6 +15,9 @@ from psycopg2 import sql from common import database, PromLogCountsHandler, install_stacksampler from common.flask_stats import request_stats, after_request +from google.oauth2 import id_token +from google.auth.transport import requests + psycopg2.extras.register_uuid() app = flask.Flask('thrimshim') app.after_request(after_request) @@ -35,6 +38,24 @@ def cors(app): return app(environ, _start_response) return handle +@app.route('/thrimshim/auth-test', methods=['GET', 'POST']) +@request_stats +def auth_test(): + if flask.request.method == 'POST': + userToken = flask.request.json.token + try: + # Alternate method, query this endpoint: https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123 + idinfo = id_token.verify_oauth2_token(userToken, requests.Request(), None) + + # ID token is valid. Get the user's Google Account ID from the decoded token. + userid = idinfo['sub'] + + return json.dumps(idinfo) + except ValueError: + # Invalid token + pass + else: + return "Hello World!" @app.route('/metrics') @request_stats From f3b1b991e5a6c5ba16720a967c90a225dddcdd56 Mon Sep 17 00:00:00 2001 From: mg Date: Wed, 11 Sep 2019 19:28:54 -0300 Subject: [PATCH 05/26] Adding dependencies and retrieving account info. --- thrimshim/setup.py | 4 +++- thrimshim/thrimshim/main.py | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/thrimshim/setup.py b/thrimshim/setup.py index a3f14c4..5a5d8e5 100644 --- a/thrimshim/setup.py +++ b/thrimshim/setup.py @@ -10,7 +10,9 @@ setup( "gevent", "psycogreen", "psycopg2", - "google-auth" + "requests", + "six", + "google-auth", "wubloader-common", ], ) diff --git a/thrimshim/thrimshim/main.py b/thrimshim/thrimshim/main.py index bd5857a..9566050 100644 --- a/thrimshim/thrimshim/main.py +++ b/thrimshim/thrimshim/main.py @@ -42,15 +42,17 @@ def cors(app): @request_stats def auth_test(): if flask.request.method == 'POST': - userToken = flask.request.json.token + userToken = flask.request.json['token'] try: # Alternate method, query this endpoint: https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123 idinfo = id_token.verify_oauth2_token(userToken, requests.Request(), None) # ID token is valid. Get the user's Google Account ID from the decoded token. - userid = idinfo['sub'] + # userid = idinfo['sub'] - return json.dumps(idinfo) + userEmail = idinfo['email'] + + return json.dumps(userEmail) except ValueError: # Invalid token pass From f4d0fbf42ed1818df099c375e43385ea63b08fd3 Mon Sep 17 00:00:00 2001 From: mg Date: Wed, 11 Sep 2019 20:29:34 -0300 Subject: [PATCH 06/26] Adding issuer validation. --- thrimshim/thrimshim/main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/thrimshim/thrimshim/main.py b/thrimshim/thrimshim/main.py index 9566050..7a2aadb 100644 --- a/thrimshim/thrimshim/main.py +++ b/thrimshim/thrimshim/main.py @@ -43,13 +43,16 @@ def cors(app): def auth_test(): if flask.request.method == 'POST': userToken = flask.request.json['token'] + # Reference: https://developers.google.com/identity/sign-in/web/backend-auth try: # Alternate method, query this endpoint: https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123 idinfo = id_token.verify_oauth2_token(userToken, requests.Request(), None) + if idinfo['iss'] not in ['accounts.google.com', 'https://accounts.google.com']: + raise ValueError('Wrong issuer.') + # ID token is valid. Get the user's Google Account ID from the decoded token. # userid = idinfo['sub'] - userEmail = idinfo['email'] return json.dumps(userEmail) From 4779d88e371437790a11d94b2f0262e5bc7261b4 Mon Sep 17 00:00:00 2001 From: Christopher Usher Date: Fri, 13 Sep 2019 01:38:15 +0100 Subject: [PATCH 07/26] code style clean up --- thrimshim/setup.py | 3 +-- thrimshim/thrimshim/main.py | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/thrimshim/setup.py b/thrimshim/setup.py index 5a5d8e5..364c47f 100644 --- a/thrimshim/setup.py +++ b/thrimshim/setup.py @@ -8,11 +8,10 @@ setup( "argh", "flask", "gevent", + "google-auth", "psycogreen", "psycopg2", "requests", - "six", - "google-auth", "wubloader-common", ], ) diff --git a/thrimshim/thrimshim/main.py b/thrimshim/thrimshim/main.py index 7a2aadb..f6acf4f 100644 --- a/thrimshim/thrimshim/main.py +++ b/thrimshim/thrimshim/main.py @@ -47,18 +47,15 @@ def auth_test(): try: # Alternate method, query this endpoint: https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123 idinfo = id_token.verify_oauth2_token(userToken, requests.Request(), None) - if idinfo['iss'] not in ['accounts.google.com', 'https://accounts.google.com']: raise ValueError('Wrong issuer.') - # ID token is valid. Get the user's Google Account ID from the decoded token. # userid = idinfo['sub'] userEmail = idinfo['email'] - return json.dumps(userEmail) except ValueError: # Invalid token - pass + return 'Invalid token. Access denied.', 403 else: return "Hello World!" From 377a6697b0ee210791e3b51ad351dff4161f9841 Mon Sep 17 00:00:00 2001 From: Christopher Usher Date: Sat, 14 Sep 2019 02:28:35 +0100 Subject: [PATCH 08/26] decorator to avoid duplication authentication code --- thrimshim/thrimshim/main.py | 49 +++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/thrimshim/thrimshim/main.py b/thrimshim/thrimshim/main.py index f6acf4f..1322158 100644 --- a/thrimshim/thrimshim/main.py +++ b/thrimshim/thrimshim/main.py @@ -1,4 +1,5 @@ import datetime +from functools import wraps import json import logging import signal @@ -38,6 +39,42 @@ def cors(app): return app(environ, _start_response) return handle + + +def authenticate(f): + """Authenticate a token against the database. + + Reference: https://developers.google.com/identity/sign-in/web/backend-auth""" + @wraps(f) + def decorated_function(*args): + if flask.request.method == 'POST': + userToken = flask.request.json['token'] + # check whether token is valid + try: + idinfo = id_token.verify_oauth2_token(userToken, requests.Request(), None) + if idinfo['iss'] not in ['accounts.google.com', 'https://accounts.google.com']: + raise ValueError('Wrong issuer.') + except ValueError: + return 'Invalid token. Access denied.', 403 + email = idinfo['email'] + # check whether user is in the database + conn = app.db_manager.get_conn() + results = database.query(conn, """ + SELECT email + FROM editors + WHERE email = %s""", email) + row = results.fetchone() + if row is None: + return 'Unknown user. Access denied.', 403 + + return f(*args, editor=email) + + else: + return f(*args) + + return decorated_function + + @app.route('/thrimshim/auth-test', methods=['GET', 'POST']) @request_stats def auth_test(): @@ -89,11 +126,11 @@ def get_all_rows(): @app.route('/thrimshim/', methods=['GET', 'POST']) @request_stats -def thrimshim(ident): +def thrimshim(ident, editor=None): """Comunicate between Thrimbletrimmer and the Wubloader database.""" if flask.request.method == 'POST': row = flask.request.json - return update_row(ident, row) + return update_row(ident, row, editor) else: return get_row(ident) @@ -120,7 +157,7 @@ def get_row(ident): logging.info('Row {} fetched'.format(ident)) return json.dumps(response) -def update_row(ident, new_row): +def update_row(ident, new_row, editor): """Updates row of database with id = ident with the edit columns in new_row.""" @@ -174,7 +211,6 @@ def update_row(ident, new_row): return 'Invalid state {}'.format(new_row['state']), 400 new_row['uploader'] = None new_row['error'] = None - editor = 'PLACEHOLDER' # TODO replace with email form authentication new_row['editor'] = editor new_row['edit_time'] = datetime.datetime.utcnow() @@ -198,7 +234,7 @@ def update_row(ident, new_row): @app.route('/thrimshim/manual-link/', methods=['POST']) @request_stats -def manual_link(ident): +def manual_link(ident, editor=None): """Manually set a video_link if the state is 'UNEDITED' or 'DONE' and the upload_location is 'manual'.""" link = flask.request.json @@ -212,7 +248,6 @@ def manual_link(ident): return 'Row {} not found'.format(ident), 404 if old_row.state != 'UNEDITED' and not (old_row.state == 'DONE' and old_row.upload_location == 'manual'): return 'Invalid state {} for manual video link'.format(old_row.state), 403 - editor = 'PLACEHOLDER' # TODO replace with email form authentication now = datetime.datetime.utcnow() results = database.query(conn, """ UPDATE events @@ -226,7 +261,7 @@ def manual_link(ident): @app.route('/thrimshim/reset/', methods=['POST']) @request_stats -def reset_row(ident): +def reset_row(ident, editor=None): """Clear state and video_link columns and reset state to 'UNEDITED'.""" conn = app.db_manager.get_conn() results = database.query(conn, """ From c8e44af20d183206935d3ef38c5270e42a848aaa Mon Sep 17 00:00:00 2001 From: Christopher Usher Date: Sat, 14 Sep 2019 03:19:12 +0100 Subject: [PATCH 09/26] authentication implemented and tested as much as possible without authentication in thrimbletrimmer --- thrimshim/thrimshim/main.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/thrimshim/thrimshim/main.py b/thrimshim/thrimshim/main.py index 1322158..80c1164 100644 --- a/thrimshim/thrimshim/main.py +++ b/thrimshim/thrimshim/main.py @@ -46,7 +46,7 @@ def authenticate(f): Reference: https://developers.google.com/identity/sign-in/web/backend-auth""" @wraps(f) - def decorated_function(*args): + def decorated_function(*args, **kwargs): if flask.request.method == 'POST': userToken = flask.request.json['token'] # check whether token is valid @@ -65,15 +65,24 @@ def authenticate(f): WHERE email = %s""", email) row = results.fetchone() if row is None: - return 'Unknown user. Access denied.', 403 + return 'Unknown user. Access denied.', 403 - return f(*args, editor=email) + return f(*args, editor=email, **kwargs) else: - return f(*args) + return f(*args, **kwargs) return decorated_function +@app.route('/thrimshim/test', methods=['GET', 'POST']) +@request_stats +@authenticate +def test(editor=None): + if flask.request.method == 'POST': + return json.dumps(editor) + else: + return "Hello World!" + @app.route('/thrimshim/auth-test', methods=['GET', 'POST']) @request_stats @@ -125,6 +134,7 @@ def get_all_rows(): return json.dumps(rows) @app.route('/thrimshim/', methods=['GET', 'POST']) +@authenticate @request_stats def thrimshim(ident, editor=None): """Comunicate between Thrimbletrimmer and the Wubloader database.""" @@ -233,6 +243,7 @@ def update_row(ident, new_row, editor): return '' @app.route('/thrimshim/manual-link/', methods=['POST']) +@authenticate @request_stats def manual_link(ident, editor=None): """Manually set a video_link if the state is 'UNEDITED' or 'DONE' and the @@ -260,6 +271,7 @@ def manual_link(ident, editor=None): @app.route('/thrimshim/reset/', methods=['POST']) +@authenticate @request_stats def reset_row(ident, editor=None): """Clear state and video_link columns and reset state to 'UNEDITED'.""" From d55d6fb7bc42ff9fe88311e8d91338aee78f38b9 Mon Sep 17 00:00:00 2001 From: Christopher Usher Date: Sun, 15 Sep 2019 01:09:22 +0100 Subject: [PATCH 10/26] added option not to authenticate thrimshim --- docker-compose.jsonnet | 1 + thrimshim/thrimshim/main.py | 47 ++++++++++++++----------------------- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/docker-compose.jsonnet b/docker-compose.jsonnet index 2725a5a..a19e755 100644 --- a/docker-compose.jsonnet +++ b/docker-compose.jsonnet @@ -176,6 +176,7 @@ command: [ "--backdoor-port", std.toString($.backdoor_port), $.db_connect, + "--no-authentication", ], // Mount the segments directory at /mnt volumes: ["%s:/mnt" % $.segments_path], diff --git a/thrimshim/thrimshim/main.py b/thrimshim/thrimshim/main.py index 80c1164..8b167f1 100644 --- a/thrimshim/thrimshim/main.py +++ b/thrimshim/thrimshim/main.py @@ -48,6 +48,9 @@ def authenticate(f): @wraps(f) def decorated_function(*args, **kwargs): if flask.request.method == 'POST': + if app.no_authentication: + return f(*args, editor='NOT_AUTH', **kwargs) + userToken = flask.request.json['token'] # check whether token is valid try: @@ -56,8 +59,9 @@ def authenticate(f): raise ValueError('Wrong issuer.') except ValueError: return 'Invalid token. Access denied.', 403 - email = idinfo['email'] + # check whether user is in the database + email = idinfo['email'] conn = app.db_manager.get_conn() results = database.query(conn, """ SELECT email @@ -74,7 +78,7 @@ def authenticate(f): return decorated_function -@app.route('/thrimshim/test', methods=['GET', 'POST']) +@app.route('/thrimshim/auth-test', methods=['GET', 'POST']) @request_stats @authenticate def test(editor=None): @@ -83,28 +87,6 @@ def test(editor=None): else: return "Hello World!" - -@app.route('/thrimshim/auth-test', methods=['GET', 'POST']) -@request_stats -def auth_test(): - if flask.request.method == 'POST': - userToken = flask.request.json['token'] - # Reference: https://developers.google.com/identity/sign-in/web/backend-auth - try: - # Alternate method, query this endpoint: https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123 - idinfo = id_token.verify_oauth2_token(userToken, requests.Request(), None) - if idinfo['iss'] not in ['accounts.google.com', 'https://accounts.google.com']: - raise ValueError('Wrong issuer.') - # ID token is valid. Get the user's Google Account ID from the decoded token. - # userid = idinfo['sub'] - userEmail = idinfo['email'] - return json.dumps(userEmail) - except ValueError: - # Invalid token - return 'Invalid token. Access denied.', 403 - else: - return "Hello World!" - @app.route('/metrics') @request_stats def metrics(): @@ -134,8 +116,8 @@ def get_all_rows(): return json.dumps(rows) @app.route('/thrimshim/', methods=['GET', 'POST']) -@authenticate @request_stats +@authenticate def thrimshim(ident, editor=None): """Comunicate between Thrimbletrimmer and the Wubloader database.""" if flask.request.method == 'POST': @@ -243,8 +225,8 @@ def update_row(ident, new_row, editor): return '' @app.route('/thrimshim/manual-link/', methods=['POST']) -@authenticate @request_stats +@authenticate def manual_link(ident, editor=None): """Manually set a video_link if the state is 'UNEDITED' or 'DONE' and the upload_location is 'manual'.""" @@ -271,8 +253,8 @@ def manual_link(ident, editor=None): @app.route('/thrimshim/reset/', methods=['POST']) -@authenticate @request_stats +@authenticate def reset_row(ident, editor=None): """Clear state and video_link columns and reset state to 'UNEDITED'.""" conn = app.db_manager.get_conn() @@ -291,13 +273,16 @@ def reset_row(ident, editor=None): @argh.arg('--port', help='Port server will listen on. Default is 8004.') @argh.arg('connection-string', help='Postgres connection string, which is either a space-separated list of key=value pairs, or a URI like: postgresql://USER:PASSWORD@HOST/DBNAME?KEY=VALUE') @argh.arg('--backdoor-port', help='Port for gevent.backdoor access. By default disabled.') -def main(connection_string, host='0.0.0.0', port=8004, backdoor_port=0): +@argh.arg('--no-authentication', help='Do not authenticate') +def main(connection_string, host='0.0.0.0', port=8004, backdoor_port=0, + no_authentication=False): """Thrimshim service.""" server = WSGIServer((host, port), cors(app)) app.db_manager = database.DBManager(dsn=connection_string) + app.no_authentication = no_authentication def stop(): - logging.info("Shutting down") + logging.info('Shutting down') server.stop() gevent.signal(signal.SIGTERM, stop) @@ -307,6 +292,8 @@ def main(connection_string, host='0.0.0.0', port=8004, backdoor_port=0): if backdoor_port: gevent.backdoor.BackdoorServer(('127.0.0.1', backdoor_port), locals=locals()).start() - logging.info("Starting up") + logging.info('Starting up') + if app.no_authentication: + logging.warning('Not authenticating POST requests') server.serve_forever() logging.info("Gracefully shut down") From 3f2c30849fbf722945833eae218197fec2168083 Mon Sep 17 00:00:00 2001 From: Christopher Usher Date: Sun, 15 Sep 2019 01:14:00 +0100 Subject: [PATCH 11/26] not authenticating no longer default in docker-compose --- docker-compose.jsonnet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.jsonnet b/docker-compose.jsonnet index a19e755..503a490 100644 --- a/docker-compose.jsonnet +++ b/docker-compose.jsonnet @@ -176,7 +176,7 @@ command: [ "--backdoor-port", std.toString($.backdoor_port), $.db_connect, - "--no-authentication", + // "--no-authentication", //uncomment to run thrimshim without authentication ], // Mount the segments directory at /mnt volumes: ["%s:/mnt" % $.segments_path], From 0cca85b4aa73c851fd866f82f3d15a7ed16d6e1f Mon Sep 17 00:00:00 2001 From: mg Date: Sun, 15 Sep 2019 01:43:38 -0300 Subject: [PATCH 12/26] Starting thrimbletrimmer integration. --- docker-compose.jsonnet | 12 +-- nginx/Dockerfile | 1 + nginx/generate-config | 6 +- thrimbletrimmer/TestPage.html | 134 ++++++++++++++++++++++++++++++++++ 4 files changed, 146 insertions(+), 7 deletions(-) create mode 100644 thrimbletrimmer/TestPage.html diff --git a/docker-compose.jsonnet b/docker-compose.jsonnet index 503a490..731ce00 100644 --- a/docker-compose.jsonnet +++ b/docker-compose.jsonnet @@ -13,18 +13,19 @@ // For each service, whether to deploy that service. enabled:: { - downloader: true, + downloader: false, restreamer: true, - backfiller: true, - cutter: true, + backfiller: false, + cutter: false, sheetsync: false, thrimshim: true, nginx: true, - postgres: false, + postgres: true, + thrimbletrimmer: true, }, // Twitch channel to capture - channel:: "desertbus", + channel:: "rpglimitbreak", // Stream qualities to capture qualities:: ["source", "480p"], @@ -212,6 +213,7 @@ [if $.enabled.nginx then "nginx"]: { # mapping of services to internal ports for nginx to forward local forward_ports = { + thrimbletrimmer: 80, restreamer: 8000, downloader: 8001, backfiller: 8002, diff --git a/nginx/Dockerfile b/nginx/Dockerfile index 7a12782..085e408 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -1,4 +1,5 @@ # nginx container contains config that exposes all the various services metrics FROM nginx:latest ADD nginx/generate-config / +COPY thrimbletrimmer /etc/nginx/html/thrimbletrimmer ENTRYPOINT ["/bin/sh", "-c", "/generate-config && nginx -g \"daemon off;\""] diff --git a/nginx/generate-config b/nginx/generate-config index b837428..07adbf0 100755 --- a/nginx/generate-config +++ b/nginx/generate-config @@ -14,8 +14,10 @@ LOCATIONS=$( [ "$name" == "restreamer" ] && generate_location / "http://restreamer:$port" # thrimshim takes any calls to thrimshim/ [ "$name" == "thrimshim" ] && generate_location /thrimshim "http://thrimshim:$port" - # all services have metrics under /metrics/SERVICE - generate_location "/metrics/$name" "http://$name:$port/metrics" + # thrimbletrimmer takes any calls to thrimbletrimmer/ + [ "$name" == "thrimbletrimmer" ] && echo -e "\t\tlocation /thrimbletrimmer { }" + # all services have metrics under /metrics/SERVICE, except for thrimebletrimmer + [ "$name" != "thrimbletrimmer" ] && generate_location "/metrics/$name" "http://$name:$port/metrics" done ) diff --git a/thrimbletrimmer/TestPage.html b/thrimbletrimmer/TestPage.html new file mode 100644 index 0000000..885e676 --- /dev/null +++ b/thrimbletrimmer/TestPage.html @@ -0,0 +1,134 @@ + + + + + + + Thrimbletrimmer Goes Forth + + + + + + + +
+ Sign out + + +
+
+ + + + All Rows +
+ +
+
+
+
+
+
+
+
+ + + \ No newline at end of file From aed2a77a88e16718a38ef595bbb7dd3f9fbfe6f2 Mon Sep 17 00:00:00 2001 From: mg Date: Mon, 16 Sep 2019 19:16:47 -0300 Subject: [PATCH 13/26] Initial merge of Thrimbletrimmer proper into repo. --- nginx/generate-config | 3 +- thrimbletrimmer/TestPage.html | 266 +++++----- thrimbletrimmer/dashboard.html | 121 +++++ thrimbletrimmer/index.html | 151 ++++++ .../plugins/video.js/dist/video-js.min.css | 1 + .../plugins/video.js/dist/video.min.js | 21 + .../videojs-contrib-quality-levels.min.js | 2 + .../dist/videojs-hls-quality-selector.css | 7 + .../dist/videojs-hls-quality-selector.css.bak | 7 + .../dist/videojs-hls-quality-selector.min.js | 7 + .../videojs-hls-quality-selector.min.js.bak | 7 + .../dist/videojs-trimming-controls.cjs.js | 456 +++++++++++++++++ .../dist/videojs-trimming-controls.css | 1 + .../dist/videojs-trimming-controls.es.js | 452 +++++++++++++++++ .../dist/videojs-trimming-controls.js | 460 ++++++++++++++++++ .../dist/videojs-trimming-controls.min.js | 2 + thrimbletrimmer/scripts/IO.js | 132 +++++ thrimbletrimmer/scripts/keyboardShortcuts.js | 77 +++ thrimbletrimmer/scripts/playerSetup.js | 89 ++++ thrimbletrimmer/styles/style.css | 34 ++ 20 files changed, 2162 insertions(+), 134 deletions(-) create mode 100644 thrimbletrimmer/dashboard.html create mode 100644 thrimbletrimmer/index.html create mode 100644 thrimbletrimmer/plugins/video.js/dist/video-js.min.css create mode 100644 thrimbletrimmer/plugins/video.js/dist/video.min.js create mode 100644 thrimbletrimmer/plugins/videojs-contrib-quality-levels/dist/videojs-contrib-quality-levels.min.js create mode 100644 thrimbletrimmer/plugins/videojs-hls-quality-selector/dist/videojs-hls-quality-selector.css create mode 100644 thrimbletrimmer/plugins/videojs-hls-quality-selector/dist/videojs-hls-quality-selector.css.bak create mode 100644 thrimbletrimmer/plugins/videojs-hls-quality-selector/dist/videojs-hls-quality-selector.min.js create mode 100644 thrimbletrimmer/plugins/videojs-hls-quality-selector/dist/videojs-hls-quality-selector.min.js.bak create mode 100644 thrimbletrimmer/plugins/videojs-trimming-controls/dist/videojs-trimming-controls.cjs.js create mode 100644 thrimbletrimmer/plugins/videojs-trimming-controls/dist/videojs-trimming-controls.css create mode 100644 thrimbletrimmer/plugins/videojs-trimming-controls/dist/videojs-trimming-controls.es.js create mode 100644 thrimbletrimmer/plugins/videojs-trimming-controls/dist/videojs-trimming-controls.js create mode 100644 thrimbletrimmer/plugins/videojs-trimming-controls/dist/videojs-trimming-controls.min.js create mode 100644 thrimbletrimmer/scripts/IO.js create mode 100644 thrimbletrimmer/scripts/keyboardShortcuts.js create mode 100644 thrimbletrimmer/scripts/playerSetup.js create mode 100644 thrimbletrimmer/styles/style.css diff --git a/nginx/generate-config b/nginx/generate-config index 07adbf0..fe563e5 100755 --- a/nginx/generate-config +++ b/nginx/generate-config @@ -14,7 +14,7 @@ LOCATIONS=$( [ "$name" == "restreamer" ] && generate_location / "http://restreamer:$port" # thrimshim takes any calls to thrimshim/ [ "$name" == "thrimshim" ] && generate_location /thrimshim "http://thrimshim:$port" - # thrimbletrimmer takes any calls to thrimbletrimmer/ + # thrimbletrimmer takes any calls to thrimbletrimmer/; serves content from /etc/nginx/html/thrimbletrimmer [ "$name" == "thrimbletrimmer" ] && echo -e "\t\tlocation /thrimbletrimmer { }" # all services have metrics under /metrics/SERVICE, except for thrimebletrimmer [ "$name" != "thrimbletrimmer" ] && generate_location "/metrics/$name" "http://$name:$port/metrics" @@ -29,6 +29,7 @@ events { } http { + include /etc/nginx/mime.types; server { listen 80; $LOCATIONS diff --git a/thrimbletrimmer/TestPage.html b/thrimbletrimmer/TestPage.html index 885e676..0daf0a7 100644 --- a/thrimbletrimmer/TestPage.html +++ b/thrimbletrimmer/TestPage.html @@ -1,134 +1,134 @@ - - - - - - - Thrimbletrimmer Goes Forth - - - - - - - -
- Sign out - - -
-
- - - - All Rows -
- -
-
-
-
-
-
-
-
- - + + + + + + + Thrimbletrimmer Goes Forth + + + + + + + +
+ Sign out + + +
+
+ + + + All Rows +
+ +
+
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/thrimbletrimmer/dashboard.html b/thrimbletrimmer/dashboard.html new file mode 100644 index 0000000..eb67a70 --- /dev/null +++ b/thrimbletrimmer/dashboard.html @@ -0,0 +1,121 @@ + + + + + + + Thrimbletrimmer Goes Forth + + + + + + + + +
+ + +
+

Wubloader Queue

+ + + + + + + + + + + +
Start TimeEnd TimeEvent TypeDescriptionStateEditManual LinkReset
+
+
+ + + \ No newline at end of file diff --git a/thrimbletrimmer/index.html b/thrimbletrimmer/index.html new file mode 100644 index 0000000..aafb4b3 --- /dev/null +++ b/thrimbletrimmer/index.html @@ -0,0 +1,151 @@ + + + + + + + Thrimbletrimmer Goes Forth + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Wubloader URLStreamStart TimeEnd TimeAllow HolesExperimental
WubloadersStreamsHours + UTC + Bustime +
Advanced Options:
ThrimShim ID:
+ +
+ +
+ +
+
+ Title:
+ +
+
+ Description:
+ +
+ + + Go To Dashboard + Help +
+ +
+ + + + + + + + + +
+Sign out + + + \ No newline at end of file diff --git a/thrimbletrimmer/plugins/video.js/dist/video-js.min.css b/thrimbletrimmer/plugins/video.js/dist/video-js.min.css new file mode 100644 index 0000000..68c82db --- /dev/null +++ b/thrimbletrimmer/plugins/video.js/dist/video-js.min.css @@ -0,0 +1 @@ +@charset "UTF-8";.video-js .vjs-big-play-button .vjs-icon-placeholder:before,.video-js .vjs-modal-dialog,.vjs-button>.vjs-icon-placeholder:before,.vjs-modal-dialog .vjs-modal-dialog-content{position:absolute;top:0;left:0;width:100%;height:100%}.video-js .vjs-big-play-button .vjs-icon-placeholder:before,.vjs-button>.vjs-icon-placeholder:before{text-align:center}@font-face{font-family:VideoJS;src:url(data:application/font-woff;charset=utf-8;base64,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) format("woff");font-weight:400;font-style:normal}.video-js .vjs-big-play-button .vjs-icon-placeholder:before,.video-js .vjs-play-control .vjs-icon-placeholder,.vjs-icon-play{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-big-play-button .vjs-icon-placeholder:before,.video-js .vjs-play-control .vjs-icon-placeholder:before,.vjs-icon-play:before{content:"\f101"}.vjs-icon-play-circle{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-play-circle:before{content:"\f102"}.video-js .vjs-play-control.vjs-playing .vjs-icon-placeholder,.vjs-icon-pause{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-play-control.vjs-playing .vjs-icon-placeholder:before,.vjs-icon-pause:before{content:"\f103"}.video-js .vjs-mute-control.vjs-vol-0 .vjs-icon-placeholder,.vjs-icon-volume-mute{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-mute-control.vjs-vol-0 .vjs-icon-placeholder:before,.vjs-icon-volume-mute:before{content:"\f104"}.video-js .vjs-mute-control.vjs-vol-1 .vjs-icon-placeholder,.vjs-icon-volume-low{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-mute-control.vjs-vol-1 .vjs-icon-placeholder:before,.vjs-icon-volume-low:before{content:"\f105"}.video-js .vjs-mute-control.vjs-vol-2 .vjs-icon-placeholder,.vjs-icon-volume-mid{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-mute-control.vjs-vol-2 .vjs-icon-placeholder:before,.vjs-icon-volume-mid:before{content:"\f106"}.video-js .vjs-mute-control .vjs-icon-placeholder,.vjs-icon-volume-high{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-mute-control .vjs-icon-placeholder:before,.vjs-icon-volume-high:before{content:"\f107"}.video-js .vjs-fullscreen-control .vjs-icon-placeholder,.vjs-icon-fullscreen-enter{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-fullscreen-control .vjs-icon-placeholder:before,.vjs-icon-fullscreen-enter:before{content:"\f108"}.video-js.vjs-fullscreen .vjs-fullscreen-control .vjs-icon-placeholder,.vjs-icon-fullscreen-exit{font-family:VideoJS;font-weight:400;font-style:normal}.video-js.vjs-fullscreen .vjs-fullscreen-control .vjs-icon-placeholder:before,.vjs-icon-fullscreen-exit:before{content:"\f109"}.vjs-icon-square{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-square:before{content:"\f10a"}.vjs-icon-spinner{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-spinner:before{content:"\f10b"}.video-js .vjs-subs-caps-button .vjs-icon-placeholder,.video-js .vjs-subtitles-button .vjs-icon-placeholder,.video-js.video-js:lang(en-AU) .vjs-subs-caps-button .vjs-icon-placeholder,.video-js.video-js:lang(en-GB) .vjs-subs-caps-button .vjs-icon-placeholder,.video-js.video-js:lang(en-IE) .vjs-subs-caps-button .vjs-icon-placeholder,.video-js.video-js:lang(en-NZ) .vjs-subs-caps-button .vjs-icon-placeholder,.vjs-icon-subtitles{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-subs-caps-button .vjs-icon-placeholder:before,.video-js .vjs-subtitles-button .vjs-icon-placeholder:before,.video-js.video-js:lang(en-AU) .vjs-subs-caps-button .vjs-icon-placeholder:before,.video-js.video-js:lang(en-GB) .vjs-subs-caps-button .vjs-icon-placeholder:before,.video-js.video-js:lang(en-IE) .vjs-subs-caps-button .vjs-icon-placeholder:before,.video-js.video-js:lang(en-NZ) .vjs-subs-caps-button .vjs-icon-placeholder:before,.vjs-icon-subtitles:before{content:"\f10c"}.video-js .vjs-captions-button .vjs-icon-placeholder,.video-js:lang(en) .vjs-subs-caps-button .vjs-icon-placeholder,.video-js:lang(fr-CA) .vjs-subs-caps-button .vjs-icon-placeholder,.vjs-icon-captions{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-captions-button .vjs-icon-placeholder:before,.video-js:lang(en) .vjs-subs-caps-button .vjs-icon-placeholder:before,.video-js:lang(fr-CA) .vjs-subs-caps-button .vjs-icon-placeholder:before,.vjs-icon-captions:before{content:"\f10d"}.video-js .vjs-chapters-button .vjs-icon-placeholder,.vjs-icon-chapters{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-chapters-button .vjs-icon-placeholder:before,.vjs-icon-chapters:before{content:"\f10e"}.vjs-icon-share{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-share:before{content:"\f10f"}.vjs-icon-cog{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-cog:before{content:"\f110"}.video-js .vjs-play-progress,.video-js .vjs-volume-level,.vjs-icon-circle,.vjs-seek-to-live-control .vjs-icon-placeholder{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-play-progress:before,.video-js .vjs-volume-level:before,.vjs-icon-circle:before,.vjs-seek-to-live-control .vjs-icon-placeholder:before{content:"\f111"}.vjs-icon-circle-outline{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-circle-outline:before{content:"\f112"}.vjs-icon-circle-inner-circle{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-circle-inner-circle:before{content:"\f113"}.vjs-icon-hd{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-hd:before{content:"\f114"}.video-js .vjs-control.vjs-close-button .vjs-icon-placeholder,.vjs-icon-cancel{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-control.vjs-close-button .vjs-icon-placeholder:before,.vjs-icon-cancel:before{content:"\f115"}.video-js .vjs-play-control.vjs-ended .vjs-icon-placeholder,.vjs-icon-replay{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-play-control.vjs-ended .vjs-icon-placeholder:before,.vjs-icon-replay:before{content:"\f116"}.vjs-icon-facebook{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-facebook:before{content:"\f117"}.vjs-icon-gplus{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-gplus:before{content:"\f118"}.vjs-icon-linkedin{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-linkedin:before{content:"\f119"}.vjs-icon-twitter{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-twitter:before{content:"\f11a"}.vjs-icon-tumblr{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-tumblr:before{content:"\f11b"}.vjs-icon-pinterest{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-pinterest:before{content:"\f11c"}.video-js .vjs-descriptions-button .vjs-icon-placeholder,.vjs-icon-audio-description{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-descriptions-button .vjs-icon-placeholder:before,.vjs-icon-audio-description:before{content:"\f11d"}.video-js .vjs-audio-button .vjs-icon-placeholder,.vjs-icon-audio{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-audio-button .vjs-icon-placeholder:before,.vjs-icon-audio:before{content:"\f11e"}.vjs-icon-next-item{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-next-item:before{content:"\f11f"}.vjs-icon-previous-item{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-previous-item:before{content:"\f120"}.video-js .vjs-picture-in-picture-control .vjs-icon-placeholder,.vjs-icon-picture-in-picture-enter{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-picture-in-picture-control .vjs-icon-placeholder:before,.vjs-icon-picture-in-picture-enter:before{content:"\f121"}.video-js.vjs-picture-in-picture .vjs-picture-in-picture-control .vjs-icon-placeholder,.vjs-icon-picture-in-picture-exit{font-family:VideoJS;font-weight:400;font-style:normal}.video-js.vjs-picture-in-picture .vjs-picture-in-picture-control .vjs-icon-placeholder:before,.vjs-icon-picture-in-picture-exit:before{content:"\f122"}.video-js{display:block;vertical-align:top;box-sizing:border-box;color:#fff;background-color:#000;position:relative;padding:0;font-size:10px;line-height:1;font-weight:400;font-style:normal;font-family:Arial,Helvetica,sans-serif;word-break:initial}.video-js:-moz-full-screen{position:absolute}.video-js:-webkit-full-screen{width:100%!important;height:100%!important}.video-js[tabindex="-1"]{outline:0}.video-js *,.video-js :after,.video-js :before{box-sizing:inherit}.video-js ul{font-family:inherit;font-size:inherit;line-height:inherit;list-style-position:outside;margin-left:0;margin-right:0;margin-top:0;margin-bottom:0}.video-js.vjs-16-9,.video-js.vjs-4-3,.video-js.vjs-fluid{width:100%;max-width:100%;height:0}.video-js.vjs-16-9{padding-top:56.25%}.video-js.vjs-4-3{padding-top:75%}.video-js.vjs-fill{width:100%;height:100%}.video-js .vjs-tech{position:absolute;top:0;left:0;width:100%;height:100%}body.vjs-full-window{padding:0;margin:0;height:100%}.vjs-full-window .video-js.vjs-fullscreen{position:fixed;overflow:hidden;z-index:1000;left:0;top:0;bottom:0;right:0}.video-js.vjs-fullscreen{width:100%!important;height:100%!important;padding-top:0!important}.video-js.vjs-fullscreen.vjs-user-inactive{cursor:none}.vjs-hidden{display:none!important}.vjs-disabled{opacity:.5;cursor:default}.video-js .vjs-offscreen{height:1px;left:-9999px;position:absolute;top:0;width:1px}.vjs-lock-showing{display:block!important;opacity:1;visibility:visible}.vjs-no-js{padding:20px;color:#fff;background-color:#000;font-size:18px;font-family:Arial,Helvetica,sans-serif;text-align:center;width:300px;height:150px;margin:0 auto}.vjs-no-js a,.vjs-no-js a:visited{color:#66a8cc}.video-js .vjs-big-play-button{font-size:3em;line-height:1.5em;height:1.63332em;width:3em;display:block;position:absolute;top:10px;left:10px;padding:0;cursor:pointer;opacity:1;border:.06666em solid #fff;background-color:#2b333f;background-color:rgba(43,51,63,.7);border-radius:.3em;transition:all .4s}.vjs-big-play-centered .vjs-big-play-button{top:50%;left:50%;margin-top:-.81666em;margin-left:-1.5em}.video-js .vjs-big-play-button:focus,.video-js:hover .vjs-big-play-button{border-color:#fff;background-color:#73859f;background-color:rgba(115,133,159,.5);transition:all 0s}.vjs-controls-disabled .vjs-big-play-button,.vjs-error .vjs-big-play-button,.vjs-has-started .vjs-big-play-button,.vjs-using-native-controls .vjs-big-play-button{display:none}.vjs-has-started.vjs-paused.vjs-show-big-play-button-on-pause .vjs-big-play-button{display:block}.video-js button{background:0 0;border:none;color:inherit;display:inline-block;font-size:inherit;line-height:inherit;text-transform:none;text-decoration:none;transition:none;-webkit-appearance:none;-moz-appearance:none;appearance:none}.vjs-control .vjs-button{width:100%;height:100%}.video-js .vjs-control.vjs-close-button{cursor:pointer;height:3em;position:absolute;right:0;top:.5em;z-index:2}.video-js .vjs-modal-dialog{background:rgba(0,0,0,.8);background:linear-gradient(180deg,rgba(0,0,0,.8),rgba(255,255,255,0));overflow:auto}.video-js .vjs-modal-dialog>*{box-sizing:border-box}.vjs-modal-dialog .vjs-modal-dialog-content{font-size:1.2em;line-height:1.5;padding:20px 24px;z-index:1}.vjs-menu-button{cursor:pointer}.vjs-menu-button.vjs-disabled{cursor:default}.vjs-workinghover .vjs-menu-button.vjs-disabled:hover .vjs-menu{display:none}.vjs-menu .vjs-menu-content{display:block;padding:0;margin:0;font-family:Arial,Helvetica,sans-serif;overflow:auto}.vjs-menu .vjs-menu-content>*{box-sizing:border-box}.vjs-scrubbing .vjs-control.vjs-menu-button:hover .vjs-menu{display:none}.vjs-menu li{list-style:none;margin:0;padding:.2em 0;line-height:1.4em;font-size:1.2em;text-align:center;text-transform:lowercase}.js-focus-visible .vjs-menu li.vjs-menu-item:hover,.vjs-menu li.vjs-menu-item:focus,.vjs-menu li.vjs-menu-item:hover{background-color:#73859f;background-color:rgba(115,133,159,.5)}.js-focus-visible .vjs-menu li.vjs-selected:hover,.vjs-menu li.vjs-selected,.vjs-menu li.vjs-selected:focus,.vjs-menu li.vjs-selected:hover{background-color:#fff;color:#2b333f}.vjs-menu li.vjs-menu-title{text-align:center;text-transform:uppercase;font-size:1em;line-height:2em;padding:0;margin:0 0 .3em 0;font-weight:700;cursor:default}.vjs-menu-button-popup .vjs-menu{display:none;position:absolute;bottom:0;width:10em;left:-3em;height:0;margin-bottom:1.5em;border-top-color:rgba(43,51,63,.7)}.vjs-menu-button-popup .vjs-menu .vjs-menu-content{background-color:#2b333f;background-color:rgba(43,51,63,.7);position:absolute;width:100%;bottom:1.5em;max-height:15em}.vjs-layout-tiny .vjs-menu-button-popup .vjs-menu .vjs-menu-content,.vjs-layout-x-small .vjs-menu-button-popup .vjs-menu .vjs-menu-content{max-height:5em}.vjs-layout-small .vjs-menu-button-popup .vjs-menu .vjs-menu-content{max-height:10em}.vjs-layout-medium .vjs-menu-button-popup .vjs-menu .vjs-menu-content{max-height:14em}.vjs-layout-huge .vjs-menu-button-popup .vjs-menu .vjs-menu-content,.vjs-layout-large .vjs-menu-button-popup .vjs-menu .vjs-menu-content,.vjs-layout-x-large .vjs-menu-button-popup .vjs-menu .vjs-menu-content{max-height:25em}.vjs-menu-button-popup .vjs-menu.vjs-lock-showing,.vjs-workinghover .vjs-menu-button-popup:hover .vjs-menu{display:block}.video-js .vjs-menu-button-inline{transition:all .4s;overflow:hidden}.video-js .vjs-menu-button-inline:before{width:2.222222222em}.video-js .vjs-menu-button-inline.vjs-slider-active,.video-js .vjs-menu-button-inline:focus,.video-js .vjs-menu-button-inline:hover,.video-js.vjs-no-flex .vjs-menu-button-inline{width:12em}.vjs-menu-button-inline .vjs-menu{opacity:0;height:100%;width:auto;position:absolute;left:4em;top:0;padding:0;margin:0;transition:all .4s}.vjs-menu-button-inline.vjs-slider-active .vjs-menu,.vjs-menu-button-inline:focus .vjs-menu,.vjs-menu-button-inline:hover .vjs-menu{display:block;opacity:1}.vjs-no-flex .vjs-menu-button-inline .vjs-menu{display:block;opacity:1;position:relative;width:auto}.vjs-no-flex .vjs-menu-button-inline.vjs-slider-active .vjs-menu,.vjs-no-flex .vjs-menu-button-inline:focus .vjs-menu,.vjs-no-flex .vjs-menu-button-inline:hover .vjs-menu{width:auto}.vjs-menu-button-inline .vjs-menu-content{width:auto;height:100%;margin:0;overflow:hidden}.video-js .vjs-control-bar{display:none;width:100%;position:absolute;bottom:0;left:0;right:0;height:3em;background-color:#2b333f;background-color:rgba(43,51,63,.7)}.vjs-has-started .vjs-control-bar{display:flex;visibility:visible;opacity:1;transition:visibility .1s,opacity .1s}.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar{visibility:visible;opacity:0;transition:visibility 1s,opacity 1s}.vjs-controls-disabled .vjs-control-bar,.vjs-error .vjs-control-bar,.vjs-using-native-controls .vjs-control-bar{display:none!important}.vjs-audio.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar{opacity:1;visibility:visible}.vjs-has-started.vjs-no-flex .vjs-control-bar{display:table}.video-js .vjs-control{position:relative;text-align:center;margin:0;padding:0;height:100%;width:4em;flex:none}.vjs-button>.vjs-icon-placeholder:before{font-size:1.8em;line-height:1.67}.video-js .vjs-control:focus,.video-js .vjs-control:focus:before,.video-js .vjs-control:hover:before{text-shadow:0 0 1em #fff}.video-js .vjs-control-text{border:0;clip:rect(0 0 0 0);height:1px;overflow:hidden;padding:0;position:absolute;width:1px}.vjs-no-flex .vjs-control{display:table-cell;vertical-align:middle}.video-js .vjs-custom-control-spacer{display:none}.video-js .vjs-progress-control{cursor:pointer;flex:auto;display:flex;align-items:center;min-width:4em;touch-action:none}.video-js .vjs-progress-control.disabled{cursor:default}.vjs-live .vjs-progress-control{display:none}.vjs-liveui .vjs-progress-control{display:flex;align-items:center}.vjs-no-flex .vjs-progress-control{width:auto}.video-js .vjs-progress-holder{flex:auto;transition:all .2s;height:.3em}.video-js .vjs-progress-control .vjs-progress-holder{margin:0 10px}.video-js .vjs-progress-control:hover .vjs-progress-holder{font-size:1.6666666667em}.video-js .vjs-progress-control:hover .vjs-progress-holder.disabled{font-size:1em}.video-js .vjs-progress-holder .vjs-load-progress,.video-js .vjs-progress-holder .vjs-load-progress div,.video-js .vjs-progress-holder .vjs-play-progress{position:absolute;display:block;height:100%;margin:0;padding:0;width:0}.video-js .vjs-play-progress{background-color:#fff}.video-js .vjs-play-progress:before{font-size:.9em;position:absolute;right:-.5em;top:-.3333333333em;z-index:1}.video-js .vjs-load-progress{background:rgba(115,133,159,.5)}.video-js .vjs-load-progress div{background:rgba(115,133,159,.75)}.video-js .vjs-time-tooltip{background-color:#fff;background-color:rgba(255,255,255,.8);border-radius:.3em;color:#000;float:right;font-family:Arial,Helvetica,sans-serif;font-size:1em;padding:6px 8px 8px 8px;pointer-events:none;position:absolute;top:-3.4em;visibility:hidden;z-index:1}.video-js .vjs-progress-holder:focus .vjs-time-tooltip{display:none}.video-js .vjs-progress-control:hover .vjs-progress-holder:focus .vjs-time-tooltip,.video-js .vjs-progress-control:hover .vjs-time-tooltip{display:block;font-size:.6em;visibility:visible}.video-js .vjs-progress-control.disabled:hover .vjs-time-tooltip{font-size:1em}.video-js .vjs-progress-control .vjs-mouse-display{display:none;position:absolute;width:1px;height:100%;background-color:#000;z-index:1}.vjs-no-flex .vjs-progress-control .vjs-mouse-display{z-index:0}.video-js .vjs-progress-control:hover .vjs-mouse-display{display:block}.video-js.vjs-user-inactive .vjs-progress-control .vjs-mouse-display{visibility:hidden;opacity:0;transition:visibility 1s,opacity 1s}.video-js.vjs-user-inactive.vjs-no-flex .vjs-progress-control .vjs-mouse-display{display:none}.vjs-mouse-display .vjs-time-tooltip{color:#fff;background-color:#000;background-color:rgba(0,0,0,.8)}.video-js .vjs-slider{position:relative;cursor:pointer;padding:0;margin:0 .45em 0 .45em;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#73859f;background-color:rgba(115,133,159,.5)}.video-js .vjs-slider.disabled{cursor:default}.video-js .vjs-slider:focus{text-shadow:0 0 1em #fff;box-shadow:0 0 1em #fff}.video-js .vjs-mute-control{cursor:pointer;flex:none}.video-js .vjs-volume-control{cursor:pointer;margin-right:1em;display:flex}.video-js .vjs-volume-control.vjs-volume-horizontal{width:5em}.video-js .vjs-volume-panel .vjs-volume-control{visibility:visible;opacity:0;width:1px;height:1px;margin-left:-1px}.video-js .vjs-volume-panel{transition:width 1s}.video-js .vjs-volume-panel .vjs-mute-control:hover~.vjs-volume-control,.video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active,.video-js .vjs-volume-panel .vjs-volume-control:active,.video-js .vjs-volume-panel .vjs-volume-control:hover,.video-js .vjs-volume-panel:active .vjs-volume-control,.video-js .vjs-volume-panel:focus .vjs-volume-control,.video-js .vjs-volume-panel:hover .vjs-volume-control{visibility:visible;opacity:1;position:relative;transition:visibility .1s,opacity .1s,height .1s,width .1s,left 0s,top 0s}.video-js .vjs-volume-panel .vjs-mute-control:hover~.vjs-volume-control.vjs-volume-horizontal,.video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-horizontal,.video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-horizontal,.video-js .vjs-volume-panel .vjs-volume-control:hover.vjs-volume-horizontal,.video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-horizontal,.video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-horizontal,.video-js .vjs-volume-panel:hover .vjs-volume-control.vjs-volume-horizontal{width:5em;height:3em}.video-js .vjs-volume-panel .vjs-mute-control:hover~.vjs-volume-control.vjs-volume-vertical,.video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-vertical,.video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-vertical,.video-js .vjs-volume-panel .vjs-volume-control:hover.vjs-volume-vertical,.video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-vertical,.video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-vertical,.video-js .vjs-volume-panel:hover .vjs-volume-control.vjs-volume-vertical{left:-3.5em}.video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active,.video-js .vjs-volume-panel.vjs-volume-panel-horizontal:active,.video-js .vjs-volume-panel.vjs-volume-panel-horizontal:hover{width:9em;transition:width .1s}.video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-mute-toggle-only{width:4em}.video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical{height:8em;width:3em;left:-3000em;transition:visibility 1s,opacity 1s,height 1s 1s,width 1s 1s,left 1s 1s,top 1s 1s}.video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal{transition:visibility 1s,opacity 1s,height 1s 1s,width 1s,left 1s 1s,top 1s 1s}.video-js.vjs-no-flex .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal{width:5em;height:3em;visibility:visible;opacity:1;position:relative;transition:none}.video-js.vjs-no-flex .vjs-volume-control.vjs-volume-vertical,.video-js.vjs-no-flex .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical{position:absolute;bottom:3em;left:.5em}.video-js .vjs-volume-panel{display:flex}.video-js .vjs-volume-bar{margin:1.35em .45em}.vjs-volume-bar.vjs-slider-horizontal{width:5em;height:.3em}.vjs-volume-bar.vjs-slider-vertical{width:.3em;height:5em;margin:1.35em auto}.video-js .vjs-volume-level{position:absolute;bottom:0;left:0;background-color:#fff}.video-js .vjs-volume-level:before{position:absolute;font-size:.9em}.vjs-slider-vertical .vjs-volume-level{width:.3em}.vjs-slider-vertical .vjs-volume-level:before{top:-.5em;left:-.3em}.vjs-slider-horizontal .vjs-volume-level{height:.3em}.vjs-slider-horizontal .vjs-volume-level:before{top:-.3em;right:-.5em}.video-js .vjs-volume-panel.vjs-volume-panel-vertical{width:4em}.vjs-volume-bar.vjs-slider-vertical .vjs-volume-level{height:100%}.vjs-volume-bar.vjs-slider-horizontal .vjs-volume-level{width:100%}.video-js .vjs-volume-vertical{width:3em;height:8em;bottom:8em;background-color:#2b333f;background-color:rgba(43,51,63,.7)}.video-js .vjs-volume-horizontal .vjs-menu{left:-2em}.vjs-poster{display:inline-block;vertical-align:middle;background-repeat:no-repeat;background-position:50% 50%;background-size:contain;background-color:#000;cursor:pointer;margin:0;padding:0;position:absolute;top:0;right:0;bottom:0;left:0;height:100%}.vjs-has-started .vjs-poster{display:none}.vjs-audio.vjs-has-started .vjs-poster{display:block}.vjs-using-native-controls .vjs-poster{display:none}.video-js .vjs-live-control{display:flex;align-items:flex-start;flex:auto;font-size:1em;line-height:3em}.vjs-no-flex .vjs-live-control{display:table-cell;width:auto;text-align:left}.video-js.vjs-liveui .vjs-live-control,.video-js:not(.vjs-live) .vjs-live-control{display:none}.video-js .vjs-seek-to-live-control{cursor:pointer;flex:none;display:inline-flex;height:100%;padding-left:.5em;padding-right:.5em;font-size:1em;line-height:3em;width:auto;min-width:4em}.vjs-no-flex .vjs-seek-to-live-control{display:table-cell;width:auto;text-align:left}.video-js.vjs-live:not(.vjs-liveui) .vjs-seek-to-live-control,.video-js:not(.vjs-live) .vjs-seek-to-live-control{display:none}.vjs-seek-to-live-control.vjs-control.vjs-at-live-edge{cursor:auto}.vjs-seek-to-live-control .vjs-icon-placeholder{margin-right:.5em;color:#888}.vjs-seek-to-live-control.vjs-control.vjs-at-live-edge .vjs-icon-placeholder{color:red}.video-js .vjs-time-control{flex:none;font-size:1em;line-height:3em;min-width:2em;width:auto;padding-left:1em;padding-right:1em}.vjs-live .vjs-time-control{display:none}.video-js .vjs-current-time,.vjs-no-flex .vjs-current-time{display:none}.video-js .vjs-duration,.vjs-no-flex .vjs-duration{display:none}.vjs-time-divider{display:none;line-height:3em}.vjs-live .vjs-time-divider{display:none}.video-js .vjs-play-control{cursor:pointer}.video-js .vjs-play-control .vjs-icon-placeholder{flex:none}.vjs-text-track-display{position:absolute;bottom:3em;left:0;right:0;top:0;pointer-events:none}.video-js.vjs-user-inactive.vjs-playing .vjs-text-track-display{bottom:1em}.video-js .vjs-text-track{font-size:1.4em;text-align:center;margin-bottom:.1em}.vjs-subtitles{color:#fff}.vjs-captions{color:#fc6}.vjs-tt-cue{display:block}video::-webkit-media-text-track-display{transform:translateY(-3em)}.video-js.vjs-user-inactive.vjs-playing video::-webkit-media-text-track-display{transform:translateY(-1.5em)}.video-js .vjs-picture-in-picture-control{cursor:pointer;flex:none}.video-js .vjs-fullscreen-control{cursor:pointer;flex:none}.vjs-playback-rate .vjs-playback-rate-value,.vjs-playback-rate>.vjs-menu-button{position:absolute;top:0;left:0;width:100%;height:100%}.vjs-playback-rate .vjs-playback-rate-value{pointer-events:none;font-size:1.5em;line-height:2;text-align:center}.vjs-playback-rate .vjs-menu{width:4em;left:0}.vjs-error .vjs-error-display .vjs-modal-dialog-content{font-size:1.4em;text-align:center}.vjs-error .vjs-error-display:before{color:#fff;content:"X";font-family:Arial,Helvetica,sans-serif;font-size:4em;left:0;line-height:1;margin-top:-.5em;position:absolute;text-shadow:.05em .05em .1em #000;text-align:center;top:50%;vertical-align:middle;width:100%}.vjs-loading-spinner{display:none;position:absolute;top:50%;left:50%;margin:-25px 0 0 -25px;opacity:.85;text-align:left;border:6px solid rgba(43,51,63,.7);box-sizing:border-box;background-clip:padding-box;width:50px;height:50px;border-radius:25px;visibility:hidden}.vjs-seeking .vjs-loading-spinner,.vjs-waiting .vjs-loading-spinner{display:block;-webkit-animation:vjs-spinner-show 0s linear .3s forwards;animation:vjs-spinner-show 0s linear .3s forwards}.vjs-loading-spinner:after,.vjs-loading-spinner:before{content:"";position:absolute;margin:-6px;box-sizing:inherit;width:inherit;height:inherit;border-radius:inherit;opacity:1;border:inherit;border-color:transparent;border-top-color:#fff}.vjs-seeking .vjs-loading-spinner:after,.vjs-seeking .vjs-loading-spinner:before,.vjs-waiting .vjs-loading-spinner:after,.vjs-waiting .vjs-loading-spinner:before{-webkit-animation:vjs-spinner-spin 1.1s cubic-bezier(.6,.2,0,.8) infinite,vjs-spinner-fade 1.1s linear infinite;animation:vjs-spinner-spin 1.1s cubic-bezier(.6,.2,0,.8) infinite,vjs-spinner-fade 1.1s linear infinite}.vjs-seeking .vjs-loading-spinner:before,.vjs-waiting .vjs-loading-spinner:before{border-top-color:#fff}.vjs-seeking .vjs-loading-spinner:after,.vjs-waiting .vjs-loading-spinner:after{border-top-color:#fff;-webkit-animation-delay:.44s;animation-delay:.44s}@keyframes vjs-spinner-show{to{visibility:visible}}@-webkit-keyframes vjs-spinner-show{to{visibility:visible}}@keyframes vjs-spinner-spin{100%{transform:rotate(360deg)}}@-webkit-keyframes vjs-spinner-spin{100%{-webkit-transform:rotate(360deg)}}@keyframes vjs-spinner-fade{0%{border-top-color:#73859f}20%{border-top-color:#73859f}35%{border-top-color:#fff}60%{border-top-color:#73859f}100%{border-top-color:#73859f}}@-webkit-keyframes vjs-spinner-fade{0%{border-top-color:#73859f}20%{border-top-color:#73859f}35%{border-top-color:#fff}60%{border-top-color:#73859f}100%{border-top-color:#73859f}}.vjs-chapters-button .vjs-menu ul{width:24em}.video-js .vjs-subs-caps-button+.vjs-menu .vjs-captions-menu-item .vjs-menu-item-text .vjs-icon-placeholder{vertical-align:middle;display:inline-block;margin-bottom:-.1em}.video-js .vjs-subs-caps-button+.vjs-menu .vjs-captions-menu-item .vjs-menu-item-text .vjs-icon-placeholder:before{font-family:VideoJS;content:"";font-size:1.5em;line-height:inherit}.video-js .vjs-audio-button+.vjs-menu .vjs-main-desc-menu-item .vjs-menu-item-text .vjs-icon-placeholder{vertical-align:middle;display:inline-block;margin-bottom:-.1em}.video-js .vjs-audio-button+.vjs-menu .vjs-main-desc-menu-item .vjs-menu-item-text .vjs-icon-placeholder:before{font-family:VideoJS;content:" ";font-size:1.5em;line-height:inherit}.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-audio-button,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-captions-button,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-chapters-button,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-current-time,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-descriptions-button,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-duration,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-playback-rate,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-remaining-time,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-subtitles-button,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-time-divider,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-control,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-audio-button,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-captions-button,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-chapters-button,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-current-time,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-descriptions-button,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-duration,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-playback-rate,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-remaining-time,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-subtitles-button,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-time-divider,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-control,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-audio-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-captions-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-chapters-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-current-time,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-descriptions-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-duration,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-playback-rate,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-remaining-time,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-subtitles-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-time-divider,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-control{display:none}.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal:active,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal:hover,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal:active,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal:hover,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal:active,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal:hover{width:auto;width:initial}.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-subs-caps-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small:not(.vjs-live) .vjs-subs-caps-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small:not(.vjs-liveui) .vjs-subs-caps-button{display:none}.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-custom-control-spacer,.video-js:not(.vjs-fullscreen).vjs-layout-x-small.vjs-liveui .vjs-custom-control-spacer{flex:auto;display:block}.video-js:not(.vjs-fullscreen).vjs-layout-tiny.vjs-no-flex .vjs-custom-control-spacer,.video-js:not(.vjs-fullscreen).vjs-layout-x-small.vjs-liveui.vjs-no-flex .vjs-custom-control-spacer{width:auto}.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-progress-control,.video-js:not(.vjs-fullscreen).vjs-layout-x-small.vjs-liveui .vjs-progress-control{display:none}.vjs-modal-dialog.vjs-text-track-settings{background-color:#2b333f;background-color:rgba(43,51,63,.75);color:#fff;height:70%}.vjs-text-track-settings .vjs-modal-dialog-content{display:table}.vjs-text-track-settings .vjs-track-settings-colors,.vjs-text-track-settings .vjs-track-settings-controls,.vjs-text-track-settings .vjs-track-settings-font{display:table-cell}.vjs-text-track-settings .vjs-track-settings-controls{text-align:right;vertical-align:bottom}@supports (display:grid){.vjs-text-track-settings .vjs-modal-dialog-content{display:grid;grid-template-columns:1fr 1fr;grid-template-rows:1fr;padding:20px 24px 0 24px}.vjs-track-settings-controls .vjs-default-button{margin-bottom:20px}.vjs-text-track-settings .vjs-track-settings-controls{grid-column:1/-1}.vjs-layout-small .vjs-text-track-settings .vjs-modal-dialog-content,.vjs-layout-tiny .vjs-text-track-settings .vjs-modal-dialog-content,.vjs-layout-x-small .vjs-text-track-settings .vjs-modal-dialog-content{grid-template-columns:1fr}}.vjs-track-setting>select{margin-right:1em;margin-bottom:.5em}.vjs-text-track-settings fieldset{margin:5px;padding:3px;border:none}.vjs-text-track-settings fieldset span{display:inline-block}.vjs-text-track-settings fieldset span>select{max-width:7.3em}.vjs-text-track-settings legend{color:#fff;margin:0 0 5px 0}.vjs-text-track-settings .vjs-label{position:absolute;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px);display:block;margin:0 0 5px 0;padding:0;border:0;height:1px;width:1px;overflow:hidden}.vjs-track-settings-controls button:active,.vjs-track-settings-controls button:focus{outline-style:solid;outline-width:medium;background-image:linear-gradient(0deg,#fff 88%,#73859f 100%)}.vjs-track-settings-controls button:hover{color:rgba(43,51,63,.75)}.vjs-track-settings-controls button{background-color:#fff;background-image:linear-gradient(-180deg,#fff 88%,#73859f 100%);color:#2b333f;cursor:pointer;border-radius:2px}.vjs-track-settings-controls .vjs-default-button{margin-right:1em}@media print{.video-js>:not(.vjs-tech):not(.vjs-poster){visibility:hidden}}.vjs-resize-manager{position:absolute;top:0;left:0;width:100%;height:100%;border:none;z-index:-1000}.js-focus-visible .video-js :focus:not(.focus-visible){outline:0;background:0 0}.video-js .vjs-menu :focus:not(:focus-visible),.video-js :focus:not(:focus-visible){outline:0;background:0 0} \ No newline at end of file diff --git a/thrimbletrimmer/plugins/video.js/dist/video.min.js b/thrimbletrimmer/plugins/video.js/dist/video.min.js new file mode 100644 index 0000000..09d6304 --- /dev/null +++ b/thrimbletrimmer/plugins/video.js/dist/video.min.js @@ -0,0 +1,21 @@ +/** + * @license + * Video.js 7.6.0 + * Copyright Brightcove, Inc. + * Available under Apache License Version 2.0 + * + * + * Includes vtt.js + * Available under Apache License Version 2.0 + * + */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("global/window"),require("global/document")):"function"==typeof define&&define.amd?define(["global/window","global/document"],t):(e=e||self).videojs=t(e.window,e.document)}(this,function(v,h){v=v&&v.hasOwnProperty("default")?v.default:v,h=h&&h.hasOwnProperty("default")?h.default:h;var d="7.6.0";function p(e,t){e.prototype=Object.create(t.prototype),(e.prototype.constructor=e).__proto__=t}function a(e,t){return(a=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function s(e,t,i){return(s=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(e){return!1}}()?Reflect.construct:function(e,t,i){var n=[null];n.push.apply(n,t);var r=new(Function.bind.apply(e,n));return i&&a(r,i.prototype),r}).apply(null,arguments)}function f(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function t(e,t){return t=t||e.slice(0),e.raw=t,e}var u=[],e=function(s,o){return function(e,t,i){var n=o.levels[t],r=new RegExp("^("+n+")$");if("log"!==e&&i.unshift(e.toUpperCase()+":"),i.unshift(s+":"),u&&u.push([].concat(i)),v.console){var a=v.console[e];a||"debug"!==e||(a=v.console.info||v.console.log),a&&n&&r.test(e)&&a[Array.isArray(i)?"apply":"call"](v.console,i)}}};var m=function t(i){function n(){for(var e=arguments.length,t=new Array(e),i=0;i',i=n.firstChild,n.setAttribute("style","display:none; position:absolute;"),h.body.appendChild(n));for(var a={},s=0;sx',e=t.firstChild.href}return e}function Ft(e){if("string"==typeof e){var t=/^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i.exec(e);if(t)return t.pop().toLowerCase()}return""}function Vt(e){var t=v.location,i=Nt(e);return(":"===i.protocol?t.protocol:i.protocol)+i.host!==t.protocol+t.host}var Ht=function(n){function e(e){var t;void 0===e&&(e=[]);for(var i=e.length-1;0<=i;i--)if(e[i].enabled){Mt(e,e[i]);break}return(t=n.call(this,e)||this).changing_=!1,t}p(e,n);var t=e.prototype;return t.addTrack=function(e){var t=this;e.enabled&&Mt(this,e),n.prototype.addTrack.call(this,e),e.addEventListener&&(e.enabledChange_=function(){t.changing_||(t.changing_=!0,Mt(t,e),t.changing_=!1,t.trigger("change"))},e.addEventListener("enabledchange",e.enabledChange_))},t.removeTrack=function(e){n.prototype.removeTrack.call(this,e),e.removeEventListener&&e.enabledChange_&&(e.removeEventListener("enabledchange",e.enabledChange_),e.enabledChange_=null)},e}(Dt),qt=function(n){function e(e){var t;void 0===e&&(e=[]);for(var i=e.length-1;0<=i;i--)if(e[i].selected){Bt(e,e[i]);break}return(t=n.call(this,e)||this).changing_=!1,Object.defineProperty(f(t),"selectedIndex",{get:function(){for(var e=0;e>0},ToUint32:function(e){return this.ToNumber(e)>>>0},ToUint16:function(e){var t=this.ToNumber(e);return Ki(t)||0===t||!Qi(t)?0:function(e,t){var i=e%t;return Math.floor(0<=i?i:i+t)}(xi(t)*Math.floor(Math.abs(t)),65536)},ToString:function(e){return on(e)},ToObject:function(e){return this.CheckObjectCoercible(e),an(e)},CheckObjectCoercible:function(e,t){if(null==e)throw new sn(t||"Cannot call method on "+e);return e},IsCallable:Di,SameValue:function(e,t){return e===t?0!==e||1/e==1/t:Ki(e)&&Ki(t)},Type:function(e){return null===e?"Null":"undefined"==typeof e?"Undefined":"function"==typeof e||"object"==typeof e?"Object":"number"==typeof e?"Number":"boolean"==typeof e?"Boolean":"string"==typeof e?"String":void 0},IsPropertyDescriptor:function(e){if("Object"!==this.Type(e))return!1;var t={"[[Configurable]]":!0,"[[Enumerable]]":!0,"[[Get]]":!0,"[[Set]]":!0,"[[Value]]":!0,"[[Writable]]":!0};for(var i in e)if(Wi(e,i)&&!t[i])return!1;var n=Wi(e,"[[Value]]"),r=Wi(e,"[[Get]]")||Wi(e,"[[Set]]");if(n&&r)throw new sn("Property Descriptors may not be both accessor and data descriptors");return!0},IsAccessorDescriptor:function(e){return"undefined"!=typeof e&&(Oi(this,"Property Descriptor","Desc",e),!(!Wi(e,"[[Get]]")&&!Wi(e,"[[Set]]")))},IsDataDescriptor:function(e){return"undefined"!=typeof e&&(Oi(this,"Property Descriptor","Desc",e),!(!Wi(e,"[[Value]]")&&!Wi(e,"[[Writable]]")))},IsGenericDescriptor:function(e){return"undefined"!=typeof e&&(Oi(this,"Property Descriptor","Desc",e),!this.IsAccessorDescriptor(e)&&!this.IsDataDescriptor(e))},FromPropertyDescriptor:function(e){if("undefined"==typeof e)return e;if(Oi(this,"Property Descriptor","Desc",e),this.IsDataDescriptor(e))return{value:e["[[Value]]"],writable:!!e["[[Writable]]"],enumerable:!!e["[[Enumerable]]"],configurable:!!e["[[Configurable]]"]};if(this.IsAccessorDescriptor(e))return{get:e["[[Get]]"],set:e["[[Set]]"],enumerable:!!e["[[Enumerable]]"],configurable:!!e["[[Configurable]]"]};throw new sn("FromPropertyDescriptor must be called with a fully populated Property Descriptor")},ToPropertyDescriptor:function(e){if("Object"!==this.Type(e))throw new sn("ToPropertyDescriptor requires an object");var t={};if(Wi(e,"enumerable")&&(t["[[Enumerable]]"]=this.ToBoolean(e.enumerable)),Wi(e,"configurable")&&(t["[[Configurable]]"]=this.ToBoolean(e.configurable)),Wi(e,"value")&&(t["[[Value]]"]=e.value),Wi(e,"writable")&&(t["[[Writable]]"]=this.ToBoolean(e.writable)),Wi(e,"get")){var i=e.get;if("undefined"!=typeof i&&!this.IsCallable(i))throw new TypeError("getter must be a function");t["[[Get]]"]=i}if(Wi(e,"set")){var n=e.set;if("undefined"!=typeof n&&!this.IsCallable(n))throw new sn("setter must be a function");t["[[Set]]"]=n}if((Wi(t,"[[Get]]")||Wi(t,"[[Set]]"))&&(Wi(t,"[[Value]]")||Wi(t,"[[Writable]]")))throw new sn("Invalid property descriptor. Cannot both specify accessors and a value or writable attribute");return t}},ln=ai.call(Function.call,String.prototype.replace),cn=/^[\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF]+/,hn=/[\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF]+$/,dn=ai.call(Function.call,Bi());ji(dn,{getPolyfill:Bi,implementation:Mi,shim:function(){var e=Bi();return ji(String.prototype,{trim:e},{trim:function(){return String.prototype.trim!==e}}),e}});var pn=dn,fn=Object.prototype.toString,mn=Object.prototype.hasOwnProperty,gn=function(e,t,i){if(!Di(t))throw new TypeError("iterator must be a function");var n;3<=arguments.length&&(n=i),"[object Array]"===fn.call(e)?function(e,t,i){for(var n=0,r=e.length;n=e?t.push(r):r.startTime===r.endTime&&r.startTime<=e&&r.startTime+.5>=e&&t.push(r)}if(o=!1,t.length!==this.activeCues_.length)o=!0;else for(var a=0;a","‎":"‎","‏":"‏"," ":" "},Fn={c:"span",i:"i",b:"b",u:"u",ruby:"ruby",rt:"rt",v:"span",lang:"span"},Vn={v:"title",lang:"lang"},Hn={rt:"ruby"};function qn(a,i){function e(){if(!i)return null;var e,t=i.match(/^([^<]*)(<[^>]*>?)?/);return e=t[1]?t[1]:t[2],i=i.substr(e.length),e}function t(e){return jn[e]}function n(e){for(;f=e.match(/&(amp|lt|gt|lrm|rlm|nbsp);/);)e=e.replace(f[0],t);return e}function r(e,t){var i=Fn[e];if(!i)return null;var n=a.document.createElement(i);n.localName=i;var r=Vn[e];return r&&t&&(n[r]=t.trim()),n}for(var s,o,u,l=a.document.createElement("div"),c=l,h=[];null!==(s=e());)if("<"!==s[0])c.appendChild(a.document.createTextNode(n(s)));else{if("/"===s[1]){h.length&&h[h.length-1]===s.substr(2).replace(">","")&&(h.pop(),c=c.parentNode);continue}var d,p=Rn(s.substr(1,s.length-2));if(p){d=a.document.createProcessingInstruction("timestamp",p),c.appendChild(d);continue}var f=s.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/);if(!f)continue;if(!(d=r(f[1],f[3])))continue;if(o=c,Hn[(u=d).localName]&&Hn[u.localName]!==o.localName)continue;f[2]&&(d.className=f[2].substr(1).replace("."," ")),h.push(f[1]),c.appendChild(d),c=d}return l}var zn=[[1470,1470],[1472,1472],[1475,1475],[1478,1478],[1488,1514],[1520,1524],[1544,1544],[1547,1547],[1549,1549],[1563,1563],[1566,1610],[1645,1647],[1649,1749],[1765,1766],[1774,1775],[1786,1805],[1807,1808],[1810,1839],[1869,1957],[1969,1969],[1984,2026],[2036,2037],[2042,2042],[2048,2069],[2074,2074],[2084,2084],[2088,2088],[2096,2110],[2112,2136],[2142,2142],[2208,2208],[2210,2220],[8207,8207],[64285,64285],[64287,64296],[64298,64310],[64312,64316],[64318,64318],[64320,64321],[64323,64324],[64326,64449],[64467,64829],[64848,64911],[64914,64967],[65008,65020],[65136,65140],[65142,65276],[67584,67589],[67592,67592],[67594,67637],[67639,67640],[67644,67644],[67647,67669],[67671,67679],[67840,67867],[67872,67897],[67903,67903],[67968,68023],[68030,68031],[68096,68096],[68112,68115],[68117,68119],[68121,68147],[68160,68167],[68176,68184],[68192,68223],[68352,68405],[68416,68437],[68440,68466],[68472,68479],[68608,68680],[126464,126467],[126469,126495],[126497,126498],[126500,126500],[126503,126503],[126505,126514],[126516,126519],[126521,126521],[126523,126523],[126530,126530],[126535,126535],[126537,126537],[126539,126539],[126541,126543],[126545,126546],[126548,126548],[126551,126551],[126553,126553],[126555,126555],[126557,126557],[126559,126559],[126561,126562],[126564,126564],[126567,126570],[126572,126578],[126580,126583],[126585,126588],[126590,126590],[126592,126601],[126603,126619],[126625,126627],[126629,126633],[126635,126651],[1114109,1114109]];function Wn(e){for(var t=0;t=i[0]&&e<=i[1])return!0}return!1}function Gn(){}function $n(e,t,i){Gn.call(this),this.cue=t,this.cueDiv=qn(e,t.text);var n={color:"rgba(255, 255, 255, 1)",backgroundColor:"rgba(0, 0, 0, 0.8)",position:"relative",left:0,right:0,top:0,bottom:0,display:"inline",writingMode:""===t.vertical?"horizontal-tb":"lr"===t.vertical?"vertical-lr":"vertical-rl",unicodeBidi:"plaintext"};this.applyStyles(n,this.cueDiv),this.div=e.document.createElement("div"),n={direction:function(e){var t=[],i="";if(!e||!e.childNodes)return"ltr";function r(e,t){for(var i=t.childNodes.length-1;0<=i;i--)e.push(t.childNodes[i])}function a(e){if(!e||!e.length)return null;var t=e.pop(),i=t.textContent||t.innerText;if(i){var n=i.match(/^.*(\n|\r)/);return n?n[e.length=0]:i}return"ruby"===t.tagName?a(e):t.childNodes?(r(e,t),a(e)):void 0}for(r(t,e);i=a(t);)for(var n=0;nh&&(c=c<0?-1:1,c*=Math.ceil(h/l)*l),r<0&&(c+=""===n.vertical?o.height:o.width,a=a.reverse()),i.move(d,c)}else{var p=i.lineHeight/o.height*100;switch(n.lineAlign){case"middle":r-=p/2;break;case"end":r-=p}switch(n.vertical){case"":t.applyStyles({top:t.formatStyle(r,"%")});break;case"rl":t.applyStyles({left:t.formatStyle(r,"%")});break;case"lr":t.applyStyles({right:t.formatStyle(r,"%")})}a=["+y","-x","+x","-y"],i=new Xn(t)}var f=function(e,t){for(var i,n=new Xn(e),r=1,a=0;ae.left&&this.tope.top},Xn.prototype.overlapsAny=function(e){for(var t=0;t=e.top&&this.bottom<=e.bottom&&this.left>=e.left&&this.right<=e.right},Xn.prototype.overlapsOppositeAxis=function(e,t){switch(t){case"+x":return this.lefte.right;case"+y":return this.tope.bottom}},Xn.prototype.intersectPercentage=function(e){return Math.max(0,Math.min(this.right,e.right)-Math.max(this.left,e.left))*Math.max(0,Math.min(this.bottom,e.bottom)-Math.max(this.top,e.top))/(this.height*this.width)},Xn.prototype.toCSSCompatValues=function(e){return{top:this.top-e.top,bottom:e.bottom-this.bottom,left:this.left-e.left,right:e.right-this.right,height:this.height,width:this.width}},Xn.getSimpleBoxPosition=function(e){var t=e.div?e.div.offsetHeight:e.tagName?e.offsetHeight:0,i=e.div?e.div.offsetWidth:e.tagName?e.offsetWidth:0,n=e.div?e.div.offsetTop:e.tagName?e.offsetTop:0;return{left:(e=e.div?e.div.getBoundingClientRect():e.tagName?e.getBoundingClientRect():e).left,right:e.right,top:e.top||n,height:e.height||t,bottom:e.bottom||n+(e.height||t),width:e.width||i}},Yn.StringDecoder=function(){return{decode:function(e){if(!e)return"";if("string"!=typeof e)throw new Error("Error - expected string data.");return decodeURIComponent(encodeURIComponent(e))}}},Yn.convertCueToDOMTree=function(e,t){return e&&t?qn(e,t):null};Yn.processCues=function(n,r,e){if(!n||!r||!e)return null;for(;e.firstChild;)e.removeChild(e.firstChild);var a=n.document.createElement("div");if(a.style.position="absolute",a.style.left="0",a.style.right="0",a.style.top="0",a.style.bottom="0",a.style.margin="1.5%",e.appendChild(a),function(e){for(var t=0;t