-
Notifications
You must be signed in to change notification settings - Fork 115
/
createVideo.ts
148 lines (131 loc) · 5.86 KB
/
createVideo.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import UploadCommandBase from '../../base/UploadCommandBase'
import { getInputJson } from '../../helpers/InputOutput'
import { asValidatedMetadata, metadataToBytes } from '../../helpers/serialization'
import { VideoInputParameters, VideoFileMetadata } from '../../Types'
import { createType } from '@joystream/types'
import { flags } from '@oclif/command'
import { ContentMetadata, IVideoMetadata, VideoMetadata } from '@joystream/metadata-protobuf'
import { VideoInputSchema } from '../../schemas/ContentDirectory'
import chalk from 'chalk'
import ExitCodes from '../../ExitCodes'
import ContentDirectoryCommandBase from '../../base/ContentDirectoryCommandBase'
import { PalletContentIterableEnumsChannelActionPermission as ChannelActionPermission } from '@polkadot/types/lookup'
export default class CreateVideoCommand extends UploadCommandBase {
static description = 'Create video (non nft) under specific channel inside content directory.'
static flags = {
input: flags.string({
char: 'i',
required: true,
description: `Path to JSON file to use as input`,
}),
channelId: flags.integer({
char: 'c',
required: true,
description: 'ID of the Channel',
}),
context: ContentDirectoryCommandBase.channelManagementContextFlag,
...UploadCommandBase.flags,
}
setVideoMetadataDefaults(metadata: IVideoMetadata, videoFileMetadata: VideoFileMetadata): IVideoMetadata {
return {
duration: videoFileMetadata.duration,
mediaPixelWidth: videoFileMetadata.width,
mediaPixelHeight: videoFileMetadata.height,
mediaType: {
codecName: videoFileMetadata.codecName,
container: videoFileMetadata.container,
mimeMediaType: videoFileMetadata.mimeType,
},
...metadata,
}
}
async run(): Promise<void> {
const { input, channelId, context } = this.parse(CreateVideoCommand).flags
// Get context
const channel = await this.getApi().channelById(channelId)
const [actor, address] = await this.getChannelManagementActor(channel, context)
const keypair = await this.getDecodedPair(address)
// Ensure actor is authorized to create video
const requiredPermissions: ChannelActionPermission['type'][] = ['AddVideo']
if (!(await this.hasRequiredChannelAgentPermissions(actor, channel, requiredPermissions))) {
this.error(
`Only channel owner or collaborator with ${requiredPermissions} permissions can add video to channel ${channelId}!`,
{
exit: ExitCodes.AccessDenied,
}
)
}
// Get input from file
const videoCreationParametersInput = await getInputJson<VideoInputParameters>(input, VideoInputSchema)
let meta = asValidatedMetadata(VideoMetadata, videoCreationParametersInput)
// Video assets
const { videoPath, thumbnailPhotoPath, subtitles } = videoCreationParametersInput
const [resolvedVideoAssets, videoAssetIndices] = await this.resolveAndValidateAssets(
{ videoPath, thumbnailPhotoPath },
input
)
// Set assets indices in the metadata
meta.video = videoAssetIndices.videoPath
meta.thumbnailPhoto = videoAssetIndices.thumbnailPhotoPath
// Subtitle assets
let subtitleAssetIndex = Object.values(videoAssetIndices).filter((v) => v !== undefined).length
const resolvedSubtitleAssets = (
await Promise.all(
(subtitles || []).map(async (subtitleInputParameters, i) => {
const { subtitleAssetPath } = subtitleInputParameters
const [[resolvedAsset]] = await this.resolveAndValidateAssets({ subtitleAssetPath }, input)
// Set assets indices in the metadata
if (meta.subtitles && resolvedAsset) {
meta.subtitles[i].newAsset = subtitleAssetIndex++
}
return resolvedAsset
})
)
).filter((r) => r)
// Try to get video file metadata
if (videoAssetIndices.videoPath !== undefined) {
const videoFileMetadata = await this.getVideoFileMetadata(resolvedVideoAssets[videoAssetIndices.videoPath].path)
this.log('Video media file parameters established:', videoFileMetadata)
meta = this.setVideoMetadataDefaults(meta, videoFileMetadata)
}
// Prepare and send the extrinsic
const assets = await this.prepareAssetsForExtrinsic(
[...resolvedVideoAssets, ...resolvedSubtitleAssets],
'createVideo'
)
const expectedVideoStateBloatBond = await this.getApi().videoStateBloatBond()
const expectedDataObjectStateBloatBond = await this.getApi().dataObjectStateBloatBond()
const videoCreationParameters = createType('PalletContentVideoCreationParametersRecord', {
assets,
meta: metadataToBytes(ContentMetadata, { videoMetadata: meta }),
expectedVideoStateBloatBond,
expectedDataObjectStateBloatBond,
autoIssueNft: null,
storageBucketsNumWitness: await this.getStorageBucketsNumWitness(channelId),
})
this.jsonPrettyPrint(JSON.stringify({ assets: assets?.toJSON(), metadata: meta }))
await this.requireConfirmation('Do you confirm the provided input?', true)
const result = await this.sendAndFollowNamedTx(keypair, 'content', 'createVideo', [
actor,
channelId,
videoCreationParameters,
])
const [, , videoId, , dataObjects] = this.getEvent(result, 'content', 'VideoCreated').data
this.log(chalk.green(`Video with id ${chalk.cyanBright(videoId.toString())} successfully created!`))
if (dataObjects.size !== (assets?.objectCreationList.length || 0)) {
this.error('Unexpected number of video assets in VideoCreated event!', {
exit: ExitCodes.UnexpectedRuntimeState,
})
}
if (dataObjects.size) {
await this.uploadAssets(
`dynamic:channel:${channelId.toString()}`,
[...dataObjects].map((id, index) => ({
dataObjectId: id,
path: [...resolvedVideoAssets, ...resolvedSubtitleAssets][index].path,
})),
input
)
}
}
}