Video file metadata PR cleanup
This commit is contained in:
parent
8319d6ae72
commit
7b81edc854
|
@ -13,6 +13,7 @@ import {
|
||||||
import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc'
|
import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc'
|
||||||
import { VideoState } from '../../../../shared/models/videos'
|
import { VideoState } from '../../../../shared/models/videos'
|
||||||
import { logger } from '@server/helpers/logger'
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { ActivityVideoFileMetadataObject } from '@shared/models'
|
||||||
|
|
||||||
function sanitizeAndCheckVideoTorrentUpdateActivity (activity: any) {
|
function sanitizeAndCheckVideoTorrentUpdateActivity (activity: any) {
|
||||||
return isBaseActivityValid(activity, 'Update') &&
|
return isBaseActivityValid(activity, 'Update') &&
|
||||||
|
@ -104,7 +105,15 @@ function isRemoteVideoUrlValid (url: any) {
|
||||||
(url.mediaType || url.mimeType) === 'application/x-mpegURL' &&
|
(url.mediaType || url.mimeType) === 'application/x-mpegURL' &&
|
||||||
isActivityPubUrlValid(url.href) &&
|
isActivityPubUrlValid(url.href) &&
|
||||||
isArray(url.tag)
|
isArray(url.tag)
|
||||||
)
|
) ||
|
||||||
|
isAPVideoFileMetadataObject(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAPVideoFileMetadataObject (url: any): url is ActivityVideoFileMetadataObject {
|
||||||
|
return url &&
|
||||||
|
url.type === 'Link' &&
|
||||||
|
url.mediaType === 'application/json' &&
|
||||||
|
isArray(url.rel) && url.rel.includes('metadata')
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -113,7 +122,8 @@ export {
|
||||||
sanitizeAndCheckVideoTorrentUpdateActivity,
|
sanitizeAndCheckVideoTorrentUpdateActivity,
|
||||||
isRemoteStringIdentifierValid,
|
isRemoteStringIdentifierValid,
|
||||||
sanitizeAndCheckVideoTorrentObject,
|
sanitizeAndCheckVideoTorrentObject,
|
||||||
isRemoteVideoUrlValid
|
isRemoteVideoUrlValid,
|
||||||
|
isAPVideoFileMetadataObject
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
@ -170,7 +170,7 @@ async function getVideoFileFPS (path: string) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMetadataFromFile<T> (path: string, cb = metadata => metadata) {
|
async function getMetadataFromFile <T> (path: string, cb = metadata => metadata) {
|
||||||
return new Promise<T>((res, rej) => {
|
return new Promise<T>((res, rej) => {
|
||||||
ffmpeg.ffprobe(path, (err, metadata) => {
|
ffmpeg.ffprobe(path, (err, metadata) => {
|
||||||
if (err) return rej(err)
|
if (err) return rej(err)
|
||||||
|
|
|
@ -9,13 +9,13 @@ import {
|
||||||
ActivityPlaylistUrlObject,
|
ActivityPlaylistUrlObject,
|
||||||
ActivityTagObject,
|
ActivityTagObject,
|
||||||
ActivityUrlObject,
|
ActivityUrlObject,
|
||||||
|
ActivityVideoFileMetadataObject,
|
||||||
ActivityVideoUrlObject,
|
ActivityVideoUrlObject,
|
||||||
VideoState,
|
VideoState
|
||||||
ActivityVideoFileMetadataObject
|
|
||||||
} from '../../../shared/index'
|
} from '../../../shared/index'
|
||||||
import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
|
import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
|
||||||
import { VideoPrivacy } from '../../../shared/models/videos'
|
import { VideoPrivacy } from '../../../shared/models/videos'
|
||||||
import { sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validators/activitypub/videos'
|
import { sanitizeAndCheckVideoTorrentObject, isAPVideoFileMetadataObject } from '../../helpers/custom-validators/activitypub/videos'
|
||||||
import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos'
|
import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos'
|
||||||
import { deleteNonExistingModels, resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils'
|
import { deleteNonExistingModels, resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils'
|
||||||
import { logger } from '../../helpers/logger'
|
import { logger } from '../../helpers/logger'
|
||||||
|
@ -26,7 +26,8 @@ import {
|
||||||
P2P_MEDIA_LOADER_PEER_VERSION,
|
P2P_MEDIA_LOADER_PEER_VERSION,
|
||||||
PREVIEWS_SIZE,
|
PREVIEWS_SIZE,
|
||||||
REMOTE_SCHEME,
|
REMOTE_SCHEME,
|
||||||
STATIC_PATHS, THUMBNAILS_SIZE
|
STATIC_PATHS,
|
||||||
|
THUMBNAILS_SIZE
|
||||||
} from '../../initializers/constants'
|
} from '../../initializers/constants'
|
||||||
import { TagModel } from '../../models/video/tag'
|
import { TagModel } from '../../models/video/tag'
|
||||||
import { VideoModel } from '../../models/video/video'
|
import { VideoModel } from '../../models/video/video'
|
||||||
|
@ -69,7 +70,8 @@ import {
|
||||||
MVideoAPWithoutCaption,
|
MVideoAPWithoutCaption,
|
||||||
MVideoFile,
|
MVideoFile,
|
||||||
MVideoFullLight,
|
MVideoFullLight,
|
||||||
MVideoId, MVideoImmutable,
|
MVideoId,
|
||||||
|
MVideoImmutable,
|
||||||
MVideoThumbnail
|
MVideoThumbnail
|
||||||
} from '../../typings/models'
|
} from '../../typings/models'
|
||||||
import { MThumbnail } from '../../typings/models/video/thumbnail'
|
import { MThumbnail } from '../../typings/models/video/thumbnail'
|
||||||
|
@ -527,10 +529,6 @@ function isAPHashTagObject (url: any): url is ActivityHashTagObject {
|
||||||
return url && url.type === 'Hashtag'
|
return url && url.type === 'Hashtag'
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAPVideoFileMetadataObject (url: any): url is ActivityVideoFileMetadataObject {
|
|
||||||
return url && url.type === 'Link' && url.mediaType === 'application/json' && url.hasAttribute('rel') && url.rel.includes('metadata')
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAccountLight, waitThumbnail = false) {
|
async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAccountLight, waitThumbnail = false) {
|
||||||
logger.debug('Adding remote video %s.', videoObject.id)
|
logger.debug('Adding remote video %s.', videoObject.id)
|
||||||
|
|
||||||
|
@ -701,11 +699,11 @@ function videoFileActivityUrlToDBAttributes (
|
||||||
|
|
||||||
// Fetch associated metadata url, if any
|
// Fetch associated metadata url, if any
|
||||||
const metadata = urls.filter(isAPVideoFileMetadataObject)
|
const metadata = urls.filter(isAPVideoFileMetadataObject)
|
||||||
.find(u =>
|
.find(u => {
|
||||||
u.height === fileUrl.height &&
|
return u.height === fileUrl.height &&
|
||||||
u.fps === fileUrl.fps &&
|
u.fps === fileUrl.fps &&
|
||||||
u.rel.includes(fileUrl.mediaType)
|
u.rel.includes(fileUrl.mediaType)
|
||||||
)
|
})
|
||||||
|
|
||||||
const mediaType = fileUrl.mediaType
|
const mediaType = fileUrl.mediaType
|
||||||
const attribute = {
|
const attribute = {
|
||||||
|
|
|
@ -237,12 +237,9 @@ async function onVideoFileTranscoding (video: MVideoWithFile, videoFile: MVideoF
|
||||||
|
|
||||||
await move(transcodingPath, outputPath)
|
await move(transcodingPath, outputPath)
|
||||||
|
|
||||||
const extractedVideo = extractVideo(video)
|
|
||||||
|
|
||||||
videoFile.size = stats.size
|
videoFile.size = stats.size
|
||||||
videoFile.fps = fps
|
videoFile.fps = fps
|
||||||
videoFile.metadata = metadata
|
videoFile.metadata = metadata
|
||||||
videoFile.metadataUrl = extractedVideo.getVideoFileMetadataUrl(videoFile, extractedVideo.getBaseUrls().baseUrlHttp)
|
|
||||||
|
|
||||||
await createTorrentAndSetInfoHash(video, videoFile)
|
await createTorrentAndSetInfoHash(video, videoFile)
|
||||||
|
|
||||||
|
|
|
@ -30,18 +30,16 @@ import { MIMETYPES, MEMOIZE_LENGTH, MEMOIZE_TTL } from '../../initializers/const
|
||||||
import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileVideo } from '../../typings/models/video/video-file'
|
import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileVideo } from '../../typings/models/video/video-file'
|
||||||
import { MStreamingPlaylistVideo, MVideo } from '@server/typings/models'
|
import { MStreamingPlaylistVideo, MVideo } from '@server/typings/models'
|
||||||
import * as memoizee from 'memoizee'
|
import * as memoizee from 'memoizee'
|
||||||
|
import validator from 'validator'
|
||||||
|
|
||||||
export enum ScopeNames {
|
export enum ScopeNames {
|
||||||
WITH_VIDEO = 'WITH_VIDEO',
|
WITH_VIDEO = 'WITH_VIDEO',
|
||||||
WITH_VIDEO_OR_PLAYLIST = 'WITH_VIDEO_OR_PLAYLIST',
|
|
||||||
WITH_METADATA = 'WITH_METADATA'
|
WITH_METADATA = 'WITH_METADATA'
|
||||||
}
|
}
|
||||||
|
|
||||||
const METADATA_FIELDS = [ 'metadata', 'metadataUrl' ]
|
|
||||||
|
|
||||||
@DefaultScope(() => ({
|
@DefaultScope(() => ({
|
||||||
attributes: {
|
attributes: {
|
||||||
exclude: [ METADATA_FIELDS[0] ]
|
exclude: [ 'metadata' ]
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@Scopes(() => ({
|
@Scopes(() => ({
|
||||||
|
@ -53,35 +51,9 @@ const METADATA_FIELDS = [ 'metadata', 'metadataUrl' ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
[ScopeNames.WITH_VIDEO_OR_PLAYLIST]: (videoIdOrUUID: string | number) => {
|
|
||||||
const where = (typeof videoIdOrUUID === 'number')
|
|
||||||
? { id: videoIdOrUUID }
|
|
||||||
: { uuid: videoIdOrUUID }
|
|
||||||
|
|
||||||
return {
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: VideoModel.unscoped(),
|
|
||||||
required: false,
|
|
||||||
where
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: VideoStreamingPlaylistModel.unscoped(),
|
|
||||||
required: false,
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: VideoModel.unscoped(),
|
|
||||||
required: true,
|
|
||||||
where
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[ScopeNames.WITH_METADATA]: {
|
[ScopeNames.WITH_METADATA]: {
|
||||||
attributes: {
|
attributes: {
|
||||||
include: METADATA_FIELDS
|
include: [ 'metadata' ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
@ -223,10 +195,8 @@ export class VideoFileModel extends Model<VideoFileModel> {
|
||||||
|
|
||||||
static async doesVideoExistForVideoFile (id: number, videoIdOrUUID: number | string) {
|
static async doesVideoExistForVideoFile (id: number, videoIdOrUUID: number | string) {
|
||||||
const videoFile = await VideoFileModel.loadWithVideoOrPlaylist(id, videoIdOrUUID)
|
const videoFile = await VideoFileModel.loadWithVideoOrPlaylist(id, videoIdOrUUID)
|
||||||
return (videoFile?.Video.id === videoIdOrUUID) ||
|
|
||||||
(videoFile?.Video.uuid === videoIdOrUUID) ||
|
return !!videoFile
|
||||||
(videoFile?.VideoStreamingPlaylist?.Video?.id === videoIdOrUUID) ||
|
|
||||||
(videoFile?.VideoStreamingPlaylist?.Video?.uuid === videoIdOrUUID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadWithMetadata (id: number) {
|
static loadWithMetadata (id: number) {
|
||||||
|
@ -238,12 +208,41 @@ export class VideoFileModel extends Model<VideoFileModel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadWithVideoOrPlaylist (id: number, videoIdOrUUID: number | string) {
|
static loadWithVideoOrPlaylist (id: number, videoIdOrUUID: number | string) {
|
||||||
return VideoFileModel.scope({
|
const whereVideo = validator.isUUID(videoIdOrUUID + '')
|
||||||
method: [
|
? { uuid: videoIdOrUUID }
|
||||||
ScopeNames.WITH_VIDEO_OR_PLAYLIST,
|
: { id: videoIdOrUUID }
|
||||||
videoIdOrUUID
|
|
||||||
|
const options = {
|
||||||
|
where: {
|
||||||
|
id
|
||||||
|
},
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: VideoModel.unscoped(),
|
||||||
|
required: false,
|
||||||
|
where: whereVideo
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: VideoStreamingPlaylistModel.unscoped(),
|
||||||
|
required: false,
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: VideoModel.unscoped(),
|
||||||
|
required: true,
|
||||||
|
where: whereVideo
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}).findByPk(id)
|
}
|
||||||
|
|
||||||
|
return VideoFileModel.findOne(options)
|
||||||
|
.then(file => {
|
||||||
|
// We used `required: false` so check we have at least a video or a streaming playlist
|
||||||
|
if (!file.Video && !file.VideoStreamingPlaylist) return null
|
||||||
|
|
||||||
|
return file
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Transaction) {
|
static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Transaction) {
|
||||||
|
|
|
@ -181,6 +181,8 @@ function videoFilesModelToFormattedJSON (
|
||||||
baseUrlWs: string,
|
baseUrlWs: string,
|
||||||
videoFiles: MVideoFileRedundanciesOpt[]
|
videoFiles: MVideoFileRedundanciesOpt[]
|
||||||
): VideoFile[] {
|
): VideoFile[] {
|
||||||
|
const video = extractVideo(model)
|
||||||
|
|
||||||
return videoFiles
|
return videoFiles
|
||||||
.map(videoFile => {
|
.map(videoFile => {
|
||||||
return {
|
return {
|
||||||
|
@ -195,7 +197,7 @@ function videoFilesModelToFormattedJSON (
|
||||||
torrentDownloadUrl: model.getTorrentDownloadUrl(videoFile, baseUrlHttp),
|
torrentDownloadUrl: model.getTorrentDownloadUrl(videoFile, baseUrlHttp),
|
||||||
fileUrl: model.getVideoFileUrl(videoFile, baseUrlHttp),
|
fileUrl: model.getVideoFileUrl(videoFile, baseUrlHttp),
|
||||||
fileDownloadUrl: model.getVideoFileDownloadUrl(videoFile, baseUrlHttp),
|
fileDownloadUrl: model.getVideoFileDownloadUrl(videoFile, baseUrlHttp),
|
||||||
metadataUrl: videoFile.metadataUrl // only send the metadataUrl and not the metadata over the wire
|
metadataUrl: video.getVideoFileMetadataUrl(videoFile, baseUrlHttp)
|
||||||
} as VideoFile
|
} as VideoFile
|
||||||
})
|
})
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
|
|
|
@ -1849,7 +1849,8 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
|
|
||||||
getVideoFileMetadataUrl (videoFile: MVideoFile, baseUrlHttp: string) {
|
getVideoFileMetadataUrl (videoFile: MVideoFile, baseUrlHttp: string) {
|
||||||
const path = '/api/v1/videos/'
|
const path = '/api/v1/videos/'
|
||||||
return videoFile.metadata
|
|
||||||
|
return this.isOwned()
|
||||||
? baseUrlHttp + path + this.uuid + '/metadata/' + videoFile.id
|
? baseUrlHttp + path + this.uuid + '/metadata/' + videoFile.id
|
||||||
: videoFile.metadataUrl
|
: videoFile.metadataUrl
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,13 +27,14 @@ import {
|
||||||
root,
|
root,
|
||||||
ServerInfo,
|
ServerInfo,
|
||||||
setAccessTokensToServers,
|
setAccessTokensToServers,
|
||||||
uploadVideo,
|
uploadVideo, uploadVideoAndGetId,
|
||||||
waitJobs,
|
waitJobs,
|
||||||
webtorrentAdd
|
webtorrentAdd
|
||||||
} from '../../../../shared/extra-utils'
|
} from '../../../../shared/extra-utils'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants'
|
import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants'
|
||||||
import { FfprobeData } from 'fluent-ffmpeg'
|
import { FfprobeData } from 'fluent-ffmpeg'
|
||||||
|
import { VideoFileMetadata } from '@shared/models/videos/video-file-metadata'
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
|
||||||
|
@ -470,61 +471,56 @@ describe('Test video transcoding', function () {
|
||||||
it('Should provide valid ffprobe data', async function () {
|
it('Should provide valid ffprobe data', async function () {
|
||||||
this.timeout(160000)
|
this.timeout(160000)
|
||||||
|
|
||||||
const videoAttributes = {
|
const videoUUID = (await uploadVideoAndGetId({ server: servers[1], videoName: 'ffprobe data' })).uuid
|
||||||
name: 'my super name for server 1',
|
|
||||||
description: 'my super description for server 1',
|
|
||||||
fixture: 'video_short.webm'
|
|
||||||
}
|
|
||||||
await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
|
|
||||||
|
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
|
|
||||||
const res = await getVideosList(servers[1].url)
|
|
||||||
|
|
||||||
const videoOnOrigin = res.body.data.find(v => v.name === videoAttributes.name)
|
|
||||||
const res2 = await getVideo(servers[1].url, videoOnOrigin.id)
|
|
||||||
const videoOnOriginDetails: VideoDetails = res2.body
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', videoOnOrigin.uuid + '-240.mp4')
|
const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', videoUUID + '-240.mp4')
|
||||||
const metadata = await getMetadataFromFile(path)
|
const metadata = await getMetadataFromFile<VideoFileMetadata>(path)
|
||||||
|
|
||||||
|
// expected format properties
|
||||||
for (const p of [
|
for (const p of [
|
||||||
// expected format properties
|
'tags.encoder',
|
||||||
'format.encoder',
|
'format_long_name',
|
||||||
'format.format_long_name',
|
'size',
|
||||||
'format.size',
|
'bit_rate'
|
||||||
'format.bit_rate',
|
|
||||||
// expected stream properties
|
|
||||||
'stream[0].codec_long_name',
|
|
||||||
'stream[0].profile',
|
|
||||||
'stream[0].width',
|
|
||||||
'stream[0].height',
|
|
||||||
'stream[0].display_aspect_ratio',
|
|
||||||
'stream[0].avg_frame_rate',
|
|
||||||
'stream[0].pix_fmt'
|
|
||||||
]) {
|
]) {
|
||||||
expect(metadata).to.have.nested.property(p)
|
expect(metadata.format).to.have.nested.property(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expected stream properties
|
||||||
|
for (const p of [
|
||||||
|
'codec_long_name',
|
||||||
|
'profile',
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
'display_aspect_ratio',
|
||||||
|
'avg_frame_rate',
|
||||||
|
'pix_fmt'
|
||||||
|
]) {
|
||||||
|
expect(metadata.streams[0]).to.have.nested.property(p)
|
||||||
|
}
|
||||||
|
|
||||||
expect(metadata).to.not.have.nested.property('format.filename')
|
expect(metadata).to.not.have.nested.property('format.filename')
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const server of servers) {
|
for (const server of servers) {
|
||||||
const res = await getVideosList(server.url)
|
const res2 = await getVideo(server.url, videoUUID)
|
||||||
|
const videoDetails: VideoDetails = res2.body
|
||||||
const video = res.body.data.find(v => v.name === videoAttributes.name)
|
|
||||||
const res2 = await getVideo(server.url, video.id)
|
|
||||||
const videoDetails = res2.body
|
|
||||||
|
|
||||||
const videoFiles = videoDetails.files
|
const videoFiles = videoDetails.files
|
||||||
for (const [ index, file ] of videoFiles.entries()) {
|
.concat(videoDetails.streamingPlaylists[0].files)
|
||||||
|
expect(videoFiles).to.have.lengthOf(8)
|
||||||
|
|
||||||
|
for (const file of videoFiles) {
|
||||||
expect(file.metadata).to.be.undefined
|
expect(file.metadata).to.be.undefined
|
||||||
|
expect(file.metadataUrl).to.exist
|
||||||
expect(file.metadataUrl).to.contain(servers[1].url)
|
expect(file.metadataUrl).to.contain(servers[1].url)
|
||||||
expect(file.metadataUrl).to.contain(videoOnOrigin.uuid)
|
expect(file.metadataUrl).to.contain(videoUUID)
|
||||||
|
|
||||||
const res3 = await getVideoFileMetadataUrl(file.metadataUrl)
|
const res3 = await getVideoFileMetadataUrl(file.metadataUrl)
|
||||||
const metadata: FfprobeData = res3.body
|
const metadata: FfprobeData = res3.body
|
||||||
expect(metadata).to.have.nested.property('format.size')
|
expect(metadata).to.have.nested.property('format.size')
|
||||||
expect(metadata.format.size).to.equal(videoOnOriginDetails.files[index].metadata.format.size)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue