Browse Source

query node - video metadata improvements II

ondratra 3 years ago
parent
commit
8ae1190fee
2 changed files with 119 additions and 38 deletions
  1. 53 35
      query-node/mappings/src/content/utils.ts
  2. 66 3
      query-node/mappings/src/content/video.ts

+ 53 - 35
query-node/mappings/src/content/utils.ts

@@ -108,18 +108,37 @@ export interface IReadProtobufArgumentsWithAssets extends IReadProtobufArguments
 */
 export class PropertyChange<T> {
 
-  static newUnset<T>() {
+  static newUnset<T>(): PropertyChange<T> {
     return new PropertyChange<T>('unset')
   }
 
-  static newNoChange<T>() {
+  static newNoChange<T>(): PropertyChange<T> {
     return new PropertyChange<T>('nochange')
   }
 
-  static newChange<T>(value: T) {
+  static newChange<T>(value: T): PropertyChange<T> {
     return new PropertyChange<T>('change', value)
   }
 
+  /*
+    Determines property change from the given object property.
+  */
+  static fromObjectProperty<
+    T,
+    Key extends string,
+    ChangedObject extends {[key in Key]?: T}
+  >(object: ChangedObject, key: Key): PropertyChange<T> {
+    if (!(key in object)) {
+      return PropertyChange.newNoChange<T>()
+    }
+
+    if (object[key] === undefined) {
+      return PropertyChange.newUnset<T>()
+    }
+
+    return PropertyChange.newChange<T>(object[key] as T)
+  }
+
   private type: string
   private value?: T
 
@@ -163,6 +182,17 @@ export class PropertyChange<T> {
   }
 }
 
+export interface RawVideoMetadata {
+  encoding: {
+    codecName: PropertyChange<string>
+    container: PropertyChange<string>
+    mimeMediaType: PropertyChange<string>
+  }
+  pixelWidth: PropertyChange<number>
+  pixelHeight: PropertyChange<number>
+  size: PropertyChange<number>
+}
+
 /*
   Reads information from the event and protobuf metadata and constructs changeset that's fit to be used when saving to db.
 */
@@ -265,9 +295,11 @@ export async function readProtobufWithAssets<T extends Channel | Video>(
     // prepare media meta information if needed
     if ('mediaType' in metaAsObject || 'mediaPixelWidth' in metaAsObject || 'mediaPixelHeight' in metaAsObject) {
       // prepare video file size if poosible
-      const videoSize = await extractVideoSize(parameters.assets, metaAsObject.video)
+      const videoSize = extractVideoSize(parameters.assets, metaAsObject.video)
 
-      result.mediaMetadata = await prepareVideoMetadata(metaAsObject, videoSize, parameters.blockNumber)
+      // NOTE: type hack - `RawVideoMetadata` is inserted instead of VideoMediaMetadata - it should be edited in `video.ts`
+      //       see `integrateVideoMetadata()` in `video.ts` for more info
+      result.mediaMetadata = prepareVideoMetadata(metaAsObject, videoSize, parameters.blockNumber) as unknown as VideoMediaMetadata
 
       // remove extra values
       delete metaAsObject.mediaType
@@ -523,7 +555,7 @@ function integrateAsset<T>(propertyName: string, result: Object, asset: Property
   result[nameDataObject] = newValue
 }
 
-async function extractVideoSize(assets: NewAsset[], assetIndex: number | undefined): Promise<number | undefined> {
+function extractVideoSize(assets: NewAsset[], assetIndex: number | undefined): number | undefined {
   // escape if no asset is required
   if (assetIndex === undefined) {
     return undefined
@@ -611,35 +643,21 @@ async function prepareLicense(licenseProtobuf: LicenseMetadata.AsObject | undefi
   return license
 }
 
-async function prepareVideoMetadata(videoProtobuf: VideoMetadata.AsObject, videoSize: number | undefined, blockNumber: number): Promise<VideoMediaMetadata> {
-  // TODO: handle situations when only some metadata are set (e.g. pixelWidth and mediaType is defined, but pixelHeight is missing)
-  // TODO: handle update of VideoMediaEncoding and VideoMediaMetadata
-  //       right now when only some of mediaType(mb partial), mediaPixelWidth, or mediaPixelHeight is set, the update discards previous values
-  // create new encoding info
-  const encoding = new VideoMediaEncoding({
-    ...videoProtobuf.mediaType,
-
-    createdById: '1',
-    updatedById: '1',
-  })
-
-  // create new video metadata
-  const videoMeta = new VideoMediaMetadata({
-    encoding,
-    pixelWidth: videoProtobuf.mediaPixelWidth,
-    pixelHeight: videoProtobuf.mediaPixelHeight,
-    createdInBlock: blockNumber,
-
-    createdById: '1',
-    updatedById: '1',
-  })
-
-  // fill in video size if provided
-  if (videoSize !== undefined) {
-    videoMeta.size = videoSize
-  }
-
-  return videoMeta
+function prepareVideoMetadata(videoProtobuf: VideoMetadata.AsObject, videoSize: number | undefined, blockNumber: number): RawVideoMetadata {
+  const rawMeta = {
+    encoding: {
+      codecName: PropertyChange.fromObjectProperty<string, 'codecName', MediaTypeMetadata.AsObject>(videoProtobuf.mediaType || {}, 'codecName'),
+      container: PropertyChange.fromObjectProperty<string, 'container', MediaTypeMetadata.AsObject>(videoProtobuf.mediaType || {}, 'container'),
+      mimeMediaType: PropertyChange.fromObjectProperty<string, 'mimeMediaType', MediaTypeMetadata.AsObject>(videoProtobuf.mediaType || {}, 'mimeMediaType'),
+    },
+    pixelWidth: PropertyChange.fromObjectProperty<number, 'mediaPixelWidth', VideoMetadata.AsObject>(videoProtobuf, 'mediaPixelWidth'),
+    pixelHeight: PropertyChange.fromObjectProperty<number, 'mediaPixelHeight', VideoMetadata.AsObject>(videoProtobuf, 'mediaPixelHeight'),
+    size: videoSize === undefined
+      ? PropertyChange.newNoChange()
+      : PropertyChange.newChange(videoSize)
+  } as RawVideoMetadata
+
+  return rawMeta
 }
 
 async function prepareVideoCategory(categoryId: number | undefined, db: DatabaseManager): Promise<PropertyChange<VideoCategory>> {

+ 66 - 3
query-node/mappings/src/content/video.ts

@@ -16,7 +16,8 @@ import {
 import {
   convertContentActorToDataObjectOwner,
   readProtobuf,
-  readProtobufWithAssets
+  readProtobufWithAssets,
+  RawVideoMetadata,
 } from './utils'
 
 // primary entities
@@ -25,6 +26,8 @@ import {
   Channel,
   Video,
   VideoCategory,
+  VideoMediaEncoding,
+  VideoMediaMetadata,
 } from 'query-node'
 
 // secondary entities
@@ -182,6 +185,9 @@ export async function content_VideoCreated(
     inconsistentState('Trying to add video to non-existing channel', channelId)
   }
 
+  // prepare video media metadata (if any)
+  const fixedProtobuf = integrateVideoMediaMetadata(null, protobufContent, event.blockNumber)
+
   // create new video
   const video = new Video({
     // main data
@@ -203,7 +209,7 @@ export async function content_VideoCreated(
     updatedAt: new Date(fixBlockTimestamp(event.blockTimestamp).toNumber()),
 
     // integrate metadata
-    ...protobufContent
+    ...fixedProtobuf
   })
 
   // save video
@@ -249,11 +255,14 @@ export async function content_VideoUpdated(
       }
     )
 
+    // prepare video media metadata (if any)
+    const fixedProtobuf = integrateVideoMediaMetadata(video, protobufContent, event.blockNumber)
+
     // remember original license
     const originalLicense = video.license
 
     // update all fields read from protobuf
-    for (let [key, value] of Object.entries(protobufContent)) {
+    for (let [key, value] of Object.entries(fixedProtobuf)) {
       video[key] = value
     }
 
@@ -392,3 +401,57 @@ export async function content_FeaturedVideosSet(
   // emit log event
   logger.info('New featured videos have been set', {videoIds})
 }
+
+/////////////////// Helpers ////////////////////////////////////////////////////
+
+/*
+  Integrates video metadata-related data into existing data (if any) or creates a new record.
+
+  NOTE: type hack - `RawVideoMetadata` is accepted for `metadata` instead of `Partial<Video>`
+        see `prepareVideoMetadata()` in `utils.ts` for more info
+*/
+function integrateVideoMediaMetadata(
+  existingRecord: Video | null,
+  metadata: Partial<Video>,
+  blockNumber: number,
+): Partial<Video> {
+  if (!metadata.mediaMetadata) {
+    return metadata
+  }
+
+  // fix TS type
+  const rawMediaMetadata = metadata.mediaMetadata as unknown as RawVideoMetadata
+
+  // ensure encoding object
+  const encoding = (existingRecord && existingRecord.mediaMetadata && existingRecord.mediaMetadata.encoding)
+    || new VideoMediaEncoding({
+        createdById: '1',
+        updatedById: '1',
+      })
+
+  // integrate media encoding-related data
+  rawMediaMetadata.encoding.codecName.integrateInto(encoding, 'codecName')
+  rawMediaMetadata.encoding.container.integrateInto(encoding, 'container')
+  rawMediaMetadata.encoding.mimeMediaType.integrateInto(encoding, 'mimeMediaType')
+
+  // ensure media metadata object
+  const mediaMetadata = (existingRecord && existingRecord.mediaMetadata) || new VideoMediaMetadata({
+    createdInBlock: blockNumber,
+
+    createdById: '1',
+    updatedById: '1',
+  })
+
+  // integrate media-related data
+  rawMediaMetadata.pixelWidth.integrateInto(encoding, 'pixelWidth')
+  rawMediaMetadata.pixelHeight.integrateInto(encoding, 'pixelHeight')
+  rawMediaMetadata.size.integrateInto(encoding, 'size')
+
+  // connect encoding to media metadata object
+  mediaMetadata.encoding = encoding
+
+  return {
+    ...metadata,
+    mediaMetadata
+  }
+}