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