Sfoglia il codice sorgente

Merge pull request #3 from Lezek123/cli-upload-video-input

File input to uploadVideo - suggested approach
Metin Demir 4 anni fa
parent
commit
bb301a9a10
2 ha cambiato i file con 127 aggiunte e 78 eliminazioni
  1. 117 72
      cli/src/commands/media/uploadVideo.ts
  2. 10 6
      cli/src/helpers/InputOutput.ts

+ 117 - 72
cli/src/commands/media/uploadVideo.ts

@@ -20,7 +20,7 @@ import toBuffer from 'it-to-buffer'
 import ffmpegInstaller from '@ffmpeg-installer/ffmpeg'
 import ffmpeg from 'fluent-ffmpeg'
 import MediaCommandBase from '../../base/MediaCommandBase'
-import { getInputJson, IOFlags } from '../../helpers/InputOutput'
+import { getInputJson, validateInput, IOFlags } from '../../helpers/InputOutput'
 
 ffmpeg.setFfmpegPath(ffmpegInstaller.path)
 
@@ -38,8 +38,7 @@ type VideoMetadata = {
 export default class UploadVideoCommand extends MediaCommandBase {
   static description = 'Upload a new Video to a channel (requires a membership).'
   static flags = {
-    // TODO: ...IOFlags, - providing input as json
-    ...IOFlags,
+    input: IOFlags.input,
     channel: flags.integer({
       char: 'c',
       required: false,
@@ -220,6 +219,117 @@ export default class UploadVideoCommand extends MediaCommandBase {
     }
   }
 
+  private async promptForVideoInput(
+    channelId: number,
+    fileSize: number,
+    contentId: ContentId,
+    videoMetadata: VideoMetadata | null
+  ) {
+    // Set the defaults
+    const videoMediaDefaults: Partial<VideoMediaEntity> = {
+      pixelWidth: videoMetadata?.width,
+      pixelHeight: videoMetadata?.height,
+    }
+    const videoDefaults: Partial<VideoEntity> = {
+      duration: videoMetadata?.duration,
+      skippableIntroDuration: 0,
+    }
+
+    // Prompt for data
+    const videoJsonSchema = (VideoEntitySchema as unknown) as JSONSchema
+    const videoMediaJsonSchema = (VideoMediaEntitySchema as unknown) as JSONSchema
+
+    const videoMediaPrompter = new JsonSchemaPrompter<VideoMediaEntity>(videoMediaJsonSchema, videoMediaDefaults)
+    const videoPrompter = new JsonSchemaPrompter<VideoEntity>(videoJsonSchema, videoDefaults)
+
+    // Prompt for the data
+    const encodingSuggestion =
+      videoMetadata && videoMetadata.codecFullName ? ` (suggested: ${videoMetadata.codecFullName})` : ''
+    const encoding = await this.promptForEntityId(
+      `Choose Video encoding${encodingSuggestion}`,
+      'VideoMediaEncoding',
+      'name'
+    )
+    const { pixelWidth, pixelHeight } = await videoMediaPrompter.promptMultipleProps(['pixelWidth', 'pixelHeight'])
+    const language = await this.promptForEntityId('Choose Video language', 'Language', 'name')
+    const category = await this.promptForEntityId('Choose Video category', 'ContentCategory', 'name')
+    const videoProps = await videoPrompter.promptMultipleProps([
+      'title',
+      'description',
+      'thumbnailURL',
+      'duration',
+      'isPublic',
+      'isExplicit',
+      'hasMarketing',
+      'skippableIntroDuration',
+    ])
+
+    const license = await videoPrompter.promptSingleProp('license', () => this.promptForNewLicense())
+    const publishedBeforeJoystream = await videoPrompter.promptSingleProp('publishedBeforeJoystream', () =>
+      this.promptForPublishedBeforeJoystream()
+    )
+
+    // Create final inputs
+    const videoMediaInput: VideoMediaEntity = {
+      encoding,
+      pixelWidth,
+      pixelHeight,
+      size: fileSize,
+      location: { new: { joystreamMediaLocation: { new: { dataObjectId: contentId.encode() } } } },
+    }
+    return {
+      ...videoProps,
+      channel: channelId,
+      language,
+      category,
+      license,
+      media: { new: videoMediaInput },
+      publishedBeforeJoystream,
+    }
+  }
+
+  private async getVideoInputFromFile(
+    filePath: string,
+    channelId: number,
+    fileSize: number,
+    contentId: ContentId,
+    videoMetadata: VideoMetadata | null
+  ) {
+    let videoInput = await getInputJson<any>(filePath)
+    if (typeof videoInput !== 'object' || videoInput === null) {
+      this.error('Invalid input json - expected an object', { exit: ExitCodes.InvalidInput })
+    }
+    const videoMediaDefaults: Partial<VideoMediaEntity> = {
+      pixelWidth: videoMetadata?.width,
+      pixelHeight: videoMetadata?.height,
+      size: fileSize,
+    }
+    const videoDefaults: Partial<VideoEntity> = {
+      channel: channelId,
+      duration: videoMetadata?.duration,
+    }
+    const inputVideoMedia =
+      videoInput.media && typeof videoInput.media === 'object' && (videoInput.media as any).new
+        ? (videoInput.media as any).new
+        : {}
+    videoInput = {
+      ...videoDefaults,
+      ...videoInput,
+      media: {
+        new: {
+          ...videoMediaDefaults,
+          ...inputVideoMedia,
+          location: { new: { joystreamMediaLocation: { new: { dataObjectId: contentId.encode() } } } },
+        },
+      },
+    }
+
+    const videoJsonSchema = (VideoEntitySchema as unknown) as JSONSchema
+    await validateInput(videoInput, videoJsonSchema)
+
+    return videoInput as VideoEntity
+  }
+
   async run() {
     const account = await this.getRequiredSelectedAccount()
     const memberId = await this.getRequiredMemberId()
@@ -232,11 +342,6 @@ export default class UploadVideoCommand extends MediaCommandBase {
       flags: { channel: inputChannelId, input, confirm },
     } = this.parse(UploadVideoCommand)
 
-    // Keep this here in case if provided input is not correct it will prevent uploading video
-    const videoJsonSchema = (VideoEntitySchema as unknown) as JSONSchema
-    let videoInput: VideoEntity | null = await getInputJson<VideoEntity>(input, videoJsonSchema)
-    this.jsonPrettyPrint(JSON.stringify(videoInput))
-
     // Basic file validation
     if (!fs.existsSync(filePath)) {
       this.error('File does not exist under provided path!', { exit: ExitCodes.FileNotFound })
@@ -311,72 +416,12 @@ export default class UploadVideoCommand extends MediaCommandBase {
 
     await this.uploadVideo(filePath, fileSize, uploadUrl)
 
-    // Prompting for the data:
-
-    // Set the defaults
-    const videoMediaDefaults: Partial<VideoMediaEntity> = {
-      pixelWidth: videoMetadata?.width,
-      pixelHeight: videoMetadata?.height,
-    }
-    const videoDefaults: Partial<VideoEntity> = {
-      duration: videoMetadata?.duration,
-      skippableIntroDuration: 0,
-    }
-
     // No input, create prompting helpers
-    if (!videoInput) {
-      const videoMediaJsonSchema = (VideoMediaEntitySchema as unknown) as JSONSchema
-
-      const videoMediaPrompter = new JsonSchemaPrompter<VideoMediaEntity>(videoMediaJsonSchema, videoMediaDefaults)
-      const videoPrompter = new JsonSchemaPrompter<VideoEntity>(videoJsonSchema, videoDefaults)
-
-      // Prompt for the data
-      const encodingSuggestion =
-        videoMetadata && videoMetadata.codecFullName ? ` (suggested: ${videoMetadata.codecFullName})` : ''
-      const encoding = await this.promptForEntityId(
-        `Choose Video encoding${encodingSuggestion}`,
-        'VideoMediaEncoding',
-        'name'
-      )
-      const { pixelWidth, pixelHeight } = await videoMediaPrompter.promptMultipleProps(['pixelWidth', 'pixelHeight'])
-      const language = await this.promptForEntityId('Choose Video language', 'Language', 'name')
-      const category = await this.promptForEntityId('Choose Video category', 'ContentCategory', 'name')
-      const videoProps = await videoPrompter.promptMultipleProps([
-        'title',
-        'description',
-        'thumbnailURL',
-        'duration',
-        'isPublic',
-        'isExplicit',
-        'hasMarketing',
-        'skippableIntroDuration',
-      ])
-
-      const license = await videoPrompter.promptSingleProp('license', () => this.promptForNewLicense())
-      const publishedBeforeJoystream = await videoPrompter.promptSingleProp('publishedBeforeJoystream', () =>
-        this.promptForPublishedBeforeJoystream()
-      )
+    const videoInput = input
+      ? await this.getVideoInputFromFile(input, channelId, fileSize, contentId, videoMetadata)
+      : await this.promptForVideoInput(channelId, fileSize, contentId, videoMetadata)
 
-      // Create final inputs
-      const videoMediaInput: VideoMediaEntity = {
-        encoding,
-        pixelWidth,
-        pixelHeight,
-        size: fileSize,
-        location: { new: { joystreamMediaLocation: { new: { dataObjectId: contentId.encode() } } } },
-      }
-      videoInput = {
-        ...videoProps,
-        channel: channelId,
-        language,
-        category,
-        license,
-        media: { new: videoMediaInput },
-        publishedBeforeJoystream,
-      }
-
-      this.jsonPrettyPrint(JSON.stringify(videoInput))
-    }
+    this.jsonPrettyPrint(JSON.stringify(videoInput))
 
     if (!confirm) await this.requireConfirmation('Do you confirm the provided input?')
 

+ 10 - 6
cli/src/helpers/InputOutput.ts

@@ -39,12 +39,7 @@ export async function getInputJson<T>(inputPath?: string, schema?: JSONSchema, s
       throw new CLIError(`JSON parsing failed for file: ${inputPath}`, { exit: ExitCodes.InvalidInput })
     }
     if (schema) {
-      const ajv = new Ajv()
-      schema = await $RefParser.dereference(schemaPath || DEFAULT_SCHEMA_PATH, schema, {})
-      const valid = ajv.validate(schema, jsonObj) as boolean
-      if (!valid) {
-        throw new CLIError(`Input JSON file is not valid: ${ajv.errorsText()}`)
-      }
+      await validateInput(jsonObj, schema, schemaPath)
     }
 
     return jsonObj as T
@@ -53,6 +48,15 @@ export async function getInputJson<T>(inputPath?: string, schema?: JSONSchema, s
   return null
 }
 
+export async function validateInput(input: unknown, schema: JSONSchema, schemaPath?: string): Promise<void> {
+  const ajv = new Ajv({ allErrors: true })
+  schema = await $RefParser.dereference(schemaPath || DEFAULT_SCHEMA_PATH, schema, {})
+  const valid = ajv.validate(schema, input) as boolean
+  if (!valid) {
+    throw new CLIError(`Input JSON file is not valid: ${ajv.errorsText()}`)
+  }
+}
+
 export function saveOutputJson(outputPath: string | undefined, fileName: string, data: any): void {
   if (outputPath) {
     let outputFilePath = path.join(outputPath, fileName)