]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/models/video/video-file.ts
Merge remote-tracking branch 'weblate/develop' into develop
[github/Chocobozzz/PeerTube.git] / server / models / video / video-file.ts
CommitLineData
c48e82b5
C
1import {
2 AllowNull,
3 BelongsTo,
4 Column,
5 CreatedAt,
6 DataType,
7 Default,
8 ForeignKey,
9 HasMany,
10 Is,
11 Model,
12 Table,
13 UpdatedAt
14} from 'sequelize-typescript'
3a6f351b 15import {
14e2014a 16 isVideoFileExtnameValid,
3a6f351b
C
17 isVideoFileInfoHashValid,
18 isVideoFileResolutionValid,
19 isVideoFileSizeValid,
20 isVideoFPSResolutionValid
21} from '../../helpers/custom-validators/videos'
3acc5084 22import { parseAggregateResult, throwIfNotValid } from '../utils'
3fd3ab2d 23import { VideoModel } from './video'
c48e82b5 24import { VideoRedundancyModel } from '../redundancy/video-redundancy'
ae9bbed4 25import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
d7a25329 26import { FindOptions, Op, QueryTypes, Transaction } from 'sequelize'
35f28e94 27import { MIMETYPES, MEMOIZE_LENGTH, MEMOIZE_TTL } from '../../initializers/constants'
d7a25329 28import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileVideo } from '../../typings/models/video/video-file'
907b8f93 29import { MStreamingPlaylistVideo, MVideo } from '@server/typings/models'
35f28e94 30import * as memoizee from 'memoizee'
93e1258c 31
3fd3ab2d
C
32@Table({
33 tableName: 'videoFile',
34 indexes: [
93e1258c 35 {
d7a25329
C
36 fields: [ 'videoId' ],
37 where: {
38 videoId: {
39 [Op.ne]: null
40 }
41 }
42 },
43 {
44 fields: [ 'videoStreamingPlaylistId' ],
45 where: {
46 videoStreamingPlaylistId: {
47 [Op.ne]: null
48 }
49 }
93e1258c 50 },
d7a25329 51
93e1258c 52 {
3fd3ab2d 53 fields: [ 'infoHash' ]
8cd72bd3 54 },
d7a25329 55
8cd72bd3
C
56 {
57 fields: [ 'videoId', 'resolution', 'fps' ],
d7a25329
C
58 unique: true,
59 where: {
60 videoId: {
61 [Op.ne]: null
62 }
63 }
64 },
65 {
66 fields: [ 'videoStreamingPlaylistId', 'resolution', 'fps' ],
67 unique: true,
68 where: {
69 videoStreamingPlaylistId: {
70 [Op.ne]: null
71 }
72 }
93e1258c 73 }
93e1258c 74 ]
3fd3ab2d
C
75})
76export class VideoFileModel extends Model<VideoFileModel> {
77 @CreatedAt
78 createdAt: Date
79
80 @UpdatedAt
81 updatedAt: Date
82
83 @AllowNull(false)
84 @Is('VideoFileResolution', value => throwIfNotValid(value, isVideoFileResolutionValid, 'resolution'))
85 @Column
86 resolution: number
87
88 @AllowNull(false)
89 @Is('VideoFileSize', value => throwIfNotValid(value, isVideoFileSizeValid, 'size'))
90 @Column(DataType.BIGINT)
91 size: number
92
93 @AllowNull(false)
14e2014a
C
94 @Is('VideoFileExtname', value => throwIfNotValid(value, isVideoFileExtnameValid, 'extname'))
95 @Column
3fd3ab2d
C
96 extname: string
97
98 @AllowNull(false)
09209296 99 @Is('VideoFileInfohash', value => throwIfNotValid(value, isVideoFileInfoHashValid, 'info hash'))
3fd3ab2d
C
100 @Column
101 infoHash: string
102
2e7cf5ae
C
103 @AllowNull(false)
104 @Default(-1)
3a6f351b
C
105 @Is('VideoFileFPS', value => throwIfNotValid(value, isVideoFPSResolutionValid, 'fps'))
106 @Column
107 fps: number
108
3fd3ab2d
C
109 @ForeignKey(() => VideoModel)
110 @Column
111 videoId: number
112
113 @BelongsTo(() => VideoModel, {
93e1258c 114 foreignKey: {
d7a25329 115 allowNull: true
93e1258c
C
116 },
117 onDelete: 'CASCADE'
118 })
3fd3ab2d 119 Video: VideoModel
cc43831a 120
d7a25329
C
121 @ForeignKey(() => VideoStreamingPlaylistModel)
122 @Column
123 videoStreamingPlaylistId: number
124
125 @BelongsTo(() => VideoStreamingPlaylistModel, {
126 foreignKey: {
127 allowNull: true
128 },
129 onDelete: 'CASCADE'
130 })
131 VideoStreamingPlaylist: VideoStreamingPlaylistModel
132
c48e82b5
C
133 @HasMany(() => VideoRedundancyModel, {
134 foreignKey: {
09209296 135 allowNull: true
c48e82b5
C
136 },
137 onDelete: 'CASCADE',
138 hooks: true
139 })
140 RedundancyVideos: VideoRedundancyModel[]
141
35f28e94
C
142 static doesInfohashExistCached = memoizee(VideoFileModel.doesInfohashExist, {
143 promise: true,
144 max: MEMOIZE_LENGTH.INFO_HASH_EXISTS,
145 maxAge: MEMOIZE_TTL.INFO_HASH_EXISTS
146 })
147
09209296 148 static doesInfohashExist (infoHash: string) {
cc43831a
C
149 const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1'
150 const options = {
d5d9b6d7 151 type: QueryTypes.SELECT as QueryTypes.SELECT,
cc43831a
C
152 bind: { infoHash },
153 raw: true
154 }
155
156 return VideoModel.sequelize.query(query, options)
3acc5084 157 .then(results => results.length === 1)
cc43831a 158 }
e5565833 159
25378bc8
C
160 static loadWithVideo (id: number) {
161 const options = {
162 include: [
163 {
164 model: VideoModel.unscoped(),
165 required: true
166 }
167 ]
168 }
169
9b39106d 170 return VideoFileModel.findByPk(id, options)
25378bc8
C
171 }
172
3acc5084 173 static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Transaction) {
ae9bbed4
C
174 const query = {
175 include: [
176 {
177 model: VideoModel.unscoped(),
178 required: true,
179 include: [
180 {
181 model: VideoStreamingPlaylistModel.unscoped(),
182 required: true,
183 where: {
184 id: streamingPlaylistId
185 }
186 }
187 ]
188 }
189 ],
190 transaction
191 }
192
193 return VideoFileModel.findAll(query)
194 }
195
3acc5084
C
196 static getStats () {
197 const query: FindOptions = {
44b9c0ba
C
198 include: [
199 {
200 attributes: [],
201 model: VideoModel.unscoped(),
202 where: {
203 remote: false
204 }
205 }
206 ]
44b9c0ba 207 }
3acc5084
C
208
209 return VideoFileModel.aggregate('size', 'SUM', query)
210 .then(result => ({
211 totalLocalVideoFilesSize: parseAggregateResult(result)
212 }))
44b9c0ba
C
213 }
214
d7a25329
C
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
220 ) {
221 const baseWhere = {
222 fps: videoFile.fps,
223 resolution: videoFile.resolution
224 }
225
226 if (mode === 'streaming-playlist') Object.assign(baseWhere, { videoStreamingPlaylistId: videoFile.videoStreamingPlaylistId })
227 else Object.assign(baseWhere, { videoId: videoFile.videoId })
228
229 const element = await VideoFileModel.findOne({ where: baseWhere, transaction })
230 if (!element) return videoFile.save({ transaction })
231
232 for (const k of Object.keys(videoFile.toJSON())) {
233 element[k] = videoFile[k]
234 }
235
236 return element.save({ transaction })
237 }
238
239 getVideoOrStreamingPlaylist (this: MVideoFileVideo | MVideoFileStreamingPlaylistVideo): MVideo | MStreamingPlaylistVideo {
240 if (this.videoId) return (this as MVideoFileVideo).Video
241
242 return (this as MVideoFileStreamingPlaylistVideo).VideoStreamingPlaylist
243 }
244
536598cf
C
245 isAudio () {
246 return !!MIMETYPES.AUDIO.EXT_MIMETYPE[this.extname]
247 }
248
453e83ea 249 hasSameUniqueKeysThan (other: MVideoFile) {
e5565833
C
250 return this.fps === other.fps &&
251 this.resolution === other.resolution &&
d7a25329
C
252 (
253 (this.videoId !== null && this.videoId === other.videoId) ||
254 (this.videoStreamingPlaylistId !== null && this.videoStreamingPlaylistId === other.videoStreamingPlaylistId)
255 )
e5565833 256 }
93e1258c 257}