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