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