f2dbbfde82821570aa443bc67d5711e4ec66839f
[oweals/peertube.git] / server / models / video / video-caption.ts
1 import * as Sequelize from 'sequelize'
2 import {
3   AllowNull,
4   BeforeDestroy,
5   BelongsTo,
6   Column,
7   CreatedAt,
8   ForeignKey,
9   Is,
10   Model,
11   Scopes,
12   Table,
13   UpdatedAt
14 } from 'sequelize-typescript'
15 import { throwIfNotValid } from '../utils'
16 import { VideoModel } from './video'
17 import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions'
18 import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model'
19 import { STATIC_PATHS, VIDEO_LANGUAGES } from '../../initializers/constants'
20 import { join } from 'path'
21 import { logger } from '../../helpers/logger'
22 import { remove } from 'fs-extra'
23 import { CONFIG } from '../../initializers/config'
24
25 export enum ScopeNames {
26   WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE'
27 }
28
29 @Scopes({
30   [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: {
31     include: [
32       {
33         attributes: [ 'uuid', 'remote' ],
34         model: () => VideoModel.unscoped(),
35         required: true
36       }
37     ]
38   }
39 })
40
41 @Table({
42   tableName: 'videoCaption',
43   indexes: [
44     {
45       fields: [ 'videoId' ]
46     },
47     {
48       fields: [ 'videoId', 'language' ],
49       unique: true
50     }
51   ]
52 })
53 export class VideoCaptionModel extends Model<VideoCaptionModel> {
54   @CreatedAt
55   createdAt: Date
56
57   @UpdatedAt
58   updatedAt: Date
59
60   @AllowNull(false)
61   @Is('VideoCaptionLanguage', value => throwIfNotValid(value, isVideoCaptionLanguageValid, 'language'))
62   @Column
63   language: string
64
65   @ForeignKey(() => VideoModel)
66   @Column
67   videoId: number
68
69   @BelongsTo(() => VideoModel, {
70     foreignKey: {
71       allowNull: false
72     },
73     onDelete: 'CASCADE'
74   })
75   Video: VideoModel
76
77   @BeforeDestroy
78   static async removeFiles (instance: VideoCaptionModel) {
79     if (!instance.Video) {
80       instance.Video = await instance.$get('Video') as VideoModel
81     }
82
83     if (instance.isOwned()) {
84       logger.info('Removing captions %s of video %s.', instance.Video.uuid, instance.language)
85
86       try {
87         await instance.removeCaptionFile()
88       } catch (err) {
89         logger.error('Cannot remove caption file of video %s.', instance.Video.uuid)
90       }
91     }
92
93     return undefined
94   }
95
96   static loadByVideoIdAndLanguage (videoId: string | number, language: string) {
97     const videoInclude = {
98       model: VideoModel.unscoped(),
99       attributes: [ 'id', 'remote', 'uuid' ],
100       where: { }
101     }
102
103     if (typeof videoId === 'string') videoInclude.where['uuid'] = videoId
104     else videoInclude.where['id'] = videoId
105
106     const query = {
107       where: {
108         language
109       },
110       include: [
111         videoInclude
112       ]
113     }
114
115     return VideoCaptionModel.findOne(query)
116   }
117
118   static insertOrReplaceLanguage (videoId: number, language: string, transaction: Sequelize.Transaction) {
119     const values = {
120       videoId,
121       language
122     }
123
124     return VideoCaptionModel.upsert<VideoCaptionModel>(values, { transaction, returning: true })
125       .then(([ caption ]) => caption)
126   }
127
128   static listVideoCaptions (videoId: number) {
129     const query = {
130       order: [ [ 'language', 'ASC' ] ],
131       where: {
132         videoId
133       }
134     }
135
136     return VideoCaptionModel.scope(ScopeNames.WITH_VIDEO_UUID_AND_REMOTE).findAll(query)
137   }
138
139   static getLanguageLabel (language: string) {
140     return VIDEO_LANGUAGES[language] || 'Unknown'
141   }
142
143   static deleteAllCaptionsOfRemoteVideo (videoId: number, transaction: Sequelize.Transaction) {
144     const query = {
145       where: {
146         videoId
147       },
148       transaction
149     }
150
151     return VideoCaptionModel.destroy(query)
152   }
153
154   isOwned () {
155     return this.Video.remote === false
156   }
157
158   toFormattedJSON (): VideoCaption {
159     return {
160       language: {
161         id: this.language,
162         label: VideoCaptionModel.getLanguageLabel(this.language)
163       },
164       captionPath: this.getCaptionStaticPath()
165     }
166   }
167
168   getCaptionStaticPath () {
169     return join(STATIC_PATHS.VIDEO_CAPTIONS, this.getCaptionName())
170   }
171
172   getCaptionName () {
173     return `${this.Video.uuid}-${this.language}.vtt`
174   }
175
176   removeCaptionFile () {
177     return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName())
178   }
179 }