import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc'
import { VideoState } from '../../../../shared/models/videos'
import { logger } from '@server/helpers/logger'
+import { ActivityVideoFileMetadataObject } from '@shared/models'
function sanitizeAndCheckVideoTorrentUpdateActivity (activity: any) {
return isBaseActivityValid(activity, 'Update') &&
(url.mediaType || url.mimeType) === 'application/x-mpegURL' &&
isActivityPubUrlValid(url.href) &&
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')
}
// ---------------------------------------------------------------------------
sanitizeAndCheckVideoTorrentUpdateActivity,
isRemoteStringIdentifierValid,
sanitizeAndCheckVideoTorrentObject,
- isRemoteVideoUrlValid
+ isRemoteVideoUrlValid,
+ isAPVideoFileMetadataObject
}
// ---------------------------------------------------------------------------
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) => {
ffmpeg.ffprobe(path, (err, metadata) => {
if (err) return rej(err)
ActivityPlaylistUrlObject,
ActivityTagObject,
ActivityUrlObject,
+ ActivityVideoFileMetadataObject,
ActivityVideoUrlObject,
- VideoState,
- ActivityVideoFileMetadataObject
+ VideoState
} from '../../../shared/index'
import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
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 { deleteNonExistingModels, resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils'
import { logger } from '../../helpers/logger'
P2P_MEDIA_LOADER_PEER_VERSION,
PREVIEWS_SIZE,
REMOTE_SCHEME,
- STATIC_PATHS, THUMBNAILS_SIZE
+ STATIC_PATHS,
+ THUMBNAILS_SIZE
} from '../../initializers/constants'
import { TagModel } from '../../models/video/tag'
import { VideoModel } from '../../models/video/video'
MVideoAPWithoutCaption,
MVideoFile,
MVideoFullLight,
- MVideoId, MVideoImmutable,
+ MVideoId,
+ MVideoImmutable,
MVideoThumbnail
} from '../../typings/models'
import { MThumbnail } from '../../typings/models/video/thumbnail'
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) {
logger.debug('Adding remote video %s.', videoObject.id)
// Fetch associated metadata url, if any
const metadata = urls.filter(isAPVideoFileMetadataObject)
- .find(u =>
- u.height === fileUrl.height &&
- u.fps === fileUrl.fps &&
- u.rel.includes(fileUrl.mediaType)
- )
+ .find(u => {
+ return u.height === fileUrl.height &&
+ u.fps === fileUrl.fps &&
+ u.rel.includes(fileUrl.mediaType)
+ })
const mediaType = fileUrl.mediaType
const attribute = {
await move(transcodingPath, outputPath)
- const extractedVideo = extractVideo(video)
-
videoFile.size = stats.size
videoFile.fps = fps
videoFile.metadata = metadata
- videoFile.metadataUrl = extractedVideo.getVideoFileMetadataUrl(videoFile, extractedVideo.getBaseUrls().baseUrlHttp)
await createTorrentAndSetInfoHash(video, videoFile)
import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileVideo } from '../../typings/models/video/video-file'
import { MStreamingPlaylistVideo, MVideo } from '@server/typings/models'
import * as memoizee from 'memoizee'
+import validator from 'validator'
export enum ScopeNames {
WITH_VIDEO = 'WITH_VIDEO',
- WITH_VIDEO_OR_PLAYLIST = 'WITH_VIDEO_OR_PLAYLIST',
WITH_METADATA = 'WITH_METADATA'
}
-const METADATA_FIELDS = [ 'metadata', 'metadataUrl' ]
-
@DefaultScope(() => ({
attributes: {
- exclude: [ METADATA_FIELDS[0] ]
+ exclude: [ 'metadata' ]
}
}))
@Scopes(() => ({
}
]
},
- [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]: {
attributes: {
- include: METADATA_FIELDS
+ include: [ 'metadata' ]
}
}
}))
static async doesVideoExistForVideoFile (id: number, videoIdOrUUID: number | string) {
const videoFile = await VideoFileModel.loadWithVideoOrPlaylist(id, videoIdOrUUID)
- return (videoFile?.Video.id === videoIdOrUUID) ||
- (videoFile?.Video.uuid === videoIdOrUUID) ||
- (videoFile?.VideoStreamingPlaylist?.Video?.id === videoIdOrUUID) ||
- (videoFile?.VideoStreamingPlaylist?.Video?.uuid === videoIdOrUUID)
+
+ return !!videoFile
}
static loadWithMetadata (id: number) {
}
static loadWithVideoOrPlaylist (id: number, videoIdOrUUID: number | string) {
- return VideoFileModel.scope({
- method: [
- ScopeNames.WITH_VIDEO_OR_PLAYLIST,
- videoIdOrUUID
+ const whereVideo = validator.isUUID(videoIdOrUUID + '')
+ ? { uuid: videoIdOrUUID }
+ : { id: 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) {
baseUrlWs: string,
videoFiles: MVideoFileRedundanciesOpt[]
): VideoFile[] {
+ const video = extractVideo(model)
+
return videoFiles
.map(videoFile => {
return {
torrentDownloadUrl: model.getTorrentDownloadUrl(videoFile, baseUrlHttp),
fileUrl: model.getVideoFileUrl(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
})
.sort((a, b) => {
getVideoFileMetadataUrl (videoFile: MVideoFile, baseUrlHttp: string) {
const path = '/api/v1/videos/'
- return videoFile.metadata
+
+ return this.isOwned()
? baseUrlHttp + path + this.uuid + '/metadata/' + videoFile.id
: videoFile.metadataUrl
}
root,
ServerInfo,
setAccessTokensToServers,
- uploadVideo,
+ uploadVideo, uploadVideoAndGetId,
waitJobs,
webtorrentAdd
} from '../../../../shared/extra-utils'
import { join } from 'path'
import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants'
import { FfprobeData } from 'fluent-ffmpeg'
+import { VideoFileMetadata } from '@shared/models/videos/video-file-metadata'
const expect = chai.expect
it('Should provide valid ffprobe data', async function () {
this.timeout(160000)
- const videoAttributes = {
- 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)
-
+ const videoUUID = (await uploadVideoAndGetId({ server: servers[1], videoName: 'ffprobe data' })).uuid
await waitJobs(servers)
- const res = await getVideosList(servers[1].url)
+ {
+ const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', videoUUID + '-240.mp4')
+ const metadata = await getMetadataFromFile<VideoFileMetadata>(path)
- 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
+ // expected format properties
+ for (const p of [
+ 'tags.encoder',
+ 'format_long_name',
+ 'size',
+ 'bit_rate'
+ ]) {
+ expect(metadata.format).to.have.nested.property(p)
+ }
- {
- const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', videoOnOrigin.uuid + '-240.mp4')
- const metadata = await getMetadataFromFile(path)
+ // expected stream properties
for (const p of [
- // expected format properties
- 'format.encoder',
- 'format.format_long_name',
- 'format.size',
- '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'
+ 'codec_long_name',
+ 'profile',
+ 'width',
+ 'height',
+ 'display_aspect_ratio',
+ 'avg_frame_rate',
+ 'pix_fmt'
]) {
- expect(metadata).to.have.nested.property(p)
+ expect(metadata.streams[0]).to.have.nested.property(p)
}
+
expect(metadata).to.not.have.nested.property('format.filename')
}
for (const server of servers) {
- const res = await getVideosList(server.url)
-
- const video = res.body.data.find(v => v.name === videoAttributes.name)
- const res2 = await getVideo(server.url, video.id)
- const videoDetails = res2.body
+ const res2 = await getVideo(server.url, videoUUID)
+ const videoDetails: VideoDetails = res2.body
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.metadataUrl).to.exist
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 metadata: FfprobeData = res3.body
expect(metadata).to.have.nested.property('format.size')
- expect(metadata.format.size).to.equal(videoOnOriginDetails.files[index].metadata.format.size)
}
}
})