14 } from 'sequelize-typescript'
16 isVideoFileExtnameValid,
17 isVideoFileInfoHashValid,
18 isVideoFileResolutionValid,
20 isVideoFPSResolutionValid
21 } from '../../helpers/custom-validators/videos'
22 import { parseAggregateResult, throwIfNotValid } from '../utils'
23 import { VideoModel } from './video'
24 import { VideoRedundancyModel } from '../redundancy/video-redundancy'
25 import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
26 import { FindOptions, Op, QueryTypes, Transaction } from 'sequelize'
27 import { MIMETYPES, MEMOIZE_LENGTH, MEMOIZE_TTL } from '../../initializers/constants'
28 import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileVideo } from '../../typings/models/video/video-file'
29 import { MStreamingPlaylistVideo, MVideo } from '@server/typings/models'
30 import * as memoizee from 'memoizee'
33 tableName: 'videoFile',
36 fields: [ 'videoId' ],
44 fields: [ 'videoStreamingPlaylistId' ],
46 videoStreamingPlaylistId: {
53 fields: [ 'infoHash' ]
57 fields: [ 'videoId', 'resolution', 'fps' ],
66 fields: [ 'videoStreamingPlaylistId', 'resolution', 'fps' ],
69 videoStreamingPlaylistId: {
76 export class VideoFileModel extends Model<VideoFileModel> {
84 @Is('VideoFileResolution', value => throwIfNotValid(value, isVideoFileResolutionValid, 'resolution'))
89 @Is('VideoFileSize', value => throwIfNotValid(value, isVideoFileSizeValid, 'size'))
90 @Column(DataType.BIGINT)
94 @Is('VideoFileExtname', value => throwIfNotValid(value, isVideoFileExtnameValid, 'extname'))
99 @Is('VideoFileInfohash', value => throwIfNotValid(value, isVideoFileInfoHashValid, 'info hash'))
105 @Is('VideoFileFPS', value => throwIfNotValid(value, isVideoFPSResolutionValid, 'fps'))
109 @ForeignKey(() => VideoModel)
113 @BelongsTo(() => VideoModel, {
121 @ForeignKey(() => VideoStreamingPlaylistModel)
123 videoStreamingPlaylistId: number
125 @BelongsTo(() => VideoStreamingPlaylistModel, {
131 VideoStreamingPlaylist: VideoStreamingPlaylistModel
133 @HasMany(() => VideoRedundancyModel, {
140 RedundancyVideos: VideoRedundancyModel[]
142 static doesInfohashExistCached = memoizee(VideoFileModel.doesInfohashExist, {
144 max: MEMOIZE_LENGTH.INFO_HASH_EXISTS,
145 maxAge: MEMOIZE_TTL.INFO_HASH_EXISTS
148 static doesInfohashExist (infoHash: string) {
149 const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1'
151 type: QueryTypes.SELECT as QueryTypes.SELECT,
156 return VideoModel.sequelize.query(query, options)
157 .then(results => results.length === 1)
160 static loadWithVideo (id: number) {
164 model: VideoModel.unscoped(),
170 return VideoFileModel.findByPk(id, options)
173 static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Transaction) {
177 model: VideoModel.unscoped(),
181 model: VideoStreamingPlaylistModel.unscoped(),
184 id: streamingPlaylistId
193 return VideoFileModel.findAll(query)
197 const query: FindOptions = {
201 model: VideoModel.unscoped(),
209 return VideoFileModel.aggregate('size', 'SUM', query)
211 totalLocalVideoFilesSize: parseAggregateResult(result)
215 // Redefine upsert because sequelize does not use an appropriate where clause in the update query with 2 unique indexes
216 static async customUpsert (
217 videoFile: MVideoFile,
218 mode: 'streaming-playlist' | 'video',
219 transaction: Transaction
223 resolution: videoFile.resolution
226 if (mode === 'streaming-playlist') Object.assign(baseWhere, { videoStreamingPlaylistId: videoFile.videoStreamingPlaylistId })
227 else Object.assign(baseWhere, { videoId: videoFile.videoId })
229 const element = await VideoFileModel.findOne({ where: baseWhere, transaction })
230 if (!element) return videoFile.save({ transaction })
232 for (const k of Object.keys(videoFile.toJSON())) {
233 element[k] = videoFile[k]
236 return element.save({ transaction })
239 getVideoOrStreamingPlaylist (this: MVideoFileVideo | MVideoFileStreamingPlaylistVideo): MVideo | MStreamingPlaylistVideo {
240 if (this.videoId) return (this as MVideoFileVideo).Video
242 return (this as MVideoFileStreamingPlaylistVideo).VideoStreamingPlaylist
246 return !!MIMETYPES.AUDIO.EXT_MIMETYPE[this.extname]
249 hasSameUniqueKeysThan (other: MVideoFile) {
250 return this.fps === other.fps &&
251 this.resolution === other.resolution &&
253 (this.videoId !== null && this.videoId === other.videoId) ||
254 (this.videoStreamingPlaylistId !== null && this.videoStreamingPlaylistId === other.videoStreamingPlaylistId)