Merge branch 'feature/audio-upload' into develop
[oweals/peertube.git] / server / models / video / video-file.ts
1 import {
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'
15 import {
16   isVideoFileExtnameValid,
17   isVideoFileInfoHashValid,
18   isVideoFileResolutionValid,
19   isVideoFileSizeValid,
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, QueryTypes, Transaction } from 'sequelize'
27 import { MIMETYPES } from '../../initializers/constants'
28
29 @Table({
30   tableName: 'videoFile',
31   indexes: [
32     {
33       fields: [ 'videoId' ]
34     },
35     {
36       fields: [ 'infoHash' ]
37     },
38     {
39       fields: [ 'videoId', 'resolution', 'fps' ],
40       unique: true
41     }
42   ]
43 })
44 export class VideoFileModel extends Model<VideoFileModel> {
45   @CreatedAt
46   createdAt: Date
47
48   @UpdatedAt
49   updatedAt: Date
50
51   @AllowNull(false)
52   @Is('VideoFileResolution', value => throwIfNotValid(value, isVideoFileResolutionValid, 'resolution'))
53   @Column
54   resolution: number
55
56   @AllowNull(false)
57   @Is('VideoFileSize', value => throwIfNotValid(value, isVideoFileSizeValid, 'size'))
58   @Column(DataType.BIGINT)
59   size: number
60
61   @AllowNull(false)
62   @Is('VideoFileExtname', value => throwIfNotValid(value, isVideoFileExtnameValid, 'extname'))
63   @Column
64   extname: string
65
66   @AllowNull(false)
67   @Is('VideoFileInfohash', value => throwIfNotValid(value, isVideoFileInfoHashValid, 'info hash'))
68   @Column
69   infoHash: string
70
71   @AllowNull(false)
72   @Default(-1)
73   @Is('VideoFileFPS', value => throwIfNotValid(value, isVideoFPSResolutionValid, 'fps'))
74   @Column
75   fps: number
76
77   @ForeignKey(() => VideoModel)
78   @Column
79   videoId: number
80
81   @BelongsTo(() => VideoModel, {
82     foreignKey: {
83       allowNull: false
84     },
85     onDelete: 'CASCADE'
86   })
87   Video: VideoModel
88
89   @HasMany(() => VideoRedundancyModel, {
90     foreignKey: {
91       allowNull: true
92     },
93     onDelete: 'CASCADE',
94     hooks: true
95   })
96   RedundancyVideos: VideoRedundancyModel[]
97
98   static doesInfohashExist (infoHash: string) {
99     const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1'
100     const options = {
101       type: QueryTypes.SELECT,
102       bind: { infoHash },
103       raw: true
104     }
105
106     return VideoModel.sequelize.query(query, options)
107               .then(results => results.length === 1)
108   }
109
110   static loadWithVideo (id: number) {
111     const options = {
112       include: [
113         {
114           model: VideoModel.unscoped(),
115           required: true
116         }
117       ]
118     }
119
120     return VideoFileModel.findByPk(id, options)
121   }
122
123   static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Transaction) {
124     const query = {
125       include: [
126         {
127           model: VideoModel.unscoped(),
128           required: true,
129           include: [
130             {
131               model: VideoStreamingPlaylistModel.unscoped(),
132               required: true,
133               where: {
134                 id: streamingPlaylistId
135               }
136             }
137           ]
138         }
139       ],
140       transaction
141     }
142
143     return VideoFileModel.findAll(query)
144   }
145
146   static getStats () {
147     const query: FindOptions = {
148       include: [
149         {
150           attributes: [],
151           model: VideoModel.unscoped(),
152           where: {
153             remote: false
154           }
155         }
156       ]
157     }
158
159     return VideoFileModel.aggregate('size', 'SUM', query)
160       .then(result => ({
161         totalLocalVideoFilesSize: parseAggregateResult(result)
162       }))
163   }
164
165   isAudio () {
166     return !!MIMETYPES.AUDIO.EXT_MIMETYPE[this.extname]
167   }
168
169   hasSameUniqueKeysThan (other: VideoFileModel) {
170     return this.fps === other.fps &&
171       this.resolution === other.resolution &&
172       this.videoId === other.videoId
173   }
174 }