]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/video/video-caption.ts
Implement captions/subtitles
[github/Chocobozzz/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
79 if (instance.isOwned()) {
80 if (!instance.Video) {
81 instance.Video = await instance.$get('Video') as VideoModel
82 }
83
84 logger.debug('Removing captions %s of video %s.', instance.Video.uuid, instance.language)
85 return instance.removeCaptionFile()
86 }
87
88 return undefined
89 }
90
91 static loadByVideoIdAndLanguage (videoId: string | number, language: string) {
92 const videoInclude = {
93 model: VideoModel.unscoped(),
94 attributes: [ 'id', 'remote', 'uuid' ],
95 where: { }
96 }
97
98 if (typeof videoId === 'string') videoInclude.where['uuid'] = videoId
99 else videoInclude.where['id'] = videoId
100
101 const query = {
102 where: {
103 language
104 },
105 include: [
106 videoInclude
107 ]
108 }
109
110 return VideoCaptionModel.findOne(query)
111 }
112
113 static insertOrReplaceLanguage (videoId: number, language: string, transaction: Sequelize.Transaction) {
114 const values = {
115 videoId,
116 language
117 }
118
119 return VideoCaptionModel.upsert(values, { transaction })
120 }
121
122 static listVideoCaptions (videoId: number) {
123 const query = {
124 order: [ [ 'language', 'ASC' ] ],
125 where: {
126 videoId
127 }
128 }
129
130 return VideoCaptionModel.scope(ScopeNames.WITH_VIDEO_UUID_AND_REMOTE).findAll(query)
131 }
132
133 static getLanguageLabel (language: string) {
134 return VIDEO_LANGUAGES[language] || 'Unknown'
135 }
136
137 static deleteAllCaptionsOfRemoteVideo (videoId: number, transaction: Sequelize.Transaction) {
138 const query = {
139 where: {
140 videoId
141 },
142 transaction
143 }
144
145 return VideoCaptionModel.destroy(query)
146 }
147
148 isOwned () {
149 return this.Video.remote === false
150 }
151
152 toFormattedJSON (): VideoCaption {
153 return {
154 language: {
155 id: this.language,
156 label: VideoCaptionModel.getLanguageLabel(this.language)
157 },
158 captionPath: this.getCaptionStaticPath()
159 }
160 }
161
162 getCaptionStaticPath () {
163 return join(STATIC_PATHS.VIDEO_CAPTIONS, this.getCaptionName())
164 }
165
166 getCaptionName () {
167 return `${this.Video.uuid}-${this.language}.vtt`
168 }
169
170 removeCaptionFile () {
171 return unlinkPromise(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName())
172 }
173 }