Browse Source

content-metadata lib

Mokhtar Naamani 4 years ago
parent
commit
b742d7363b

+ 3 - 2
.github/workflows/content-directory-schemas.yml → .github/workflows/content-metadata.yml

@@ -1,4 +1,4 @@
-name: content-directory-schemas
+name: content-metadata
 on: [pull_request, push]
 
 jobs:
@@ -17,4 +17,5 @@ jobs:
     - name: validate
       run: |
         yarn install --frozen-lockfile
-        yarn workspace @joystream/cd-schemas checks --quiet
+        yarn workspace @joystream/content-metadata checks --quiet
+        yarn workspace @joystream/content-metadata test

+ 3 - 0
content-metadata/.eslintignore

@@ -0,0 +1,3 @@
+lib/
+proto/
+.eslintrc.js

+ 16 - 0
content-metadata/.eslintrc.js

@@ -0,0 +1,16 @@
+module.exports = {
+  env: {
+    mocha: true,
+  },
+  parserOptions: {
+    project: './tsconfig.json'
+  },
+  extends: [
+    '@joystream/eslint-config'
+  ],
+  rules: {
+    'no-unused-vars': 'off', // Required by the typescript rule below
+    '@typescript-eslint/no-unused-vars': ['error'],
+    '@typescript-eslint/no-floating-promises': 'error',
+  },
+}

+ 2 - 0
content-metadata/.gitignore

@@ -0,0 +1,2 @@
+node_modules/
+lib/

+ 2 - 0
content-metadata/.prettierignore

@@ -0,0 +1,2 @@
+lib/
+doc/

+ 32 - 0
content-metadata/README.md

