X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fmodels%2Fvideo%2Fvideo-caption.ts;h=2eaa77407e35740f99bc72c92402a90d7089f55e;hb=26818a73ba0d7fd53ca69eba0c8e525f3670b5a8;hp=ad580176857b3727a0f1a00c0f81fe6a1db89d2c;hpb=282e61e6c11f79e919c543871783fe1a00298d18;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts index ad5801768..2eaa77407 100644 --- a/server/models/video/video-caption.ts +++ b/server/models/video/video-caption.ts @@ -1,3 +1,5 @@ +import { remove } from 'fs-extra' +import { join } from 'path' import { OrderItem, Transaction } from 'sequelize' import { AllowNull, @@ -5,6 +7,7 @@ import { BelongsTo, Column, CreatedAt, + DataType, ForeignKey, Is, Model, @@ -12,17 +15,16 @@ import { Table, UpdatedAt } from 'sequelize-typescript' -import { buildWhereIdOrUUID, throwIfNotValid } from '../utils' -import { VideoModel } from './video' -import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' +import { MVideo, MVideoCaption, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/types/models' +import { buildUUID } from '@shared/extra-utils' +import { AttributesOnly } from '@shared/typescript-utils' import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' -import { LAZY_STATIC_PATHS, VIDEO_LANGUAGES } from '../../initializers/constants' -import { join } from 'path' +import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' import { logger } from '../../helpers/logger' -import { remove } from 'fs-extra' import { CONFIG } from '../../initializers/config' -import * as Bluebird from 'bluebird' -import { MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/typings/models' +import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, VIDEO_LANGUAGES, WEBSERVER } from '../../initializers/constants' +import { buildWhereIdOrUUID, throwIfNotValid } from '../shared' +import { VideoModel } from './video' export enum ScopeNames { WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' @@ -43,6 +45,10 @@ export enum ScopeNames { @Table({ tableName: 'videoCaption', indexes: [ + { + fields: [ 'filename' ], + unique: true + }, { fields: [ 'videoId' ] }, @@ -52,7 +58,7 @@ export enum ScopeNames { } ] }) -export class VideoCaptionModel extends Model { +export class VideoCaptionModel extends Model>> { @CreatedAt createdAt: Date @@ -64,6 +70,14 @@ export class VideoCaptionModel extends Model { @Column language: string + @AllowNull(false) + @Column + filename: string + + @AllowNull(true) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.COMMONS.URL.max)) + fileUrl: string + @ForeignKey(() => VideoModel) @Column videoId: number @@ -77,25 +91,25 @@ export class VideoCaptionModel extends Model { Video: VideoModel @BeforeDestroy - static async removeFiles (instance: VideoCaptionModel) { + static async removeFiles (instance: VideoCaptionModel, options) { if (!instance.Video) { - instance.Video = await instance.$get('Video') as VideoModel + instance.Video = await instance.$get('Video', { transaction: options.transaction }) } if (instance.isOwned()) { - logger.info('Removing captions %s of video %s.', instance.Video.uuid, instance.language) + logger.info('Removing caption %s.', instance.filename) try { await instance.removeCaptionFile() } catch (err) { - logger.error('Cannot remove caption file of video %s.', instance.Video.uuid) + logger.error('Cannot remove caption file %s.', instance.filename) } } return undefined } - static loadByVideoIdAndLanguage (videoId: string | number, language: string): Bluebird { + static loadByVideoIdAndLanguage (videoId: string | number, language: string, transaction?: Transaction): Promise { const videoInclude = { model: VideoModel.unscoped(), attributes: [ 'id', 'remote', 'uuid' ], @@ -108,28 +122,45 @@ export class VideoCaptionModel extends Model { }, include: [ videoInclude - ] + ], + transaction } return VideoCaptionModel.findOne(query) } - static insertOrReplaceLanguage (videoId: number, language: string, transaction: Transaction) { - const values = { - videoId, - language + static loadWithVideoByFilename (filename: string): Promise { + const query = { + where: { + filename + }, + include: [ + { + model: VideoModel.unscoped(), + attributes: [ 'id', 'remote', 'uuid' ] + } + ] } - return (VideoCaptionModel.upsert(values, { transaction, returning: true }) as any) // FIXME: typings - .then(([ caption ]) => caption) + return VideoCaptionModel.findOne(query) } - static listVideoCaptions (videoId: number): Bluebird { + static async insertOrReplaceLanguage (caption: MVideoCaption, transaction: Transaction) { + const existing = await VideoCaptionModel.loadByVideoIdAndLanguage(caption.videoId, caption.language, transaction) + + // Delete existing file + if (existing) await existing.destroy({ transaction }) + + return caption.save({ transaction }) + } + + static listVideoCaptions (videoId: number, transaction?: Transaction): Promise { const query = { order: [ [ 'language', 'ASC' ] ] as OrderItem[], where: { videoId - } + }, + transaction } return VideoCaptionModel.scope(ScopeNames.WITH_VIDEO_UUID_AND_REMOTE).findAll(query) @@ -150,6 +181,10 @@ export class VideoCaptionModel extends Model { return VideoCaptionModel.destroy(query) } + static generateCaptionName (language: string) { + return `${buildUUID()}-${language}.vtt` + } + isOwned () { return this.Video.remote === false } @@ -160,19 +195,30 @@ export class VideoCaptionModel extends Model { id: this.language, label: VideoCaptionModel.getLanguageLabel(this.language) }, - captionPath: this.getCaptionStaticPath() + captionPath: this.getCaptionStaticPath(), + updatedAt: this.updatedAt.toISOString() } } - getCaptionStaticPath (this: MVideoCaptionFormattable) { - return join(LAZY_STATIC_PATHS.VIDEO_CAPTIONS, this.getCaptionName()) + getCaptionStaticPath (this: MVideoCaption) { + return join(LAZY_STATIC_PATHS.VIDEO_CAPTIONS, this.filename) } - getCaptionName (this: MVideoCaptionFormattable) { - return `${this.Video.uuid}-${this.language}.vtt` + removeCaptionFile (this: MVideoCaption) { + return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.filename) } - removeCaptionFile (this: MVideoCaptionFormattable) { - return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName()) + getFileUrl (video: MVideo) { + if (!this.Video) this.Video = video as VideoModel + + if (video.isOwned()) return WEBSERVER.URL + this.getCaptionStaticPath() + + return this.fileUrl + } + + isEqual (this: MVideoCaption, other: MVideoCaption) { + if (this.fileUrl) return this.fileUrl === other.fileUrl + + return this.filename === other.filename } }