]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/models/video/video-caption.ts
chore(refactor): remove shared folder dependencies to the server
[github/Chocobozzz/PeerTube.git] / server / models / video / video-caption.ts
CommitLineData
b49f22d8
C
1import { remove } from 'fs-extra'
2import { join } from 'path'
1735c825 3import { OrderItem, Transaction } from 'sequelize'
40e87e9e
C
4import {
5 AllowNull,
6 BeforeDestroy,
7 BelongsTo,
8 Column,
a1587156
C
9 CreatedAt,
10 DataType,
40e87e9e
C
11 ForeignKey,
12 Is,
13 Model,
14 Scopes,
15 Table,
16 UpdatedAt
17} from 'sequelize-typescript'
06aad801 18import { buildUUID } from '@shared/core-utils/uuid'
8efc27bf 19import { MVideo, MVideoCaption, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/types/models'
16c016e8 20import { AttributesOnly } from '@shared/core-utils'
59c76ffa 21import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model'
b49f22d8 22import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions'
40e87e9e 23import { logger } from '../../helpers/logger'
6dd9de95 24import { CONFIG } from '../../initializers/config'
b49f22d8
C
25import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, VIDEO_LANGUAGES, WEBSERVER } from '../../initializers/constants'
26import { buildWhereIdOrUUID, throwIfNotValid } from '../utils'
27import { VideoModel } from './video'
40e87e9e
C
28
29export enum ScopeNames {
30 WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE'
31}
32
3acc5084 33@Scopes(() => ({
40e87e9e
C
34 [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: {
35 include: [
36 {
453e83ea 37 attributes: [ 'id', 'uuid', 'remote' ],
3acc5084 38 model: VideoModel.unscoped(),
40e87e9e
C
39 required: true
40 }
41 ]
42 }
3acc5084 43}))
40e87e9e
C
44
45@Table({
46 tableName: 'videoCaption',
47 indexes: [
6302d599
C
48 {
49 fields: [ 'filename' ],
50 unique: true
51 },
40e87e9e
C
52 {
53 fields: [ 'videoId' ]
54 },
55 {
56 fields: [ 'videoId', 'language' ],
57 unique: true
58 }
59 ]
60})
16c016e8 61export class VideoCaptionModel extends Model<Partial<AttributesOnly<VideoCaptionModel>>> {
40e87e9e
C
62 @CreatedAt
63 createdAt: Date
64
65 @UpdatedAt
66 updatedAt: Date
67
68 @AllowNull(false)
69 @Is('VideoCaptionLanguage', value => throwIfNotValid(value, isVideoCaptionLanguageValid, 'language'))
70 @Column
71 language: string
72
6302d599
C
73 @AllowNull(false)
74 @Column
75 filename: string
76
ca6d3622
C
77 @AllowNull(true)
78 @Column(DataType.STRING(CONSTRAINTS_FIELDS.COMMONS.URL.max))
79 fileUrl: string
80
40e87e9e
C
81 @ForeignKey(() => VideoModel)
82 @Column
83 videoId: number
84
85 @BelongsTo(() => VideoModel, {
86 foreignKey: {
87 allowNull: false
88 },
89 onDelete: 'CASCADE'
90 })
91 Video: VideoModel
92
93 @BeforeDestroy
eae0365b 94 static async removeFiles (instance: VideoCaptionModel, options) {
f4001cf4 95 if (!instance.Video) {
eae0365b 96 instance.Video = await instance.$get('Video', { transaction: options.transaction })
f4001cf4 97 }
40e87e9e
C
98
99 if (instance.isOwned()) {
6302d599 100 logger.info('Removing caption %s.', instance.filename)
f4001cf4
C
101
102 try {
103 await instance.removeCaptionFile()
104 } catch (err) {
6302d599 105 logger.error('Cannot remove caption file %s.', instance.filename)
f4001cf4 106 }
40e87e9e
C
107 }
108
109 return undefined
110 }
111
57a0a9cd 112 static loadByVideoIdAndLanguage (videoId: string | number, language: string, transaction?: Transaction): Promise<MVideoCaptionVideo> {
40e87e9e
C
113 const videoInclude = {
114 model: VideoModel.unscoped(),
115 attributes: [ 'id', 'remote', 'uuid' ],
eae0365b 116 where: buildWhereIdOrUUID(videoId)
40e87e9e
C
117 }
118
40e87e9e
C
119 const query = {
120 where: {
121 language
122 },
123 include: [
124 videoInclude
eae0365b
C
125 ],
126 transaction
40e87e9e
C
127 }
128
129 return VideoCaptionModel.findOne(query)
130 }
131
6302d599
C
132 static loadWithVideoByFilename (filename: string): Promise<MVideoCaptionVideo> {
133 const query = {
134 where: {
135 filename
136 },
137 include: [
138 {
139 model: VideoModel.unscoped(),
140 attributes: [ 'id', 'remote', 'uuid' ]
141 }
142 ]
40e87e9e
C
143 }
144
6302d599
C
145 return VideoCaptionModel.findOne(query)
146 }
147
148 static async insertOrReplaceLanguage (caption: MVideoCaption, transaction: Transaction) {
57a0a9cd
C
149 const existing = await VideoCaptionModel.loadByVideoIdAndLanguage(caption.videoId, caption.language, transaction)
150
6302d599
C
151 // Delete existing file
152 if (existing) await existing.destroy({ transaction })
153
154 return caption.save({ transaction })
40e87e9e
C
155 }
156
ff0ea0cd 157 static listVideoCaptions (videoId: number, transaction?: Transaction): Promise<MVideoCaptionVideo[]> {
40e87e9e 158 const query = {
1735c825 159 order: [ [ 'language', 'ASC' ] ] as OrderItem[],
40e87e9e
C
160 where: {
161 videoId
57a0a9cd
C
162 },
163 transaction
40e87e9e
C
164 }
165
166 return VideoCaptionModel.scope(ScopeNames.WITH_VIDEO_UUID_AND_REMOTE).findAll(query)
167 }
168
169 static getLanguageLabel (language: string) {
170 return VIDEO_LANGUAGES[language] || 'Unknown'
171 }
172
1735c825 173 static deleteAllCaptionsOfRemoteVideo (videoId: number, transaction: Transaction) {
40e87e9e
C
174 const query = {
175 where: {
176 videoId
177 },
178 transaction
179 }
180
181 return VideoCaptionModel.destroy(query)
182 }
183
6302d599 184 static generateCaptionName (language: string) {
d4a8e7a6 185 return `${buildUUID()}-${language}.vtt`
6302d599
C
186 }
187
40e87e9e
C
188 isOwned () {
189 return this.Video.remote === false
190 }
191
1ca9f7c3 192 toFormattedJSON (this: MVideoCaptionFormattable): VideoCaption {
40e87e9e
C
193 return {
194 language: {
195 id: this.language,
196 label: VideoCaptionModel.getLanguageLabel(this.language)
197 },
198 captionPath: this.getCaptionStaticPath()
199 }
200 }
201
6302d599
C
202 getCaptionStaticPath (this: MVideoCaption) {
203 return join(LAZY_STATIC_PATHS.VIDEO_CAPTIONS, this.filename)
40e87e9e
C
204 }
205
6302d599
C
206 removeCaptionFile (this: MVideoCaption) {
207 return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.filename)
40e87e9e 208 }
ca6d3622 209
8efc27bf 210 getFileUrl (video: MVideo) {
ca6d3622
C
211 if (!this.Video) this.Video = video as VideoModel
212
213 if (video.isOwned()) return WEBSERVER.URL + this.getCaptionStaticPath()
ca6d3622 214
d9a2a031 215 return this.fileUrl
ca6d3622 216 }
57a0a9cd
C
217
218 isEqual (this: MVideoCaption, other: MVideoCaption) {
219 if (this.fileUrl) return this.fileUrl === other.fileUrl
220
221 return this.filename === other.filename
222 }
40e87e9e 223}