@@ -0,0 +1,32 @@
+# Joystream Content Directory Metadata Library
+
+## Message Specs
+
+Specs are documented in [doc](./doc) folder
+
+## Helper methods
+Simple methods to construct Licenses messages
+
+## Compiling `.proto` files:
+pre-requisists:
+    - [protoc](https://github.com/protocolbuffers/protobuf/releases)
+    - [golang](https://golang.org/)
+    - [protoc-gen-doc](https://github.com/pseudomuto/protoc-gen-doc) to generate docs
+
+```
+yarn compile
+```
+
+## Test
+```
+yarn test
+```
+
+## Build
+After compiling proto files
+
+```
+yarn build
+```
+
+

+ 20 - 0
content-metadata/compile.sh

@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+
+# Path to this plugin
+PROTOC_GEN_TS_PATH="./node_modules/.bin/protoc-gen-ts"
+
+# Directory to write generated code to (.js and .d.ts files)
+OUT_DIR="./"
+# mkdir -p ${OUT_DIR}
+
+# Directory to write generated documentation to
+OUT_DIR_DOC="./doc"
+
+mkdir -p ${OUT_DIR_DOC}
+
+protoc \
+    --plugin="protoc-gen-ts=${PROTOC_GEN_TS_PATH}" \
+    --js_out="import_style=commonjs,binary:${OUT_DIR}" \
+    --ts_out="${OUT_DIR}" \
+    --doc_out="${OUT_DIR_DOC}" --doc_opt=markdown,index.md \
+    proto/*.proto

+ 167 - 0
content-metadata/doc/index.md

@@ -0,0 +1,167 @@
+# Protocol Documentation
+<a name="top"></a>
+
+## Table of Contents
+
+- [proto/Channel.proto](#proto/Channel.proto)
+    - [ChannelMetadata](#.ChannelMetadata)
+  
+- [proto/Video.proto](#proto/Video.proto)
+    - [License](#.License)
+    - [MediaType](#.MediaType)
+    - [PublishedBeforeJoystream](#.PublishedBeforeJoystream)
+    - [VideoMetadata](#.VideoMetadata)
+  
+- [Scalar Value Types](#scalar-value-types)
+
+
+
+<a name="proto/Channel.proto"></a>
+<p align="right"><a href="#top">Top</a></p>
+
+## proto/Channel.proto
+
+
+
+<a name=".ChannelMetadata"></a>
+
+### ChannelMetadata
+
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| title | [string](#string) | optional |  |
+| description | [string](#string) | optional |  |
+| is_public | [bool](#bool) | optional |  |
+| language | [int32](#int32) | optional |  |
+
+
+
+
+
+ 
+
+ 
+
+ 
+
+ 
+
+
+
+<a name="proto/Video.proto"></a>
+<p align="right"><a href="#top">Top</a></p>
+
+## proto/Video.proto
+
+
+
+<a name=".License"></a>
+
+### License
+Joystream Specific License type
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| code | [int32](#int32) | optional | License code defined by Joystream |
+| attribution | [string](#string) | optional | Text for licenses that require an attribution |
+| custom_text | [string](#string) | optional | Text for custom license type |
+
+
+
+
+
+
+<a name=".MediaType"></a>
+
+### MediaType
+Rich format description of video media type
+
+enum AVCodecID from FFmpeg: libavcodec/codec_id.h
+optional int32 codec_id = 1;
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| codec_name | [string](#string) | optional | string name field from FFmpeg libavcodec/codec_desc.c |
+| container | [string](#string) | optional | Video container format (eg. &#39;MP4&#39;, &#39;WebM&#39;, &#39;Ogg&#39; ...) https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Video_codecs |
+| mime_media_type | [string](#string) | optional | MIME Media Type https://www.iana.org/assignments/media-types/media-types.xhtml#video eg. &#39;video/mp4&#39; |
+
+
+
+
+
+
+<a name=".PublishedBeforeJoystream"></a>
+
+### PublishedBeforeJoystream
+
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| is_published | [bool](#bool) | optional |  |
+| timestamp | [uint32](#uint32) | optional |  |
+
+
+
+
+
+
+<a name=".VideoMetadata"></a>
+
+### VideoMetadata
+
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| title | [string](#string) | optional |  |
+| description | [string](#string) | optional |  |
+| duration | [int32](#int32) | optional | Duration in seconds of the video |
+| media_pixel_height | [int32](#int32) | optional | Resolution of the video |
+| media_pixel_width | [int32](#int32) | optional |  |
+| media_type | [MediaType](#MediaType) | optional | Rich media type information about the media format |
+| language | [string](#string) | optional | ISO_639-1 code: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes useful: npm package https://www.npmjs.com/package/iso-639-1 |
+| license | [License](#License) | optional | License type for the media |
+| published_before_joystream | [PublishedBeforeJoystream](#PublishedBeforeJoystream) | optional |  |
+| has_marketing | [bool](#bool) | optional | Set to true if video has marketing/adverts in the stream |
+| is_public | [bool](#bool) | optional | Set to true if it should be visiable to public |
+| is_explicit | [bool](#bool) | optional | Set to true if video has explicit language or scenes Should this be a curator managed property instead? |
+
+
+
+
+
+ 
+
+ 
+
+ 
+
+ 
+
+
+
+## Scalar Value Types
+
+| .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby |
+| ----------- | ----- | --- | ---- | ------ | -- | -- | --- | ---- |
+| <a name="double" /> double |  | double | double | float | float64 | double | float | Float |
+| <a name="float" /> float |  | float | float | float | float32 | float | float | Float |
+| <a name="int32" /> int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
+| <a name="int64" /> int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | int64 | long | integer/string | Bignum |
+| <a name="uint32" /> uint32 | Uses variable-length encoding. | uint32 | int | int/long | uint32 | uint | integer | Bignum or Fixnum (as required) |
+| <a name="uint64" /> uint64 | Uses variable-length encoding. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum or Fixnum (as required) |
+| <a name="sint32" /> sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
+| <a name="sint64" /> sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | int64 | long | integer/string | Bignum |
+| <a name="fixed32" /> fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | uint32 | uint | integer | Bignum or Fixnum (as required) |
+| <a name="fixed64" /> fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum |
+| <a name="sfixed32" /> sfixed32 | Always four bytes. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
+| <a name="sfixed64" /> sfixed64 | Always eight bytes. | int64 | long | int/long | int64 | long | integer/string | Bignum |
+| <a name="bool" /> bool |  | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass |
+| <a name="string" /> string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) |
+| <a name="bytes" /> bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) |
+

+ 39 - 0
content-metadata/package.json

@@ -0,0 +1,39 @@
+{
+  "name": "@joystream/content-metadata",
+  "version": "1.0.0",
+  "description": "Joystream Content Metadata Protobuf Library ",
+  "main": "lib/index.js",
+  "types": "lib/index.d.ts",
+  "repository": "https://github.com/joystream/joystream",
+  "author": "Joystream Contributors",
+  "license": "MIT",
+  "private": false,
+  "scripts": {
+    "build": "tsc",
+    "compile": "./compile.sh",
+    "test": "env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha -r ts-node/register 'test/**/*.ts'",
+    "lint": "eslint ./src --ext .ts",
+    "checks": "tsc --noEmit --pretty && prettier ./ --check && yarn lint",
+    "format": "prettier ./ --write"
+  },
+  "files": [
+    "lib/**/*",
+    "doc/**",
+    "proto/**"
+  ],
+  "dependencies": {
+    "google-protobuf": "^3.14.0"
+  },
+  "devDependencies": {
+    "@types/chai": "^4.2.11",
+    "@types/mocha": "^8.2.0",
+    "chai": "^4.2.0",
+    "eslint": "^7.18.0",
+    "eslint-plugin-prettier": "^3.3.1",
+    "mocha": "^8.2.1",
+    "prettier": "2.0.2",
+    "ts-node": "^8.8.1",
+    "ts-protoc-gen": "^0.14.0",
+    "typescript": "^4.1.3"
+  }
+}

+ 8 - 0
content-metadata/proto/Channel.proto

@@ -0,0 +1,8 @@
+syntax = "proto2";
+
+message ChannelMetadata {
+    optional string title = 1;
+    optional string description = 2;
+    optional bool is_public = 3;
+    optional int32 language = 4;
+}

+ 44 - 0
content-metadata/proto/Channel_pb.d.ts

@@ -0,0 +1,44 @@
+// package:
+// file: proto/Channel.proto
+
+import * as jspb from 'google-protobuf'
+
+export class ChannelMetadata extends jspb.Message {
+  hasTitle(): boolean
+  clearTitle(): void
+  getTitle(): string | undefined
+  setTitle(value: string): void
+
+  hasDescription(): boolean
+  clearDescription(): void
+  getDescription(): string | undefined
+  setDescription(value: string): void
+
+  hasIsPublic(): boolean
+  clearIsPublic(): void
+  getIsPublic(): boolean | undefined
+  setIsPublic(value: boolean): void
+
+  hasLanguage(): boolean
+  clearLanguage(): void
+  getLanguage(): number | undefined
+  setLanguage(value: number): void
+
+  serializeBinary(): Uint8Array
+  toObject(includeInstance?: boolean): ChannelMetadata.AsObject
+  static toObject(includeInstance: boolean, msg: ChannelMetadata): ChannelMetadata.AsObject
+  static extensions: { [key: number]: jspb.ExtensionFieldInfo<jspb.Message> }
+  static extensionsBinary: { [key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message> }
+  static serializeBinaryToWriter(message: ChannelMetadata, writer: jspb.BinaryWriter): void
+  static deserializeBinary(bytes: Uint8Array): ChannelMetadata
+  static deserializeBinaryFromReader(message: ChannelMetadata, reader: jspb.BinaryReader): ChannelMetadata
+}
+
+export namespace ChannelMetadata {
+  export type AsObject = {
+    title?: string
+    description?: string
+    isPublic?: boolean
+    language?: number
+  }
+}

+ 296 - 0
content-metadata/proto/Channel_pb.js

@@ -0,0 +1,296 @@
+// source: proto/Channel.proto
+/**
+ * @fileoverview
+ * @enhanceable
+ * @suppress {messageConventions} JS Compiler reports an error if a variable or
+ *     field starts with 'MSG_' and isn't a translatable message.
+ * @public
+ */
+// GENERATED CODE -- DO NOT EDIT!
+/* eslint-disable */
+// @ts-nocheck
+
+var jspb = require('google-protobuf')
+var goog = jspb
+var global = Function('return this')()
+
+goog.exportSymbol('proto.ChannelMetadata', null, global)
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.ChannelMetadata = function (opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null)
+}
+goog.inherits(proto.ChannelMetadata, jspb.Message)
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.ChannelMetadata.displayName = 'proto.ChannelMetadata'
+}
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+  /**
+   * Creates an object representation of this proto.
+   * Field names that are reserved in JavaScript and will be renamed to pb_name.
+   * Optional fields that are not set will be set to undefined.
+   * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+   * For the list of reserved names please see:
+   *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+   * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+   *     JSPB instance for transitional soy proto support:
+   *     http://goto/soy-param-migration
+   * @return {!Object}
+   */
+  proto.ChannelMetadata.prototype.toObject = function (opt_includeInstance) {
+    return proto.ChannelMetadata.toObject(opt_includeInstance, this)
+  }
+
+  /**
+   * Static version of the {@see toObject} method.
+   * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+   *     the JSPB instance for transitional soy proto support:
+   *     http://goto/soy-param-migration
+   * @param {!proto.ChannelMetadata} msg The msg instance to transform.
+   * @return {!Object}
+   * @suppress {unusedLocalVariables} f is only used for nested messages
+   */
+  proto.ChannelMetadata.toObject = function (includeInstance, msg) {
+    var f,
+      obj = {
+        title: (f = jspb.Message.getField(msg, 1)) == null ? undefined : f,
+        description: (f = jspb.Message.getField(msg, 2)) == null ? undefined : f,
+        isPublic: (f = jspb.Message.getBooleanField(msg, 3)) == null ? undefined : f,
+        language: (f = jspb.Message.getField(msg, 4)) == null ? undefined : f,
+      }
+
+    if (includeInstance) {
+      obj.$jspbMessageInstance = msg
+    }
+    return obj
+  }
+}
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.ChannelMetadata}
+ */
+proto.ChannelMetadata.deserializeBinary = function (bytes) {
+  var reader = new jspb.BinaryReader(bytes)
+  var msg = new proto.ChannelMetadata()
+  return proto.ChannelMetadata.deserializeBinaryFromReader(msg, reader)
+}
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.ChannelMetadata} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.ChannelMetadata}
+ */
+proto.ChannelMetadata.deserializeBinaryFromReader = function (msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break
+    }
+    var field = reader.getFieldNumber()
+    switch (field) {
+      case 1:
+        var value = /** @type {string} */ (reader.readString())
+        msg.setTitle(value)
+        break
+      case 2:
+        var value = /** @type {string} */ (reader.readString())
+        msg.setDescription(value)
+        break
+      case 3:
+        var value = /** @type {boolean} */ (reader.readBool())
+        msg.setIsPublic(value)
+        break
+      case 4:
+        var value = /** @type {number} */ (reader.readInt32())
+        msg.setLanguage(value)
+        break
+      default:
+        reader.skipField()
+        break
+    }
+  }
+  return msg
+}
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.ChannelMetadata.prototype.serializeBinary = function () {
+  var writer = new jspb.BinaryWriter()
+  proto.ChannelMetadata.serializeBinaryToWriter(this, writer)
+  return writer.getResultBuffer()
+}
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.ChannelMetadata} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.ChannelMetadata.serializeBinaryToWriter = function (message, writer) {
+  var f = undefined
+  f = /** @type {string} */ (jspb.Message.getField(message, 1))
+  if (f != null) {
+    writer.writeString(1, f)
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 2))
+  if (f != null) {
+    writer.writeString(2, f)
+  }
+  f = /** @type {boolean} */ (jspb.Message.getField(message, 3))
+  if (f != null) {
+    writer.writeBool(3, f)
+  }
+  f = /** @type {number} */ (jspb.Message.getField(message, 4))
+  if (f != null) {
+    writer.writeInt32(4, f)
+  }
+}
+
+/**
+ * optional string title = 1;
+ * @return {string}
+ */
+proto.ChannelMetadata.prototype.getTitle = function () {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ''))
+}
+
+/**
+ * @param {string} value
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.setTitle = function (value) {
+  return jspb.Message.setField(this, 1, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.clearTitle = function () {
+  return jspb.Message.setField(this, 1, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.ChannelMetadata.prototype.hasTitle = function () {
+  return jspb.Message.getField(this, 1) != null
+}
+
+/**
+ * optional string description = 2;
+ * @return {string}
+ */
+proto.ChannelMetadata.prototype.getDescription = function () {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ''))
+}
+
+/**
+ * @param {string} value
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.setDescription = function (value) {
+  return jspb.Message.setField(this, 2, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.clearDescription = function () {
+  return jspb.Message.setField(this, 2, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.ChannelMetadata.prototype.hasDescription = function () {
+  return jspb.Message.getField(this, 2) != null
+}
+
+/**
+ * optional bool is_public = 3;
+ * @return {boolean}
+ */
+proto.ChannelMetadata.prototype.getIsPublic = function () {
+  return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 3, false))
+}
+
+/**
+ * @param {boolean} value
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.setIsPublic = function (value) {
+  return jspb.Message.setField(this, 3, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.clearIsPublic = function () {
+  return jspb.Message.setField(this, 3, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.ChannelMetadata.prototype.hasIsPublic = function () {
+  return jspb.Message.getField(this, 3) != null
+}
+
+/**
+ * optional int32 language = 4;
+ * @return {number}
+ */
+proto.ChannelMetadata.prototype.getLanguage = function () {
+  return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 4, 0))
+}
+
+/**
+ * @param {number} value
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.setLanguage = function (value) {
+  return jspb.Message.setField(this, 4, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.clearLanguage = function () {
+  return jspb.Message.setField(this, 4, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.ChannelMetadata.prototype.hasLanguage = function () {
+  return jspb.Message.getField(this, 4) != null
+}
+
+goog.object.extend(exports, proto)

+ 74 - 0
content-metadata/proto/Video.proto

@@ -0,0 +1,74 @@
+// required/optional feilds are deprecated in proto v3
+// https://www.ben-morris.com/handling-protocol-buffers-backwards-compatibility-between-versions-2-and-3-using-c/
+// For our usecase we wish to re-use same message to create and update 
+// subset of fields. For this reason we need the explicit information
+// about wether a field has been set or not and this is only possible with proto v2.
+syntax = "proto2";
+
+message PublishedBeforeJoystream {
+    optional bool is_published = 1;
+    optional uint32 timestamp = 2;
+}
+
+// Joystream Specific License type
+message License {
+    // License code defined by Joystream
+    optional int32 code = 1;
+    // Text for licenses that require an attribution
+    optional string attribution = 2;
+    // Text for custom license type
+    optional string custom_text = 3;
+}
+
+// Rich format description of video media type
+message MediaType {
+    // enum AVCodecID from FFmpeg: libavcodec/codec_id.h
+    // optional int32 codec_id = 1;
+
+    // string name field from FFmpeg libavcodec/codec_desc.c
+    optional string codec_name = 1;
+
+    // Video container format (eg. 'MP4', 'WebM', 'Ogg' ...)
+    // https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Video_codecs
+    optional string container = 2;
+
+    // MIME Media Type
+    // https://www.iana.org/assignments/media-types/media-types.xhtml#video
+    // eg. 'video/mp4'
+    optional string mime_media_type = 3;
+}
+
+message VideoMetadata {
+    optional string title = 1;
+
+    optional string description = 2;
+
+    // Duration in seconds of the video
+    optional int32 duration = 3;
+
+    // Resolution of the video
+    optional int32 media_pixel_height = 4;
+    optional int32 media_pixel_width = 5;
+
+    // Rich media type information about the media format
+    optional MediaType media_type = 6;
+
+    // ISO_639-1 code: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+    // useful: npm package https://www.npmjs.com/package/iso-639-1
+    optional string language = 7;
+
+    // License type for the media
+    optional License license = 8;
+
+    optional PublishedBeforeJoystream published_before_joystream = 9;
+
+    // Set to true if video has marketing/adverts in the stream
+    optional bool has_marketing = 10;
+
+    // Set to true if it should be visiable to public
+    optional bool is_public = 11;
+
+    // Set to true if video has explicit language or scenes
+    // Should this be a curator managed property instead?
+    optional bool is_explicit = 12;
+}

+ 191 - 0
content-metadata/proto/Video_pb.d.ts

@@ -0,0 +1,191 @@
+// package:
+// file: proto/Video.proto
+
+import * as jspb from 'google-protobuf'
+
+export class PublishedBeforeJoystream extends jspb.Message {
+  hasIsPublished(): boolean
+  clearIsPublished(): void
+  getIsPublished(): boolean | undefined
+  setIsPublished(value: boolean): void
+
+  hasTimestamp(): boolean
+  clearTimestamp(): void
+  getTimestamp(): number | undefined
+  setTimestamp(value: number): void
+
+  serializeBinary(): Uint8Array
+  toObject(includeInstance?: boolean): PublishedBeforeJoystream.AsObject
+  static toObject(includeInstance: boolean, msg: PublishedBeforeJoystream): PublishedBeforeJoystream.AsObject
+  static extensions: { [key: number]: jspb.ExtensionFieldInfo<jspb.Message> }
+  static extensionsBinary: { [key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message> }
+  static serializeBinaryToWriter(message: PublishedBeforeJoystream, writer: jspb.BinaryWriter): void
+  static deserializeBinary(bytes: Uint8Array): PublishedBeforeJoystream
+  static deserializeBinaryFromReader(
+    message: PublishedBeforeJoystream,
+    reader: jspb.BinaryReader
+  ): PublishedBeforeJoystream
+}
+
+export namespace PublishedBeforeJoystream {
+  export type AsObject = {
+    isPublished?: boolean
+    timestamp?: number
+  }
+}
+
+export class License extends jspb.Message {
+  hasCode(): boolean
+  clearCode(): void
+  getCode(): number | undefined
+  setCode(value: number): void
+
+  hasAttribution(): boolean
+  clearAttribution(): void
+  getAttribution(): string | undefined
+  setAttribution(value: string): void
+
+  hasCustomText(): boolean
+  clearCustomText(): void
+  getCustomText(): string | undefined
+  setCustomText(value: string): void
+
+  serializeBinary(): Uint8Array
+  toObject(includeInstance?: boolean): License.AsObject
+  static toObject(includeInstance: boolean, msg: License): License.AsObject
+  static extensions: { [key: number]: jspb.ExtensionFieldInfo<jspb.Message> }
+  static extensionsBinary: { [key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message> }
+  static serializeBinaryToWriter(message: License, writer: jspb.BinaryWriter): void
+  static deserializeBinary(bytes: Uint8Array): License
+  static deserializeBinaryFromReader(message: License, reader: jspb.BinaryReader): License
+}
+
+export namespace License {
+  export type AsObject = {
+    code?: number
+    attribution?: string
+    customText?: string
+  }
+}
+
+export class MediaType extends jspb.Message {
+  hasCodecName(): boolean
+  clearCodecName(): void
+  getCodecName(): string | undefined
+  setCodecName(value: string): void
+
+  hasContainer(): boolean
+  clearContainer(): void
+  getContainer(): string | undefined
+  setContainer(value: string): void
+
+  hasMimeMediaType(): boolean
+  clearMimeMediaType(): void
+  getMimeMediaType(): string | undefined
+  setMimeMediaType(value: string): void
+
+  serializeBinary(): Uint8Array
+  toObject(includeInstance?: boolean): MediaType.AsObject
+  static toObject(includeInstance: boolean, msg: MediaType): MediaType.AsObject
+  static extensions: { [key: number]: jspb.ExtensionFieldInfo<jspb.Message> }
+  static extensionsBinary: { [key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message> }
+  static serializeBinaryToWriter(message: MediaType, writer: jspb.BinaryWriter): void
+  static deserializeBinary(bytes: Uint8Array): MediaType
+  static deserializeBinaryFromReader(message: MediaType, reader: jspb.BinaryReader): MediaType
+}
+
+export namespace MediaType {
+  export type AsObject = {
+    codecName?: string
+    container?: string
+    mimeMediaType?: string
+  }
+}
+
+export class VideoMetadata extends jspb.Message {
+  hasTitle(): boolean
+  clearTitle(): void
+  getTitle(): string | undefined
+  setTitle(value: string): void
+
+  hasDescription(): boolean
+  clearDescription(): void
+  getDescription(): string | undefined
+  setDescription(value: string): void
+
+  hasDuration(): boolean
+  clearDuration(): void
+  getDuration(): number | undefined
+  setDuration(value: number): void
+
+  hasMediaPixelHeight(): boolean
+  clearMediaPixelHeight(): void
+  getMediaPixelHeight(): number | undefined
+  setMediaPixelHeight(value: number): void
+
+  hasMediaPixelWidth(): boolean
+  clearMediaPixelWidth(): void
+  getMediaPixelWidth(): number | undefined
+  setMediaPixelWidth(value: number): void
+
+  hasMediaType(): boolean
+  clearMediaType(): void
+  getMediaType(): MediaType | undefined
+  setMediaType(value?: MediaType): void
+
+  hasLanguage(): boolean
+  clearLanguage(): void
+  getLanguage(): string | undefined
+  setLanguage(value: string): void
+
+  hasLicense(): boolean
+  clearLicense(): void
+  getLicense(): License | undefined
+  setLicense(value?: License): void
+
+  hasPublishedBeforeJoystream(): boolean
+  clearPublishedBeforeJoystream(): void
+  getPublishedBeforeJoystream(): PublishedBeforeJoystream | undefined
+  setPublishedBeforeJoystream(value?: PublishedBeforeJoystream): void
+
+  hasHasMarketing(): boolean
+  clearHasMarketing(): void
+  getHasMarketing(): boolean | undefined
+  setHasMarketing(value: boolean): void
+
+  hasIsPublic(): boolean
+  clearIsPublic(): void
+  getIsPublic(): boolean | undefined
+  setIsPublic(value: boolean): void
+
+  hasIsExplicit(): boolean
+  clearIsExplicit(): void
+  getIsExplicit(): boolean | undefined
+  setIsExplicit(value: boolean): void
+
+  serializeBinary(): Uint8Array
+  toObject(includeInstance?: boolean): VideoMetadata.AsObject
+  static toObject(includeInstance: boolean, msg: VideoMetadata): VideoMetadata.AsObject
+  static extensions: { [key: number]: jspb.ExtensionFieldInfo<jspb.Message> }
+  static extensionsBinary: { [key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message> }
+  static serializeBinaryToWriter(message: VideoMetadata, writer: jspb.BinaryWriter): void
+  static deserializeBinary(bytes: Uint8Array): VideoMetadata
+  static deserializeBinaryFromReader(message: VideoMetadata, reader: jspb.BinaryReader): VideoMetadata
+}
+
+export namespace VideoMetadata {
+  export type AsObject = {
+    title?: string
+    description?: string
+    duration?: number
+    mediaPixelHeight?: number
+    mediaPixelWidth?: number
+    mediaType?: MediaType.AsObject
+    language?: string
+    license?: License.AsObject
+    publishedBeforeJoystream?: PublishedBeforeJoystream.AsObject
+    hasMarketing?: boolean
+    isPublic?: boolean
+    isExplicit?: boolean
+  }
+}

+ 1302 - 0
content-metadata/proto/Video_pb.js

@@ -0,0 +1,1302 @@
+// source: proto/Video.proto
+/**
+ * @fileoverview
+ * @enhanceable
+ * @suppress {messageConventions} JS Compiler reports an error if a variable or
+ *     field starts with 'MSG_' and isn't a translatable message.
+ * @public
+ */
+// GENERATED CODE -- DO NOT EDIT!
+/* eslint-disable */
+// @ts-nocheck
+
+var jspb = require('google-protobuf')
+var goog = jspb
+var global = Function('return this')()
+
+goog.exportSymbol('proto.License', null, global)
+goog.exportSymbol('proto.MediaType', null, global)
+goog.exportSymbol('proto.PublishedBeforeJoystream', null, global)
+goog.exportSymbol('proto.VideoMetadata', null, global)
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.PublishedBeforeJoystream = function (opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null)
+}
+goog.inherits(proto.PublishedBeforeJoystream, jspb.Message)
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.PublishedBeforeJoystream.displayName = 'proto.PublishedBeforeJoystream'
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.License = function (opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null)
+}
+goog.inherits(proto.License, jspb.Message)
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.License.displayName = 'proto.License'
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.MediaType = function (opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null)
+}
+goog.inherits(proto.MediaType, jspb.Message)
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.MediaType.displayName = 'proto.MediaType'
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.VideoMetadata = function (opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null)
+}
+goog.inherits(proto.VideoMetadata, jspb.Message)
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.VideoMetadata.displayName = 'proto.VideoMetadata'
+}
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+  /**
+   * Creates an object representation of this proto.
+   * Field names that are reserved in JavaScript and will be renamed to pb_name.
+   * Optional fields that are not set will be set to undefined.
+   * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+   * For the list of reserved names please see:
+   *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+   * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+   *     JSPB instance for transitional soy proto support:
+   *     http://goto/soy-param-migration
+   * @return {!Object}
+   */
+  proto.PublishedBeforeJoystream.prototype.toObject = function (opt_includeInstance) {
+    return proto.PublishedBeforeJoystream.toObject(opt_includeInstance, this)
+  }
+
+  /**
+   * Static version of the {@see toObject} method.
+   * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+   *     the JSPB instance for transitional soy proto support:
+   *     http://goto/soy-param-migration
+   * @param {!proto.PublishedBeforeJoystream} msg The msg instance to transform.
+   * @return {!Object}
+   * @suppress {unusedLocalVariables} f is only used for nested messages
+   */
+  proto.PublishedBeforeJoystream.toObject = function (includeInstance, msg) {
+    var f,
+      obj = {
+        isPublished: (f = jspb.Message.getBooleanField(msg, 1)) == null ? undefined : f,
+        timestamp: (f = jspb.Message.getField(msg, 2)) == null ? undefined : f,
+      }
+
+    if (includeInstance) {
+      obj.$jspbMessageInstance = msg
+    }
+    return obj
+  }
+}
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.PublishedBeforeJoystream}
+ */
+proto.PublishedBeforeJoystream.deserializeBinary = function (bytes) {
+  var reader = new jspb.BinaryReader(bytes)
+  var msg = new proto.PublishedBeforeJoystream()
+  return proto.PublishedBeforeJoystream.deserializeBinaryFromReader(msg, reader)
+}
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.PublishedBeforeJoystream} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.PublishedBeforeJoystream}
+ */
+proto.PublishedBeforeJoystream.deserializeBinaryFromReader = function (msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break
+    }
+    var field = reader.getFieldNumber()
+    switch (field) {
+      case 1:
+        var value = /** @type {boolean} */ (reader.readBool())
+        msg.setIsPublished(value)
+        break
+      case 2:
+        var value = /** @type {number} */ (reader.readUint32())
+        msg.setTimestamp(value)
+        break
+      default:
+        reader.skipField()
+        break
+    }
+  }
+  return msg
+}
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.PublishedBeforeJoystream.prototype.serializeBinary = function () {
+  var writer = new jspb.BinaryWriter()
+  proto.PublishedBeforeJoystream.serializeBinaryToWriter(this, writer)
+  return writer.getResultBuffer()
+}
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.PublishedBeforeJoystream} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.PublishedBeforeJoystream.serializeBinaryToWriter = function (message, writer) {
+  var f = undefined
+  f = /** @type {boolean} */ (jspb.Message.getField(message, 1))
+  if (f != null) {
+    writer.writeBool(1, f)
+  }
+  f = /** @type {number} */ (jspb.Message.getField(message, 2))
+  if (f != null) {
+    writer.writeUint32(2, f)
+  }
+}
+
+/**
+ * optional bool is_published = 1;
+ * @return {boolean}
+ */
+proto.PublishedBeforeJoystream.prototype.getIsPublished = function () {
+  return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 1, false))
+}
+
+/**
+ * @param {boolean} value
+ * @return {!proto.PublishedBeforeJoystream} returns this
+ */
+proto.PublishedBeforeJoystream.prototype.setIsPublished = function (value) {
+  return jspb.Message.setField(this, 1, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.PublishedBeforeJoystream} returns this
+ */
+proto.PublishedBeforeJoystream.prototype.clearIsPublished = function () {
+  return jspb.Message.setField(this, 1, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.PublishedBeforeJoystream.prototype.hasIsPublished = function () {
+  return jspb.Message.getField(this, 1) != null
+}
+
+/**
+ * optional uint32 timestamp = 2;
+ * @return {number}
+ */
+proto.PublishedBeforeJoystream.prototype.getTimestamp = function () {
+  return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 2, 0))
+}
+
+/**
+ * @param {number} value
+ * @return {!proto.PublishedBeforeJoystream} returns this
+ */
+proto.PublishedBeforeJoystream.prototype.setTimestamp = function (value) {
+  return jspb.Message.setField(this, 2, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.PublishedBeforeJoystream} returns this
+ */
+proto.PublishedBeforeJoystream.prototype.clearTimestamp = function () {
+  return jspb.Message.setField(this, 2, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.PublishedBeforeJoystream.prototype.hasTimestamp = function () {
+  return jspb.Message.getField(this, 2) != null
+}
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+  /**
+   * Creates an object representation of this proto.
+   * Field names that are reserved in JavaScript and will be renamed to pb_name.
+   * Optional fields that are not set will be set to undefined.
+   * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+   * For the list of reserved names please see:
+   *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+   * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+   *     JSPB instance for transitional soy proto support:
+   *     http://goto/soy-param-migration
+   * @return {!Object}
+   */
+  proto.License.prototype.toObject = function (opt_includeInstance) {
+    return proto.License.toObject(opt_includeInstance, this)
+  }
+
+  /**
+   * Static version of the {@see toObject} method.
+   * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+   *     the JSPB instance for transitional soy proto support:
+   *     http://goto/soy-param-migration
+   * @param {!proto.License} msg The msg instance to transform.
+   * @return {!Object}
+   * @suppress {unusedLocalVariables} f is only used for nested messages
+   */
+  proto.License.toObject = function (includeInstance, msg) {
+    var f,
+      obj = {
+        code: (f = jspb.Message.getField(msg, 1)) == null ? undefined : f,
+        attribution: (f = jspb.Message.getField(msg, 2)) == null ? undefined : f,
+        customText: (f = jspb.Message.getField(msg, 3)) == null ? undefined : f,
+      }
+
+    if (includeInstance) {
+      obj.$jspbMessageInstance = msg
+    }
+    return obj
+  }
+}
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.License}
+ */
+proto.License.deserializeBinary = function (bytes) {
+  var reader = new jspb.BinaryReader(bytes)
+  var msg = new proto.License()
+  return proto.License.deserializeBinaryFromReader(msg, reader)
+}
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.License} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.License}
+ */
+proto.License.deserializeBinaryFromReader = function (msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break
+    }
+    var field = reader.getFieldNumber()
+    switch (field) {
+      case 1:
+        var value = /** @type {number} */ (reader.readInt32())
+        msg.setCode(value)
+        break
+      case 2:
+        var value = /** @type {string} */ (reader.readString())
+        msg.setAttribution(value)
+        break
+      case 3:
+        var value = /** @type {string} */ (reader.readString())
+        msg.setCustomText(value)
+        break
+      default:
+        reader.skipField()
+        break
+    }
+  }
+  return msg
+}
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.License.prototype.serializeBinary = function () {
+  var writer = new jspb.BinaryWriter()
+  proto.License.serializeBinaryToWriter(this, writer)
+  return writer.getResultBuffer()
+}
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.License} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.License.serializeBinaryToWriter = function (message, writer) {
+  var f = undefined
+  f = /** @type {number} */ (jspb.Message.getField(message, 1))
+  if (f != null) {
+    writer.writeInt32(1, f)
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 2))
+  if (f != null) {
+    writer.writeString(2, f)
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 3))
+  if (f != null) {
+    writer.writeString(3, f)
+  }
+}
+
+/**
+ * optional int32 code = 1;
+ * @return {number}
+ */
+proto.License.prototype.getCode = function () {
+  return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0))
+}
+
+/**
+ * @param {number} value
+ * @return {!proto.License} returns this
+ */
+proto.License.prototype.setCode = function (value) {
+  return jspb.Message.setField(this, 1, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.License} returns this
+ */
+proto.License.prototype.clearCode = function () {
+  return jspb.Message.setField(this, 1, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.License.prototype.hasCode = function () {
+  return jspb.Message.getField(this, 1) != null
+}
+
+/**
+ * optional string attribution = 2;
+ * @return {string}
+ */
+proto.License.prototype.getAttribution = function () {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ''))
+}
+
+/**
+ * @param {string} value
+ * @return {!proto.License} returns this
+ */
+proto.License.prototype.setAttribution = function (value) {
+  return jspb.Message.setField(this, 2, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.License} returns this
+ */
+proto.License.prototype.clearAttribution = function () {
+  return jspb.Message.setField(this, 2, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.License.prototype.hasAttribution = function () {
+  return jspb.Message.getField(this, 2) != null
+}
+
+/**
+ * optional string custom_text = 3;
+ * @return {string}
+ */
+proto.License.prototype.getCustomText = function () {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ''))
+}
+
+/**
+ * @param {string} value
+ * @return {!proto.License} returns this
+ */
+proto.License.prototype.setCustomText = function (value) {
+  return jspb.Message.setField(this, 3, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.License} returns this
+ */
+proto.License.prototype.clearCustomText = function () {
+  return jspb.Message.setField(this, 3, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.License.prototype.hasCustomText = function () {
+  return jspb.Message.getField(this, 3) != null
+}
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+  /**
+   * Creates an object representation of this proto.
+   * Field names that are reserved in JavaScript and will be renamed to pb_name.
+   * Optional fields that are not set will be set to undefined.
+   * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+   * For the list of reserved names please see:
+   *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+   * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+   *     JSPB instance for transitional soy proto support:
+   *     http://goto/soy-param-migration
+   * @return {!Object}
+   */
+  proto.MediaType.prototype.toObject = function (opt_includeInstance) {
+    return proto.MediaType.toObject(opt_includeInstance, this)
+  }
+
+  /**
+   * Static version of the {@see toObject} method.
+   * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+   *     the JSPB instance for transitional soy proto support:
+   *     http://goto/soy-param-migration
+   * @param {!proto.MediaType} msg The msg instance to transform.
+   * @return {!Object}
+   * @suppress {unusedLocalVariables} f is only used for nested messages
+   */
+  proto.MediaType.toObject = function (includeInstance, msg) {
+    var f,
+      obj = {
+        codecName: (f = jspb.Message.getField(msg, 1)) == null ? undefined : f,
+        container: (f = jspb.Message.getField(msg, 2)) == null ? undefined : f,
+        mimeMediaType: (f = jspb.Message.getField(msg, 3)) == null ? undefined : f,
+      }
+
+    if (includeInstance) {
+      obj.$jspbMessageInstance = msg
+    }
+    return obj
+  }
+}
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.MediaType}
+ */
+proto.MediaType.deserializeBinary = function (bytes) {
+  var reader = new jspb.BinaryReader(bytes)
+  var msg = new proto.MediaType()
+  return proto.MediaType.deserializeBinaryFromReader(msg, reader)
+}
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.MediaType} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.MediaType}
+ */
+proto.MediaType.deserializeBinaryFromReader = function (msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break
+    }
+    var field = reader.getFieldNumber()
+    switch (field) {
+      case 1:
+        var value = /** @type {string} */ (reader.readString())
+        msg.setCodecName(value)
+        break
+      case 2:
+        var value = /** @type {string} */ (reader.readString())
+        msg.setContainer(value)
+        break
+      case 3:
+        var value = /** @type {string} */ (reader.readString())
+        msg.setMimeMediaType(value)
+        break
+      default:
+        reader.skipField()
+        break
+    }
+  }
+  return msg
+}
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.MediaType.prototype.serializeBinary = function () {
+  var writer = new jspb.BinaryWriter()
+  proto.MediaType.serializeBinaryToWriter(this, writer)
+  return writer.getResultBuffer()
+}
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.MediaType} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.MediaType.serializeBinaryToWriter = function (message, writer) {
+  var f = undefined
+  f = /** @type {string} */ (jspb.Message.getField(message, 1))
+  if (f != null) {
+    writer.writeString(1, f)
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 2))
+  if (f != null) {
+    writer.writeString(2, f)
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 3))
+  if (f != null) {
+    writer.writeString(3, f)
+  }
+}
+
+/**
+ * optional string codec_name = 1;
+ * @return {string}
+ */
+proto.MediaType.prototype.getCodecName = function () {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ''))
+}
+
+/**
+ * @param {string} value
+ * @return {!proto.MediaType} returns this
+ */
+proto.MediaType.prototype.setCodecName = function (value) {
+  return jspb.Message.setField(this, 1, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.MediaType} returns this
+ */
+proto.MediaType.prototype.clearCodecName = function () {
+  return jspb.Message.setField(this, 1, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.MediaType.prototype.hasCodecName = function () {
+  return jspb.Message.getField(this, 1) != null
+}
+
+/**
+ * optional string container = 2;
+ * @return {string}
+ */
+proto.MediaType.prototype.getContainer = function () {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ''))
+}
+
+/**
+ * @param {string} value
+ * @return {!proto.MediaType} returns this
+ */
+proto.MediaType.prototype.setContainer = function (value) {
+  return jspb.Message.setField(this, 2, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.MediaType} returns this
+ */
+proto.MediaType.prototype.clearContainer = function () {
+  return jspb.Message.setField(this, 2, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.MediaType.prototype.hasContainer = function () {
+  return jspb.Message.getField(this, 2) != null
+}
+
+/**
+ * optional string mime_media_type = 3;
+ * @return {string}
+ */
+proto.MediaType.prototype.getMimeMediaType = function () {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ''))
+}
+
+/**
+ * @param {string} value
+ * @return {!proto.MediaType} returns this
+ */
+proto.MediaType.prototype.setMimeMediaType = function (value) {
+  return jspb.Message.setField(this, 3, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.MediaType} returns this
+ */
+proto.MediaType.prototype.clearMimeMediaType = function () {
+  return jspb.Message.setField(this, 3, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.MediaType.prototype.hasMimeMediaType = function () {
+  return jspb.Message.getField(this, 3) != null
+}
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+  /**
+   * Creates an object representation of this proto.
+   * Field names that are reserved in JavaScript and will be renamed to pb_name.
+   * Optional fields that are not set will be set to undefined.
+   * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+   * For the list of reserved names please see:
+   *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+   * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+   *     JSPB instance for transitional soy proto support:
+   *     http://goto/soy-param-migration
+   * @return {!Object}
+   */
+  proto.VideoMetadata.prototype.toObject = function (opt_includeInstance) {
+    return proto.VideoMetadata.toObject(opt_includeInstance, this)
+  }
+
+  /**
+   * Static version of the {@see toObject} method.
+   * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+   *     the JSPB instance for transitional soy proto support:
+   *     http://goto/soy-param-migration
+   * @param {!proto.VideoMetadata} msg The msg instance to transform.
+   * @return {!Object}
+   * @suppress {unusedLocalVariables} f is only used for nested messages
+   */
+  proto.VideoMetadata.toObject = function (includeInstance, msg) {
+    var f,
+      obj = {
+        title: (f = jspb.Message.getField(msg, 1)) == null ? undefined : f,
+        description: (f = jspb.Message.getField(msg, 2)) == null ? undefined : f,
+        duration: (f = jspb.Message.getField(msg, 3)) == null ? undefined : f,
+        mediaPixelHeight: (f = jspb.Message.getField(msg, 4)) == null ? undefined : f,
+        mediaPixelWidth: (f = jspb.Message.getField(msg, 5)) == null ? undefined : f,
+        mediaType: (f = msg.getMediaType()) && proto.MediaType.toObject(includeInstance, f),
+        language: (f = jspb.Message.getField(msg, 7)) == null ? undefined : f,
+        license: (f = msg.getLicense()) && proto.License.toObject(includeInstance, f),
+        publishedBeforeJoystream:
+          (f = msg.getPublishedBeforeJoystream()) && proto.PublishedBeforeJoystream.toObject(includeInstance, f),
+        hasMarketing: (f = jspb.Message.getBooleanField(msg, 10)) == null ? undefined : f,
+        isPublic: (f = jspb.Message.getBooleanField(msg, 11)) == null ? undefined : f,
+        isExplicit: (f = jspb.Message.getBooleanField(msg, 12)) == null ? undefined : f,
+      }
+
+    if (includeInstance) {
+      obj.$jspbMessageInstance = msg
+    }
+    return obj
+  }
+}
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.VideoMetadata}
+ */
+proto.VideoMetadata.deserializeBinary = function (bytes) {
+  var reader = new jspb.BinaryReader(bytes)
+  var msg = new proto.VideoMetadata()
+  return proto.VideoMetadata.deserializeBinaryFromReader(msg, reader)
+}
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.VideoMetadata} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.VideoMetadata}
+ */
+proto.VideoMetadata.deserializeBinaryFromReader = function (msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break
+    }
+    var field = reader.getFieldNumber()
+    switch (field) {
+      case 1:
+        var value = /** @type {string} */ (reader.readString())
+        msg.setTitle(value)
+        break
+      case 2:
+        var value = /** @type {string} */ (reader.readString())
+        msg.setDescription(value)
+        break
+      case 3:
+        var value = /** @type {number} */ (reader.readInt32())
+        msg.setDuration(value)
+        break
+      case 4:
+        var value = /** @type {number} */ (reader.readInt32())
+        msg.setMediaPixelHeight(value)
+        break
+      case 5:
+        var value = /** @type {number} */ (reader.readInt32())
+        msg.setMediaPixelWidth(value)
+        break
+      case 6:
+        var value = new proto.MediaType()
+        reader.readMessage(value, proto.MediaType.deserializeBinaryFromReader)
+        msg.setMediaType(value)
+        break
+      case 7:
+        var value = /** @type {string} */ (reader.readString())
+        msg.setLanguage(value)
+        break
+      case 8:
+        var value = new proto.License()
+        reader.readMessage(value, proto.License.deserializeBinaryFromReader)
+        msg.setLicense(value)
+        break
+      case 9:
+        var value = new proto.PublishedBeforeJoystream()
+        reader.readMessage(value, proto.PublishedBeforeJoystream.deserializeBinaryFromReader)
+        msg.setPublishedBeforeJoystream(value)
+        break
+      case 10:
+        var value = /** @type {boolean} */ (reader.readBool())
+        msg.setHasMarketing(value)
+        break
+      case 11:
+        var value = /** @type {boolean} */ (reader.readBool())
+        msg.setIsPublic(value)
+        break
+      case 12:
+        var value = /** @type {boolean} */ (reader.readBool())
+        msg.setIsExplicit(value)
+        break
+      default:
+        reader.skipField()
+        break
+    }
+  }
+  return msg
+}
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.VideoMetadata.prototype.serializeBinary = function () {
+  var writer = new jspb.BinaryWriter()
+  proto.VideoMetadata.serializeBinaryToWriter(this, writer)
+  return writer.getResultBuffer()
+}
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.VideoMetadata} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.VideoMetadata.serializeBinaryToWriter = function (message, writer) {
+  var f = undefined
+  f = /** @type {string} */ (jspb.Message.getField(message, 1))
+  if (f != null) {
+    writer.writeString(1, f)
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 2))
+  if (f != null) {
+    writer.writeString(2, f)
+  }
+  f = /** @type {number} */ (jspb.Message.getField(message, 3))
+  if (f != null) {
+    writer.writeInt32(3, f)
+  }
+  f = /** @type {number} */ (jspb.Message.getField(message, 4))
+  if (f != null) {
+    writer.writeInt32(4, f)
+  }
+  f = /** @type {number} */ (jspb.Message.getField(message, 5))
+  if (f != null) {
+    writer.writeInt32(5, f)
+  }
+  f = message.getMediaType()
+  if (f != null) {
+    writer.writeMessage(6, f, proto.MediaType.serializeBinaryToWriter)
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 7))
+  if (f != null) {
+    writer.writeString(7, f)
+  }
+  f = message.getLicense()
+  if (f != null) {
+    writer.writeMessage(8, f, proto.License.serializeBinaryToWriter)
+  }
+  f = message.getPublishedBeforeJoystream()
+  if (f != null) {
+    writer.writeMessage(9, f, proto.PublishedBeforeJoystream.serializeBinaryToWriter)
+  }
+  f = /** @type {boolean} */ (jspb.Message.getField(message, 10))
+  if (f != null) {
+    writer.writeBool(10, f)
+  }
+  f = /** @type {boolean} */ (jspb.Message.getField(message, 11))
+  if (f != null) {
+    writer.writeBool(11, f)
+  }
+  f = /** @type {boolean} */ (jspb.Message.getField(message, 12))
+  if (f != null) {
+    writer.writeBool(12, f)
+  }
+}
+
+/**
+ * optional string title = 1;
+ * @return {string}
+ */
+proto.VideoMetadata.prototype.getTitle = function () {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ''))
+}
+
+/**
+ * @param {string} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setTitle = function (value) {
+  return jspb.Message.setField(this, 1, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearTitle = function () {
+  return jspb.Message.setField(this, 1, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasTitle = function () {
+  return jspb.Message.getField(this, 1) != null
+}
+
+/**
+ * optional string description = 2;
+ * @return {string}
+ */
+proto.VideoMetadata.prototype.getDescription = function () {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ''))
+}
+
+/**
+ * @param {string} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setDescription = function (value) {
+  return jspb.Message.setField(this, 2, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearDescription = function () {
+  return jspb.Message.setField(this, 2, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasDescription = function () {
+  return jspb.Message.getField(this, 2) != null
+}
+
+/**
+ * optional int32 duration = 3;
+ * @return {number}
+ */
+proto.VideoMetadata.prototype.getDuration = function () {
+  return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 3, 0))
+}
+
+/**
+ * @param {number} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setDuration = function (value) {
+  return jspb.Message.setField(this, 3, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearDuration = function () {
+  return jspb.Message.setField(this, 3, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasDuration = function () {
+  return jspb.Message.getField(this, 3) != null
+}
+
+/**
+ * optional int32 media_pixel_height = 4;
+ * @return {number}
+ */
+proto.VideoMetadata.prototype.getMediaPixelHeight = function () {
+  return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 4, 0))
+}
+
+/**
+ * @param {number} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setMediaPixelHeight = function (value) {
+  return jspb.Message.setField(this, 4, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearMediaPixelHeight = function () {
+  return jspb.Message.setField(this, 4, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasMediaPixelHeight = function () {
+  return jspb.Message.getField(this, 4) != null
+}
+
+/**
+ * optional int32 media_pixel_width = 5;
+ * @return {number}
+ */
+proto.VideoMetadata.prototype.getMediaPixelWidth = function () {
+  return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 5, 0))
+}
+
+/**
+ * @param {number} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setMediaPixelWidth = function (value) {
+  return jspb.Message.setField(this, 5, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearMediaPixelWidth = function () {
+  return jspb.Message.setField(this, 5, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasMediaPixelWidth = function () {
+  return jspb.Message.getField(this, 5) != null
+}
+
+/**
+ * optional MediaType media_type = 6;
+ * @return {?proto.MediaType}
+ */
+proto.VideoMetadata.prototype.getMediaType = function () {
+  return /** @type{?proto.MediaType} */ (jspb.Message.getWrapperField(this, proto.MediaType, 6))
+}
+
+/**
+ * @param {?proto.MediaType|undefined} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setMediaType = function (value) {
+  return jspb.Message.setWrapperField(this, 6, value)
+}
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearMediaType = function () {
+  return this.setMediaType(undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasMediaType = function () {
+  return jspb.Message.getField(this, 6) != null
+}
+
+/**
+ * optional string language = 7;
+ * @return {string}
+ */
+proto.VideoMetadata.prototype.getLanguage = function () {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 7, ''))
+}
+
+/**
+ * @param {string} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setLanguage = function (value) {
+  return jspb.Message.setField(this, 7, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearLanguage = function () {
+  return jspb.Message.setField(this, 7, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasLanguage = function () {
+  return jspb.Message.getField(this, 7) != null
+}
+
+/**
+ * optional License license = 8;
+ * @return {?proto.License}
+ */
+proto.VideoMetadata.prototype.getLicense = function () {
+  return /** @type{?proto.License} */ (jspb.Message.getWrapperField(this, proto.License, 8))
+}
+
+/**
+ * @param {?proto.License|undefined} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setLicense = function (value) {
+  return jspb.Message.setWrapperField(this, 8, value)
+}
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearLicense = function () {
+  return this.setLicense(undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasLicense = function () {
+  return jspb.Message.getField(this, 8) != null
+}
+
+/**
+ * optional PublishedBeforeJoystream published_before_joystream = 9;
+ * @return {?proto.PublishedBeforeJoystream}
+ */
+proto.VideoMetadata.prototype.getPublishedBeforeJoystream = function () {
+  return /** @type{?proto.PublishedBeforeJoystream} */ (jspb.Message.getWrapperField(
+    this,
+    proto.PublishedBeforeJoystream,
+    9
+  ))
+}
+
+/**
+ * @param {?proto.PublishedBeforeJoystream|undefined} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setPublishedBeforeJoystream = function (value) {
+  return jspb.Message.setWrapperField(this, 9, value)
+}
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearPublishedBeforeJoystream = function () {
+  return this.setPublishedBeforeJoystream(undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasPublishedBeforeJoystream = function () {
+  return jspb.Message.getField(this, 9) != null
+}
+
+/**
+ * optional bool has_marketing = 10;
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.getHasMarketing = function () {
+  return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 10, false))
+}
+
+/**
+ * @param {boolean} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setHasMarketing = function (value) {
+  return jspb.Message.setField(this, 10, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearHasMarketing = function () {
+  return jspb.Message.setField(this, 10, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasHasMarketing = function () {
+  return jspb.Message.getField(this, 10) != null
+}
+
+/**
+ * optional bool is_public = 11;
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.getIsPublic = function () {
+  return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 11, false))
+}
+
+/**
+ * @param {boolean} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setIsPublic = function (value) {
+  return jspb.Message.setField(this, 11, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearIsPublic = function () {
+  return jspb.Message.setField(this, 11, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasIsPublic = function () {
+  return jspb.Message.getField(this, 11) != null
+}
+
+/**
+ * optional bool is_explicit = 12;
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.getIsExplicit = function () {
+  return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 12, false))
+}
+
+/**
+ * @param {boolean} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setIsExplicit = function (value) {
+  return jspb.Message.setField(this, 12, value)
+}
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearIsExplicit = function () {
+  return jspb.Message.setField(this, 12, undefined)
+}
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasIsExplicit = function () {
+  return jspb.Message.getField(this, 12) != null
+}
+
+goog.object.extend(exports, proto)

+ 74 - 0
content-metadata/src/KnownLicenses.json

@@ -0,0 +1,74 @@
+[
+  {
+    "code": 1000,
+    "name": "CUSTOM",
+    "longName": "Custom License",
+    "description": "A user defined License",
+    "url": "",
+    "attributionRequired": false
+  },
+  {
+    "code": 1001,
+    "name": "PDM",
+    "longName": "Public Domain",
+    "description": "For items which are not protected by copyright. This is not a license, but rather a copyright status. Some government-produced works, items with expired copyrights, and those which are ineligible for copyright protection may be included in this category.",
+    "url": "https://creativecommons.org/share-your-work/public-domain/pdm",
+    "attributionRequired": false
+  },
+  {
+    "code": 1002,
+    "name": "CC0",
+    "longName": "Public Domain Dedication",
+    "description": "The CC0 (Public Domain Dedication) License allows creators to waive all rights to their creations and release them into the Public Domain.",
+    "url": "https://creativecommons.org/share-your-work/public-domain/cc0",
+    "attributionRequired": true
+  },
+  {
+    "code": 1003,
+    "name": "CC_BY",
+    "longName": "Creative Commons Attribution License",
+    "description": "Sharing and adapting this content is permitted, but attribution must be provided. Read the License Deed for more information.",
+    "url": "https://creativecommons.org/licenses/by/4.0",
+    "attributionRequired": true
+  },
+  {
+    "code": 1004,
+    "name": "CC_BY_SA",
+    "longName": "Creative Commons Attribution-ShareAlike License",
+    "description": "Sharing and adapting this content is permitted, but attribution must be provided. Any derivative works must be distributed under the same license. Read the License Deed for more information.",
+    "url": "https://creativecommons.org/licenses/by-sa/4.0",
+    "attributionRequired": true
+  },
+  {
+    "code": 1005,
+    "name": "CC_BY_ND",
+    "longName": "Creative Commons Attribution-NoDerivs License",
+    "description": "Sharing this content is permitted, but attribution must be provided. You may not remix, transform, or build upon the material. Read the License Deed for more information.",
+    "url": "https://creativecommons.org/licenses/by-nd/4.0",
+    "attributionRequired": true
+  },
+  {
+    "code": 1006,
+    "name": "CC_BY_NC",
+    "longName": "Creative Commons Attribution-NonCommercial License",
+    "description": "Sharing and adapting this content is permitted, but attribution must be provided. Commercial use is not permitted. Read the License Deed for more information.",
+    "url": "https://creativecommons.org/licenses/by-nc/4.0",
+    "attributionRequired": true
+  },
+  {
+    "code": 1007,
+    "name": "CC_BY_NC_SA",
+    "longName": "Creative Commons Attribution-NonCommercial-ShareAlike License",
+    "description": "Sharing and adapting this content is permitted, but attribution must be provided. Any derivative works must be distributed under the same license. Commercial use is not permitted. Read the License Deed for more information.",
+    "url": "https://creativecommons.org/licenses/by-nc-sa/4.0",
+    "attributionRequired": true
+  },
+  {
+    "code": 1008,
+    "name": "CC_BY_NC_ND",
+    "longName": "Creative Commons Attribution-NonCommercial-NoDerivs License",
+    "description": "Sharing this content is permitted, but attribution must be provided. You may not remix, transform, or build upon the material. Commercial use is not permitted. Read the License Deed for more information.",
+    "url": "https://creativecommons.org/licenses/by-nc-nd/4.0",
+    "attributionRequired": true
+  }
+]

+ 7 - 0
content-metadata/src/index.ts

@@ -0,0 +1,7 @@
+// Some helpers for constructing known licences
+import licences from './licenses'
+export { licences }
+
+// protobuf message constructors
+export * from '../proto/Video_pb'
+export * from '../proto/Channel_pb'

+ 68 - 0
content-metadata/src/licenses.ts

@@ -0,0 +1,68 @@
+import LICENSES from './KnownLicenses.json'
+import { License } from '../proto/Video_pb'
+
+export type LicenseCode = number
+
+type KnownLicense = {
+  code: LicenseCode
+  name: string
+  longName: string
+  description: string
+  url: string
+  attributionRequired: boolean
+}
+
+export const KnownLicenses = new Map<LicenseCode, KnownLicense>()
+
+LICENSES.forEach((license: KnownLicense) => {
+  KnownLicenses.set(license.code, license)
+})
+
+export const CUSTOM_LICENSE_CODE: LicenseCode = 1000
+
+export function getLicenseCodeByName(name: string): LicenseCode | undefined {
+  for (const [code, license] of KnownLicenses) {
+    if (license.name === name) return code
+  }
+}
+
+export function createKnownLicenseFromCode(code: LicenseCode, attribution?: string): License {
+  if (code === CUSTOM_LICENSE_CODE) {
+    throw new Error('Use createCustomLicense() instead')
+  }
+
+  const knownLicense = KnownLicenses.get(code)
+
+  if (!knownLicense) {
+    throw new Error('Unknown License Code')
+  }
+
+  const license = new License()
+
+  license.setCode(code)
+
+  if (knownLicense.attributionRequired) {
+    if (attribution === undefined) {
+      throw new Error('Attribution required for selected license')
+    }
+    license.setAttribution(attribution)
+  }
+
+  return license
+}
+
+export function createCustomLicense(customText: string): License {
+  const license = new License()
+
+  license.setCode(CUSTOM_LICENSE_CODE)
+  license.setCustomText(customText)
+  return license
+}
+
+export default {
+  CUSTOM_LICENSE_CODE,
+  KnownLicenses,
+  createCustomLicense,
+  createKnownLicenseFromCode,
+  getLicenseCodeByName,
+}

+ 35 - 0
content-metadata/test/license-codes.ts

@@ -0,0 +1,35 @@
+import { KnownLicenses, CUSTOM_LICENSE_CODE, getLicenseCodeByName, createKnownLicenseFromCode } from '../src/licenses'
+import { VideoMetadata } from '../src/index'
+import { assert } from 'chai'
+
+describe('Known License Codes', () => {
+  it('Should not have license code default value 0', () => {
+    assert(!KnownLicenses.has(0))
+  })
+
+  it('Correct Nunber of Known Licenses', () => {
+    assert.equal(KnownLicenses.size, 9)
+  })
+
+  it('Custom License defined', () => {
+    assert(KnownLicenses.has(CUSTOM_LICENSE_CODE))
+  })
+
+  it('Pre-defined Joystream license codes', () => {
+    assert(KnownLicenses.has(1001))
+    assert(KnownLicenses.has(1002))
+    assert(KnownLicenses.has(1003))
+    assert(KnownLicenses.has(1004))
+    assert(KnownLicenses.has(1005))
+    assert(KnownLicenses.has(1006))
+    assert(KnownLicenses.has(1007))
+    assert(KnownLicenses.has(1008))
+  })
+
+  it('Can create known licence by name', () => {
+    const licenseCode = getLicenseCodeByName('CC_BY') as number
+    const license = createKnownLicenseFromCode(licenseCode as number, 'Attribution: Joystream')
+    const videoMeta = new VideoMetadata()
+    videoMeta.setLicense(license)
+  })
+})

+ 46 - 0
content-metadata/test/video.ts

@@ -0,0 +1,46 @@
+import { VideoMetadata, PublishedBeforeJoystream } from '../proto/Video_pb'
+import { assert, expect } from 'chai'
+
+describe('Video Metadata', () => {
+  it('Create Video Metadata', () => {
+    const meta = new VideoMetadata()
+
+    const title = 'Video Title'
+    const description = 'Video Description'
+    const duration = 100
+
+    meta.setTitle(title)
+    meta.setDescription(description)
+    meta.setDuration(duration)
+
+    // Test optional field
+    expect(meta.hasPublishedBeforeJoystream()).equals(false, 'PublishedBeforeJoystream field should NOT be set')
+
+    // Set published before joystream field
+    const published = new PublishedBeforeJoystream()
+    const isPublished = true
+    const timestamp = 10000
+    published.setIsPublished(isPublished)
+    published.setTimestamp(timestamp)
+    meta.setPublishedBeforeJoystream(published)
+    // Field show now be set
+    expect(meta.hasPublishedBeforeJoystream()).equals(true, 'PublishedBeforeJoystream field should be set')
+
+    assert.deepEqual(VideoMetadata.deserializeBinary(meta.serializeBinary()), meta)
+
+    assert.deepEqual(meta.toObject(), {
+      title,
+      description,
+      duration,
+      mediaPixelHeight: undefined,
+      mediaPixelWidth: undefined,
+      mediaType: undefined,
+      language: undefined,
+      license: undefined,
+      publishedBeforeJoystream: { isPublished, timestamp },
+      hasMarketing: undefined,
+      isPublic: undefined,
+      isExplicit: undefined,
+    })
+  })
+})

+ 15 - 0
content-metadata/tsconfig.json

@@ -0,0 +1,15 @@
+{
+  "compilerOptions": {
+    "target": "esnext",
+    "module": "commonjs",
+    "outDir": "lib",
+    "strict": true,
+    "declaration": true,
+    "esModuleInterop": true,
+    "forceConsistentCasingInFileNames": true,
+    "skipLibCheck": true,
+    "resolveJsonModule": true,
+  },
+  "include": ["src"],
+  "exclude": ["node_modules", "test"]
+}

+ 2 - 1
package.json

@@ -23,7 +23,8 @@
     "utils/api-scripts",
     "content-directory-schemas",
     "query-node",
-    "query-node/generated/*"
+    "query-node/generated/*",
+    "content-metadata"
   ],
   "resolutions": {
     "@polkadot/api": "1.26.1",

+ 374 - 76
yarn.lock

@@ -1556,6 +1556,22 @@
   resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
   integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==
 
+"@eslint/eslintrc@^0.3.0":
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.3.0.tgz#d736d6963d7003b6514e6324bec9c602ac340318"
+  integrity sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==
+  dependencies:
+    ajv "^6.12.4"
+    debug "^4.1.1"
+    espree "^7.3.0"
+    globals "^12.1.0"
+    ignore "^4.0.6"
+    import-fresh "^3.2.1"
+    js-yaml "^3.13.1"
+    lodash "^4.17.20"
+    minimatch "^3.0.4"
+    strip-json-comments "^3.1.1"
+
 "@evocateur/libnpmaccess@^3.1.2":
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/@evocateur/libnpmaccess/-/libnpmaccess-3.1.2.tgz#ecf7f6ce6b004e9f942b098d92200be4a4b1c845"
@@ -5019,6 +5035,11 @@
   resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea"
   integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==
 
+"@types/mocha@^8.2.0":
+  version "8.2.0"
+  resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.0.tgz#3eb56d13a1de1d347ecb1957c6860c911704bc44"
+  integrity sha512-/Sge3BymXo4lKc31C8OINJgXLaw+7vL1/L1pGiBNpGrBiT8FQiaFpSYV0uhTaG4y78vcMBTMFsWaHDvuD+xGzQ==
+
 "@types/mustache@^4.0.1":
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/@types/mustache/-/mustache-4.0.1.tgz#e4d421ed2d06d463b120621774185a5cd1b92d77"
@@ -5607,6 +5628,11 @@
   dependencies:
     eslint-visitor-keys "^1.1.0"
 
+"@ungap/promise-all-settled@1.1.2":
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
+  integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
+
 "@vue/babel-helper-vue-jsx-merge-props@^1.0.0":
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.0.0.tgz#048fe579958da408fb7a8b2a3ec050b50a661040"
@@ -6143,6 +6169,11 @@ acorn-jsx@^5.2.0:
   resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe"
   integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==
 
+acorn-jsx@^5.3.1:
+  version "5.3.1"
+  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
+  integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==
+
 acorn-logical-assignment@^0.1.0:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/acorn-logical-assignment/-/acorn-logical-assignment-0.1.3.tgz#4c81ec136994e4b505e2de42636c923722e88377"
@@ -6222,6 +6253,11 @@ acorn@^7.1.1, acorn@^7.3.1:
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c"
   integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==
 
+acorn@^7.4.0:
+  version "7.4.1"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
+  integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
+
 adal-node@^0.1.28:
   version "0.1.28"
   resolved "https://registry.yarnpkg.com/adal-node/-/adal-node-0.1.28.tgz#468c4bb3ebbd96b1270669f4b9cba4e0065ea485"
@@ -6354,6 +6390,16 @@ ajv@^6.11.0:
     json-schema-traverse "^0.4.1"
     uri-js "^4.2.2"
 
+ajv@^6.12.4:
+  version "6.12.6"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+  integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+  dependencies:
+    fast-deep-equal "^3.1.1"
+    fast-json-stable-stringify "^2.0.0"
+    json-schema-traverse "^0.4.1"
+    uri-js "^4.2.2"
+
 ajv@^6.5.2, ajv@^6.5.4:
   version "6.12.2"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd"
@@ -6364,6 +6410,16 @@ ajv@^6.5.2, ajv@^6.5.4:
     json-schema-traverse "^0.4.1"
     uri-js "^4.2.2"
 
+ajv@^7.0.2:
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.0.3.tgz#13ae747eff125cafb230ac504b2406cf371eece2"
+  integrity sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ==
+  dependencies:
+    fast-deep-equal "^3.1.1"
+    json-schema-traverse "^1.0.0"
+    require-from-string "^2.0.2"
+    uri-js "^4.2.2"
+
 algoliasearch@^3.24.5:
   version "3.35.1"
   resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-3.35.1.tgz#297d15f534a3507cab2f5dfb996019cac7568f0c"
@@ -6409,16 +6465,16 @@ ansi-align@^3.0.0:
   dependencies:
     string-width "^3.0.0"
 
+ansi-colors@4.1.1, ansi-colors@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
+  integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
+
 ansi-colors@^3.0.0, ansi-colors@^3.2.1:
   version "3.2.4"
   resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
   integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==
 
-ansi-colors@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
-  integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
-
 ansi-escapes@^3.0.0, ansi-escapes@^3.1.0, ansi-escapes@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
@@ -7079,6 +7135,11 @@ astral-regex@^1.0.0:
   resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
   integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
 
+astral-regex@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
+  integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
+
 async-each@^1.0.0, async-each@^1.0.1:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
@@ -8799,6 +8860,21 @@ check-error@^1.0.2:
   resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
   integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=
 
+chokidar@3.4.3, chokidar@^3.4.0:
+  version "3.4.3"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b"
+  integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==
+  dependencies:
+    anymatch "~3.1.1"
+    braces "~3.0.2"
+    glob-parent "~5.1.0"
+    is-binary-path "~2.1.0"
+    is-glob "~4.0.1"
+    normalize-path "~3.0.0"
+    readdirp "~3.5.0"
+  optionalDependencies:
+    fsevents "~2.1.2"
+
 chokidar@^1.6.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
@@ -8834,21 +8910,6 @@ chokidar@^2.0.3, chokidar@^2.0.4, chokidar@^2.1.8:
   optionalDependencies:
     fsevents "^1.2.7"
 
-chokidar@^3.4.0:
-  version "3.4.3"
-  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b"
-  integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==
-  dependencies:
-    anymatch "~3.1.1"
-    braces "~3.0.2"
-    glob-parent "~5.1.0"
-    is-binary-path "~2.1.0"
-    is-glob "~4.0.1"
-    normalize-path "~3.0.0"
-    readdirp "~3.5.0"
-  optionalDependencies:
-    fsevents "~2.1.2"
-
 chokidar@^3.4.1:
   version "3.4.1"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.1.tgz#e905bdecf10eaa0a0b1db0c664481cc4cbc22ba1"
@@ -10590,6 +10651,13 @@ debug@3.1.0, debug@=3.1.0, debug@~3.1.0:
   dependencies:
     ms "2.0.0"
 
+debug@4.2.0, debug@^4, debug@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1"
+  integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==
+  dependencies:
+    ms "2.1.2"
+
 debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6:
   version "3.2.6"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
@@ -10597,13 +10665,6 @@ debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6:
   dependencies:
     ms "^2.1.1"
 
-debug@^4, debug@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1"
-  integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==
-  dependencies:
-    ms "2.1.2"
-
 debuglog@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
@@ -10622,6 +10683,11 @@ decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0:
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
 
+decamelize@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
+  integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==
+
 decimal.js@^10.2.0:
   version "10.2.0"
   resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231"
@@ -11071,7 +11137,7 @@ diff@3.5.0, diff@^3.1.0, diff@^3.5.0:
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
   integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
 
-diff@^4.0.1:
+diff@4.0.2, diff@^4.0.1:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
   integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
@@ -12134,6 +12200,13 @@ eslint-plugin-prettier@^3.1.3, eslint-plugin-prettier@^3.1.4:
   dependencies:
     prettier-linter-helpers "^1.0.0"
 
+eslint-plugin-prettier@^3.3.1:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.1.tgz#7079cfa2497078905011e6f82e8dd8453d1371b7"
+  integrity sha512-Rq3jkcFY8RYeQLgk2cCwuc0P7SEFwDravPhsJZOQ5N4YI4DSg50NyqJ/9gdZHzQlHf8MvafSesbNJCcP/FF6pQ==
+  dependencies:
+    prettier-linter-helpers "^1.0.0"
+
 eslint-plugin-promise@^4.2.1:
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
@@ -12216,6 +12289,14 @@ eslint-scope@^5.1.0:
     esrecurse "^4.1.0"
     estraverse "^4.1.1"
 
+eslint-scope@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
+  integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
+  dependencies:
+    esrecurse "^4.3.0"
+    estraverse "^4.1.1"
+
 eslint-utils@^1.3.1, eslint-utils@^1.4.2, eslint-utils@^1.4.3:
   version "1.4.3"
   resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f"
@@ -12247,6 +12328,11 @@ eslint-visitor-keys@^1.3.0:
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
   integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
 
+eslint-visitor-keys@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
+  integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
+
 eslint@^6.8.0:
   version "6.8.0"
   resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb"
@@ -12290,6 +12376,49 @@ eslint@^6.8.0:
     text-table "^0.2.0"
     v8-compile-cache "^2.0.3"
 
+eslint@^7.18.0:
+  version "7.18.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.18.0.tgz#7fdcd2f3715a41fe6295a16234bd69aed2c75e67"
+  integrity sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==
+  dependencies:
+    "@babel/code-frame" "^7.0.0"
+    "@eslint/eslintrc" "^0.3.0"
+    ajv "^6.10.0"
+    chalk "^4.0.0"
+    cross-spawn "^7.0.2"
+    debug "^4.0.1"
+    doctrine "^3.0.0"
+    enquirer "^2.3.5"
+    eslint-scope "^5.1.1"
+    eslint-utils "^2.1.0"
+    eslint-visitor-keys "^2.0.0"
+    espree "^7.3.1"
+    esquery "^1.2.0"
+    esutils "^2.0.2"
+    file-entry-cache "^6.0.0"
+    functional-red-black-tree "^1.0.1"
+    glob-parent "^5.0.0"
+    globals "^12.1.0"
+    ignore "^4.0.6"
+    import-fresh "^3.0.0"
+    imurmurhash "^0.1.4"
+    is-glob "^4.0.0"
+    js-yaml "^3.13.1"
+    json-stable-stringify-without-jsonify "^1.0.1"
+    levn "^0.4.1"
+    lodash "^4.17.20"
+    minimatch "^3.0.4"
+    natural-compare "^1.4.0"
+    optionator "^0.9.1"
+    progress "^2.0.0"
+    regexpp "^3.1.0"
+    semver "^7.2.1"
+    strip-ansi "^6.0.0"
+    strip-json-comments "^3.1.0"
+    table "^6.0.4"
+    text-table "^0.2.0"
+    v8-compile-cache "^2.0.3"
+
 eslint@^7.6.0:
   version "7.6.0"
   resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.6.0.tgz#522d67cfaea09724d96949c70e7a0550614d64d6"
@@ -12350,6 +12479,15 @@ espree@^7.2.0:
     acorn-jsx "^5.2.0"
     eslint-visitor-keys "^1.3.0"
 
+espree@^7.3.0, espree@^7.3.1:
+  version "7.3.1"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6"
+  integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==
+  dependencies:
+    acorn "^7.4.0"
+    acorn-jsx "^5.3.1"
+    eslint-visitor-keys "^1.3.0"
+
 esprima-fb@^15001.1.0-dev-harmony-fb:
   version "15001.1.0-dev-harmony-fb"
   resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz#30a947303c6b8d5e955bee2b99b1d233206a6901"
@@ -12386,12 +12524,19 @@ esrecurse@^4.1.0:
   dependencies:
     estraverse "^4.1.0"
 
+esrecurse@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
+  integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+  dependencies:
+    estraverse "^5.2.0"
+
 estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
   integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
 
-estraverse@^5.1.0:
+estraverse@^5.1.0, estraverse@^5.2.0:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880"
   integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==
@@ -12995,6 +13140,13 @@ file-entry-cache@^5.0.1:
   dependencies:
     flat-cache "^2.0.1"
 
+file-entry-cache@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.0.tgz#7921a89c391c6d93efec2169ac6bf300c527ea0a"
+  integrity sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA==
+  dependencies:
+    flat-cache "^3.0.4"
+
 file-exists-dazinatorfork@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/file-exists-dazinatorfork/-/file-exists-dazinatorfork-1.0.2.tgz#cd8d0d85f63e39dc81eceb0b687c44a2cca95c47"
@@ -13206,6 +13358,14 @@ find-up@3.0.0, find-up@^3.0.0:
   dependencies:
     locate-path "^3.0.0"
 
+find-up@5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
+  integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+  dependencies:
+    locate-path "^6.0.0"
+    path-exists "^4.0.0"
+
 find-up@^1.0.0:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
@@ -13262,6 +13422,19 @@ flat-cache@^2.0.1:
     rimraf "2.6.3"
     write "1.0.3"
 
+flat-cache@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
+  integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==
+  dependencies:
+    flatted "^3.1.0"
+    rimraf "^3.0.2"
+
+flat@^5.0.2:
+  version "5.0.2"
+  resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
+  integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
+
 flatmap@0.0.3:
   version "0.0.3"
   resolved "https://registry.yarnpkg.com/flatmap/-/flatmap-0.0.3.tgz#1f18a4d938152d495965f9c958d923ab2dd669b4"
@@ -13277,6 +13450,11 @@ flatted@^2.0.1:
   resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
   integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==
 
+flatted@^3.1.0:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469"
+  integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==
+
 flatten@^1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
@@ -13942,7 +14120,7 @@ glob2base@^0.0.12:
   dependencies:
     find-index "^0.1.1"
 
-glob@*, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1:
+glob@*, glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1:
   version "7.1.6"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
   integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
@@ -14253,6 +14431,11 @@ google-libphonenumber@^3.1.6, google-libphonenumber@^3.2.8:
   resolved "https://registry.yarnpkg.com/google-libphonenumber/-/google-libphonenumber-3.2.14.tgz#f206fa466511427c83aa6b893dd8d949eb8e30ae"
   integrity sha512-4r7mQRbk7EUYV1gyfP1SInYuQsjuDtRXCGLSotxeYDJaj/aF1xFO5PV/GSQeIxXWhIw050DujROICvWpZ1XYRw==
 
+google-protobuf@^3.14.0, google-protobuf@^3.6.1:
+  version "3.14.0"
+  resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.14.0.tgz#20373d22046e63831a5110e11a84f713cc43651e"
+  integrity sha512-bwa8dBuMpOxg7COyqkW6muQuvNnWgVN8TX/epDRGW5m0jcrmq2QJyCyiV8ZE2/6LaIIqJtiv9bYokFhfpy/o6w==
+
 got@^6.3.0, got@^6.7.1:
   version "6.7.1"
   resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0"
@@ -14656,7 +14839,7 @@ he@1.1.1:
   resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
   integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
 
-he@1.2.x, he@^1.1.0, he@^1.2.0:
+he@1.2.0, he@1.2.x, he@^1.1.0, he@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
   integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
@@ -16500,6 +16683,11 @@ is-plain-obj@^2.0.0:
   resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.0.0.tgz#7fd1a7f1b69e160cde9181d2313f445c68aa2679"
   integrity sha512-EYisGhpgSCwspmIuRHGjROWTon2Xp8Z7U03Wubk/bTL5TTRC5R1rGVgyjzBrk9+ULdH6cRD06KRcw/xfqhVYKQ==
 
+is-plain-obj@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
+  integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
+
 is-plain-object@3.0.0, is-plain-object@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.0.tgz#47bfc5da1b5d50d64110806c199359482e75a928"
@@ -18171,7 +18359,7 @@ js-tokens@^3.0.2:
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
   integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
 
-js-yaml@^3.10.0, js-yaml@^3.14.0:
+js-yaml@3.14.0, js-yaml@^3.10.0, js-yaml@^3.14.0:
   version "3.14.0"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
   integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
@@ -18311,6 +18499,11 @@ json-schema-traverse@^0.4.1:
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
   integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
 
+json-schema-traverse@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
+  integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
+
 json-schema@0.2.3:
   version "0.2.3"
   resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
@@ -19075,6 +19268,13 @@ locate-path@^5.0.0:
   dependencies:
     p-locate "^4.1.0"
 
+locate-path@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
+  integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+  dependencies:
+    p-locate "^5.0.0"
+
 lodash-es@^4.17.14:
   version "4.17.15"
   resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
@@ -19280,6 +19480,13 @@ log-driver@^1.2.7:
   resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8"
   integrity sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==
 
+log-symbols@4.0.0, log-symbols@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
+  integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
+  dependencies:
+    chalk "^4.0.0"
+
 log-symbols@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18"
@@ -19301,13 +19508,6 @@ log-symbols@^3.0.0:
   dependencies:
     chalk "^2.4.2"
 
-log-symbols@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
-  integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
-  dependencies:
-    chalk "^4.0.0"
-
 log-update@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/log-update/-/log-update-2.3.0.tgz#88328fd7d1ce7938b29283746f0b1bc126b24708"
@@ -20278,6 +20478,37 @@ mocha@^5.2.0:
     mkdirp "0.5.1"
     supports-color "5.4.0"
 
+mocha@^8.2.1:
+  version "8.2.1"
+  resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.2.1.tgz#f2fa68817ed0e53343d989df65ccd358bc3a4b39"
+  integrity sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==
+  dependencies:
+    "@ungap/promise-all-settled" "1.1.2"
+    ansi-colors "4.1.1"
+    browser-stdout "1.3.1"
+    chokidar "3.4.3"
+    debug "4.2.0"
+    diff "4.0.2"
+    escape-string-regexp "4.0.0"
+    find-up "5.0.0"
+    glob "7.1.6"
+    growl "1.10.5"
+    he "1.2.0"
+    js-yaml "3.14.0"
+    log-symbols "4.0.0"
+    minimatch "3.0.4"
+    ms "2.1.2"
+    nanoid "3.1.12"
+    serialize-javascript "5.0.1"
+    strip-json-comments "3.1.1"
+    supports-color "7.2.0"
+    which "2.0.2"
+    wide-align "1.1.3"
+    workerpool "6.0.2"
+    yargs "13.3.2"
+    yargs-parser "13.1.2"
+    yargs-unparser "2.0.0"
+
 mock-stdin@^0.3.1:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/mock-stdin/-/mock-stdin-0.3.1.tgz#c657d9642d90786435c64ca5e99bbd4d09bd7dd3"
@@ -20631,16 +20862,16 @@ nan@^2.13.2:
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
   integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
 
+nanoid@3.1.12, nanoid@^3.0.2, nanoid@^3.1.3:
+  version "3.1.12"
+  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.12.tgz#6f7736c62e8d39421601e4a0c77623a97ea69654"
+  integrity sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==
+
 nanoid@^2.1.0:
   version "2.1.11"
   resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280"
   integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==
 
-nanoid@^3.0.2, nanoid@^3.1.3:
-  version "3.1.12"
-  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.12.tgz#6f7736c62e8d39421601e4a0c77623a97ea69654"
-  integrity sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==
-
 nanomatch@^1.2.9:
   version "1.2.13"
   resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@@ -21888,6 +22119,13 @@ p-locate@^4.1.0:
   dependencies:
     p-limit "^2.2.0"
 
+p-locate@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
+  integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+  dependencies:
+    p-limit "^3.0.2"
+
 p-map-series@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-1.0.0.tgz#bf98fe575705658a9e1351befb85ae4c1f07bdca"
@@ -25009,6 +25247,11 @@ require-directory@^2.1.1:
   resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
   integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
 
+require-from-string@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
+  integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+
 require-main-filename@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
@@ -25650,6 +25893,13 @@ serialize-error@^7.0.1:
   dependencies:
     type-fest "^0.13.1"
 
+serialize-javascript@5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4"
+  integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==
+  dependencies:
+    randombytes "^2.1.0"
+
 serialize-javascript@^2.1.0, serialize-javascript@^2.1.2:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
@@ -25965,6 +26215,15 @@ slice-ansi@^2.1.0:
     astral-regex "^1.0.0"
     is-fullwidth-code-point "^2.0.0"
 
+slice-ansi@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
+  integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==
+  dependencies:
+    ansi-styles "^4.0.0"
+    astral-regex "^2.0.0"
+    is-fullwidth-code-point "^3.0.0"
+
 slide@^1.1.6:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
@@ -26785,6 +27044,11 @@ strip-indent@^3.0.0:
   dependencies:
     min-indent "^1.0.0"
 
+strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
+  integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+
 strip-json-comments@^2.0.0, strip-json-comments@~2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
@@ -26795,11 +27059,6 @@ strip-json-comments@^3.0.1:
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
   integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==
 
-strip-json-comments@^3.1.0:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
-  integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
-
 strip-markdown@^3.0.3:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/strip-markdown/-/strip-markdown-3.1.2.tgz#172f6f89f9a98896e65a65422e0507f2bbac1667"
@@ -27027,6 +27286,13 @@ supports-color@5.4.0:
   dependencies:
     has-flag "^3.0.0"
 
+supports-color@7.2.0:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+  integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+  dependencies:
+    has-flag "^4.0.0"
+
 supports-color@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
@@ -27141,6 +27407,16 @@ table@^5.2.3, table@^5.4.6:
     slice-ansi "^2.1.0"
     string-width "^3.0.0"
 
+table@^6.0.4:
+  version "6.0.7"
+  resolved "https://registry.yarnpkg.com/table/-/table-6.0.7.tgz#e45897ffbcc1bcf9e8a87bf420f2c9e5a7a52a34"
+  integrity sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==
+  dependencies:
+    ajv "^7.0.2"
+    lodash "^4.17.20"
+    slice-ansi "^4.0.0"
+    string-width "^4.2.0"
+
 tapable@^1.0.0, tapable@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
@@ -27906,6 +28182,13 @@ ts-pnp@^1.1.2:
   resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
   integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==
 
+ts-protoc-gen@^0.14.0:
+  version "0.14.0"
+  resolved "https://registry.yarnpkg.com/ts-protoc-gen/-/ts-protoc-gen-0.14.0.tgz#a6f4c3fc37d1d449915551c18404fb7e9aa8fef6"
+  integrity sha512-2z6w2HioMCMVNcgNHBcEvudmQfzrn+3BjAlz+xgYZ9L0o8n8UG8WUiTJcbXHFiEg2SU8IltwH2pm1otLoMSKwg==
+  dependencies:
+    google-protobuf "^3.6.1"
+
 tsconfig-paths-webpack-plugin@^3.2.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.3.0.tgz#a7461723c20623ca9148621a5ce36532682ad2ff"
@@ -28188,7 +28471,7 @@ typescript-formatter@^7.2.2:
     commandpost "^1.0.0"
     editorconfig "^0.15.0"
 
-typescript@3.5.2, typescript@^3.0.3, typescript@^3.7.2, typescript@^3.7.5, typescript@^3.8.3, typescript@^3.9.5, typescript@^3.9.6, typescript@^3.9.7:
+typescript@3.5.2, typescript@^3.0.3, typescript@^3.7.2, typescript@^3.7.5, typescript@^3.8.3, typescript@^3.9.5, typescript@^3.9.6, typescript@^3.9.7, typescript@^4.1.3:
   version "3.9.7"
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
   integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
@@ -29586,14 +29869,14 @@ which@1, which@^1.1.1, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1:
   dependencies:
     isexe "^2.0.0"
 
-which@^2.0.0, which@^2.0.1, which@^2.0.2:
+which@2.0.2, which@^2.0.0, which@^2.0.1, which@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
   integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
   dependencies:
     isexe "^2.0.0"
 
-wide-align@^1.1.0:
+wide-align@1.1.3, wide-align@^1.1.0:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
   integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
@@ -29660,6 +29943,11 @@ worker-rpc@^0.1.0:
   dependencies:
     microevent.ts "~0.1.1"
 
+workerpool@6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.0.2.tgz#e241b43d8d033f1beb52c7851069456039d1d438"
+  integrity sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==
+
 wrap-ansi@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
@@ -29973,6 +30261,14 @@ yargs-parser@10.x, yargs-parser@^10.0.0:
   dependencies:
     camelcase "^4.1.0"
 
+yargs-parser@13.1.2, yargs-parser@^13.0.0, yargs-parser@^13.1.2:
+  version "13.1.2"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
+  integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==
+  dependencies:
+    camelcase "^5.0.0"
+    decamelize "^1.2.0"
+
 yargs-parser@18.x, yargs-parser@^18.1.2, yargs-parser@^18.1.3:
   version "18.1.3"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
@@ -29989,14 +30285,6 @@ yargs-parser@^11.1.1:
     camelcase "^5.0.0"
     decamelize "^1.2.0"
 
-yargs-parser@^13.0.0, yargs-parser@^13.1.2:
-  version "13.1.2"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
-  integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==
-  dependencies:
-    camelcase "^5.0.0"
-    decamelize "^1.2.0"
-
 yargs-parser@^13.1.1:
   version "13.1.1"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0"
@@ -30041,6 +30329,32 @@ yargs-parser@^8.1.0:
   dependencies:
     camelcase "^4.1.0"
 
+yargs-unparser@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb"
+  integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==
+  dependencies:
+    camelcase "^6.0.0"
+    decamelize "^4.0.0"
+    flat "^5.0.2"
+    is-plain-obj "^2.1.0"
+
+yargs@13.3.2, yargs@^13.2.2, yargs@^13.3.2:
+  version "13.3.2"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
+  integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==
+  dependencies:
+    cliui "^5.0.0"
+    find-up "^3.0.0"
+    get-caller-file "^2.0.1"
+    require-directory "^2.1.1"
+    require-main-filename "^2.0.0"
+    set-blocking "^2.0.0"
+    string-width "^3.0.0"
+    which-module "^2.0.0"
+    y18n "^4.0.0"
+    yargs-parser "^13.1.2"
+
 yargs@^10.0.3:
   version "10.1.2"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5"
@@ -30077,22 +30391,6 @@ yargs@^12.0.2:
     y18n "^3.2.1 || ^4.0.0"
     yargs-parser "^11.1.1"
 
-yargs@^13.2.2, yargs@^13.3.2:
-  version "13.3.2"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
-  integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==
-  dependencies:
-    cliui "^5.0.0"
-    find-up "^3.0.0"
-    get-caller-file "^2.0.1"
-    require-directory "^2.1.1"
-    require-main-filename "^2.0.0"
-    set-blocking "^2.0.0"
-    string-width "^3.0.0"
-    which-module "^2.0.0"
-    y18n "^4.0.0"
-    yargs-parser "^13.1.2"
-
 yargs@^13.3.0:
   version "13.3.0"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83"