]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/models/video/video-caption.ts
Generate a name for thumbnails
[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'
b49f22d8
C
18import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub'
19import { MVideoAccountLight, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/types/models'
59c76ffa 20import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model'
b49f22d8 21import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions'
40e87e9e 22import { logger } from '../../helpers/logger'
6dd9de95 23import { CONFIG } from '../../initializers/config'
b49f22d8
C
24import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, VIDEO_LANGUAGES, WEBSERVER } from '../../initializers/constants'
25import { buildWhereIdOrUUID, throwIfNotValid } from '../utils'
26import { VideoModel } from './video'
40e87e9e
C
27
28export enum ScopeNames {
29 WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE'
30}
31
3acc5084 32@Scopes(() => ({
40e87e9e
C
33 [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: {
34 include: [
35 {
453e83ea 36 attributes: [ 'id', 'uuid', 'remote' ],
3acc5084 37 model: VideoModel.unscoped(),
40e87e9e
C
38 required: true
39 }
40 ]
41 }
3acc5084 42}))
40e87e9e
C
43
44@Table({
45 tableName: 'videoCaption',
46 indexes: [
47 {
48 fields: [ 'videoId' ]
49 },
50 {
51 fields: [ 'videoId', 'language' ],
52 unique: true
53 }
54 ]
55})
b49f22d8 56export class VideoCaptionModel extends Model {
40e87e9e
C
57 @CreatedAt
58 createdAt: Date
59
60 @UpdatedAt
61 updatedAt: Date
62
63 @AllowNull(false)
64 @Is('VideoCaptionLanguage', value => throwIfNotValid(value, isVideoCaptionLanguageValid, 'language'))
65 @Column
66 language: string
67
ca6d3622
C
68 @AllowNull(true)
69 @Column(DataType.STRING(CONSTRAINTS_FIELDS.COMMONS.URL.max))
70 fileUrl: string
71
40e87e9e
C
72 @ForeignKey(() => VideoModel)
73 @Column
74 videoId: number
75
76 @BelongsTo(() => VideoModel, {
77 foreignKey: {
78 allowNull: false
79 },
80 onDelete: 'CASCADE'
81 })
82 Video: VideoModel
83
84 @BeforeDestroy
85 static async removeFiles (instance: VideoCaptionModel) {
f4001cf4 86 if (!instance.Video) {
e6122097 87 instance.Video = await instance.$get('Video')
f4001cf4 88 }
40e87e9e
C
89
90 if (instance.isOwned()) {
8e0fd45e 91 logger.info('Removing captions %s of video %s.', instance.Video.uuid, instance.language)
f4001cf4
C
92
93 try {
94 await instance.removeCaptionFile()
95 } catch (err) {
96 logger.error('Cannot remove caption file of video %s.', instance.Video.uuid)
97 }
40e87e9e
C
98 }
99
100 return undefined
101 }
102
b49f22d8 103 static loadByVideoIdAndLanguage (videoId: string | number, language: string): Promise<MVideoCaptionVideo> {
40e87e9e
C
104 const videoInclude = {
105 model: VideoModel.unscoped(),
106 attributes: [ 'id', 'remote', 'uuid' ],
3acc5084 107 where: buildWhereIdOrUUID(videoId)
40e87e9e
C
108 }
109
40e87e9e
C
110 const query = {
111 where: {
112 language
113 },
114 include: [
115 videoInclude
116 ]
117 }
118
119 return VideoCaptionModel.findOne(query)
120 }
121
ca6d3622 122 static insertOrReplaceLanguage (videoId: number, language: string, fileUrl: string, transaction: Transaction) {
40e87e9e
C
123 const values = {
124 videoId,
ca6d3622
C
125 language,
126 fileUrl
40e87e9e
C
127 }
128
0374b6b5 129 return VideoCaptionModel.upsert(values, { transaction, returning: true })
d382f4e9 130 .then(([ caption ]) => caption)
40e87e9e
C
131 }
132
b49f22d8 133 static listVideoCaptions (videoId: number): Promise<MVideoCaptionVideo[]> {
40e87e9e 134 const query = {
1735c825 135 order: [ [ 'language', 'ASC' ] ] as OrderItem[],
40e87e9e
C
136 where: {
137 videoId
138 }
139 }
140
141 return VideoCaptionModel.scope(ScopeNames.WITH_VIDEO_UUID_AND_REMOTE).findAll(query)
142 }
143
144 static getLanguageLabel (language: string) {
145 return VIDEO_LANGUAGES[language] || 'Unknown'
146 }
147
1735c825 148 static deleteAllCaptionsOfRemoteVideo (videoId: number, transaction: Transaction) {
40e87e9e
C
149 const query = {
150 where: {
151 videoId
152 },
153 transaction
154 }
155
156 return VideoCaptionModel.destroy(query)
157 }
158
159 isOwned () {
160 return this.Video.remote === false
161 }
162
1ca9f7c3 163 toFormattedJSON (this: MVideoCaptionFormattable): VideoCaption {
40e87e9e
C
164 return {
165 language: {
166 id: this.language,
167 label: VideoCaptionModel.getLanguageLabel(this.language)
168 },
169 captionPath: this.getCaptionStaticPath()
170 }
171 }
172
1ca9f7c3 173 getCaptionStaticPath (this: MVideoCaptionFormattable) {
557b13ae 174 return join(LAZY_STATIC_PATHS.VIDEO_CAPTIONS, this.getCaptionName())
40e87e9e
C
175 }
176
1ca9f7c3 177 getCaptionName (this: MVideoCaptionFormattable) {
40e87e9e
C
178 return `${this.Video.uuid}-${this.language}.vtt`
179 }
180
1ca9f7c3 181 removeCaptionFile (this: MVideoCaptionFormattable) {
62689b94 182 return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName())
40e87e9e 183 }
ca6d3622
C
184
185 getFileUrl (video: MVideoAccountLight) {
186 if (!this.Video) this.Video = video as VideoModel
187
188 if (video.isOwned()) return WEBSERVER.URL + this.getCaptionStaticPath()
189 if (this.fileUrl) return this.fileUrl
190
191 // Fallback if we don't have a file URL
192 return buildRemoteVideoBaseUrl(video, this.getCaptionStaticPath())
193 }
40e87e9e 194}